Convert TCP networking to sync and binary, add DHCP and IP stack init on boot

This commit is contained in:
Stone_Red
2026-06-18 23:54:23 +02:00
parent 79bc87f5c7
commit ba5d670d1a
8 changed files with 345 additions and 143 deletions
+58 -1
View File
@@ -1,5 +1,11 @@
global using Sys = Cosmos.Kernel.System; global using Sys = Cosmos.Kernel.System;
using Cosmos.Kernel.HAL.Interfaces.Devices;
using Cosmos.Kernel.System.Network;
using Cosmos.Kernel.System.Network.Config;
using Cosmos.Kernel.System.Network.IPv4.UDP.DHCP;
using Cosmos.Kernel.System.Timer;
using RemSox.Cryptography; using RemSox.Cryptography;
using RemSox.Processes; using RemSox.Processes;
using RemSox.Processing; using RemSox.Processing;
@@ -11,7 +17,7 @@ namespace RemSox;
/// <summary> /// <summary>
/// Main kernel class - inherits from Cosmos.Kernel.System.Kernel. /// Main kernel class - inherits from Cosmos.Kernel.System.Kernel.
/// </summary> /// </summary>
public class Kernel : Sys.Kernel public partial class Kernel : Sys.Kernel
{ {
protected override void BeforeRun() protected override void BeforeRun()
{ {
@@ -31,6 +37,57 @@ public class Kernel : Sys.Kernel
new StopRemoteDesktopCommand() new StopRemoteDesktopCommand()
]); ]);
// Xml serializer test
Console.WriteLine("[Kernel] Testing XML serialization...");
Thread.Sleep(1000);
System.Drawing.Point point = new(10, 20);
string xml = $"<Point><X>{point.X}</X><Y>{point.Y}</Y></Point>";
Console.WriteLine("[Kernel] XML Serialization Test: " + xml);
Console.WriteLine("[TCP Server] Starting...");
INetworkDevice? device = NetworkManager.PrimaryDevice;
if (device == null)
{
Console.WriteLine("[ERROR] No network device.");
return;
}
Console.WriteLine("[TCP Server] Waiting for link...");
int attempts = 0;
while (!device.LinkUp && attempts < 30)
{
TimerManager.Wait(100);
attempts++;
}
if (!device.Ready)
{
Console.WriteLine("[ERROR] Device not ready.");
return;
}
Console.WriteLine("[TCP Server] Initializing network stack...");
NetworkStack.Initialize();
Console.WriteLine("[TCP Server] Running DHCP...");
DHCPClient dhcp = new DHCPClient();
if (dhcp.SendDiscoverPacket() == -1)
{
Console.WriteLine("[ERROR] DHCP failed.");
return;
}
IPConfig? config = NetworkConfigManager.Get(device);
if (config?.IPAddress == null)
{
Console.WriteLine("[ERROR] No IP from DHCP.");
return;
}
Console.WriteLine("[TCP Server] IP: " + config.IPAddress);
Sys.Graphics.Canvas canvas = Sys.Graphics.FullScreenCanvas.GetFullScreenCanvas(); Sys.Graphics.Canvas canvas = Sys.Graphics.FullScreenCanvas.GetFullScreenCanvas();
Sys.Mouse.MouseManager.Initialize(); Sys.Mouse.MouseManager.Initialize();
+1 -2
View File
@@ -7,12 +7,11 @@ public class TcpConnection(TcpClient client) : IDisposable
{ {
public TcpClient Client { get; } = client; public TcpClient Client { get; } = client;
public NetworkStream Stream { get; } = client.GetStream(); public NetworkStream Stream { get; } = client.GetStream();
public SemaphoreSlim SendLock { get; } = new(1, 1); public object SendLock { get; } = new();
public void Dispose() public void Dispose()
{ {
Stream.Dispose(); Stream.Dispose();
Client.Dispose(); Client.Dispose();
SendLock.Dispose();
} }
} }
+145 -49
View File
@@ -2,9 +2,8 @@ using RemSox.Networking;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.Json; using System.Text;
public abstract class TcpRpcBase(IPacketCrypto? crypto = null) public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
{ {
@@ -15,57 +14,65 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
// Security Guard: Prevent OOM/DoS via oversized length headers (Default: 32MB) // Security Guard: Prevent OOM/DoS via oversized length headers (Default: 32MB)
protected const int MaxMessageSize = 32 * 1024 * 1024; protected const int MaxMessageSize = 32 * 1024 * 1024;
/// <summary> public void ListenTo(string type, Func<TcpConnection, byte[], Task> handler)
/// Base message registration for handlers tracking the sending connection. {
/// </summary> handlers[type] = async (conn, msg) => await handler(conn, msg.Payload);
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] }
public void ListenTo<T>(string type, Func<TcpConnection, T, Task> handler)
public void ListenTo(string type, Func<byte[], Task> handler)
{
handlers[type] = async (conn, msg) => await handler(msg.Payload);
}
public void RespondTo(string type, Func<byte[], Task<byte[]>> handler)
{ {
handlers[type] = async (conn, msg) => handlers[type] = async (conn, msg) =>
{ {
T data = JsonSerializer.Deserialize<T>(msg.Payload)!; byte[] res = await handler(msg.Payload);
await handler(conn, data); SendRaw(conn, new TcpMessage
{
Type = type,
RequestId = msg.RequestId,
Payload = res
});
}; };
} }
/// <summary> protected void SendRaw(TcpConnection conn, TcpMessage msg)
/// Overloaded listener where connection mapping can be ignored (Convenient for Clients).
/// </summary>
public void ListenTo<T>(string type, Func<T, Task> handler)
{ {
ListenTo<T>(type, async (_, data) => await handler(data)); byte[] rawData = SerializeMessage(msg);
}
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")]
protected async Task SendRaw(TcpConnection conn, TcpMessage msg)
{
byte[] data = JsonSerializer.SerializeToUtf8Bytes(msg);
if (crypto is not null) if (crypto is not null)
{ {
data = crypto.Encrypt(data); rawData = crypto.Encrypt(rawData);
} }
// Allocate a single contiguous frame buffer to prevent inter-thread fragmentation byte[] packet = new byte[4 + rawData.Length];
// and eliminate redundant Socket Write Syscalls. BinaryPrimitives.WriteInt32LittleEndian(packet, rawData.Length);
byte[] packet = new byte[4 + data.Length]; Array.Copy(rawData, 0, packet, 4, rawData.Length);
BinaryPrimitives.WriteInt32LittleEndian(packet, data.Length);
Array.Copy(data, 0, packet, 4, data.Length);
// Enforce sequence safety across multiple threads pushing data out of a singular stream lock (conn.SendLock)
await conn.SendLock.WaitAsync();
try
{ {
await conn.Stream.WriteAsync(packet); conn.Stream.Write(packet, 0, packet.Length);
}
finally
{
_ = conn.SendLock.Release();
} }
} }
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] private static void ReadExact(NetworkStream stream, byte[] buffer, int offset, int count)
protected async Task HandleConnection(TcpConnection conn, CancellationToken token = default) {
int totalRead = 0;
while (totalRead < count)
{
int read = stream.Read(buffer, offset + totalRead, count - totalRead);
if (read == 0)
{
Thread.Sleep(10);
continue;
}
totalRead += read;
}
}
protected void HandleConnection(TcpConnection conn, CancellationToken token = default)
{ {
NetworkStream stream = conn.Stream; NetworkStream stream = conn.Stream;
byte[] lengthBytes = new byte[4]; byte[] lengthBytes = new byte[4];
@@ -74,32 +81,41 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
{ {
while (!token.IsCancellationRequested && conn.Client.Connected) while (!token.IsCancellationRequested && conn.Client.Connected)
{ {
// 1. Frame Length Read Console.WriteLine("[TcpRpc] Waiting for incoming packet...");
await stream.ReadExactlyAsync(lengthBytes, token);
ReadExact(stream, lengthBytes, 0, 4);
int length = BinaryPrimitives.ReadInt32LittleEndian(lengthBytes); int length = BinaryPrimitives.ReadInt32LittleEndian(lengthBytes);
// DoS Payload Protection Check
if (length is <= 0 or > MaxMessageSize) if (length is <= 0 or > MaxMessageSize)
{ {
throw new InvalidDataException($"Protocol violation: Received packet length of {length} bytes exceeds limits."); Console.WriteLine($"[TcpRpc] Invalid packet length: {length}");
Thread.Sleep(10);
continue;
} }
// 2. Body Payload Read Console.WriteLine($"[TcpRpc] Incoming packet length: {length} bytes");
byte[] buffer = new byte[length]; byte[] buffer = new byte[length];
await stream.ReadExactlyAsync(buffer, token); ReadExact(stream, buffer, 0, length);
Console.WriteLine($"[TcpRpc] Incoming packet payload: {BitConverter.ToString(buffer)}");
if (crypto is not null) if (crypto is not null)
{ {
buffer = crypto.Decrypt(buffer); buffer = crypto.Decrypt(buffer);
} }
TcpMessage? msg = JsonSerializer.Deserialize<TcpMessage>(buffer); Console.WriteLine($"[TcpRpc] Decrypted packet payload: {BitConverter.ToString(buffer)}");
TcpMessage? msg = DeserializeMessage(buffer);
if (msg is null) if (msg is null)
{ {
Console.WriteLine("[TcpRpc] Failed to deserialize incoming message.");
continue; continue;
} }
// 3. Response Handler Check Console.WriteLine($"[TcpRpc] Incoming message type: {msg.Type}, requestId: {msg.RequestId}, payload length: {msg.Payload.Length} bytes");
if (pendingRequests.TryGetValue(msg.RequestId, out TaskCompletionSource<byte[]>? tcs)) if (pendingRequests.TryGetValue(msg.RequestId, out TaskCompletionSource<byte[]>? tcs))
{ {
_ = tcs.TrySetResult(msg.Payload); _ = tcs.TrySetResult(msg.Payload);
@@ -107,11 +123,8 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
continue; continue;
} }
// 4. Inbound Router Handler
if (handlers.TryGetValue(msg.Type, out Func<TcpConnection, TcpMessage, Task>? handler)) if (handlers.TryGetValue(msg.Type, out Func<TcpConnection, TcpMessage, Task>? handler))
{ {
// Decouple handler execution to the ThreadPool so slow business logic
// doesn't bottleneck packet processing from the network stream interface.
_ = Task.Run(async () => _ = Task.Run(async () =>
{ {
try try
@@ -120,7 +133,6 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
} }
catch catch
{ {
// Operational log placement here for user exceptions inside delegates
} }
}, token); }, token);
} }
@@ -128,7 +140,6 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
} }
catch catch
{ {
// Explicitly handles natural dropping scenarios cleanly
} }
finally finally
{ {
@@ -137,5 +148,90 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
} }
} }
private static byte[] SerializeMessage(TcpMessage msg)
{
byte[] typeBytes = Encoding.UTF8.GetBytes(msg.Type);
byte[] requestIdBytes = Encoding.UTF8.GetBytes(msg.RequestId);
byte[] data = new byte[4 + typeBytes.Length + 4 + requestIdBytes.Length + 4 + msg.Payload.Length];
int offset = 0;
WriteInt32(data, ref offset, typeBytes.Length);
typeBytes.CopyTo(data, offset);
offset += typeBytes.Length;
WriteInt32(data, ref offset, requestIdBytes.Length);
requestIdBytes.CopyTo(data, offset);
offset += requestIdBytes.Length;
WriteInt32(data, ref offset, msg.Payload.Length);
msg.Payload.CopyTo(data, offset);
return data;
}
private static TcpMessage? DeserializeMessage(byte[] data)
{
int offset = 0;
if (offset + 4 > data.Length)
{
return null;
}
int typeLen = ReadInt32(data, ref offset);
if (offset + typeLen > data.Length)
{
return null;
}
string type = Encoding.UTF8.GetString(data, offset, typeLen);
offset += typeLen;
if (offset + 4 > data.Length)
{
return null;
}
int requestIdLen = ReadInt32(data, ref offset);
if (offset + requestIdLen > data.Length)
{
return null;
}
string requestId = Encoding.UTF8.GetString(data, offset, requestIdLen);
offset += requestIdLen;
if (offset + 4 > data.Length)
{
return null;
}
int payloadLen = ReadInt32(data, ref offset);
if (offset + payloadLen > data.Length)
{
return null;
}
byte[] payload = new byte[payloadLen];
Array.Copy(data, offset, payload, 0, payloadLen);
return new TcpMessage { Type = type, RequestId = requestId, Payload = payload };
}
private static void WriteInt32(byte[] data, ref int offset, int value)
{
data[offset++] = (byte)(value & 0xFF);
data[offset++] = (byte)((value >> 8) & 0xFF);
data[offset++] = (byte)((value >> 16) & 0xFF);
data[offset++] = (byte)((value >> 24) & 0xFF);
}
private static int ReadInt32(byte[] data, ref int offset)
{
int val = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24);
offset += 4;
return val;
}
protected abstract void OnConnectionClosed(TcpConnection conn); protected abstract void OnConnectionClosed(TcpConnection conn);
} }
+13 -20
View File
@@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.Json;
namespace RemSox.Networking; namespace RemSox.Networking;
@@ -8,19 +7,17 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
{ {
private TcpConnection? connection; private TcpConnection? connection;
public async Task ConnectAsync(string host, int port) public void Connect(IPAddress ipAddress, int port)
{ {
TcpClient client = new(); TcpClient client = new();
await client.ConnectAsync(host, port); client.Connect(ipAddress, port);
connection = new TcpConnection(client); connection = new TcpConnection(client);
// Run network monitoring task loop background-detached _ = Task.Run(() => HandleConnection(connection));
_ = HandleConnection(connection);
} }
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] public async Task<byte[]> RequestAsync(string type, byte[] request, TimeSpan timeout = default)
public async Task<TRes> RequestAsync<TReq, TRes>(string type, TReq request, TimeSpan timeout = default)
{ {
if (connection is null) if (connection is null)
{ {
@@ -29,7 +26,7 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
if (timeout == default) if (timeout == default)
{ {
timeout = TimeSpan.FromSeconds(30); // Default fallback timeout timeout = TimeSpan.FromSeconds(30);
} }
string requestId = Guid.NewGuid().ToString(); string requestId = Guid.NewGuid().ToString();
@@ -38,19 +35,17 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
try try
{ {
await SendRaw(connection, new TcpMessage SendRaw(connection, new TcpMessage
{ {
Type = type, Type = type,
RequestId = requestId, RequestId = requestId,
Payload = JsonSerializer.SerializeToUtf8Bytes(request) Payload = request
}); });
// Enforce async timeout safety to prevent permanent dictionary leaks on dropped calls
using CancellationTokenSource timeoutCts = new(timeout); using CancellationTokenSource timeoutCts = new(timeout);
await using (timeoutCts.Token.Register(() => tcs.TrySetCanceled())) using (timeoutCts.Token.Register(() => tcs.TrySetCanceled()))
{ {
byte[] responseBytes = await tcs.Task; return await tcs.Task;
return JsonSerializer.Deserialize<TRes>(responseBytes)!;
} }
} }
finally finally
@@ -59,19 +54,18 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
} }
} }
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] public void Send(string type, byte[] data)
public Task SendAsync<T>(string type, T data)
{ {
if (connection is null) if (connection is null)
{ {
throw new InvalidOperationException("Client not connected."); throw new InvalidOperationException("Client not connected.");
} }
return SendRaw(connection, new TcpMessage SendRaw(connection, new TcpMessage
{ {
Type = type, Type = type,
RequestId = Guid.NewGuid().ToString(), RequestId = Guid.NewGuid().ToString(),
Payload = JsonSerializer.SerializeToUtf8Bytes(data) Payload = data
}); });
} }
@@ -82,7 +76,6 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
connection = null; connection = null;
} }
// Fail-fast all lingering tasks waiting on an dead connection loop
foreach (TaskCompletionSource<byte[]> req in pendingRequests.Values) foreach (TaskCompletionSource<byte[]> req in pendingRequests.Values)
{ {
_ = req.TrySetException(new SocketException((int)SocketError.ConnectionReset)); _ = req.TrySetException(new SocketException((int)SocketError.ConnectionReset));
+21 -51
View File
@@ -1,19 +1,17 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text.Json;
namespace RemSox.Networking; namespace RemSox.Networking;
public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto) public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
{ {
// Fix: Using ConcurrentDictionary to prevent collection errors during structural additions/prunings
private readonly ConcurrentDictionary<TcpConnection, byte> connections = new(); private readonly ConcurrentDictionary<TcpConnection, byte> connections = new();
private readonly ConcurrentDictionary<string, byte> activeEndpoints = new();
private TcpListener? listener; private TcpListener? listener;
private CancellationTokenSource? cts; private CancellationTokenSource? cts;
public async Task StartAsync(int port, CancellationToken token = default) public void StartAsync(int port, CancellationToken token = default)
{ {
cts = CancellationTokenSource.CreateLinkedTokenSource(token); cts = CancellationTokenSource.CreateLinkedTokenSource(token);
@@ -24,17 +22,25 @@ public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
{ {
while (!cts.IsCancellationRequested) while (!cts.IsCancellationRequested)
{ {
TcpClient client = await listener.AcceptTcpClientAsync(cts.Token); TcpClient client = listener.AcceptTcpClient();
string ep = client.Client.RemoteEndPoint?.ToString() ?? "";
if (!activeEndpoints.TryAdd(ep, 0))
{
client.Close();
Thread.Sleep(10);
continue;
}
Console.WriteLine($"Client connected: {ep}");
TcpConnection conn = new(client); TcpConnection conn = new(client);
_ = connections.TryAdd(conn, 0); _ = connections.TryAdd(conn, 0);
_ = HandleConnection(conn, cts.Token); _ = Task.Run(() => HandleConnection(conn, cts.Token));
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// Expected shutdown scenario
} }
} }
@@ -51,62 +57,26 @@ public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
connections.Clear(); connections.Clear();
} }
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] public void SendRawToAll(string type, byte[] payload)
public void RespondTo<TReq, TRes>(string type, Func<TReq, Task<TRes>> handler)
{ {
handlers[type] = async (conn, msg) =>
{
TReq req = JsonSerializer.Deserialize<TReq>(msg.Payload)!;
TRes res = await handler(req);
await SendRaw(conn, new TcpMessage
{
Type = type,
RequestId = msg.RequestId,
Payload = JsonSerializer.SerializeToUtf8Bytes(res)
});
};
}
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")]
public Task SendToAll<T>(string type, T data)
{
byte[] payloadBytes = JsonSerializer.SerializeToUtf8Bytes(data);
List<Task> sendTasks = [];
foreach (TcpConnection conn in connections.Keys) foreach (TcpConnection conn in connections.Keys)
{ {
sendTasks.Add(SendRaw(conn, new TcpMessage SendRaw(conn, new TcpMessage
{
Type = type,
RequestId = Guid.NewGuid().ToString(),
Payload = payloadBytes
}));
}
return Task.WhenAll(sendTasks);
}
/// <summary> Broadcasts raw binary payload to all connected clients. </summary>
public Task SendRawToAll(string type, byte[] payload)
{
List<Task> sendTasks = [];
foreach (TcpConnection conn in connections.Keys)
{
sendTasks.Add(SendRaw(conn, new TcpMessage
{ {
Type = type, Type = type,
RequestId = Guid.NewGuid().ToString(), RequestId = Guid.NewGuid().ToString(),
Payload = payload Payload = payload
})); });
} }
return Task.WhenAll(sendTasks);
} }
protected override void OnConnectionClosed(TcpConnection conn) protected override void OnConnectionClosed(TcpConnection conn)
{ {
_ = connections.TryRemove(conn, out _); _ = connections.TryRemove(conn, out _);
string ep = conn.Client.Client.RemoteEndPoint?.ToString() ?? "";
_ = activeEndpoints.TryRemove(ep, out _);
Console.WriteLine($"Client disconnected: {ep}");
} }
} }
+104 -18
View File
@@ -7,6 +7,8 @@ using RemSox.UI;
using RemSox.UI.GUI.Rendering; using RemSox.UI.GUI.Rendering;
using RemSox.UI.GUI.Windows; using RemSox.UI.GUI.Windows;
using System.Text;
namespace RemSox.Processes; namespace RemSox.Processes;
internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server") internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
@@ -17,12 +19,6 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
public int Port { get; private set; } public int Port { get; private set; }
// Remote input message record types (JSON-serialized over TCP)
private sealed record MouseMoveMsg(int X, int Y);
private sealed record MouseButtonMsg(int X, int Y, string Button);
private sealed record MouseWheelMsg(int X, int Y, int Delta);
private sealed record KeyEventMsg(int Key, string KeyChar, bool Shift, bool Alt, bool Control, bool Pressed);
internal override void Start(string[] args) internal override void Start(string[] args)
{ {
if (args.Length == 0 || !int.TryParse(args[0], out int port) || port <= 0 || port > 65535) if (args.Length == 0 || !int.TryParse(args[0], out int port) || port <= 0 || port > 65535)
@@ -37,7 +33,7 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
server = new TcpRpcServer(); server = new TcpRpcServer();
networkSource = new NetworkRenderSource(server); networkSource = new NetworkRenderSource(server);
server.ListenTo<string>("SyncRequest", async _ => server.ListenTo("SyncRequest", async _ =>
{ {
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas(); Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
RenderCommand screenInfo = new() RenderCommand screenInfo = new()
@@ -88,35 +84,37 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
return; return;
} }
server.ListenTo<MouseMoveMsg>("MouseMove", async (msg) => server.ListenTo("MouseMove", async (payload) =>
{ {
var msg = DeserializeMouseMove(payload);
WindowManager.EnqueueMouseEvent(MouseEvent.Move(msg.X, msg.Y)); WindowManager.EnqueueMouseEvent(MouseEvent.Move(msg.X, msg.Y));
}); });
server.ListenTo<MouseButtonMsg>("MouseDown", async (msg) => server.ListenTo("MouseDown", async (payload) =>
{ {
MouseButton button = ParseButton(msg.Button); var msg = DeserializeMouseButton(payload);
WindowManager.EnqueueMouseEvent(MouseEvent.ButtonDown(msg.X, msg.Y, button)); WindowManager.EnqueueMouseEvent(MouseEvent.ButtonDown(msg.X, msg.Y, ParseButton(msg.Button)));
}); });
server.ListenTo<MouseButtonMsg>("MouseUp", async (msg) => server.ListenTo("MouseUp", async (payload) =>
{ {
MouseButton button = ParseButton(msg.Button); var msg = DeserializeMouseButton(payload);
WindowManager.EnqueueMouseEvent(MouseEvent.ButtonUp(msg.X, msg.Y, button)); WindowManager.EnqueueMouseEvent(MouseEvent.ButtonUp(msg.X, msg.Y, ParseButton(msg.Button)));
}); });
server.ListenTo<MouseWheelMsg>("MouseWheel", async (msg) => server.ListenTo("MouseWheel", async (payload) =>
{ {
var msg = DeserializeMouseWheel(payload);
WindowManager.EnqueueMouseEvent(MouseEvent.Wheel(msg.X, msg.Y, msg.Delta)); WindowManager.EnqueueMouseEvent(MouseEvent.Wheel(msg.X, msg.Y, msg.Delta));
}); });
server.ListenTo<KeyEventMsg>("KeyEvent", async (msg) => server.ListenTo("KeyEvent", async (payload) =>
{ {
var msg = DeserializeKeyEvent(payload);
ConsoleKeyEx key = (ConsoleKeyEx)msg.Key; ConsoleKeyEx key = (ConsoleKeyEx)msg.Key;
char keyChar = msg.KeyChar.Length > 0 ? msg.KeyChar[0] : '\0'; char keyChar = msg.KeyChar.Length > 0 ? msg.KeyChar[0] : '\0';
bool isPressed = msg.Pressed;
KeyEvent keyEvent = new(keyChar, key, msg.Shift, msg.Alt, msg.Control, isPressed ? KeyEvent.KeyEventType.Make : KeyEvent.KeyEventType.Break); KeyEvent keyEvent = new(keyChar, key, msg.Shift, msg.Alt, msg.Control, msg.Pressed ? KeyEvent.KeyEventType.Make : KeyEvent.KeyEventType.Break);
WindowManager.EnqueueKeyEvent(keyEvent); WindowManager.EnqueueKeyEvent(keyEvent);
}); });
} }
@@ -131,4 +129,92 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
_ => MouseButton.None _ => MouseButton.None
}; };
} }
private static byte[] SerializeMouseMove(int x, int y)
{
byte[] data = new byte[8];
WriteInt32(data, 0, x);
WriteInt32(data, 4, y);
return data;
}
private static (int X, int Y) DeserializeMouseMove(byte[] data)
{
return (ReadInt32(data, 0), ReadInt32(data, 4));
}
private static byte[] SerializeMouseButton(int x, int y, string button)
{
byte[] buttonBytes = Encoding.UTF8.GetBytes(button);
byte[] data = new byte[8 + 4 + buttonBytes.Length];
WriteInt32(data, 0, x);
WriteInt32(data, 4, y);
WriteInt32(data, 8, buttonBytes.Length);
buttonBytes.CopyTo(data, 12);
return data;
}
private static (int X, int Y, string Button) DeserializeMouseButton(byte[] data)
{
int x = ReadInt32(data, 0);
int y = ReadInt32(data, 4);
int len = ReadInt32(data, 8);
string button = Encoding.UTF8.GetString(data, 12, len);
return (x, y, button);
}
private static byte[] SerializeMouseWheel(int x, int y, int delta)
{
byte[] data = new byte[12];
WriteInt32(data, 0, x);
WriteInt32(data, 4, y);
WriteInt32(data, 8, delta);
return data;
}
private static (int X, int Y, int Delta) DeserializeMouseWheel(byte[] data)
{
return (ReadInt32(data, 0), ReadInt32(data, 4), ReadInt32(data, 8));
}
private static byte[] SerializeKeyEvent(int key, string keyChar, bool shift, bool alt, bool control, bool pressed)
{
byte[] charBytes = Encoding.UTF8.GetBytes(keyChar);
byte[] data = new byte[4 + 4 + charBytes.Length + 4];
WriteInt32(data, 0, key);
WriteInt32(data, 4, charBytes.Length);
charBytes.CopyTo(data, 8);
int offset = 8 + charBytes.Length;
data[offset++] = shift ? (byte)1 : (byte)0;
data[offset++] = alt ? (byte)1 : (byte)0;
data[offset++] = control ? (byte)1 : (byte)0;
data[offset] = pressed ? (byte)1 : (byte)0;
return data;
}
private static (int Key, string KeyChar, bool Shift, bool Alt, bool Control, bool Pressed) DeserializeKeyEvent(byte[] data)
{
int key = ReadInt32(data, 0);
int charLen = ReadInt32(data, 4);
string keyChar = Encoding.UTF8.GetString(data, 8, charLen);
int offset = 8 + charLen;
bool shift = data[offset] != 0;
bool alt = data[offset + 1] != 0;
bool control = data[offset + 2] != 0;
bool pressed = data[offset + 3] != 0;
return (key, keyChar, shift, alt, control, pressed);
}
private static void WriteInt32(byte[] data, int offset, int value)
{
data[offset] = (byte)(value & 0xFF);
data[offset + 1] = (byte)((value >> 8) & 0xFF);
data[offset + 2] = (byte)((value >> 16) & 0xFF);
data[offset + 3] = (byte)((value >> 24) & 0xFF);
}
private static int ReadInt32(byte[] data, int offset)
{
return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24);
}
} }
+1 -1
View File
@@ -11,7 +11,7 @@ public sealed class NetworkRenderSource(TcpRpcServer server) : IRenderSource
foreach (RenderCommand cmd in commands) foreach (RenderCommand cmd in commands)
{ {
byte[] data = cmd.ToBytes(); byte[] data = cmd.ToBytes();
_ = server.SendRawToAll(MessageType, data); server.SendRawToAll(MessageType, data);
} }
} }
+2 -1
View File
@@ -3,4 +3,5 @@ Set-Location $PSScriptRoot
cosmos build cosmos build
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
cosmos run #cosmos run
bash -c "qemu-system-x86_64 -M q35 -cpu max -m 512M -serial stdio -cdrom ./output-x64/RemSox.iso -vga std -nic user,hostfwd=tcp::9999-:9999"