diff --git a/Directory.Build.targets b/Directory.Build.targets index 66fbd114..b191f782 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,6 +1,6 @@ - + @@ -8,4 +8,8 @@ + + + + \ No newline at end of file diff --git a/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/CdpFileProvider.cs b/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/CdpFileProvider.cs index 3386a6cb..dc9c18a3 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/CdpFileProvider.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/CdpFileProvider.cs @@ -1,5 +1,4 @@ -using System.IO; -using System.Text; +using System.Text; namespace ShortDev.Microsoft.ConnectedDevices.NearShare; @@ -43,12 +42,10 @@ public static CdpFileProvider FromStream(string fileName, Stream stream) public ulong FileSize => (ulong)_buffer.Length; - public ReadOnlySpan ReadBlob(ulong start, uint length) + public void ReadBlob(ulong start, Span buffer) { - Span buffer = new byte[length]; _buffer.Position = (long)start; _buffer.Read(buffer); - return buffer; } public void Dispose() diff --git a/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/Messages/FetchDataResponse.cs b/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/Messages/FetchDataResponse.cs new file mode 100644 index 00000000..e03b1270 --- /dev/null +++ b/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/Messages/FetchDataResponse.cs @@ -0,0 +1,47 @@ +using ShortDev.Microsoft.ConnectedDevices.Serialization; + +namespace ShortDev.Microsoft.ConnectedDevices.NearShare.Messages; +internal static class FetchDataResponse +{ + public static void Write(EndianWriter writer, uint contentId, ulong start, int length, out Span blob) + { + CompactBinaryBondWriter bondWriter = new(writer.Buffer); + + bondWriter.WriteFieldBegin(Bond.BondDataType.BT_MAP, 1); + bondWriter.WriteContainerBegin(count: 4, Bond.BondDataType.BT_WSTRING, Bond.BondDataType.BT_STRUCT); + + WritePropertyBegin(ref bondWriter, "ControlMessage", PropertyType.PropertyType_UInt32); + bondWriter.WriteFieldBegin(Bond.BondDataType.BT_UINT32, 104); + bondWriter.WriteUInt32((uint)NearShareControlMsgType.FetchDataResponse); + bondWriter.WriteStructEnd(); + + WritePropertyBegin(ref bondWriter, "ContentId", PropertyType.PropertyType_UInt32); + bondWriter.WriteFieldBegin(Bond.BondDataType.BT_UINT32, 104); + bondWriter.WriteUInt32(contentId); + bondWriter.WriteStructEnd(); + + WritePropertyBegin(ref bondWriter, "BlobPosition", PropertyType.PropertyType_UInt64); + bondWriter.WriteFieldBegin(Bond.BondDataType.BT_UINT64, 106); + bondWriter.WriteUInt64(start); + bondWriter.WriteStructEnd(); + + WritePropertyBegin(ref bondWriter, "DataBlob", PropertyType.PropertyType_UInt8Array); + bondWriter.WriteFieldBegin(Bond.BondDataType.BT_LIST, 200); + bondWriter.WriteContainerBegin(length, Bond.BondDataType.BT_UINT8); + + blob = writer.Buffer.GetSpan(length)[..length]; + writer.Buffer.Advance(length); + + bondWriter.WriteStructEnd(); + + bondWriter.WriteStructEnd(); + } + + static void WritePropertyBegin(ref CompactBinaryBondWriter writer, string name, PropertyType type) + { + writer.WriteWString(name); + + writer.WriteFieldBegin(Bond.BondDataType.BT_INT32, 0); + writer.WriteInt32((int)type); + } +} diff --git a/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/NearShareSender.cs b/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/NearShareSender.cs index ab301968..05f30746 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/NearShareSender.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices.NearShare/NearShareSender.cs @@ -4,6 +4,8 @@ using ShortDev.Microsoft.ConnectedDevices.NearShare.Apps; using ShortDev.Microsoft.ConnectedDevices.NearShare.Messages; using ShortDev.Microsoft.ConnectedDevices.Serialization; +using System.Buffers; +using System.Diagnostics; namespace ShortDev.Microsoft.ConnectedDevices.NearShare; @@ -176,7 +178,13 @@ void HandleDataRequest(BinaryMsgHeader header, ValueSet payload) var length = payload.Get("BlobSize"); var fileProvider = _files?[(int)contentId] ?? throw new NullReferenceException("Could not access files to transfer"); - var blob = fileProvider.ReadBlob(start, length); + Channel.SendBinaryMessage(writer => + { + FetchDataResponse.Write(writer, contentId, start, (int)length, out var blob); + Debug.Assert(blob.Length == length); + + fileProvider.ReadBlob(start, blob); + }, header.MessageId); _fileProgress?.Report(new() { @@ -184,13 +192,6 @@ void HandleDataRequest(BinaryMsgHeader header, ValueSet payload) TotalBytes = _bytesToSend, TotalFiles = (uint)_files.Count }); - - ValueSet response = new(); - response.Add("ControlMessage", (uint)NearShareControlMsgType.FetchDataResponse); - response.Add("ContentId", contentId); - response.Add("BlobPosition", start); - response.Add("DataBlob", blob.ToArray().ToList()); // ToDo: Remove allocation - SendValueSet(response, header.MessageId); } } } diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Encryption/CdpCryptor.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Encryption/CdpCryptor.cs index bb269928..e52df167 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Encryption/CdpCryptor.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Encryption/CdpCryptor.cs @@ -1,5 +1,8 @@ using ShortDev.Microsoft.ConnectedDevices.Exceptions; using ShortDev.Microsoft.ConnectedDevices.Messages; +using ShortDev.Microsoft.ConnectedDevices.Transports; +using System.Buffers; +using System.Buffers.Binary; using System.Diagnostics; using System.Security.Cryptography; @@ -27,16 +30,13 @@ void GenerateIV(CommonHeader header, Span destination) { Debug.Assert(destination.Length == Constants.IVSize); - var aes = _ivAes; + Span raw = stackalloc byte[Constants.IVSize]; + BinaryPrimitives.WriteUInt64BigEndian(raw[..8], header.SessionId); + BinaryPrimitives.WriteUInt32BigEndian(raw[8..12], header.SequenceNumber); + BinaryPrimitives.WriteUInt16BigEndian(raw[12..14], header.FragmentIndex); + BinaryPrimitives.WriteUInt16BigEndian(raw[14..16], header.FragmentCount); - EndianWriter writer = new(Endianness.BigEndian, Constants.IVSize); - - writer.Write(header.SessionId); - writer.Write(header.SequenceNumber); - writer.Write(header.FragmentIndex); - writer.Write(header.FragmentCount); - - int bytesWritten = aes.EncryptCbc(writer.Buffer.AsSpan(), _ivData, destination, PaddingMode.None); + int bytesWritten = _ivAes.EncryptCbc(raw, _ivData, destination, PaddingMode.None); Debug.Assert(bytesWritten == destination.Length); } @@ -86,36 +86,53 @@ void VerifyHMac(CommonHeader header, ReadOnlySpan payload, ReadOnlySpan payloadBuffer) + public void EncryptMessage(IFragmentSender sender, CommonHeader header, ReadOnlySpan payloadBuffer) { - EndianWriter msgWriter = new(Endianness.BigEndian); + // Prepend payload with length + ReadOnlySpan finalPayload; + { + EndianWriter payloadWriter = new(Endianness.BigEndian); + payloadWriter.Write((uint)payloadBuffer.Length); + payloadWriter.Write(payloadBuffer); - Span iv = stackalloc byte[Constants.IVSize]; - GenerateIV(header, iv); + finalPayload = payloadWriter.Buffer.AsSpan(); + } + + // Encrypt + var msgWriter = Encrypt(header, finalPayload); - EndianWriter payloadWriter = new(Endianness.BigEndian); - payloadWriter.Write((uint)payloadBuffer.Length); - payloadWriter.Write(payloadBuffer); + // HMAC + { + var msgBuffer = msgWriter.Buffer.AsWriteableSpan(); + Span hmac = stackalloc byte[Constants.HMacSize]; + ComputeHmac(msgBuffer, hmac); + CommonHeader.ModifyMessageLength(msgBuffer, +Constants.HMacSize); + msgWriter.Write(hmac); + } - var buffer = payloadWriter.Buffer.AsSpan(); + sender.SendFragment(msgWriter.Buffer.AsSpan()); + } + + EndianWriter Encrypt(CommonHeader header, ReadOnlySpan buffer) + { // If payload size is an exact multiple of block length (16 bytes) no padding is applied PaddingMode paddingMode = buffer.Length % 16 == 0 ? PaddingMode.None : PaddingMode.PKCS7; - var encryptedPayload = _aes.EncryptCbc(buffer, iv, paddingMode); + var encryptedPayloadLength = _aes.GetCiphertextLengthCbc(buffer.Length, paddingMode); + // Write header + EndianWriter writer = new(Endianness.BigEndian); header.Flags |= MessageFlags.SessionEncrypted | MessageFlags.HasHMAC; - header.SetPayloadLength(encryptedPayload.Length); - header.Write(msgWriter); - - msgWriter.Write(encryptedPayload); + header.SetPayloadLength(encryptedPayloadLength); + header.Write(writer); - var msgBuffer = msgWriter.Buffer.AsWriteableSpan(); + Span iv = stackalloc byte[Constants.IVSize]; + GenerateIV(header, iv); - Span hmac = stackalloc byte[Constants.HMacSize]; - ComputeHmac(msgBuffer, hmac); - CommonHeader.ModifyMessageLength(msgBuffer, +Constants.HMacSize); + // Encrypt and write to msgWriter + _aes.EncryptCbc(buffer, iv, writer.Buffer.GetSpan(encryptedPayloadLength), paddingMode); + writer.Buffer.Advance(encryptedPayloadLength); - writer.Write(msgBuffer); - writer.Write(hmac); + return writer; } public void Read(ref EndianReader reader, CommonHeader header) diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Extensions.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Extensions.cs index 9038d37b..aee17b87 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Extensions.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Extensions.cs @@ -69,6 +69,8 @@ public readonly struct ArrayPoolToken(ArrayPool pool, int capacity) : IDis private readonly int _capacity = capacity; private readonly T[] _array = pool.Rent(capacity); + public T[] ArrayUnsafe => _array; + public Memory Memory => _array.AsMemory()[0.._capacity]; public Span Span => Memory.Span; diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Messages/Session/BinaryMsgHeader.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Messages/Session/BinaryMsgHeader.cs index cbee6a4e..3816f874 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Messages/Session/BinaryMsgHeader.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Messages/Session/BinaryMsgHeader.cs @@ -4,11 +4,11 @@ /// cdp.dll!cdp::BinaryFragmenter::GetMessageFragments
/// /// -public sealed class BinaryMsgHeader : ICdpHeader +public readonly struct BinaryMsgHeader() : ICdpHeader { - public uint FragmentCount { get; set; } = 1; - public uint FragmentIndex { get; set; } = 0; - public required uint MessageId { get; set; } + public uint FragmentCount { get; init; } = 1; + public uint FragmentIndex { get; init; } = 0; + public required uint MessageId { get; init; } public static BinaryMsgHeader Parse(ref EndianReader reader) => new() diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Serialization/BondWriter.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Serialization/BondWriter.cs new file mode 100644 index 00000000..1908ebea --- /dev/null +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Serialization/BondWriter.cs @@ -0,0 +1,590 @@ +using Bond; +using System.Runtime.CompilerServices; +using System.Text; + +namespace ShortDev.Microsoft.ConnectedDevices.Serialization; +public readonly ref struct CompactBinaryBondWriter(EndianBuffer buffer) +{ + const ushort Magic = 16963; + + readonly ushort version = 1; + readonly EndianWriter output = new(Endianness.LittleEndian, buffer); + + // + // Summary: + // Write protocol magic number and version + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteVersion() + { + output.Write((ushort)16963); + output.Write((ushort)version); + } + + // + // Summary: + // End writing a struct + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteStructEnd() + { + output.Write((byte)0); + } + + // + // Summary: + // End writing a base struct + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBaseEnd() + { + output.Write((byte)1); + } + + // + // Summary: + // Start writing a field + // + // Parameters: + // type: + // Type of the field + // + // id: + // Identifier of the field + // + // metadata: + // Metadata of the field + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFieldBegin(BondDataType type, ushort id) + { + if (id <= 5) + { + output.Write((byte)((uint)type | (uint)(id << 5))); + return; + } + + if (id <= 255) + { + output.Write((ushort)((uint)type | (uint)(id << 8) | 0xC0u)); + return; + } + + output.Write((byte)(type | (BondDataType)224)); + output.Write(id); + } + + // + // Summary: + // Start writing a list or set container + // + // Parameters: + // count: + // Number of elements in the container + // + // elementType: + // Type of the elements + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteContainerBegin(int count, BondDataType elementType) + { + if (2 == version && count < 7) + { + output.Write((byte)((uint)elementType | (uint)(count + 1 << 5))); + return; + } + + output.Write((byte)elementType); + IntegerHelper.WriteVarUInt32(output, (uint)count); + } + + // + // Summary: + // Start writing a map container + // + // Parameters: + // count: + // Number of elements in the container + // + // keyType: + // Type of the keys + // + // valueType: + // Type of the values + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteContainerBegin(int count, BondDataType keyType, BondDataType valueType) + { + output.Write((byte)keyType); + output.Write((byte)valueType); + IntegerHelper.WriteVarUInt32(output, (uint)count); + } + + // + // Summary: + // Write array of bytes verbatim + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBytes(ReadOnlySpan data) + { + output.Write(data); + } + + // + // Summary: + // Write an UInt8 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt8(byte value) + { + output.Write(value); + } + + // + // Summary: + // Write an UInt16 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt16(ushort value) + { + IntegerHelper.WriteVarUInt16(output, value); + } + + // + // Summary: + // Write an UInt16 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt32(uint value) + { + IntegerHelper.WriteVarUInt32(output, value); + } + + // + // Summary: + // Write an UInt64 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteUInt64(ulong value) + { + IntegerHelper.WriteVarUInt64(output, value); + } + + // + // Summary: + // Write an Int8 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt8(sbyte value) + { + output.Write((byte)value); + } + + // + // Summary: + // Write an Int16 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt16(short value) + { + IntegerHelper.WriteVarUInt16(output, IntegerHelper.EncodeZigzag16(value)); + } + + // + // Summary: + // Write an Int32 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt32(int value) + { + IntegerHelper.WriteVarUInt32(output, IntegerHelper.EncodeZigzag32(value)); + } + + // + // Summary: + // Write an Int64 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteInt64(long value) + { + IntegerHelper.WriteVarUInt64(output, IntegerHelper.EncodeZigzag64(value)); + } + + // + // Summary: + // Write a float + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteFloat(float value) + { + output.Write(value); + } + + // + // Summary: + // Write a double + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteDouble(double value) + { + output.Write(value); + } + + // + // Summary: + // Write a bool + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteBool(bool value) + { + output.Write((byte)(value ? 1u : 0u)); + } + + // + // Summary: + // Write a UTF-8 string + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteString(string value) + { + if (value.Length == 0) + { + WriteUInt32(0u); + return; + } + + int byteCount = Encoding.UTF8.GetByteCount(value); + WriteUInt32((uint)byteCount); + // output.WriteString(Encoding.UTF8, value, byteCount); + output.Write(Encoding.UTF8.GetBytes(value)); + } + + // + // Summary: + // Write a UTF-16 string + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteWString(string value) + { + if (value.Length == 0) + { + WriteUInt32(0u); + return; + } + + int size = checked(value.Length * 2); + WriteUInt32((uint)value.Length); + // output.WriteString(Encoding.Unicode, value, size); + output.Write(Encoding.Unicode.GetBytes(value)); + } + + internal static class IntegerHelper + { + public const int MaxBytesVarInt16 = 3; + + public const int MaxBytesVarInt32 = 5; + + public const int MaxBytesVarInt64 = 10; + + public static int GetVarUInt16Length(ushort value) + { + if (value < 128) + { + return 1; + } + + if (value < 16384) + { + return 2; + } + + return 3; + } + + public static void WriteVarUInt16(in EndianWriter writer, ushort value) + { + if (value >= 128) + { + writer.Write((byte)(value | 0x80u)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80u)); + value >>= 7; + } + } + + writer.Write((byte)value); + } + + public static int GetVarUInt32Length(uint value) + { + if (value < 128) + { + return 1; + } + + if (value < 16384) + { + return 2; + } + + if (value < 2097152) + { + return 3; + } + + if (value < 268435456) + { + return 4; + } + + return 5; + } + + public static void WriteVarUInt32(in EndianWriter writer, uint value) + { + if (value >= 128) + { + writer.Write((byte)(value | 0x80u)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80u)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80u)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80u)); + value >>= 7; + } + } + } + } + + writer.Write((byte)value); + } + + public static int GetVarUInt64Length(ulong value) + { + if (value < 128) + { + return 1; + } + + if (value < 16384) + { + return 2; + } + + if (value < 2097152) + { + return 3; + } + + if (value < 268435456) + { + return 4; + } + + if (value < 34359738368L) + { + return 5; + } + + if (value < 4398046511104L) + { + return 6; + } + + if (value < 562949953421312L) + { + return 7; + } + + if (value < 72057594037927936L) + { + return 8; + } + + if (value < 9223372036854775808uL) + { + return 9; + } + + return 10; + } + + public static void WriteVarUInt64(in EndianWriter writer, ulong value) + { + if (value >= 128) + { + writer.Write((byte)(value | 0x80)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80)); + value >>= 7; + if (value >= 128) + { + writer.Write((byte)(value | 0x80)); + value >>= 7; + } + } + } + } + } + } + } + } + } + + writer.Write((byte)value); + } + + public static ushort DecodeVarUInt16(byte[] data, ref int index) + { + int num = index; + uint num2 = data[num++]; + if (128 <= num2) + { + uint num3 = data[num++]; + num2 = (num2 & 0x7Fu) | ((num3 & 0x7F) << 7); + if (128 <= num3) + { + num3 = data[num++]; + num2 |= num3 << 14; + } + } + + index = num; + return (ushort)num2; + } + + public static uint DecodeVarUInt32(byte[] data, ref int index) + { + int num = index; + uint num2 = data[num++]; + if (128 <= num2) + { + uint num3 = data[num++]; + num2 = (num2 & 0x7Fu) | ((num3 & 0x7F) << 7); + if (128 <= num3) + { + num3 = data[num++]; + num2 |= (num3 & 0x7F) << 14; + if (128 <= num3) + { + num3 = data[num++]; + num2 |= (num3 & 0x7F) << 21; + if (128 <= num3) + { + num3 = data[num++]; + num2 |= num3 << 28; + } + } + } + } + + index = num; + return num2; + } + + public static ulong DecodeVarUInt64(byte[] data, ref int index) + { + int num = index; + ulong num2 = data[num++]; + if (128 <= num2) + { + ulong num3 = data[num++]; + num2 = (num2 & 0x7F) | ((num3 & 0x7F) << 7); + if (128 <= num3) + { + num3 = data[num++]; + num2 |= (num3 & 0x7F) << 14; + if (128 <= num3) + { + num3 = data[num++]; + num2 |= (num3 & 0x7F) << 21; + if (128 <= num3) + { + num3 = data[num++]; + num2 |= (num3 & 0x7F) << 28; + if (128 <= num3) + { + num3 = data[num++]; + num2 |= (num3 & 0x7F) << 35; + if (128 <= num3) + { + num3 = data[num++]; + num2 |= (num3 & 0x7F) << 42; + if (128 <= num3) + { + num3 = data[num++]; + num2 |= (num3 & 0x7F) << 49; + if (128 <= num3) + { + num3 = data[num++]; + num2 |= num3 << 56; + if (128 <= num3) + { + num++; + } + } + } + } + } + } + } + } + } + + index = num; + return num2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort EncodeZigzag16(short value) + { + return (ushort)((value << 1) ^ (value >> 15)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint EncodeZigzag32(int value) + { + return (uint)((value << 1) ^ (value >> 31)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong EncodeZigzag64(long value) + { + return (ulong)((value << 1) ^ (value >> 63)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short DecodeZigzag16(ushort value) + { + return (short)((value >> 1) ^ -(value & 1)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int DecodeZigzag32(uint value) + { + return (int)((value >> 1) ^ (0L - (long)(value & 1))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long DecodeZigzag64(ulong value) + { + return (long)((value >> 1) ^ (0L - (value & 1))); + } + } +} + diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/CdpSocket.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/CdpSocket.cs index 10b99bc7..8ae04a84 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/CdpSocket.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/CdpSocket.cs @@ -12,11 +12,8 @@ public sealed class CdpSocket : IFragmentSender, IDisposable public void SendFragment(ReadOnlySpan fragment) { - lock (OutputStream) - { - OutputStream.Write(fragment); - OutputStream.Flush(); - } + 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 index 3b7928bb..79400bac 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/MessageFragmenter.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/MessageFragmenter.cs @@ -35,19 +35,18 @@ static void SendFragment(this IFragmentSender sender, CommonHeader header, ReadO { Debug.Assert(payload.Length <= DefaultMessageFragmentSize); - EndianWriter writer = new(Endianness.BigEndian); if (cryptor != null) { - cryptor.EncryptMessage(writer, header, payload); + cryptor.EncryptMessage(sender, header, payload); } else { + EndianWriter writer = new(Endianness.BigEndian); header.SetPayloadLength(payload.Length); header.Write(writer); writer.Write(payload); + sender.SendFragment(writer.Buffer.AsSpan()); } - - sender.SendFragment(writer.Buffer.AsSpan()); } } diff --git a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/Network/NetworkTransport.cs b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/Network/NetworkTransport.cs index 431ff5fd..c0d2a6ca 100644 --- a/lib/ShortDev.Microsoft.ConnectedDevices/Transports/Network/NetworkTransport.cs +++ b/lib/ShortDev.Microsoft.ConnectedDevices/Transports/Network/NetworkTransport.cs @@ -29,6 +29,7 @@ public async Task Listen(CancellationToken cancellationToken) if (client.Client.RemoteEndPoint is not IPEndPoint endPoint) return; + client.NoDelay = true; var stream = client.GetStream(); DeviceConnected?.Invoke(this, new() { @@ -52,6 +53,7 @@ public async Task ConnectAsync(EndpointInfo endpoint) // ToDo: If the windows machine tries to connect back it uses the port assigned here not 5040!! TcpClient client = new(); await client.ConnectAsync(endpoint.ToIPEndPoint()).ConfigureAwait(false); + client.NoDelay = true; return new() { Endpoint = endpoint, diff --git a/tests/ShortDev.Microsoft.ConnectedDevices.Test/CryptorTest.cs b/tests/ShortDev.Microsoft.ConnectedDevices.Test/CryptorTest.cs index f57ddd0e..bdad91b6 100644 --- a/tests/ShortDev.Microsoft.ConnectedDevices.Test/CryptorTest.cs +++ b/tests/ShortDev.Microsoft.ConnectedDevices.Test/CryptorTest.cs @@ -1,5 +1,6 @@ using ShortDev.Microsoft.ConnectedDevices.Encryption; using ShortDev.Microsoft.ConnectedDevices.Messages; +using ShortDev.Microsoft.ConnectedDevices.Transports; using System.Security.Cryptography; namespace ShortDev.Microsoft.ConnectedDevices.Test; @@ -17,10 +18,11 @@ public void Decrypt_ShouldYieldSameAsEncrypt() var header = TestValueGenerator.RandomValue(); ReadOnlySpan payload = TestValueGenerator.RandomValue(); - EndianWriter writer = new(Endianness.BigEndian); - cryptor.EncryptMessage(writer, header, payload); + FragmentSenderSpy fragmentSender = new(); + cryptor.EncryptMessage(fragmentSender, header, payload); + Assert.NotNull(fragmentSender.Fragment); - EndianReader reader = new(Endianness.BigEndian, writer.Buffer.AsSpan()); + EndianReader reader = new(Endianness.BigEndian, fragmentSender.Fragment.Value.Span); header = CommonHeader.Parse(ref reader); var readerContent = reader.ReadToEnd(); @@ -32,4 +34,15 @@ public void Decrypt_ShouldYieldSameAsEncrypt() Assert.True(payload.SequenceEqual(decrypted[sizeof(uint)..])); } + + sealed class FragmentSenderSpy : IFragmentSender + { + public ReadOnlyMemory? Fragment { get; private set; } + public void SendFragment(ReadOnlySpan fragment) + { + Assert.Null(Fragment); + + Fragment = fragment.ToArray(); + } + } } diff --git a/tests/ShortDev.Microsoft.ConnectedDevices.Test/SerializationTest.cs b/tests/ShortDev.Microsoft.ConnectedDevices.Test/SerializationTest.cs index 4318a158..20794147 100644 --- a/tests/ShortDev.Microsoft.ConnectedDevices.Test/SerializationTest.cs +++ b/tests/ShortDev.Microsoft.ConnectedDevices.Test/SerializationTest.cs @@ -1,5 +1,8 @@ using ShortDev.Microsoft.ConnectedDevices.Encryption; using ShortDev.Microsoft.ConnectedDevices.Messages; +using ShortDev.Microsoft.ConnectedDevices.NearShare.Messages; +using ShortDev.Microsoft.ConnectedDevices.Serialization; +using System.Diagnostics; using Xunit.Abstractions; namespace ShortDev.Microsoft.ConnectedDevices.Test; @@ -73,4 +76,24 @@ static void TestRun(Endianness endianness) where T : ICdpSerializable Assert.True(writtenMemory1.Span.SequenceEqual(writtenMemory2.Span)); } } + + [Fact] + public void ValueSet() + { + ValueSet response = new(); + response.Add("ControlMessage", (uint)NearShareControlMsgType.FetchDataResponse); + response.Add("ContentId", (uint)1); + response.Add("BlobPosition", (ulong)2); + response.Add("DataBlob", (List)[42]); + + EndianWriter writer1 = new(Endianness.BigEndian); + response.Write(writer1); + + EndianWriter writer2 = new(Endianness.BigEndian); + FetchDataResponse.Write(writer2, 1, 2, length: 1, out var blob); + blob[0] = 42; + + Assert.Equal(1, blob.Length); + Assert.Equal(writer1.Buffer.ToArray(), writer2.Buffer.ToArray()); + } } \ No newline at end of file diff --git a/tests/ShortDev.Microsoft.ConnectedDevices.Test/ShortDev.Microsoft.ConnectedDevices.Test.csproj b/tests/ShortDev.Microsoft.ConnectedDevices.Test/ShortDev.Microsoft.ConnectedDevices.Test.csproj index f928ad2f..15344e24 100644 --- a/tests/ShortDev.Microsoft.ConnectedDevices.Test/ShortDev.Microsoft.ConnectedDevices.Test.csproj +++ b/tests/ShortDev.Microsoft.ConnectedDevices.Test/ShortDev.Microsoft.ConnectedDevices.Test.csproj @@ -20,6 +20,7 @@ +