@@ -2650,6 +2650,15 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap)
26502650 }
26512651 ctx->timeout = WOLFSSL_SESSION_TIMEOUT;
26522652
2653+ #if defined(OPENSSL_EXTRA) || defined(WOLFSSL_TLS_READ_AHEAD)
2654+ /* Default the read-ahead window to one full record. Contexts (and the
2655+ * WOLFSSL objects that inherit it) then always carry a concrete window, so
2656+ * the receive path uses ssl->readAheadSz directly without a per-read
2657+ * fallback. A caller override replaces it; passing 0 resets it to this
2658+ * default (see wolfSSL_CTX_set_default_read_buffer_len()). */
2659+ ctx->readAheadSz = WOLFSSL_READ_AHEAD_SZ;
2660+ #endif
2661+
26532662#ifdef WOLFSSL_DTLS
26542663 if (method->version.major == DTLS_MAJOR) {
26552664 ctx->minDowngrade = WOLFSSL_MIN_DTLS_DOWNGRADE;
@@ -7509,8 +7518,9 @@ int SetSSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup)
75097518 ssl->ConnectFilter_arg = ctx->ConnectFilter_arg;
75107519#endif
75117520
7512- #ifdef OPENSSL_EXTRA
7521+ #if defined( OPENSSL_EXTRA) || defined(WOLFSSL_TLS_READ_AHEAD)
75137522 ssl->readAhead = ctx->readAhead;
7523+ ssl->readAheadSz = ctx->readAheadSz;
75147524#endif
75157525#if defined(OPENSSL_EXTRA) && !defined(NO_BIO)
75167526 /* Don't change recv callback if currently using BIO's */
@@ -11619,6 +11629,41 @@ void ShrinkInputBuffer(WOLFSSL* ssl, int forcedFree)
1161911629 ssl->buffers.clearOutputBuffer.length > 0))
1162011630 return;
1162111631
11632+ #ifdef WOLFSSL_TLS_READ_AHEAD
11633+ /* While read-ahead is enabled, retain a dynamic input buffer sized to the
11634+ * configured window rather than shrinking all the way back to the static
11635+ * buffer, so the speculative over-read is a bounded, mostly one-time
11636+ * allocation instead of per-record churn. A forced free during connection
11637+ * teardown still reclaims everything. */
11638+ if (!forcedFree && ssl->readAhead) {
11639+ /* Already within the window: keep the buffer as-is. */
11640+ if (ssl->buffers.inputBuffer.bufferSize <= ssl->readAheadSz)
11641+ return;
11642+
11643+ /* The buffer grew past the window to receive an oversized record. When
11644+ * the window needs a dynamic buffer, reallocate down to it so the
11645+ * retained footprint tracks the window, not the largest record seen;
11646+ * when the window fits in the static buffer, fall through and shrink to
11647+ * static below.
11648+ *
11649+ * GrowInputBuffer(newBytes, usedLength) resizes the input buffer to
11650+ * newBytes + usedLength while preserving the usedLength bytes still
11651+ * pending, so requesting (readAheadSz - usedLength) new bytes yields a
11652+ * buffer of exactly readAheadSz. usedLength is <= STATIC_BUFFER_LEN <
11653+ * readAheadSz here (guaranteed above), so the subtraction stays
11654+ * positive. */
11655+ if (ssl->readAheadSz > STATIC_BUFFER_LEN) {
11656+ if (GrowInputBuffer(ssl, (int)ssl->readAheadSz - usedLength,
11657+ usedLength) != 0) {
11658+ /* Realloc failed: keep the current (larger) buffer rather than
11659+ * dropping the buffered data. */
11660+ WOLFSSL_MSG("read-ahead buffer shrink failed, keeping buffer");
11661+ }
11662+ return;
11663+ }
11664+ }
11665+ #endif
11666+
1162211667 WOLFSSL_MSG("Shrinking input buffer");
1162311668
1162411669 if (!forcedFree && usedLength > 0) {
@@ -23189,12 +23234,16 @@ static int DoAlert(WOLFSSL* ssl, byte* input, word32* inOutIdx, int* type)
2318923234 return level;
2319023235}
2319123236
23192- static int GetInputData (WOLFSSL *ssl, word32 size)
23237+ static int GetInputData_ex (WOLFSSL *ssl, word32 size, word32 readAhead )
2319323238{
2319423239 int inSz;
2319523240 int maxLength;
2319623241 int usedLength;
2319723242 int dtlsExtra = 0;
23243+ int extra = 0;
23244+ #ifndef WOLFSSL_TLS_READ_AHEAD
23245+ (void)readAhead;
23246+ #endif
2319823247
2319923248 if (ssl->options.disableRead)
2320023249 return WC_NO_ERR_TRACE(WANT_READ);
@@ -23231,10 +23280,27 @@ static int GetInputData(WOLFSSL *ssl, word32 size)
2323123280 }
2323223281
2323323282 inSz = (int)(size - (word32)usedLength); /* from last partial read */
23283+
23284+ #ifdef WOLFSSL_TLS_READ_AHEAD
23285+ /* Request more than the minimum so that a single recv() can also pull
23286+ * in the record body (and possibly following records), avoiding a
23287+ * second syscall. 'size' remains the loop-termination minimum, so a
23288+ * blocking socket never waits for read-ahead bytes the peer may not
23289+ * send. Mirrors the DTLS dtlsExtra over-read above.
23290+ *
23291+ * The buffer is grown once to hold a full record and then kept (see the
23292+ * ssl->readAhead guard in ShrinkInputBuffer), so this is a one-time
23293+ * allocation per connection, not per-record churn. */
23294+ if (readAhead > size) {
23295+ extra = (int)(readAhead - size);
23296+ inSz += extra;
23297+ }
23298+ #endif
2323423299 }
2323523300
2323623301 if (inSz > maxLength) {
23237- if (GrowInputBuffer(ssl, (int)(size + (word32)dtlsExtra), usedLength) < 0)
23302+ if (GrowInputBuffer(ssl,
23303+ (int)(size + (word32)dtlsExtra + (word32)extra), usedLength) < 0)
2323823304 return MEMORY_E;
2323923305 }
2324023306
@@ -23292,6 +23358,11 @@ static int GetInputData(WOLFSSL *ssl, word32 size)
2329223358 return 0;
2329323359}
2329423360
23361+ static int GetInputData(WOLFSSL *ssl, word32 size)
23362+ {
23363+ return GetInputData_ex(ssl, size, 0);
23364+ }
23365+
2329523366#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY)
2329623367static WC_INLINE int VerifyMacEnc(WOLFSSL* ssl, const byte* input, word32 msgSz,
2329723368 int content)
@@ -24123,7 +24194,28 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr)
2412324194
2412424195 /* get header or return error */
2412524196 if (!ssl->options.dtls) {
24126- if ((ret = GetInputData(ssl, (word32)readSz)) < 0)
24197+ word32 readAheadSz = 0;
24198+ #ifdef WOLFSSL_TLS_READ_AHEAD
24199+ /* When read-ahead is enabled, request more than the record
24200+ * header in a single recv() so the body (and possibly following
24201+ * records) can be pulled in without a second syscall. The window
24202+ * size is configurable via
24203+ * wolfSSL_CTX_set_default_read_buffer_len():
24204+ * - 0 (unset) requests one full record, the sensible default.
24205+ * - A larger value lets one recv() coalesce several back-to-back
24206+ * records.
24207+ * - A smaller (sub-record) value caps the receive buffer's
24208+ * footprint when the peer's records are known to be small.
24209+ * The value is only a speculative read window: a record larger
24210+ * than it is still received correctly, as the buffer is grown to
24211+ * the record's actual size on demand. */
24212+ if (ssl->readAhead) {
24213+ /* readAheadSz is always concrete (defaulted to
24214+ * WOLFSSL_READ_AHEAD_SZ at CTX init, never 0). */
24215+ readAheadSz = ssl->readAheadSz;
24216+ }
24217+ #endif
24218+ if ((ret = GetInputData_ex(ssl, (word32)readSz, readAheadSz)) < 0)
2412724219 return ret;
2412824220 } else {
2412924221 #ifdef WOLFSSL_DTLS
@@ -24848,6 +24940,16 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr)
2484824940 ssl->options.serverState ==
2484924941 SERVER_FINISHED_COMPLETE &&
2485024942 ssl->options.handShakeState != HANDSHAKE_DONE)))
24943+ #endif
24944+ #ifdef WOLFSSL_TLS_READ_AHEAD
24945+ /* With read-ahead, more than one record may be buffered. If
24946+ * application data was just decrypted, return it now so it
24947+ * is delivered to the caller before any following buffered
24948+ * record (e.g. a close_notify alert) is processed, which
24949+ * would otherwise discard the pending app data. The
24950+ * remaining records stay buffered for the next call. */
24951+ || (ssl->curRL.type == application_data &&
24952+ ssl->buffers.clearOutputBuffer.length > 0)
2485124953#endif
2485224954 ) {
2485324955 /* Shrink input buffer when we successfully finish record
0 commit comments