diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/CdpChannel.cs b/lib/ShortDev.Microsoft.ConnectedDevices/CdpChannel.cs index 5b30378..b079cc6 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/CdpChannel.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/CdpChannel.cs @@ -2,8 +2,6 @@ using ShortDev.Microsoft.ConnectedDevices.Messages.Control; using ShortDev.Microsoft.ConnectedDevices.Messages.Session; using ShortDev.Microsoft.ConnectedDevices.Transports; -using System; -using System.Collections.Generic; namespace ShortDev.Microsoft.ConnectedDevices; @@ -58,14 +56,14 @@ public void SendBinaryMessage(BodyCallback bodyCallback, uint msgId, List + EndianWriter writer = new(Endianness.BigEndian); + new BinaryMsgHeader() { - new BinaryMsgHeader() - { - MessageId = msgId - }.Write(writer); - bodyCallback(writer); - }); + MessageId = msgId + }.Write(writer); + bodyCallback(writer); + + Session.SendMessage(Socket, header, writer); } void IDisposable.Dispose() diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/CdpSession.cs b/lib/ShortDev.Microsoft.ConnectedDevices/CdpSession.cs index 30a38d5..a0127f5 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/CdpSession.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/CdpSession.cs @@ -95,67 +95,27 @@ internal static async Task CreateClientAndConnectAsync(ConnectedDevi } #endregion - public void SendMessage(CdpSocket socket, CommonHeader header, BodyCallback bodyCallback, bool supplyRequestId = false) + public void SendMessage(CdpSocket socket, CommonHeader header, EndianWriter payloadWriter, bool supplyRequestId = false) + => SendMessage(socket, header, payloadWriter.Buffer.AsSpan(), supplyRequestId); + + public void SendMessage(CdpSocket socket, CommonHeader header, ReadOnlySpan payload, bool supplyRequestId = false) { if (header.Type == MessageType.Session && _cryptor == null) throw new InvalidOperationException("Invalid session state!"); - // header - { - header.SessionId = SessionId.AsNumber(); - - if (supplyRequestId) - header.RequestID = RequestId(); - - if (header.Type != MessageType.Connect) - header.SequenceNumber = SequenceNumber(); - - // "CDPSvc" crashes if not supplied (AccessViolation in ShareHost.dll!ExtendCorrelationVector) - if (header.Type == MessageType.Session) - header.AdditionalHeaders.Add(AdditionalHeader.CreateCorrelationHeader()); - } - - EndianWriter payloadWriter = new(Endianness.BigEndian); - bodyCallback(payloadWriter); - var payload = payloadWriter.Buffer.AsSpan(); - - if (payload.Length <= Constants.DefaultMessageFragmentSize) - { - SendFragment(header, payload); - return; - } - - header.FragmentCount = (ushort)(payload.Length / Constants.DefaultMessageFragmentSize); + header.SessionId = SessionId.AsNumber(); - var leftover = payload.Length % Constants.DefaultMessageFragmentSize; - if (leftover != 0) - header.FragmentCount++; + if (supplyRequestId) + header.RequestID = RequestId(); - for (ushort fragmentIndex = 0; fragmentIndex < header.FragmentCount; fragmentIndex++) - { - header.FragmentIndex = fragmentIndex; + if (header.Type != MessageType.Connect) + header.SequenceNumber = SequenceNumber(); - int start = fragmentIndex * Constants.DefaultMessageFragmentSize; - int length = Math.Min(payload.Length - start, Constants.DefaultMessageFragmentSize); - SendFragment(header, payload.Slice(start, length)); - } + // "CDPSvc" crashes if not supplied (AccessViolation in ShareHost.dll!ExtendCorrelationVector) + if (header.Type == MessageType.Session) + header.AdditionalHeaders.Add(AdditionalHeader.CreateCorrelationHeader()); - void SendFragment(CommonHeader header, ReadOnlySpan fragmentPayload) - { - EndianWriter writer = new(Endianness.BigEndian); - if (_cryptor != null) - { - _cryptor.EncryptMessage(writer, header, fragmentPayload); - } - else - { - header.SetPayloadLength(fragmentPayload.Length); - header.Write(writer); - writer.Write(fragmentPayload); - } - - socket.SendData(writer); - } + socket.SendMessage(header, payload, _cryptor); } #region HandleMessages @@ -203,7 +163,7 @@ sealed class ConnectHandler(CdpSession session, UpgradeHandler upgradeHandler) readonly UpgradeHandler _upgradeHandler = upgradeHandler; TaskCompletionSource? _currentConnectPromise; - public async Task ConnectAsync(CdpSocket socket, bool upgradeSupported = true) + public Task ConnectAsync(CdpSocket socket, bool upgradeSupported = true) { if (_currentConnectPromise != null) throw new InvalidOperationException("Already connecting"); @@ -221,27 +181,27 @@ public async Task ConnectAsync(CdpSocket socket, bool upgradeSupported = true) } }; - _session.SendMessage(socket, header, writer => + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.ConnectRequest - }.Write(writer); + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.ConnectRequest + }.Write(writer); - var publicKey = _session._localEncryption.PublicKey; - new ConnectionRequest() - { - CurveType = CurveType.CT_NIST_P256_KDF_SHA512, - HmacSize = Constants.HMacSize, - MessageFragmentSize = Constants.DefaultMessageFragmentSize, - Nonce = _session._localEncryption.Nonce, - PublicKeyX = publicKey.X!, - PublicKeyY = publicKey.Y! - }.Write(writer); - }); + var publicKey = _session._localEncryption.PublicKey; + new ConnectionRequest() + { + CurveType = CurveType.CT_NIST_P256_KDF_SHA512, + HmacSize = Constants.HMacSize, + MessageFragmentSize = MessageFragmenter.DefaultMessageFragmentSize, + Nonce = _session._localEncryption.Nonce, + PublicKeyX = publicKey.X!, + PublicKeyY = publicKey.Y! + }.Write(writer); + + _session.SendMessage(socket, header, writer); - await _currentConnectPromise.Task; + return _currentConnectPromise.Task; } public void HandleConnect(CdpSocket socket, CommonHeader header, ref EndianReader reader) @@ -322,25 +282,25 @@ void HandleConnectRequest(CommonHeader header, ref EndianReader reader, CdpSocke var connectionRequest = ConnectionRequest.Parse(ref reader); _session._remoteEncryption = CdpEncryptionInfo.FromRemote(connectionRequest.PublicKeyX, connectionRequest.PublicKeyY, connectionRequest.Nonce, CdpEncryptionParams.Default); - _session.SendMessage(socket, header, writer => + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.ConnectResponse - }.Write(writer); + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.ConnectResponse + }.Write(writer); - var publicKey = _session._localEncryption.PublicKey; - new ConnectionResponse() - { - Result = ConnectionResult.Pending, - HmacSize = connectionRequest.HmacSize, - MessageFragmentSize = connectionRequest.MessageFragmentSize, - Nonce = _session._localEncryption.Nonce, - PublicKeyX = publicKey.X!, - PublicKeyY = publicKey.Y! - }.Write(writer); - }); + var publicKey = _session._localEncryption.PublicKey; + new ConnectionResponse() + { + Result = ConnectionResult.Pending, + HmacSize = connectionRequest.HmacSize, + MessageFragmentSize = connectionRequest.MessageFragmentSize, + Nonce = _session._localEncryption.Nonce, + PublicKeyX = publicKey.X!, + PublicKeyY = publicKey.Y! + }.Write(writer); + + _session.SendMessage(socket, header, writer); // We have to set cryptor after we send the message because it would be encrypted otherwise var secret = _session._localEncryption.GenerateSharedSecret(_session._remoteEncryption); @@ -357,19 +317,19 @@ void HandleConnectResponse(CommonHeader header, ref EndianReader reader, CdpSock var secret = _session._localEncryption.GenerateSharedSecret(_session._remoteEncryption); _session._cryptor = new(secret); - header.Flags = 0; - _session.SendMessage(socket, header, (writer) => + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.DeviceAuthRequest - }.Write(writer); - AuthenticationPayload.Create( - _session.Platform.DeviceInfo.DeviceCertificate!, // ToDo: User cert - hostNonce: _session._remoteEncryption!.Nonce, clientNonce: _session._localEncryption.Nonce - ).Write(writer); - }); + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.DeviceAuthRequest + }.Write(writer); + AuthenticationPayload.Create( + _session.Platform.DeviceInfo.DeviceCertificate!, // ToDo: User cert + hostNonce: _session._remoteEncryption!.Nonce, clientNonce: _session._localEncryption.Nonce + ).Write(writer); + + header.Flags = 0; + _session.SendMessage(socket, header, writer); } void HandleAuthRequest(CommonHeader header, ref EndianReader reader, CdpSocket socket, ConnectionType connectionType) @@ -378,19 +338,19 @@ void HandleAuthRequest(CommonHeader header, ref EndianReader reader, CdpSocket s if (!authRequest.VerifyThumbprint(hostNonce: _session._localEncryption.Nonce, clientNonce: _session._remoteEncryption!.Nonce)) throw new CdpSecurityException("Invalid thumbprint"); - header.Flags = 0; - _session.SendMessage(socket, header, (writer) => + EndianWriter writer = new(); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = connectionType == ConnectionType.DeviceAuthRequest ? ConnectionType.DeviceAuthResponse : ConnectionType.UserDeviceAuthResponse - }.Write(writer); - AuthenticationPayload.Create( - _session.Platform.DeviceInfo.DeviceCertificate, // ToDo: User cert - hostNonce: _session._localEncryption.Nonce, clientNonce: _session._remoteEncryption!.Nonce - ).Write(writer); - }); + ConnectionMode = ConnectionMode.Proximal, + MessageType = connectionType == ConnectionType.DeviceAuthRequest ? ConnectionType.DeviceAuthResponse : ConnectionType.UserDeviceAuthResponse + }.Write(writer); + AuthenticationPayload.Create( + _session.Platform.DeviceInfo.DeviceCertificate, // ToDo: User cert + hostNonce: _session._localEncryption.Nonce, clientNonce: _session._remoteEncryption!.Nonce + ).Write(writer); + + header.Flags = 0; + _session.SendMessage(socket, header, writer); } void HandleAuthResponse(CommonHeader header, ref EndianReader reader, CdpSocket socket, ConnectionType connectionType) @@ -401,19 +361,19 @@ void HandleAuthResponse(CommonHeader header, ref EndianReader reader, CdpSocket if (connectionType == ConnectionType.DeviceAuthResponse) { - header.Flags = 0; - _session.SendMessage(socket, header, (writer) => + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.UserDeviceAuthRequest - }.Write(writer); - AuthenticationPayload.Create( - _session.Platform.DeviceInfo.DeviceCertificate!, // ToDo: User cert - hostNonce: _session._remoteEncryption!.Nonce, clientNonce: _session._localEncryption.Nonce - ).Write(writer); - }); + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.UserDeviceAuthRequest + }.Write(writer); + AuthenticationPayload.Create( + _session.Platform.DeviceInfo.DeviceCertificate!, // ToDo: User cert + hostNonce: _session._remoteEncryption!.Nonce, clientNonce: _session._localEncryption.Nonce + ).Write(writer); + + header.Flags = 0; + _session.SendMessage(socket, header, writer); return; } @@ -438,33 +398,38 @@ async void PrepareSession(CdpSocket socket) } } - header.Flags = 0; - _session.SendMessage(socket, header, (writer) => - { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.AuthDoneRequest - }.Write(writer); - }); + SendAuthDone(); } - } - void HandleAuthDoneRequest(CommonHeader header, CdpSocket socket) - { - header.Flags = 0; - _session.SendMessage(socket, header, (writer) => + void SendAuthDone() { + EndianWriter writer = new(Endianness.BigEndian); new ConnectionHeader() { ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.AuthDoneRespone // Ack + MessageType = ConnectionType.AuthDoneRequest }.Write(writer); - new ResultPayload() - { - Result = CdpResult.Success - }.Write(writer); - }); + + header.Flags = 0; + _session.SendMessage(socket, header, writer); + } + } + + void HandleAuthDoneRequest(CommonHeader header, CdpSocket socket) + { + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() + { + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.AuthDoneRespone // Ack + }.Write(writer); + new ResultPayload() + { + Result = CdpResult.Success + }.Write(writer); + + header.Flags = 0; + _session.SendMessage(socket, header, writer); } void HandleAuthDoneResponse(CdpSocket socket, ref EndianReader reader) @@ -472,22 +437,22 @@ void HandleAuthDoneResponse(CdpSocket socket, ref EndianReader reader) var msg = ResultPayload.Parse(ref reader); msg.ThrowOnError(); + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() + { + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.DeviceInfoMessage + }.Write(writer); + new DeviceInfoMessage() + { + DeviceInfo = _session.Platform.GetCdpDeviceInfo() + }.Write(writer); + _session.SendMessage(socket, new CommonHeader() { Type = MessageType.Connect }, - (writer) => - { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.DeviceInfoMessage - }.Write(writer); - new DeviceInfoMessage() - { - DeviceInfo = _session.Platform.GetCdpDeviceInfo() - }.Write(writer); - }); + writer); _currentConnectPromise?.TrySetResult(); } @@ -499,15 +464,15 @@ void HandleDeviceInfoMessage(CommonHeader header, ref EndianReader reader, CdpSo _session.DeviceInfo = msg.DeviceInfo; - header.Flags = 0; - _session.SendMessage(socket, header, (writer) => + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.DeviceInfoResponseMessage // Ack - }.Write(writer); - }); + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.DeviceInfoResponseMessage // Ack + }.Write(writer); + + header.Flags = 0; + _session.SendMessage(socket, header, writer); } } @@ -550,19 +515,19 @@ void HandleStartChannelRequest(CommonHeader header, ref EndianReader reader, Cdp _channelRegistry.Create(channelId => CdpChannel.CreateServerChannel(this, channelId, socket, request), out var channelId); - header.Flags = 0; - SendMessage(socket, header, (writer) => + EndianWriter writer = new(Endianness.BigEndian); + new ControlHeader() { - new ControlHeader() - { - MessageType = ControlMessageType.StartChannelResponse - }.Write(writer); - new StartChannelResponse() - { - Result = ChannelResult.Success, - ChannelId = channelId - }.Write(writer); - }); + MessageType = ControlMessageType.StartChannelResponse + }.Write(writer); + new StartChannelResponse() + { + Result = ChannelResult.Success, + ChannelId = channelId + }.Write(writer); + + header.Flags = 0; + SendMessage(socket, header, writer); } event Action? OnStartChannelResponseInternal; @@ -650,34 +615,36 @@ public async Task StartClientChannelAsync(string appId, string appNa if (IsHost) throw new InvalidOperationException("Session is not a client"); - CommonHeader header = new() - { - Type = MessageType.Control - }; - SendMessage( - socket, header, - writer => - { - new ControlHeader() - { - MessageType = ControlMessageType.StartChannelRequest - }.Write(writer); - new StartChannelRequest() - { - Id = appId, - Name = appName - }.Write(writer); - }, - supplyRequestId: true - ); + var requestId = SendChannelRequest(); - var response = await WaitForChannelResponse(header.RequestID, cancellationToken); + var response = await WaitForChannelResponse(requestId, cancellationToken); response.ThrowOnError(); var channel = CdpChannel.CreateClientChannel(this, socket, response, handler); _channelRegistry.Add(channel.ChannelId, channel); - return channel; + + ulong SendChannelRequest() + { + EndianWriter writer = new(Endianness.BigEndian); + new ControlHeader() + { + MessageType = ControlMessageType.StartChannelRequest + }.Write(writer); + new StartChannelRequest() + { + Id = appId, + Name = appName + }.Write(writer); + + CommonHeader header = new() + { + Type = MessageType.Control + }; + SendMessage(socket, header, writer, supplyRequestId: true); + + return header.RequestID; + } } internal void UnregisterChannel(CdpChannel channel) diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Constants.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Constants.cs index 7d6794e..6ad58c8 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Constants.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Constants.cs @@ -15,7 +15,6 @@ public static class Constants public const string RfcommServiceId = "c7f94713-891e-496a-a0e7-983a0946126e"; public const string RfcommServiceName = "CDP Proximal Transport"; - public const int DefaultMessageFragmentSize = 16384; public const int HMacSize = 32; public const int IVSize = 16; diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Internal/UpgradeHandler.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Internal/UpgradeHandler.cs index b1ea3da..ee71fe6 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Internal/UpgradeHandler.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Internal/UpgradeHandler.cs @@ -100,15 +100,15 @@ void HandleTransportRequest(CdpSocket socket, ref EndianReader reader) Type = MessageType.Connect }; - _session.SendMessage(socket, header, (writer) => + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = allowed ? ConnectionType.TransportConfirmation : ConnectionType.UpgradeFailure - }.Write(writer); - msg.Write(writer); - }); + ConnectionMode = ConnectionMode.Proximal, + MessageType = allowed ? ConnectionType.TransportConfirmation : ConnectionType.UpgradeFailure + }.Write(writer); + msg.Write(writer); + + _session.SendMessage(socket, header, writer); } void HandleUpgradeRequest(CdpSocket socket, ref EndianReader reader) @@ -127,26 +127,25 @@ void HandleUpgradeRequest(CdpSocket socket, ref EndianReader reader) var localIp = _session.Platform.TryGetTransport()?.Handler.TryGetLocalIp(); if (localIp == null) { - _session.SendMessage(socket, header, (writer) => + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.UpgradeFailure - }.Write(writer); - new HResultPayload() - { - HResult = -1 - }.Write(writer); - }); + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.UpgradeFailure + }.Write(writer); + new HResultPayload() + { + HResult = -1 + }.Write(writer); + _session.SendMessage(socket, header, writer); return; } _upgradeIds.Add(msg.UpgradeId); - _session.SendMessage(socket, header, (writer) => { + EndianWriter writer = new(Endianness.BigEndian); new ConnectionHeader() { ConnectionMode = ConnectionMode.Proximal, @@ -163,7 +162,9 @@ void HandleUpgradeRequest(CdpSocket socket, ref EndianReader reader) EndpointMetadata.Tcp ] }.Write(writer); - }); + + _session.SendMessage(socket, header, writer); + } } void HandleUpgradeFinalization(CdpSocket socket, ref EndianReader reader) @@ -178,14 +179,14 @@ void HandleUpgradeFinalization(CdpSocket socket, ref EndianReader reader) Type = MessageType.Connect }; - _session.SendMessage(socket, header, (writer) => + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.UpgradeFinalizationResponse - }.Write(writer); - }); + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.UpgradeFinalizationResponse + }.Write(writer); + + _session.SendMessage(socket, header, writer); } #endregion @@ -223,23 +224,23 @@ void SendUpgradeRequest(CdpSocket socket, Guid upgradeId) Type = MessageType.Connect }; - _session.SendMessage(socket, header, writer => + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.UpgradeRequest - }.Write(writer); + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.UpgradeRequest + }.Write(writer); - new UpgradeRequest() - { - UpgradeId = upgradeId, - Endpoints = - [ - EndpointMetadata.Tcp - ] - }.Write(writer); - }); + new UpgradeRequest() + { + UpgradeId = upgradeId, + Endpoints = + [ + EndpointMetadata.Tcp + ] + }.Write(writer); + + _session.SendMessage(socket, header, writer); } } @@ -274,11 +275,10 @@ async void FindNewEndpoint() return; } - _session.SendMessage(oldSocket, new() - { - Type = MessageType.Connect, - }, writer => + SendUpgradFinalization(); + void SendUpgradFinalization() { + EndianWriter writer = new(Endianness.BigEndian); new ConnectionHeader() { ConnectionMode = ConnectionMode.Proximal, @@ -288,7 +288,12 @@ async void FindNewEndpoint() [ EndpointMetadata.Tcp ]); - }); + + _session.SendMessage(oldSocket, new() + { + Type = MessageType.Connect, + }, writer); + } // Cancel after timeout if upgrade has not finished yet await Task.Delay(UpgradeInstance.Timeout); @@ -310,21 +315,21 @@ void HandleUpgradeFinalizationResponse() _allowedAddresses.Add(_currentUpgrade.NewSocket.Endpoint.Address); // Request transport permission for new socket + EndianWriter writer = new(Endianness.BigEndian); + new ConnectionHeader() + { + ConnectionMode = ConnectionMode.Proximal, + MessageType = ConnectionType.TransportRequest + }.Write(writer); + new UpgradeIdPayload() + { + UpgradeId = _currentUpgrade.Id + }.Write(writer); + _session.SendMessage(_currentUpgrade.NewSocket, new() { Type = MessageType.Connect, - }, writer => - { - new ConnectionHeader() - { - ConnectionMode = ConnectionMode.Proximal, - MessageType = ConnectionType.TransportRequest - }.Write(writer); - new UpgradeIdPayload() - { - UpgradeId = _currentUpgrade.Id - }.Write(writer); - }); + }, writer); } void HandleTransportConfirmation(CdpSocket socket, ref EndianReader reader) diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Messages/CdpMessage.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Messages/CdpMessage.cs index 0e1bbc7..e8cf9de 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Messages/CdpMessage.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Messages/CdpMessage.cs @@ -1,5 +1,6 @@ using ShortDev.Microsoft.ConnectedDevices.Messages.Session; using ShortDev.Microsoft.ConnectedDevices.Serialization; +using ShortDev.Microsoft.ConnectedDevices.Transports; namespace ShortDev.Microsoft.ConnectedDevices.Messages; @@ -10,7 +11,7 @@ public sealed class CdpMessage public CdpMessage(CommonHeader header) { Header = header; - _buffer = new(header.FragmentCount * Constants.DefaultMessageFragmentSize); + _buffer = new(header.FragmentCount * MessageFragmenter.DefaultMessageFragmentSize); } public CdpMessage(CommonHeader header, byte[] payload) diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/CdpSocket.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/CdpSocket.cs index 16c7897..10b99bc 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/CdpSocket.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/CdpSocket.cs @@ -3,17 +3,20 @@ /// /// Provides direct low-level access to inter-device communication. /// -public sealed class CdpSocket : IDisposable +public sealed class CdpSocket : IFragmentSender, IDisposable { public CdpTransportType TransportType => Endpoint.TransportType; public required EndpointInfo Endpoint { get; init; } public required Stream InputStream { get; init; } public required Stream OutputStream { get; init; } - public void SendData(EndianWriter writer) + public void SendFragment(ReadOnlySpan fragment) { lock (OutputStream) - writer.CopyTo(OutputStream); + { + OutputStream.Write(fragment); + OutputStream.Flush(); + } } public bool IsClosed { get; private set; } diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/MessageFragmenter.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/MessageFragmenter.cs new file mode 100644 index 0000000..3b7928b --- /dev/null +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/MessageFragmenter.cs @@ -0,0 +1,60 @@ +using ShortDev.Microsoft.ConnectedDevices.Encryption; +using ShortDev.Microsoft.ConnectedDevices.Messages; +using System.Diagnostics; + +namespace ShortDev.Microsoft.ConnectedDevices.Transports; +public static class MessageFragmenter +{ + public const int DefaultMessageFragmentSize = 16384; + + public static void SendMessage(this IFragmentSender sender, CommonHeader header, ReadOnlySpan payload, CdpCryptor? cryptor = null) + { + if (payload.Length <= DefaultMessageFragmentSize) + { + sender.SendFragment(header, payload, cryptor); + return; + } + + header.FragmentCount = (ushort)(payload.Length / DefaultMessageFragmentSize); + + var leftover = payload.Length % DefaultMessageFragmentSize; + if (leftover != 0) + header.FragmentCount++; + + for (ushort fragmentIndex = 0; fragmentIndex < header.FragmentCount; fragmentIndex++) + { + int start = fragmentIndex * DefaultMessageFragmentSize; + int length = Math.Min(payload.Length - start, DefaultMessageFragmentSize); + + header.FragmentIndex = fragmentIndex; + sender.SendFragment(header, payload.Slice(start, length), cryptor); + } + } + + static void SendFragment(this IFragmentSender sender, CommonHeader header, ReadOnlySpan payload, CdpCryptor? cryptor) + { + Debug.Assert(payload.Length <= DefaultMessageFragmentSize); + + EndianWriter writer = new(Endianness.BigEndian); + if (cryptor != null) + { + cryptor.EncryptMessage(writer, header, payload); + } + else + { + header.SetPayloadLength(payload.Length); + header.Write(writer); + writer.Write(payload); + } + + sender.SendFragment(writer.Buffer.AsSpan()); + } +} + +public interface IFragmentSender +{ + /// + /// Sends a binary fragment. + /// + void SendFragment(ReadOnlySpan fragment); +} diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/Network/NetworkTransport.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/Network/NetworkTransport.cs index 0a7bbb5..5dc7c59 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/Network/NetworkTransport.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/Network/NetworkTransport.cs @@ -7,14 +7,14 @@ namespace ShortDev.Microsoft.ConnectedDevices.Transports.Network; public sealed class NetworkTransport(INetworkHandler handler) : ICdpTransport, ICdpDiscoverableTransport { - public CdpTransportType TransportType { get; } = CdpTransportType.Tcp; - + readonly TcpListener _listener = new(IPAddress.Any, Constants.TcpPort); public INetworkHandler Handler { get; } = handler; - readonly TcpListener _listener = new(IPAddress.Any, Constants.TcpPort); + public CdpTransportType TransportType { get; } = CdpTransportType.Tcp; + public EndpointInfo GetEndpoint() + => new(TransportType, Handler.GetLocalIp().ToString(), Constants.TcpPort.ToString()); public event DeviceConnectedEventHandler? DeviceConnected; - public async void Listen(CancellationToken cancellationToken) { _listener.Start(); @@ -24,6 +24,10 @@ public async void Listen(CancellationToken cancellationToken) while (!cancellationToken.IsCancellationRequested) { var client = await _listener.AcceptTcpClientAsync(cancellationToken); + + if (client.Client.RemoteEndPoint is not IPEndPoint endPoint) + return; + var stream = client.GetStream(); DeviceConnected?.Invoke(this, new() { @@ -32,7 +36,7 @@ public async void Listen(CancellationToken cancellationToken) OutputStream = stream, Endpoint = new EndpointInfo( TransportType, - ((IPEndPoint?)client.Client.RemoteEndPoint)?.Address.ToString() ?? throw new InvalidDataException("No ip address"), + endPoint.Address.ToString(), Constants.TcpPort.ToString() ) }); @@ -55,16 +59,6 @@ public async Task ConnectAsync(EndpointInfo endpoint) }; } - public void Dispose() - { - DeviceConnected = null; - _listener.Stop(); - _udpclient.Dispose(); - } - - public EndpointInfo GetEndpoint() - => new(TransportType, Handler.GetLocalIp().ToString(), Constants.TcpPort.ToString()); - #region Discovery (Udp) readonly UdpClient _udpclient = new(Constants.UdpPort) @@ -72,51 +66,64 @@ public EndpointInfo GetEndpoint() EnableBroadcast = true }; - bool _isAdvertising = false; - PresenceResponse? _presenceResponse; public void Advertise(LocalDeviceInfo deviceInfo, CancellationToken cancellationToken) { - _presenceResponse = PresenceResponse.Create(deviceInfo); - _isAdvertising = true; + var presenceResponse = PresenceResponse.Create(deviceInfo); + + DiscoveryMessageReceived += OnMessage; + cancellationToken.Register(() => DiscoveryMessageReceived -= OnMessage); EnsureListeningUdp(cancellationToken); - cancellationToken.Register(() => _isAdvertising = false); + + void OnMessage(IPEndPoint remoteEndPoint, DiscoveryHeader header, EndianReader reader) + { + if (header.Type != DiscoveryType.PresenceRequest) + return; + + SendPresenceResponse(remoteEndPoint.Address, presenceResponse); + } } public event DeviceDiscoveredEventHandler? DeviceDiscovered; - - bool _isDiscovering = false; - public void Discover(CancellationToken cancellationToken) + public async void Discover(CancellationToken cancellationToken) { - _isDiscovering = true; + DiscoveryMessageReceived += OnMessage; EnsureListeningUdp(cancellationToken); - cancellationToken.Register(() => _isDiscovering = false); - _ = Task.Run(async () => + try { - var msg = GeneratePresenceRequest(); while (!cancellationToken.IsCancellationRequested) { - _udpclient.Send(msg.AsSpan(), new IPEndPoint(IPAddress.Broadcast, Constants.UdpPort)); - await Task.Delay(500); + SendPresenceRequest(); + await Task.Delay(500, cancellationToken); } - }, cancellationToken); + } + catch (OperationCanceledException) { } + catch (ObjectDisposedException) { } + finally + { + DiscoveryMessageReceived -= OnMessage; + } - static EndianBuffer GeneratePresenceRequest() + void OnMessage(IPEndPoint remoteEndPoint, DiscoveryHeader header, EndianReader reader) { - EndianWriter writer = new(Endianness.BigEndian); - new CommonHeader() - { - Type = MessageType.Discovery, - MessageLength = 43 - }.Write(writer); - new DiscoveryHeader() - { - Type = DiscoveryType.PresenceRequest - }.Write(writer); - return writer.Buffer; + if (header.Type != DiscoveryType.PresenceResponse) + return; + + var response = PresenceResponse.Parse(ref reader); + DeviceDiscovered?.Invoke( + this, + new CdpDevice( + response.DeviceName, + response.DeviceType, + EndpointInfo.FromTcp(remoteEndPoint) + ) + ); } } + delegate void DiscoveryMessageReceivedHandler(IPEndPoint remoteEndPoint, DiscoveryHeader header, EndianReader reader); + event DiscoveryMessageReceivedHandler? DiscoveryMessageReceived; + bool _isListening = false; async void EnsureListeningUdp(CancellationToken cancellationToken) { @@ -153,45 +160,59 @@ void HandleMsg(UdpReceiveResult result) return; DiscoveryHeader discoveryHeaders = DiscoveryHeader.Parse(ref reader); - if (_isAdvertising && discoveryHeaders.Type == DiscoveryType.PresenceRequest) - { - SendPresenceResponse(result.RemoteEndPoint.Address); - return; - } - - if (_isDiscovering && discoveryHeaders.Type == DiscoveryType.PresenceResponse) - { - var response = PresenceResponse.Parse(ref reader); - DeviceDiscovered?.Invoke( - this, - new CdpDevice( - response.DeviceName, - response.DeviceType, - EndpointInfo.FromTcp(result.RemoteEndPoint) - ) - ); - } + DiscoveryMessageReceived?.Invoke(result.RemoteEndPoint, discoveryHeaders, reader); } + } + #endregion - void SendPresenceResponse(IPAddress device) + void SendPresenceRequest() + { + CommonHeader header = new() { - if (_presenceResponse == null) - return; + Type = MessageType.Discovery, + }; - EndianWriter writer = new(Endianness.BigEndian); - new CommonHeader() - { - Type = MessageType.Discovery, - MessageLength = 97 - }.Write(writer); - new DiscoveryHeader() - { - Type = DiscoveryType.PresenceResponse - }.Write(writer); - _presenceResponse.Write(writer); + EndianWriter payloadWriter = new(Endianness.BigEndian); + new DiscoveryHeader() + { + Type = DiscoveryType.PresenceRequest + }.Write(payloadWriter); - _udpclient.Send(writer.Buffer.AsSpan(), new IPEndPoint(device, Constants.UdpPort)); - } + new UdpFragmentSender(_udpclient, new IPEndPoint(IPAddress.Broadcast, Constants.UdpPort)) + .SendMessage(header, payloadWriter.Buffer.AsSpan()); + } + + void SendPresenceResponse(IPAddress device, PresenceResponse response) + { + CommonHeader header = new() + { + Type = MessageType.Discovery + }; + + EndianWriter payloadWriter = new(Endianness.BigEndian); + new DiscoveryHeader() + { + Type = DiscoveryType.PresenceResponse + }.Write(payloadWriter); + response.Write(payloadWriter); + + new UdpFragmentSender(_udpclient, new IPEndPoint(device, Constants.UdpPort)) + .SendMessage(header, payloadWriter.Buffer.AsSpan()); + } + + sealed class UdpFragmentSender(UdpClient client, IPEndPoint receiver) : IFragmentSender + { + public void SendFragment(ReadOnlySpan fragment) + => client.Send(fragment, receiver); + } + + public void Dispose() + { + DeviceConnected = null; + DeviceDiscovered = null; + DiscoveryMessageReceived = null; + + _listener.Stop(); + _udpclient.Dispose(); } - #endregion }