// See https://aka.ms/new-console-template for more information using System.Net; using System.Net.Sockets; using System.Numerics; using System.Text; using System.Text.RegularExpressions; using static RemoteFrameBufferClientProtocol; IPAddress tessia = new([192,168,16,253]); IRemoteFrameBufferClientStreamProvider tessia6001vnc = new IRemoteFrameBufferClientStreamProvider.RemoteFrameBufferTcpClientStreamProvider(tessia, 5901); RemoteFramebufferClient client = new(tessia6001vnc); client.Start(); Console.ReadLine(); client.Stop(); public interface INetEndPoint { public IPEndPoint EndPoint { get; } public IPAddress Address { get; } public Int32 Port { get; } public class NetEndPointFromIP : INetEndPoint { public IPEndPoint EndPoint { get; } public IPAddress Address => this.EndPoint.Address; public Int32 Port => this.EndPoint.Port; public NetEndPointFromIP(IPEndPoint endpoint) { this.EndPoint = endpoint; } public NetEndPointFromIP(IPAddress address, Int32 port) : this(new(address, port)) { } } public class NetEndPointFromHostname(String hostname, Int32 port) : INetEndPoint { public String Hostname { get; } = hostname; public IPEndPoint EndPoint => new(this.Address, this.Port); public IPAddress Address { get { IPHostEntry entry = Dns.GetHostEntry(this.Hostname); return entry.AddressList[0]; } } public Int32 Port { get; } = port; } } public interface IRemoteFrameBufferClientStreamProvider { public Stream GetRemoteFrameBufferClientStream(); public class RemoteFrameBufferTcpClientStreamProvider : IRemoteFrameBufferClientStreamProvider { private readonly INetEndPoint remoteEndPoint; private readonly IPEndPoint localEndPoint; public RemoteFrameBufferTcpClientStreamProvider(INetEndPoint remoteEndPoint, IPEndPoint? localEndPoint) { this.remoteEndPoint = remoteEndPoint; this.localEndPoint = localEndPoint ?? new(IPAddress.Any, 0); } public Stream GetRemoteFrameBufferClientStream() { TcpClient tcpClient = new TcpClient(this.localEndPoint); tcpClient.Connect(this.remoteEndPoint.EndPoint); return tcpClient.GetStream(); } public RemoteFrameBufferTcpClientStreamProvider(String hostname, Int16 port, IPEndPoint? localEndPoint = null) : this(new INetEndPoint.NetEndPointFromHostname(hostname, port), localEndPoint) { } public RemoteFrameBufferTcpClientStreamProvider(IPAddress address, Int32 port, IPEndPoint? localEndPoint = null) : this(new INetEndPoint.NetEndPointFromIP(address, port), localEndPoint) { } public RemoteFrameBufferTcpClientStreamProvider(IPEndPoint remoteEndPoint, IPEndPoint? localEndPoint = null) : this(new INetEndPoint.NetEndPointFromIP(remoteEndPoint), localEndPoint) { } } } public partial class RemoteFramebufferClient { private static Dictionary _protocolHandlers; private readonly IRemoteFrameBufferClientStreamProvider _socketProvider; private Task? backgroundWorker; private CancellationTokenSource cancellationTokenSource = new(); static RemoteFramebufferClient() { _protocolHandlers = new() { { new RfbProtoVersion(3, 3), new RemoteFrameBufferClientProtocolFactory() }, }; } public RemoteFramebufferClient(IRemoteFrameBufferClientStreamProvider socketProvider) { this._socketProvider = socketProvider; } public void Start() { if (this.backgroundWorker == null) { Console.Error.WriteLine("VNC client starting"); this.backgroundWorker = new Task(this.Worker, this.cancellationTokenSource.Token); this.backgroundWorker.ContinueWith(this.WorkerStopped); this.backgroundWorker.Start(); } } public void Stop() { if (this.backgroundWorker != null) { this.cancellationTokenSource.Cancel(); this.backgroundWorker.Wait(); } } private void Worker() { Int32 failcount = 0; while (!this.cancellationTokenSource.Token.IsCancellationRequested) { Console.Error.WriteLine("Attempting to connect"); Stream s = this._socketProvider.GetRemoteFrameBufferClientStream(); using (s) { Byte[] buf = new Byte[12]; CancellationTokenSource readTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); CancellationTokenSource readTokenSource = CancellationTokenSource.CreateLinkedTokenSource(this.cancellationTokenSource.Token, readTimeout.Token); try { s.ReadExactlyAsync(buf, 0, buf.Length, readTokenSource.Token).AsTask().Wait(); }catch (AggregateException e)// TODO: go through InnerExceptions and only throw if we are left with unhandled ones { if (e.InnerException is TaskCanceledException) { if (failcount++ >= 5) { break; } Task.Delay(1000).Wait(); continue; }else { throw new Exception("Something broke. Yeah, I know, very useful message.", e); } } failcount = 0; String protover = Encoding.ASCII.GetString(buf); Console.Error.Write($"Server protocol: {protover}"); Match m = _rfbVersionRegex().Match(protover); if (!m.Success) { throw new Exception("Cannot parse protocol version"); } RfbProtoVersion serverVersion = new(Int16.Parse(m.Groups["major"].Value), Int16.Parse(m.Groups["minor"].Value)); RfbProtoVersion maxProto = _protocolHandlers.Keys.Where((ph) => ph <= serverVersion).Max(); RemoteFrameBufferClientProtocol proto = _protocolHandlers[maxProto].Construct(s); Console.Error.WriteLine($"Using client protocol {proto.Version.Major}.{proto.Version.Minor}"); } } } private void WorkerStopped(Task t) { Console.Error.WriteLine("VNC client exited"); this.backgroundWorker = null; if (!this.cancellationTokenSource.TryReset()) { this.cancellationTokenSource = new(); } } [GeneratedRegex(@"^RFB (?'major'\d{3}).(?'minor'\d{3})$")] private static partial Regex _rfbVersionRegex(); } public struct RfbProtoVersion(Int16 major, Int16 minor) { public Int16 Major = major; public Int16 Minor = minor; public static Boolean operator ==(RfbProtoVersion a, RfbProtoVersion b) { return (a.Major == b.Major) && (a.Minor == b.Minor); } public static Boolean operator !=(RfbProtoVersion a, RfbProtoVersion b) { return !(a == b); } public static Boolean operator <(RfbProtoVersion a, RfbProtoVersion b) { if (a.Major == b.Major) { return a.Minor < b.Minor; } else { return a.Major < b.Major; } } public static Boolean operator >(RfbProtoVersion a, RfbProtoVersion b) { return b < a; } public static Boolean operator <=(RfbProtoVersion a, RfbProtoVersion b) { return b < a; } public static Boolean operator >=(RfbProtoVersion a, RfbProtoVersion b) { return a < b; } } public abstract class RemoteFrameBufferClientProtocol { protected readonly Stream vncStream; public abstract RfbProtoVersion Version { get; } public RemoteFrameBufferClientProtocol(Stream s) { this.vncStream = s; } public interface IRemoteFramebufferClientProtocolFactory { public abstract RemoteFrameBufferClientProtocol Construct(Stream s); } public class RemoteFrameBufferClientProtocolFactory : IRemoteFramebufferClientProtocolFactory where T : RemoteFrameBufferClientProtocol { public RemoteFrameBufferClientProtocol Construct(Stream s) { return (RemoteFrameBufferClientProtocol)Activator.CreateInstance(typeof(T), s)!; } } public class RemoteFrameBufferClientProtocol_3_3 : RemoteFrameBufferClientProtocol { public override RfbProtoVersion Version => new(3,3); public RemoteFrameBufferClientProtocol_3_3(Stream s) : base(s) { } } }