using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; namespace ScreenConnect; public class VirtualStreamReceiver : IMessageProcessor, IDisposable { private class VirtualReadStream : ReadOnlyStream { private VirtualStreamReceiver receiver; private Queue> queue; private int bytesAvailable; private long currentPosition; private long currentEntryBeginningPosition; private long lastRequestedPosition; public Guid StreamID { get; } public override long Position { get { return currentPosition; } set { throw new NotSupportedException(); } } public override long Length => currentPosition + bytesAvailable; public VirtualReadStream(VirtualStreamReceiver receiver, Guid streamID) { this.receiver = receiver.AssertArgumentNonNull("receiver"); StreamID = streamID; queue = new Queue>(); } public override long Seek(long offset, SeekOrigin origin) { if ((origin == SeekOrigin.Current && offset == 0L) || (origin == SeekOrigin.Begin && offset == 0L && currentPosition == 0L)) { return currentPosition; } throw new InvalidOperationException(); } public void EnqueueData(ArraySegment data) { lock (queue) { queue.Enqueue(data); bytesAvailable += data.Count; Monitor.PulseAll(queue); } } public void Init() { RequestPositionIfNeeded(); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (Extensions.SetValue(ref currentPosition, -1L)) { receiver.RequestPosition(StreamID, 0L); } } public override int Read(byte[] buffer, int offset, int count) { if (currentPosition == -1) { throw new ObjectDisposedException(GetType().Name); } RequestPositionIfNeeded(); int num = 0; lock (queue) { if (queue.Count == 0 && !Singleton.Instance.WaitSafe(queue, 60000L)) { throw new TimeoutException("Timeout waiting for virtual stream read."); } ArraySegment arraySegment = queue.Peek(); num = Math.Min(count, arraySegment.Count - (int)(currentPosition - currentEntryBeginningPosition)); Buffer.BlockCopy(arraySegment.Array, arraySegment.Offset + (int)(currentPosition - currentEntryBeginningPosition), buffer, offset, num); bytesAvailable -= num; currentPosition += num; if (currentPosition > currentEntryBeginningPosition && currentPosition - currentEntryBeginningPosition == arraySegment.Count) { currentEntryBeginningPosition = currentPosition; queue.Dequeue(); } } RequestPositionIfNeeded(); return num; } private void RequestPositionIfNeeded() { long num = currentPosition + bytesAvailable; if (lastRequestedPosition < num + 131072) { long position = num + 524288; receiver.RequestPosition(StreamID, position); lastRequestedPosition = position; } } } private Dictionary readStreams = new Dictionary(); private Proc messageHandler; private void TryDisposeReadStream(Guid streamID) { readStreams.TryGetValue(streamID).DisposeQuietly(); } public bool ProcessMessage(VirtualStreamDataMessage virtualStreamDataMessage) { VirtualReadStream value = null; lock (readStreams) { if (!readStreams.TryGetValue(virtualStreamDataMessage.StreamID, out value)) { return false; } } value.EnqueueData(virtualStreamDataMessage.Data); return true; } public VirtualStreamReceiver(Proc messageHandler) { this.messageHandler = messageHandler; } public void Dispose() { lock (readStreams) { Guid[] array = readStreams.Keys.ToArray(); foreach (Guid streamID in array) { ReleaseStream(streamID); } } } public void ReleaseStream(Guid streamID) { lock (readStreams) { TryDisposeReadStream(streamID); readStreams.Remove(streamID); } ReleaseVirtualStreamMessage arg = new ReleaseVirtualStreamMessage { StreamID = streamID }; messageHandler(arg); } public Stream OpenReadStream(Guid streamID) { VirtualReadStream virtualReadStream = null; lock (readStreams) { TryDisposeReadStream(streamID); virtualReadStream = new VirtualReadStream(this, streamID); readStreams[streamID] = virtualReadStream; } virtualReadStream.Init(); return virtualReadStream; } public void RequestPosition(Guid streamID, long position) { RequestVirtualStreamDataMessage arg = new RequestVirtualStreamDataMessage { StreamID = streamID, RequestedPosition = position }; messageHandler(arg); } }