diff --git a/Tests/SimpleDebugClient/Program.cs b/Tests/SimpleDebugClient/Program.cs
index b815ec1..fbdc00a 100644
--- a/Tests/SimpleDebugClient/Program.cs
+++ b/Tests/SimpleDebugClient/Program.cs
@@ -1,5 +1,6 @@
using Microsoft.Iris.Debug;
using Microsoft.Iris.Debug.Data;
+using Microsoft.Iris.Debug.SystemNet;
using System;
namespace SimpleDebugClient;
@@ -10,12 +11,12 @@ internal class Program
static int Main(string[] args)
{
- string connectionString = args.Length >= 2
- ? args[1] : "tcp://127.0.0.1:5556";
+ var connectionString = args.Length >= 2
+ ? new Uri(args[1]) : DebugRemoting.DEFAULT_TCP_URI;
Console.CancelKeyPress += Console_CancelKeyPress;
- Debugger = new ZmqDebuggerClient(connectionString);
+ Debugger = new NetDebuggerClient(connectionString);
Debugger.DispatcherStep += Debugger_DispatcherStep;
Debugger.InterpreterStep += Debugger_InterpreterStep;
diff --git a/Tests/SimpleIrisApp/Program.cs b/Tests/SimpleIrisApp/Program.cs
index 349263a..d14e869 100644
--- a/Tests/SimpleIrisApp/Program.cs
+++ b/Tests/SimpleIrisApp/Program.cs
@@ -1,4 +1,5 @@
using Microsoft.Iris;
+using Microsoft.Iris.Debug;
using System;
namespace SimpleIrisApp;
@@ -19,7 +20,7 @@ internal class Program
};
Application.DebugSettings.DebugConnectionUri = args.Length >= 2
- ? args[1] : "tcp://127.0.0.1:5556";
+ ? args[1] : DebugRemoting.DEFAULT_TCP_URI.OriginalString;
Application.DebugSettings.Breakpoints.Add(new("clr-res://SimpleIrisApp!MainPage.uix", 3, 22));
#endif
diff --git a/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs b/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs
new file mode 100644
index 0000000..16ca654
--- /dev/null
+++ b/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Text;
+
+namespace Microsoft.Iris.Debug.Data;
+
+public class DebuggerMessageFrame
+{
+ public DebuggerMessageFrame() { }
+
+ public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, byte[] data)
+ {
+ TransactionId = transactionId;
+ Type = type;
+ Data = data;
+ }
+
+ public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, string message, Encoding encoding = null)
+ : this(transactionId, type, (encoding ?? Encoding.UTF8).GetBytes(message))
+ {
+ }
+
+ public DebuggerMessageFrame(byte[] bytes)
+ {
+ TransactionId = BitConverter.ToInt64(bytes, 0);
+ Type = (DebuggerMessageType)BitConverter.ToInt32(bytes, sizeof(long));
+ Data = bytes.AsSpan(sizeof(long) + sizeof(DebuggerMessageType)).ToArray();
+ }
+
+ public long TransactionId { get; set; }
+
+ public DebuggerMessageType Type { get; set; }
+
+ public byte[] Data { get; set; }
+
+ public byte[] ToBytes()
+ {
+ byte[] bytes = new byte[Data.Length + sizeof(DebuggerMessageType) + sizeof(long)];
+
+#if NET5_0_OR_GREATER
+ var span = bytes.AsSpan();
+ BitConverter.TryWriteBytes(span, TransactionId);
+ BitConverter.TryWriteBytes(span[sizeof(long)..], (int)Type);
+#else
+ var idBytes = BitConverter.GetBytes(TransactionId);
+ idBytes.CopyTo(bytes, 0);
+
+ var typeBytes = BitConverter.GetBytes((int)Type);
+ typeBytes.CopyTo(bytes, sizeof(long));
+#endif
+
+ Data.CopyTo(bytes, sizeof(long) + sizeof(DebuggerMessageType));
+
+ return bytes;
+ }
+
+ public string GetDataAsString(Encoding encoding = null) => (encoding ?? Encoding.UTF8).GetString(Data);
+}
diff --git a/UIX/Microsoft/Iris/Debug/DebugRemoting.cs b/UIX/Microsoft/Iris/Debug/DebugRemoting.cs
index a078cea..4a36bad 100644
--- a/UIX/Microsoft/Iris/Debug/DebugRemoting.cs
+++ b/UIX/Microsoft/Iris/Debug/DebugRemoting.cs
@@ -1,4 +1,8 @@
-using System.Runtime.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Sockets;
+using System.Runtime.Serialization;
namespace Microsoft.Iris.Debug;
@@ -7,9 +11,34 @@ namespace Microsoft.Iris.Debug;
///
public static class DebugRemoting
{
- public const string DEFAULT_TCP_CLIENT_URI = ">tcp://127.0.0.1:5555,@tcp://127.0.0.1:55556";
+ public const string DEFAULT_TCP_CLIENT_URI = ">tcp://127.0.0.1:5555,@tcp://127.0.0.1:5556";
+ public const string DEFAULT_TCP_SERVER_URI = "@tcp://127.0.0.1:5555,>tcp://127.0.0.1:5556";
- public const string DEFAULT_TCP_SERVER_URI = "@tcp://127.0.0.1:5555,>tcp://127.0.0.1:55556";
+ public static readonly Uri DEFAULT_TCP_URI = new("tcp://127.0.0.1:5555");
internal static IFormatter CreateBsonFormatter() => new BsonFormatter(new StreamingContext(StreamingContextStates.Remoting));
+
+ internal static Data.DebuggerMessageFrame ReceiveDebuggerMessage(Socket socket)
+ {
+ try
+ {
+ byte[] sizeBuffer = new byte[sizeof(int)];
+ int bytesReceived = socket.Receive(sizeBuffer);
+
+ // Reached end of stream, no bytes to recieve
+ if (bytesReceived == 0)
+ return null;
+
+ int frameLength = BitConverter.ToInt32(sizeBuffer, 0);
+
+ byte[] frameBytes = new byte[frameLength];
+ socket.Receive(frameBytes, frameLength, SocketFlags.None);
+
+ return new(frameBytes);
+ }
+ catch (SocketException)
+ {
+ return null;
+ }
+ }
}
diff --git a/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs
index 5015f44..0d5a2ca 100644
--- a/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs
+++ b/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs
@@ -8,7 +8,7 @@ public interface IDebuggerClient
///
/// The URI the client is connected to.
///
- string ConnectionUri { get; }
+ Uri ConnectionUri { get; }
///
/// Fired when the UIX interpreter steps forward.
diff --git a/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs
index 110baba..ef711a2 100644
--- a/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs
+++ b/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs
@@ -1,4 +1,3 @@
-using Microsoft.Iris.Debug;
using Microsoft.Iris.Debug.Data;
using NetMQ;
using NetMQ.Sockets;
@@ -16,14 +15,14 @@ public class ZmqDebuggerClient : IDebuggerClient, IDisposable
private readonly PairSocket _socket;
private readonly IFormatter _formatter;
- public string ConnectionUri { get; }
+ public Uri ConnectionUri { get; }
public event EventHandler InterpreterStep;
public event Action DispatcherStep;
public ZmqDebuggerClient(string connectionUri)
{
- ConnectionUri = connectionUri ?? DebugRemoting.DEFAULT_TCP_CLIENT_URI;
+ ConnectionUri = new(connectionUri ?? DebugRemoting.DEFAULT_TCP_CLIENT_URI);
_socket = new(connectionUri);
_formatter = DebugRemoting.CreateBsonFormatter();
diff --git a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs
new file mode 100644
index 0000000..4d4a442
--- /dev/null
+++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs
@@ -0,0 +1,89 @@
+using Microsoft.Iris.Debug.Data;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace Microsoft.Iris.Debug.SystemNet;
+
+public class NetDebuggerClient : IDebuggerClient, IDisposable
+{
+ public Uri ConnectionUri { get; }
+
+ private readonly Socket _socket;
+ private readonly Queue _queue;
+ private readonly IFormatter _formatter;
+
+ public event EventHandler InterpreterStep;
+ public event Action DispatcherStep;
+
+ public NetDebuggerClient(string connectionUri) : this(new Uri(connectionUri))
+ {
+ }
+
+ public NetDebuggerClient(Uri connectionUri)
+ {
+ ConnectionUri = connectionUri ?? DebugRemoting.DEFAULT_TCP_URI;
+ _socket = new(SocketType.Stream, ProtocolType.Tcp);
+ _formatter = DebugRemoting.CreateBsonFormatter();
+
+ System.Threading.Thread connectThread = new(ConnectLoop);
+ connectThread.Start();
+ }
+
+ public void Dispose() => _socket.Dispose();
+
+ private void QueueDebuggerMessage(DebuggerMessageFrame frame)
+ {
+ _queue.Enqueue(frame.ToBytes());
+ }
+
+ private void MessageRecieveLoop()
+ {
+ while (_socket.Connected)
+ {
+ DebuggerMessageFrame frame;
+ while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket)) == null)
+ if (!_socket.Connected) return;
+
+ switch (frame.Type)
+ {
+ case DebuggerMessageType.InterpreterOpCode:
+ {
+ using MemoryStream stream = new(frame.Data);
+ var entry = (InterpreterEntry)_formatter.Deserialize(stream);
+ InterpreterStep?.Invoke(this, entry);
+ }
+ break;
+
+ case DebuggerMessageType.DispatcherStep:
+ string message = frame.GetDataAsString();
+ DispatcherStep?.Invoke(message);
+ break;
+
+ default:
+ Trace.WriteLine(TraceCategory.MarkupDebug, "Recieved unknown debugger message of type '{0}'.", frame.Type);
+ break;
+ }
+ }
+ }
+
+ private void ConnectLoop()
+ {
+ while (!_socket.Connected)
+ {
+ try
+ {
+ var endpoint = new IPEndPoint(IPAddress.Parse(ConnectionUri.Host), ConnectionUri.Port);
+ _socket.Connect(endpoint);
+ }
+ catch { }
+ }
+
+ System.Threading.Thread receiveThread = new(MessageRecieveLoop);
+ receiveThread.Start();
+ }
+}
\ No newline at end of file
diff --git a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs
new file mode 100644
index 0000000..f0899cb
--- /dev/null
+++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs
@@ -0,0 +1,106 @@
+using Microsoft.Iris.Debug.Data;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.Serialization;
+using System.Text;
+
+namespace Microsoft.Iris.Debug.SystemNet;
+
+internal class NetDebuggerServer : IDebuggerServer, IDisposable
+{
+ public static IDebuggerServer Current { get; private set; }
+
+ public Uri ConnectionUri { get; }
+
+ private readonly Queue _outQueue = new();
+ private readonly TcpListener _listener;
+ private readonly IFormatter _formatter;
+ private Socket _socket;
+
+ public NetDebuggerServer(string connectionUri) : this(new Uri(connectionUri))
+ {
+ }
+
+ public NetDebuggerServer(Uri connectionUri)
+ {
+ ConnectionUri = connectionUri ?? DebugRemoting.DEFAULT_TCP_URI;
+
+ _listener = TcpListener.Create(connectionUri.Port);
+ _listener.Start();
+
+ _formatter = DebugRemoting.CreateBsonFormatter();
+
+ System.Threading.Thread connectThread = new(ConnectLoop);
+ connectThread.Start();
+
+ Current = this;
+ }
+
+ public void LogInterpreterOpCode(object context, InterpreterEntry entry)
+ {
+ using MemoryStream entryStream = new();
+ _formatter.Serialize(entryStream, entry);
+
+ QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterOpCode, entryStream.ToArray()));
+ }
+
+ public void LogDispatcher(string message)
+ {
+ QueueDebuggerMessage(new(0, DebuggerMessageType.DispatcherStep, message));
+ }
+
+ public void Dispose()
+ {
+ _listener.Stop();
+ _socket?.Dispose();
+ }
+
+ private void QueueDebuggerMessage(DebuggerMessageFrame frame)
+ {
+ _outQueue.Enqueue(frame.ToBytes());
+ }
+
+ private void MessageRecieveLoop()
+ {
+ while (_socket.Connected)
+ {
+ DebuggerMessageFrame frame;
+ while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket)) == null)
+ ;
+
+ switch (frame.Type)
+ {
+ default:
+ Trace.WriteLine(TraceCategory.MarkupDebug, "Recieved unknown debugger message of type '{0}'.", frame.Type);
+ break;
+ }
+ }
+ }
+
+ private void MessageSendLoop()
+ {
+ while (_socket.Connected)
+ {
+ while (_outQueue.Count == 0) ;
+
+ var frameBytes = _outQueue.Dequeue();
+ _socket.Send(BitConverter.GetBytes(frameBytes.Length));
+ _socket.Send(frameBytes);
+ }
+ }
+
+ private void ConnectLoop()
+ {
+ while (!_listener.Pending()) ;
+
+ _socket = _listener.AcceptSocket();
+
+ System.Threading.Thread receiveThread = new(MessageRecieveLoop);
+ System.Threading.Thread sendThread = new(MessageSendLoop);
+ receiveThread.Start();
+ sendThread.Start();
+ }
+}