using Microsoft.Iris.Debug.Data; using System; using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; using System.Threading; #pragma warning disable SYSLIB0011 namespace Microsoft.Iris.Debug.SystemNet; public class NetDebuggerClient : IDebuggerClient, IRemoteDebuggerState, IDisposable { private readonly Socket _socket; private readonly ConcurrentQueue _outQueue = new(); private readonly ConcurrentDictionary> _requests = new(); private readonly IFormatter _formatter; private InterpreterCommand _uibCommand; private long _nextFreeTransactionId = 1; public string ConnectionString => ConnectionUri.ToString(); public Uri ConnectionUri { get; } public InterpreterCommand DebuggerCommand { get => _uibCommand; set { QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterCommand, value, _formatter)); _uibCommand = value; } } public event EventHandler InterpreterDecode; public event EventHandler InterpreterExecute; public event Action DispatcherStep; public event Action InterpreterStateChanged; public event Action Connected; public NetDebuggerClient(Uri connectionUri = null) { ConnectionUri = connectionUri ?? DebugRemoting.DEFAULT_TCP_URI; _socket = new(SocketType.Stream, ProtocolType.Tcp); _formatter = DebugRemoting.CreateBsonFormatter(); } public void Start() { Thread connectThread = new(ConnectLoop) { IsBackground = true }; connectThread.Start(); } public void Dispose() { if (_socket != null) { if (_socket.Connected) _socket.Disconnect(false); _socket.Close(); } } public void RequestLineNumberTable(string uri, Action callback) { DebuggerMessageFrame frame = new(GetNextTransactionId(), DebuggerMessageType.LineNumberTable, uri, _formatter); _requests.TryAdd(frame.TransactionId, o => callback((MarkupLineNumberEntry[])o)); QueueDebuggerMessage(frame); } public void UpdateBreakpoint(Breakpoint breakpoint) { QueueDebuggerMessage(new(0, DebuggerMessageType.UpdateBreakpoint, breakpoint, _formatter)); } private void QueueDebuggerMessage(DebuggerMessageFrame frame) { _outQueue.Enqueue(frame.ToBytes()); } private long GetNextTransactionId() { var currentId = _nextFreeTransactionId; if (currentId == -1) _nextFreeTransactionId += 2; else if (currentId == long.MaxValue) _nextFreeTransactionId = long.MinValue; else ++_nextFreeTransactionId; return currentId; } private void MessageReceiveLoop() { while (_socket.Connected) { DebuggerMessageFrame frame; while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket, _formatter)) == null) if (!_socket.Connected) return; switch (frame.Type) { case DebuggerMessageType.InterpreterDecode: var decEntry = frame.GetValue(); InterpreterDecode?.Invoke(this, decEntry); break; case DebuggerMessageType.InterpreterExecute: var execEntry = frame.GetValue(); InterpreterExecute?.Invoke(this, execEntry); break; case DebuggerMessageType.DispatcherStep: string message = frame.GetValue(); DispatcherStep?.Invoke(message); break; case DebuggerMessageType.InterpreterCommand: _uibCommand = frame.GetValue(); InterpreterStateChanged?.Invoke(_uibCommand); break; case DebuggerMessageType.LineNumberTable: if (_requests.TryRemove(frame.TransactionId, out var callback)) callback(frame.GetValue()); break; default: Trace.WriteLine(TraceCategory.MarkupDebug, "Received unknown debugger message of type '{0}'.", frame.Type); break; } } } private void MessageSendLoop() { while (_socket.Connected) { byte[] frameBytes; while (!_outQueue.TryDequeue(out frameBytes)) ; _socket.Send(BitConverter.GetBytes(frameBytes.Length)); _socket.Send(frameBytes); } } private void ConnectLoop() { var endpoint = new IPEndPoint(IPAddress.Parse(ConnectionUri.Host), ConnectionUri.Port); while (!_socket.Connected) { try { _socket.Connect(endpoint); } catch { } Thread.Sleep(500); } Connected?.Invoke(this, _socket); Thread receiveThread = new(MessageReceiveLoop) { IsBackground = true }; Thread sendThread = new(MessageSendLoop) { IsBackground = true }; receiveThread.Start(); sendThread.Start(); } }