@@ -30,7 +30,6 @@ import scala.util.control.NonFatal
3030
3131import org .apache .pekko
3232import pekko .annotation .InternalApi
33- import pekko .io .{ BufferPool , Tcp }
3433import pekko .stream ._
3534import pekko .stream .TLSProtocol ._
3635import pekko .stream .impl .Stages .DefaultAttributes
@@ -78,12 +77,11 @@ import pekko.util.ByteString
7877 private var transportInBuffer : ByteBuffer = _
7978 private var transportUnreadBuffer : ByteBuffer = _
8079 private var userInBuffer : ByteBuffer = _
81- private var bufferPool : BufferPool = _
82- private var pooledBufferSize = 0
8380 private var transportPacketSize = 0
8481 private var applicationRecordSize = 0
8582 private var maxUserInBufferSize = 0
8683 private var maxUserOutBufferSize = 0
84+ private var maxTransportOutBufferSize = 0
8785
8886 private val userInput = new UserInputSlot (plainIn)
8987 private val transportInput = new TransportInputSlot (cipherIn)
@@ -104,10 +102,6 @@ import pekko.util.ByteString
104102
105103 override def preStart (): Unit =
106104 try {
107- val tcp = Tcp (materializer.system)
108- bufferPool = tcp.bufferPool
109- pooledBufferSize = tcp.Settings .DirectBufferSize
110-
111105 engine = createSSLEngine()
112106 currentSession = engine.getSession
113107 allocateBuffers(currentSession)
@@ -135,15 +129,22 @@ import pekko.util.ByteString
135129 private def allocateBuffers (session : SSLSession ): Unit = {
136130 val packetSize = math.max(1 , session.getPacketBufferSize)
137131 val applicationSize = math.max(1 , session.getApplicationBufferSize)
138- val applicationBatchSize = applicationBufferSize(applicationSize, packetSize)
139132 transportPacketSize = packetSize
140133 applicationRecordSize = applicationSize
141- maxUserInBufferSize = applicationBatchSize
142- maxUserOutBufferSize = applicationBatchSize
143-
144- transportOutBuffer = acquireTransportBuffer(packetSize)
145- transportInBuffer = acquireTransportBuffer(packetSize)
146- transportUnreadBuffer = acquireTransportBuffer(packetSize)
134+ // multiplyExact so an out-of-range SSLSession buffer size fails explicitly here instead of
135+ // silently overflowing to a negative cap and breaking ByteBuffer.allocate later on.
136+ maxUserInBufferSize = Math .multiplyExact(applicationSize, MaxApplicationRecordsPerEngineCall )
137+ maxUserOutBufferSize = maxUserInBufferSize
138+ maxTransportOutBufferSize = Math .multiplyExact(packetSize, MaxApplicationRecordsPerEngineCall )
139+
140+ // The JDK SSLEngine operates on byte[] internally and makes an extra direct<->heap copy per
141+ // wrap/unwrap when handed a direct buffer (see Netty SslHandler's SslEngineType.JDK). Use heap
142+ // buffers and, like Netty, size them on demand: start at a single record so small-payload or
143+ // short-lived connections allocate little, and let growTransportOutBuffer enlarge the wrap
144+ // buffer when a larger batch is actually produced.
145+ transportOutBuffer = ByteBuffer .allocate(packetSize)
146+ transportInBuffer = ByteBuffer .allocate(packetSize)
147+ transportUnreadBuffer = ByteBuffer .allocate(packetSize)
147148 userInBuffer = ByteBuffer .allocate(applicationSize)
148149 userOutBuffer = ByteBuffer .allocate(applicationSize)
149150
@@ -152,12 +153,6 @@ import pekko.util.ByteString
152153 TlsEngineHelpers .emptyReadBuffer(transportUnreadBuffer)
153154 }
154155
155- private def applicationBufferSize (applicationSize : Int , packetSize : Int ): Int = {
156- val recordsThatFitTransportBuffer = math.max(1 , pooledBufferSize / packetSize)
157- val recordsPerEngineCall = math.min(MaxApplicationRecordsPerEngineCall , recordsThatFitTransportBuffer)
158- applicationSize * recordsPerEngineCall
159- }
160-
161156 private def runPump (): Unit =
162157 if ((stateBits & (StageClosedFlag | StageCompletionPendingFlag )) == 0 &&
163158 (stateBits & InitialPumpEnabledFlag ) != 0 ) {
@@ -403,6 +398,8 @@ import pekko.util.ByteString
403398 var result = engine.wrap(userInBuffer, transportOutBuffer)
404399
405400 while (canContinueWrapping(result)) {
401+ if (transportOutBuffer.remaining < transportPacketSize)
402+ growTransportOutBuffer()
406403 val userPosition = userInBuffer.position()
407404 val transportPosition = transportOutBuffer.position()
408405 result = engine.wrap(userInBuffer, transportOutBuffer)
@@ -417,7 +414,8 @@ import pekko.util.ByteString
417414 result.getStatus == OK &&
418415 result.getHandshakeStatus == NOT_HANDSHAKING &&
419416 userInBuffer.hasRemaining &&
420- transportOutBuffer.remaining >= transportPacketSize
417+ (transportOutBuffer.remaining >= transportPacketSize ||
418+ transportOutBuffer.capacity < maxTransportOutBufferSize)
421419
422420 @ tailrec
423421 private def doUnwrap (ignoreOutput : Boolean ): Unit = {
@@ -567,15 +565,17 @@ import pekko.util.ByteString
567565 }
568566
569567 private def growTransportOutBuffer (): Unit = {
570- val oldBuffer = transportOutBuffer
571- val oldCapacity = oldBuffer.capacity( )
572- if (oldCapacity > Int . MaxValue / 2 )
573- throw new IllegalStateException ( s " Cannot grow TLS transport output buffer beyond $oldCapacity bytes " )
568+ val oldCapacity = transportOutBuffer.capacity()
569+ if ( oldCapacity >= maxTransportOutBufferSize )
570+ throw new IllegalStateException (
571+ s " Cannot grow TLS transport output buffer beyond $maxTransportOutBufferSize bytes " )
574572
575- val bigger = acquireTransportBuffer(oldCapacity * 2 )
573+ // Double, but clamp to the cap so growth never overshoots maxTransportOutBufferSize regardless
574+ // of whether the cap is a power-of-two multiple of the packet size. Double in Long so the
575+ // multiplication cannot overflow before the clamp.
576+ val bigger = ByteBuffer .allocate(math.min(maxTransportOutBufferSize.toLong, oldCapacity.toLong * 2 ).toInt)
576577 transportOutBuffer.flip()
577578 bigger.put(transportOutBuffer)
578- releaseTransportBuffer(oldBuffer)
579579 transportOutBuffer = bigger
580580 }
581581
@@ -660,32 +660,13 @@ import pekko.util.ByteString
660660 }
661661
662662 private def releaseBuffers (): Unit = {
663- releaseTransportBuffer(transportOutBuffer)
664663 transportOutBuffer = null
665664 userOutBuffer = null
666- releaseTransportBuffer(transportInBuffer)
667665 transportInBuffer = null
668- releaseTransportBuffer(transportUnreadBuffer)
669666 transportUnreadBuffer = null
670667 userInBuffer = null
671668 }
672669
673- private def acquireTransportBuffer (requiredCapacity : Int ): ByteBuffer = {
674- val buffer = bufferPool.acquire()
675- if (buffer.capacity >= requiredCapacity) buffer
676- else {
677- releaseTransportBuffer(buffer)
678- throw new IllegalStateException (
679- s " TLS packet buffer requires $requiredCapacity bytes but pekko.io.tcp.direct-buffer-size is $pooledBufferSize" )
680- }
681- }
682-
683- private def releaseTransportBuffer (buffer : ByteBuffer ): Unit =
684- if (buffer ne null ) {
685- buffer.clear()
686- bufferPool.release(buffer)
687- }
688-
689670 private final class UserInputSlot (inlet : Inlet [SslTlsOutbound ]) extends InHandler {
690671 private val pendingQueue = new Array [AnyRef ](UserInputQueueSize )
691672 private var pendingHead = 0
0 commit comments