SSL, TLS e o limite de 16 KB

Caloni, 2018-05-22 computer blog

Em 2026-05-09 fiz um update com a ajuda do Chat-GPT que me corrigiu em alguns pontos. Ele é meio centrão, então fui tirando os exageros do meu texto original como "esqueça TCP" e "você vai perder dados se usar Boost::Asio". Fiquem apenas com a versão medíocre e sem alma ou discussão de nossas futuras vidas tecnológicas.

O protocolo TLS (ou SSL) tem por objetivo criar uma camada de criptografia assimétrica para a aplicação. E quando eu falo em camada não estou me referindo exatamente às camadas OSI nem às camadas TCP/IP. Isso porque TLS é uma dessas tecnologias que atravessam abstrações: formalmente ele fica entre aplicação e transporte, mas na prática influencia desde buffering até o comportamento esperado da própria aplicação.

E aprendi isso a duras penas: na ponta do depurador.

O TLS trabalha internamente com records de até 16 KB de payload (16384 bytes). Isso não significa que você não possa enviar buffers maiores que isso. Na prática, implementações como OpenSSL fragmentam automaticamente os dados em múltiplos records TLS.

O problema aparece quando usamos APIs como write_some, que não garantem o envio integral do buffer em uma única chamada. Isso já é verdade para sockets TCP normais, mas em conexões SSL/TLS o comportamento costuma ficar mais evidente.

Em outras palavras: o erro não é enviar 512 KB. O erro é assumir que uma única chamada enviará tudo.

Isso quer dizer que este snippet de código, por exemplo:

_sock.write_some(
  boost::asio::buffer(
    output.data(),
    output.size()
  ), err);

É inocente e não funciona sempre. Não porque SSL “corta” os dados em 16 KB, mas porque write_some pode escrever apenas parte do buffer. O restante continua pendente.

O detalhe importante é que write_some retorna quantos bytes foram efetivamente enviados:

size_t sent =
  _sock.write_some(
    boost::asio::buffer(
      output.data(),
      output.size()
    ), err);

Portanto, se você realmente quiser controlar manualmente os envios, precisa remover apenas os bytes efetivamente transmitidos:

do
{
  size_t chunk =
    std::min((size_t)16384, output.size());

  size_t sent =
    _sock.write_some(
      boost::asio::buffer(output.data(), chunk),
      err);

  output.erase(0, sent);
}
while (
  err.value() == boost::system::errc::success &&
  !output.empty());

Na prática, porém, o mais comum é simplesmente deixar o Boost.Asio cuidar disso usando `boost::asio::write`, que já faz internamente o loop necessário até transmitir todo o buffer:

boost::asio::write(
  _sock,
  boost::asio::buffer(output),
  err);

TLS é um bom lembrete de que abstrações de rede vazam. Em algum momento, toda aplicação precisa entender como os bytes realmente trafegam.

[comment] [Python27, protobuf, py2exe e build_exe]