using System; using System.Buffers; using System.IO; using System.Threading; namespace ScreenConnect; public abstract class CoderStream : UnseekableStream { private class MultiCallEncoderStream : CoderStream { public override bool CanRead => false; public override void Write(byte[] buffer, int offset, int count) { CursorBuffer inputBuffer = CursorBuffer.From(buffer, offset, count); while (Process(ref inputBuffer, CoderFlushType.None) && inputBuffer.RemainingCount != 0) { } } public override void Flush() { } private bool Process(ref CursorBuffer inputBuffer, CoderFlushType flushType) { using Extensions.ArrayPoolRental arrayPoolRental = ArrayPool.Shared.RentEx(16384); CursorBuffer outputBuffer = CursorBuffer.From(arrayPoolRental.RentedArray); CoderProcessResult coderProcessResult = base.Coder.To().Process(ref inputBuffer, ref outputBuffer, flushType); if (outputBuffer.CompletedCount != 0) { Extensions.Write(base.InnerStream, outputBuffer.GetCompletedSegment()); } switch (coderProcessResult) { case CoderProcessResult.BufferError: case CoderProcessResult.Success: return !CoderExtensions.IsCoderFinishedProcessingInput(inputBuffer.RemainingCount, outputBuffer.RemainingCount); case CoderProcessResult.MemoryError: case CoderProcessResult.DataError: case CoderProcessResult.StreamError: throw new IOException("Invalid data"); default: return false; } } protected override void Dispose(bool disposing) { if (disposing) { CursorBuffer inputBuffer = CursorBuffer.From(Extensions.EmptyArray()); while (Process(ref inputBuffer, CoderFlushType.Finish)) { } } base.Dispose(disposing); } public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } } private class MultiCallDecoderStream : CoderStream { private CursorBuffer innerStreamBuffer; private int disposalState; public override bool CanWrite => false; public MultiCallDecoderStream() { innerStreamBuffer = CursorBuffer.From(ArrayPool.Shared.Rent(16384), 0, 0); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (Interlocked.Exchange(ref disposalState, 1) == 0) { ArrayPool.Shared.Return(innerStreamBuffer.BackingArray); } } public override int Read(byte[] buffer, int offset, int count) { CursorBuffer outputBuffer = CursorBuffer.From(buffer, offset, count); bool flag; do { if (innerStreamBuffer.RemainingCount == 0) { int count2 = Extensions.Read(base.InnerStream, innerStreamBuffer.BackingArray.ToArraySegment()); innerStreamBuffer = CursorBuffer.From(innerStreamBuffer.BackingArray, 0, count2); } switch (base.Coder.To().Process(ref innerStreamBuffer, ref outputBuffer, CoderFlushType.Finish)) { case CoderProcessResult.BufferError: case CoderProcessResult.Success: flag = true; break; case CoderProcessResult.MemoryError: case CoderProcessResult.DataError: case CoderProcessResult.StreamError: throw new IOException("Invalid data"); default: flag = false; break; } } while (flag && outputBuffer.CompletedCount == 0 && innerStreamBuffer.CompletedCount != 0); return outputBuffer.CompletedCount; } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } public override void Flush() { throw new NotSupportedException(); } } public Stream InnerStream { get; private set; } public ICoder Coder { get; private set; } public bool OwnsInnerStream { get; private set; } public bool OwnsCoder { get; private set; } public static CoderStream Create(Stream innerStream, ICoder coder, bool encodeOrDecode, bool ownsInnerStream = false, bool ownsCoder = false) { CoderStream? coderStream = coder.As().SafeNav((IMultiCallCoder _) => (!encodeOrDecode) ? ((CoderStream)new MultiCallDecoderStream()) : ((CoderStream)new MultiCallEncoderStream())); coderStream.InnerStream = innerStream; coderStream.Coder = coder; coderStream.OwnsInnerStream = ownsInnerStream; coderStream.OwnsCoder = ownsCoder; return coderStream; } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { if (OwnsInnerStream) { InnerStream.DisposeQuietly(); } if (OwnsCoder) { Coder.DisposeQuietly(); } } } }