A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

152 lines
5.3 KiB

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Metsys.Bson;
namespace Avalonia.Remote.Protocol
{
class BsonStreamTransportConnection : IAvaloniaRemoteTransportConnection
{
private readonly IMessageTypeResolver _resolver;
private readonly Stream _inputStream;
private readonly Stream _outputStream;
private readonly Action _disposeCallback;
private readonly CancellationToken _cancel;
private readonly CancellationTokenSource _cancelSource;
private readonly MemoryStream _outputBlock = new MemoryStream();
private readonly object _lock = new object();
private bool _writeOperationPending;
private bool _readingAlreadyStarted;
private bool _writerIsBroken;
private static readonly byte[] ZeroLength = new byte[4];
public BsonStreamTransportConnection(IMessageTypeResolver resolver, Stream inputStream, Stream outputStream, Action disposeCallback)
{
_resolver = resolver;
_inputStream = inputStream;
_outputStream = outputStream;
_disposeCallback = disposeCallback;
_cancelSource = new CancellationTokenSource();
_cancel = _cancelSource.Token;
}
public void Dispose()
{
_cancelSource.Cancel();
_disposeCallback?.Invoke();
}
public void StartReading()
{
lock (_lock)
{
if(_readingAlreadyStarted)
throw new InvalidOperationException("Reading has already started");
_readingAlreadyStarted = true;
Task.Run(Reader, _cancel);
}
}
async Task ReadExact(byte[] buffer)
{
int read = 0;
while (read != buffer.Length)
{
var readNow = await _inputStream.ReadAsync(buffer, read, buffer.Length - read, _cancel)
.ConfigureAwait(false);
if (readNow == 0)
throw new EndOfStreamException();
read += readNow;
}
}
async Task Reader()
{
try
{
while (true)
{
var infoBlock = new byte[20];
await ReadExact(infoBlock).ConfigureAwait(false);
var length = BitConverter.ToInt32(infoBlock, 0);
var guidBytes = new byte[16];
Buffer.BlockCopy(infoBlock, 4, guidBytes, 0, 16);
var guid = new Guid(guidBytes);
var buffer = new byte[length];
await ReadExact(buffer).ConfigureAwait(false);
var message = Deserializer.Deserialize(new BinaryReader(new MemoryStream(buffer)),
_resolver.GetByGuid(guid));
OnMessage?.Invoke(this, message);
}
}
catch (Exception e)
{
FireException(e);
}
}
public async Task Send(object data)
{
lock (_lock)
{
if(_writerIsBroken) //Ignore further calls, since there is no point of writing to "broken" stream
return;
if (_writeOperationPending)
throw new InvalidOperationException("Previous send operation was not finished");
_writeOperationPending = true;
}
try
{
var guid = _resolver.GetGuid(data.GetType()).ToByteArray();
_outputBlock.Seek(0, SeekOrigin.Begin);
_outputBlock.SetLength(0);
_outputBlock.Write(ZeroLength, 0, 4);
_outputBlock.Write(guid, 0, guid.Length);
var serialized = Serializer.Serialize(data);
_outputBlock.Write(serialized, 0, serialized.Length);
_outputBlock.Seek(0, SeekOrigin.Begin);
var length = BitConverter.GetBytes((int)_outputBlock.Length - 20);
_outputBlock.Write(length, 0, length.Length);
_outputBlock.Seek(0, SeekOrigin.Begin);
try
{
await _outputBlock.CopyToAsync(_outputStream, 0x1000, _cancel).ConfigureAwait(false);
}
catch (Exception e) //We are only catching "network"-related exceptions here
{
lock (_lock)
{
_writerIsBroken = true;
}
FireException(e);
}
}
finally
{
lock (_lock)
{
_writeOperationPending = false;
}
}
}
void FireException(Exception e)
{
var cancel = e as OperationCanceledException;
if (cancel?.CancellationToken == _cancel)
return;
OnException?.Invoke(this, e);
}
public event Action<IAvaloniaRemoteTransportConnection, object> OnMessage;
public event Action<IAvaloniaRemoteTransportConnection, Exception> OnException;
public void Start()
{
}
}
}