55using SquidStd . Network . Buffers ;
66using SquidStd . Network . Data . Events ;
77using SquidStd . Network . Interfaces . Client ;
8+ using SquidStd . Network . Interfaces . Codecs ;
89using SquidStd . Network . Interfaces . Framing ;
910using SquidStd . Network . Interfaces . Middleware ;
1011using SquidStd . Network . Pipeline ;
@@ -32,6 +33,7 @@ public sealed class SquidStdTcpClient : INetworkConnection, IAsyncDisposable, ID
3233 private readonly Stream _stream ;
3334 private static long _sessionIdSequence ;
3435 private int _closed ;
36+ private ITransportCodec ? _codec ;
3537
3638 private CancellationTokenRegistration _externalCancellationTokenRegistration ;
3739 private byte [ ] ? _pendingBuffer ;
@@ -153,13 +155,15 @@ public SquidStdTcpClient(
153155 Socket socket ,
154156 IEnumerable < INetMiddleware > ? middlewares = null ,
155157 INetFramer ? framer = null ,
158+ ITransportCodec ? codec = null ,
156159 int receiveBufferSize = DefaultReceiveBufferSize ,
157160 int historyBufferCapacity = DefaultHistoryBufferCapacity
158161 ) : this (
159162 socket ,
160163 new NetworkStream ( socket , false ) ,
161164 middlewares ,
162165 framer ,
166+ codec ,
163167 receiveBufferSize ,
164168 historyBufferCapacity
165169 ) { }
@@ -172,6 +176,7 @@ public SquidStdTcpClient(
172176 Stream stream ,
173177 IEnumerable < INetMiddleware > ? middlewares = null ,
174178 INetFramer ? framer = null ,
179+ ITransportCodec ? codec = null ,
175180 int receiveBufferSize = DefaultReceiveBufferSize ,
176181 int historyBufferCapacity = DefaultHistoryBufferCapacity
177182 )
@@ -183,6 +188,7 @@ public SquidStdTcpClient(
183188 _stream = stream ;
184189 _middlewarePipeline = new ( middlewares ) ;
185190 _framer = framer ;
191+ _codec = codec ;
186192 _receiveBuffer = new ( historyBufferCapacity ) ;
187193 ReceiveBufferSize = receiveBufferSize ;
188194 SessionId = Interlocked . Increment ( ref _sessionIdSequence ) ;
@@ -246,13 +252,14 @@ public static async Task<SquidStdTcpClient> ConnectAsync(
246252 IPEndPoint endPoint ,
247253 IEnumerable < INetMiddleware > ? middlewares = null ,
248254 INetFramer ? framer = null ,
255+ ITransportCodec ? codec = null ,
249256 CancellationToken cancellationToken = default
250257 )
251258 {
252259 var socket = new Socket ( endPoint . AddressFamily , SocketType . Stream , ProtocolType . Tcp ) ;
253260 await socket . ConnectAsync ( endPoint , cancellationToken ) ;
254261
255- var client = new SquidStdTcpClient ( socket , middlewares , framer ) ;
262+ var client = new SquidStdTcpClient ( socket , middlewares , framer , codec ) ;
256263 await client . StartAsync ( cancellationToken ) ;
257264
258265 return client ;
@@ -346,6 +353,14 @@ public byte[] PeekData(int count = 0)
346353 }
347354 }
348355
356+ /// <summary>
357+ /// Atomically swaps the transport codec for this connection. The new codec takes effect from the next
358+ /// socket read; the caller must trigger the swap at a read boundary (no old-regime bytes still pending).
359+ /// </summary>
360+ /// <param name="codec">The new codec, or null to remove transport transformation.</param>
361+ public void SwapCodec ( ITransportCodec ? codec )
362+ => Volatile . Write ( ref _codec , codec ) ;
363+
349364 /// <summary>
350365 /// Removes all middleware components of the specified type from this client pipeline.
351366 /// </summary>
@@ -374,7 +389,28 @@ public async Task SendAsync(ReadOnlyMemory<byte> payload, CancellationToken canc
374389
375390 try
376391 {
377- await _stream . WriteAsync ( processedPayload , cancellationToken ) ;
392+ var codec = Volatile . Read ( ref _codec ) ;
393+
394+ if ( codec is null )
395+ {
396+ await _stream . WriteAsync ( processedPayload , cancellationToken ) ;
397+ }
398+ else
399+ {
400+ var sendBuffer = ArrayPool < byte > . Shared . Rent ( processedPayload . Length ) ;
401+
402+ try
403+ {
404+ processedPayload . Span . CopyTo ( sendBuffer ) ;
405+ codec . Encode ( sendBuffer . AsSpan ( 0 , processedPayload . Length ) ) ;
406+ await _stream . WriteAsync ( sendBuffer . AsMemory ( 0 , processedPayload . Length ) , cancellationToken ) ;
407+ }
408+ finally
409+ {
410+ ArrayPool < byte > . Shared . Return ( sendBuffer ) ;
411+ }
412+ }
413+
378414 await _stream . FlushAsync ( cancellationToken ) ;
379415 }
380416 catch ( Exception ex )
@@ -532,17 +568,19 @@ private async Task ReceiveLoopAsync()
532568 break ;
533569 }
534570
535- lock ( _receiveBufferSync )
536- {
537- _receiveBuffer . PushBackRange ( buffer . AsSpan ( 0 , received ) ) ;
538- }
539-
540571 var chunk = ArrayPool < byte > . Shared . Rent ( received ) ;
541572
542573 try
543574 {
544575 buffer . AsSpan ( 0 , received ) . CopyTo ( chunk ) ;
545576
577+ Volatile . Read ( ref _codec ) ? . Decode ( chunk . AsSpan ( 0 , received ) ) ;
578+
579+ lock ( _receiveBufferSync )
580+ {
581+ _receiveBuffer . PushBackRange ( chunk . AsSpan ( 0 , received ) ) ;
582+ }
583+
546584 var chunkMemory = new ReadOnlyMemory < byte > ( chunk , 0 , received ) ;
547585 var processed = await _middlewarePipeline . ExecuteAsync (
548586 this ,
0 commit comments