Files
MicrosoftIris/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs

172 lines
5.4 KiB
C#

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<byte[]> _outQueue = new();
private readonly ConcurrentDictionary<long, Action<object>> _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<InterpreterInstruction> InterpreterDecode;
public event EventHandler<InterpreterEntry> InterpreterExecute;
public event Action<string> DispatcherStep;
public event Action<InterpreterCommand> InterpreterStateChanged;
public event Action<IDebuggerState, object> 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<MarkupLineNumberEntry[]> 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<InterpreterInstruction>();
InterpreterDecode?.Invoke(this, decEntry);
break;
case DebuggerMessageType.InterpreterExecute:
var execEntry = frame.GetValue<InterpreterEntry>();
InterpreterExecute?.Invoke(this, execEntry);
break;
case DebuggerMessageType.DispatcherStep:
string message = frame.GetValue<string>();
DispatcherStep?.Invoke(message);
break;
case DebuggerMessageType.InterpreterCommand:
_uibCommand = frame.GetValue<InterpreterCommand>();
InterpreterStateChanged?.Invoke(_uibCommand);
break;
case DebuggerMessageType.LineNumberTable:
if (_requests.TryRemove(frame.TransactionId, out var callback))
callback(frame.GetValue<MarkupLineNumberEntry[]>());
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();
}
}