Schannel: Send more than one message at a time

By encrypting multiple 'Messages' before we send them we reduce
the overhead on both ourselves and the receiving end.
This brings some synthetic benchmark I have written from taking
around 50-60+ seconds to around 10.
With OpenSSL it is taking around 2 seconds, so there is still some
headroom, but this should be okay for now.

Change-Id: Ibcbc04a503a4b49197296ceaea2f0c38f528dfc4
Reviewed-by: Mate Barany <mate.barany@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Mårten Nordheim 2024-06-11 12:59:50 +02:00
parent b4b90bfbaf
commit 4e60a6b556

View File

@ -1765,43 +1765,64 @@ auto TlsCryptographSchannel::getNextEncryptedMessage() -> MessageBufferResult
Q_ASSERT(d);
auto &writeBuffer = d->tlsWriteBuffer();
auto allocateMessage = [&fullMessage](qsizetype size) -> QSpan<char> {
auto targetSize = fullMessage.size() + size;
if (fullMessage.capacity() < targetSize) {
qsizetype newSize = fullMessage.capacity() * 2;
if (newSize < targetSize)
newSize = targetSize;
fullMessage.reserve(newSize);
}
fullMessage.resizeForOverwrite(targetSize);
return QSpan(fullMessage).subspan(fullMessage.size() - size);
};
const int headerSize = int(streamSizes.cbHeader);
const int trailerSize = int(streamSizes.cbTrailer);
// Try to read 'cbMaximumMessage' bytes from buffer before encrypting.
const int size = int(std::min(writeBuffer.size(), qint64(streamSizes.cbMaximumMessage)));
fullMessage.resizeForOverwrite(headerSize + trailerSize + size);
char *header = fullMessage.data();
char *body = header + headerSize;
char *trailer = body + size;
{
// Use peek() here instead of read() so we don't lose data if encryption fails.
qint64 copied = writeBuffer.peek(body, size);
Q_ASSERT(copied == size);
}
constexpr qsizetype MessageBufferThreshold = 128 * 1024;
SecBuffer inputBuffers[] = {
createSecBuffer(header, headerSize, SECBUFFER_STREAM_HEADER),
createSecBuffer(body, size, SECBUFFER_DATA),
createSecBuffer(trailer, trailerSize, SECBUFFER_STREAM_TRAILER),
createSecBuffer(nullptr, 0, SECBUFFER_EMPTY)
};
SecBufferDesc message = {
SECBUFFER_VERSION,
ARRAYSIZE(inputBuffers),
inputBuffers
};
if (auto status = EncryptMessage(&contextHandle, 0, &message, 0); status != SEC_E_OK) {
setErrorAndEmit(d, QAbstractSocket::SslInternalError,
QSslSocket::tr("Schannel failed to encrypt data: %1")
.arg(schannelErrorToString(status)));
return result;
}
// Data was encrypted successfully, so we free() what we peek()ed earlier
writeBuffer.free(size);
qint64 writeBufferSize = 0;
while ((writeBufferSize = writeBuffer.size()) > 0
&& fullMessage.size() < MessageBufferThreshold) {
// Try to read 'cbMaximumMessage' bytes from buffer before encrypting.
const int bodySize = int(std::min(writeBufferSize, qint64(streamSizes.cbMaximumMessage)));
auto messageSize = headerSize + bodySize + trailerSize;
QSpan buffer = allocateMessage(messageSize);
char *header = buffer.data();
char *body = header + headerSize;
char *trailer = body + bodySize;
{
// Use peek() here instead of read() so we don't lose data if encryption fails.
qint64 copied = writeBuffer.peek(body, bodySize);
Q_ASSERT(copied == bodySize);
}
// The trailer's size is not final, so resize fullMessage to not send trailing junk
fullMessage.resize(inputBuffers[0].cbBuffer + inputBuffers[1].cbBuffer
+ inputBuffers[2].cbBuffer);
SecBuffer inputBuffers[] = {
createSecBuffer(header, headerSize, SECBUFFER_STREAM_HEADER),
createSecBuffer(body, bodySize, SECBUFFER_DATA),
createSecBuffer(trailer, trailerSize, SECBUFFER_STREAM_TRAILER),
createSecBuffer(nullptr, 0, SECBUFFER_EMPTY)
};
SecBufferDesc message = {
SECBUFFER_VERSION,
ARRAYSIZE(inputBuffers),
inputBuffers
};
if (auto status = EncryptMessage(&contextHandle, 0, &message, 0); status != SEC_E_OK) {
setErrorAndEmit(d, QAbstractSocket::SslInternalError,
QSslSocket::tr("Schannel failed to encrypt data: %1")
.arg(schannelErrorToString(status)));
return result;
}
// Data was encrypted successfully, so we free() what we peek()ed earlier
writeBuffer.free(bodySize);
// The trailer's size is not final, so resize fullMessage to not send trailing junk
auto finalSize = qsizetype(inputBuffers[0].cbBuffer + inputBuffers[1].cbBuffer
+ inputBuffers[2].cbBuffer);
fullMessage.chop(messageSize - finalSize);
}
result.ok = true;
return result;
}