From fa5f79c5b27260549b04980d670df70177162a08 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Sun, 10 Nov 2024 16:51:52 +0100 Subject: [PATCH] MessageWriter: support writing VariantValue. --- src/Tmds.DBus.Protocol/MessageWriter.Basic.cs | 16 +- .../MessageWriter.Variant.cs | 5 + src/Tmds.DBus.Protocol/ProtocolConstants.cs | 1 + src/Tmds.DBus.Protocol/VariantValue.cs | 180 +++++++++++++++++- test/Tmds.DBus.Protocol.Tests/ReaderTests.cs | 19 +- test/Tmds.DBus.Protocol.Tests/WriterTests.cs | 49 +---- 6 files changed, 203 insertions(+), 67 deletions(-) diff --git a/src/Tmds.DBus.Protocol/MessageWriter.Basic.cs b/src/Tmds.DBus.Protocol/MessageWriter.Basic.cs index e5750640..0e527dc2 100644 --- a/src/Tmds.DBus.Protocol/MessageWriter.Basic.cs +++ b/src/Tmds.DBus.Protocol/MessageWriter.Basic.cs @@ -22,14 +22,14 @@ public ref partial struct MessageWriter public void WriteDouble(double value) => WritePrimitiveCore(value, DBusType.Double); - public void WriteString(ReadOnlySpan value) => WriteStringCore(value); + public void WriteString(scoped ReadOnlySpan value) => WriteStringCore(value); public void WriteString(string value) => WriteStringCore(value); public void WriteSignature(Signature value) => WriteSignature(value.Data); - public void WriteSignature(ReadOnlySpan value) + public void WriteSignature(scoped ReadOnlySpan value) { int length = value.Length; WriteByte((byte)length); @@ -48,7 +48,7 @@ public void WriteSignature(string s) WriteByte(0); } - public void WriteObjectPath(ReadOnlySpan value) => WriteStringCore(value); + public void WriteObjectPath(scoped ReadOnlySpan value) => WriteStringCore(value); public void WriteObjectPath(string value) => WriteStringCore(value); @@ -108,19 +108,19 @@ public void WriteVariantDouble(double value) WriteDouble(value); } - public void WriteVariantString(ReadOnlySpan value) + public void WriteVariantString(scoped ReadOnlySpan value) { WriteSignature(ProtocolConstants.StringSignature); WriteString(value); } - public void WriteVariantSignature(ReadOnlySpan value) + public void WriteVariantSignature(scoped ReadOnlySpan value) { WriteSignature(ProtocolConstants.SignatureSignature); WriteSignature(value); } - public void WriteVariantObjectPath(ReadOnlySpan value) + public void WriteVariantObjectPath(scoped ReadOnlySpan value) { WriteSignature(ProtocolConstants.ObjectPathSignature); WriteObjectPath(value); @@ -144,7 +144,7 @@ public void WriteVariantObjectPath(string value) WriteObjectPath(value); } - private void WriteStringCore(ReadOnlySpan span) + private void WriteStringCore(scoped ReadOnlySpan span) { int length = span.Length; WriteUInt32((uint)length); @@ -174,7 +174,7 @@ private void WritePrimitiveCore(T value, DBusType type) Advance(length); } - private int WriteRaw(ReadOnlySpan data) + private int WriteRaw(scoped ReadOnlySpan data) { int totalLength = data.Length; if (totalLength <= MaxSizeHint) diff --git a/src/Tmds.DBus.Protocol/MessageWriter.Variant.cs b/src/Tmds.DBus.Protocol/MessageWriter.Variant.cs index 99fecbe8..60f1dcd2 100644 --- a/src/Tmds.DBus.Protocol/MessageWriter.Variant.cs +++ b/src/Tmds.DBus.Protocol/MessageWriter.Variant.cs @@ -6,4 +6,9 @@ public void WriteVariant(Variant value) { value.WriteTo(ref this); } + + public void WriteVariant(VariantValue value) + { + value.WriteVariantTo(ref this); + } } diff --git a/src/Tmds.DBus.Protocol/ProtocolConstants.cs b/src/Tmds.DBus.Protocol/ProtocolConstants.cs index ab935f6b..52862734 100644 --- a/src/Tmds.DBus.Protocol/ProtocolConstants.cs +++ b/src/Tmds.DBus.Protocol/ProtocolConstants.cs @@ -18,6 +18,7 @@ static class ProtocolConstants public static ReadOnlySpan StringSignature => new byte[] { (byte)'s' }; public static ReadOnlySpan ObjectPathSignature => new byte[] { (byte)'o' }; public static ReadOnlySpan SignatureSignature => new byte[] { (byte)'g' }; + public static ReadOnlySpan VariantSignature => new byte[] { (byte)'v' }; [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Tmds.DBus.Protocol/VariantValue.cs b/src/Tmds.DBus.Protocol/VariantValue.cs index 4e19658f..d0b7ce24 100644 --- a/src/Tmds.DBus.Protocol/VariantValue.cs +++ b/src/Tmds.DBus.Protocol/VariantValue.cs @@ -1122,22 +1122,192 @@ internal string Signature get { Span span = stackalloc byte[ProtocolConstants.MaxSignatureLength]; - return Encoding.UTF8.GetString(GetSignature(span)); + return Encoding.UTF8.GetString(GetSignature(Type, span)); } } + internal void WriteVariantTo(ref MessageWriter writer) + { + WriteValueTo(ref writer, nestingOffset: +1); + } + + private void WriteValueTo(ref MessageWriter writer, int nestingOffset) + { + (VariantValueType type, int nesting) = DetermineTypeAndNesting(); + + nesting += nestingOffset; + while (nesting > 1) + { + writer.WriteSignature(ProtocolConstants.VariantSignature); + nesting--; + } + if (nesting == 1) + { + WriteSignatureTo(type, ref writer); + } + + switch (type) + { + case VariantValueType.Byte: + writer.WriteByte(UnsafeGetByte()); + break; + case VariantValueType.Bool: + writer.WriteBool(UnsafeGetBool()); + break; + case VariantValueType.Int16: + writer.WriteInt16(UnsafeGetInt16()); + break; + case VariantValueType.UInt16: + writer.WriteUInt16(UnsafeGetUInt16()); + break; + case VariantValueType.Int32: + writer.WriteInt32(UnsafeGetInt32()); + break; + case VariantValueType.UInt32: + writer.WriteUInt32(UnsafeGetUInt32()); + break; + case VariantValueType.Int64: + writer.WriteInt64(UnsafeGetInt64()); + break; + case VariantValueType.UInt64: + writer.WriteUInt64(UnsafeGetUInt64()); + break; + case VariantValueType.Double: + writer.WriteDouble(UnsafeGetDouble()); + break; + case VariantValueType.String: + writer.WriteString(UnsafeGetString()); + break; + case VariantValueType.ObjectPath: + writer.WriteObjectPath(UnsafeGetString()); + break; + case VariantValueType.Signature: + writer.WriteSignature(UnsafeGetSignature()); + break; + case VariantValueType.UnixFd: + writer.WriteHandle(UnsafeReadHandle() ?? throw new ArgumentNullException("SafeHandle unavailable.")); + break; + case VariantValueType.Array: + WriteArrayTo(ref writer); + break; + case VariantValueType.Struct: + WriteStructTo(ref writer); + break; + case VariantValueType.Dictionary: + WriteDictionaryTo(ref writer); + break; + default: + throw new ArgumentException($"VariantValueType: {type}"); + } + } + + private void WriteStructTo(ref MessageWriter writer) + { + writer.WriteStructureStart(); + var items = (_o as VariantValue[])!; + int mask = (int)(_l >> StructVariantMaskShift); + foreach (var item in items) + { + item.WriteValueTo(ref writer, mask & 1); + mask >>= 1; + } + } + + private void WriteDictionaryTo(ref MessageWriter writer) + { + ArrayStart arrayStart = writer.WriteDictionaryStart(); + if (UnsafeCount > 0) + { + DBusType keyType = ToDBusType(UnsafeDetermineInnerType(DictionaryKeyTypeShift)); + DBusType valueType = ToDBusType(UnsafeDetermineInnerType(DictionaryValueTypeShift)); + int keyNestingOffset = keyType == DBusType.Variant ? 1 : 0; + int valueNestingOffset = valueType == DBusType.Variant ? 1 : 0; + + var pairs = (_o as KeyValuePair[])!; + foreach (var pair in pairs) + { + writer.WriteDictionaryEntryStart(); + pair.Key.WriteValueTo(ref writer, keyNestingOffset); + pair.Value.WriteValueTo(ref writer, valueNestingOffset); + } + } + writer.WriteDictionaryEnd(arrayStart); + } + + private void WriteArrayTo(ref MessageWriter writer) + { + DBusType itemType = ToDBusType(UnsafeDetermineInnerType(ArrayItemTypeShift)); + switch (itemType) + { + case DBusType.Byte: + writer.WriteArray((_o as byte[])!); + return; + case DBusType.Int16: + writer.WriteArray((_o as short[])!); + return; + case DBusType.UInt16: + writer.WriteArray((_o as ushort[])!); + return; + case DBusType.Int32: + writer.WriteArray((_o as int[])!); + return; + case DBusType.UInt32: + writer.WriteArray((_o as uint[])!); + return; + case DBusType.Int64: + writer.WriteArray((_o as long[])!); + return; + case DBusType.UInt64: + writer.WriteArray((_o as ulong[])!); + return; + case DBusType.Double: + writer.WriteArray((_o as double[])!); + return; + case DBusType.String: + writer.WriteArray((_o as string[])!); + return; + case DBusType.ObjectPath: + writer.WriteArray((_o as ObjectPath[])!); + return; + } + + ArrayStart arrayStart = writer.WriteArrayStart(itemType); + var items = _o as VariantValue[]; + if (items is not null) + { + int nestingOffset = itemType == DBusType.Variant ? 1 : 0; + foreach (var item in items) + { + item.WriteValueTo(ref writer, nestingOffset); + } + } + writer.WriteArrayEnd(arrayStart); + } + + private static DBusType ToDBusType(VariantValueType type) + => (DBusType)type; + + private void WriteSignatureTo(VariantValueType type, ref MessageWriter writer) + { + Span span = stackalloc byte[ProtocolConstants.MaxSignatureLength]; + ReadOnlySpan signature = GetSignature(type, span); + writer.WriteSignature(signature); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ReadOnlySpan GetSignature(scoped Span buffer) + private ReadOnlySpan GetSignature(VariantValueType type, Span buffer) { Debug.Assert(buffer.Length >= ProtocolConstants.MaxSignatureLength); - int bytesWritten = AppendTypeSignature(buffer); - return buffer.Slice(0, bytesWritten).ToArray(); + int bytesWritten = AppendTypeSignature(type, buffer); + return buffer.Slice(0, bytesWritten); } private int AppendTypeSignature(Span signature) + => AppendTypeSignature(Type, signature); + + private int AppendTypeSignature(VariantValueType type, Span signature) { - VariantValueType type = Type; switch (type) { case VariantValueType.Invalid: diff --git a/test/Tmds.DBus.Protocol.Tests/ReaderTests.cs b/test/Tmds.DBus.Protocol.Tests/ReaderTests.cs index b4048909..31976e8e 100644 --- a/test/Tmds.DBus.Protocol.Tests/ReaderTests.cs +++ b/test/Tmds.DBus.Protocol.Tests/ReaderTests.cs @@ -178,15 +178,11 @@ public bool Equals(VariantValue lhs, VariantValue other) } for (int i = 0; i < lhs.Count; i++) { - if (!lhs.GetItem(i).Equals(other.GetItem(i))) + if (!Equals(lhs.GetItem(i), other.GetItem(i))) { return false; } } - if (lhs.Count == 0 && lhs.ItemType != other.ItemType) - { - return false; - } return true; case VariantValueType.Struct: if (lhs.Count != other.Count) @@ -226,7 +222,7 @@ public bool Equals(VariantValue lhs, VariantValue other) for (int i = 0; i < lhs.Count; i++) { var pair1 = lhs.GetDictionaryEntry(i); - var pair2 = lhs.GetDictionaryEntry(i); + var pair2 = other.GetDictionaryEntry(i); if (!Equals(pair1.Key, pair2.Key) || !Equals(pair1.Value, pair2.Value)) { return false; @@ -256,8 +252,8 @@ public static IEnumerable ReadVariantValueTestData VariantValue myDictionary = new VariantValue(VariantValueType.Byte, VariantValueType.String, new[] { - KeyValuePair.Create(new VariantValue(1), new VariantValue("one")), - KeyValuePair.Create(new VariantValue(1), new VariantValue("two")), + KeyValuePair.Create(new VariantValue((byte)1), new VariantValue("one")), + KeyValuePair.Create(new VariantValue((byte)2), new VariantValue("two")), }); VariantValue stringVariantDictionary = new VariantValue(VariantValueType.String, VariantValueType.Variant, new[] @@ -303,6 +299,9 @@ public static IEnumerable ReadVariantValueTestData new object[] {new VariantValue(new Signature("sis"u8)), new byte[] {1, 103, 0, 3, 115, 105, 115, 0}, new byte[] {1, 103, 0, 3, 115, 105, 115, 0}}, + new object[] {new VariantValue(new byte[] { }), + new byte[] {2, 97, 121, 0, 0, 0, 0, 0}, + new byte[] {2, 97, 121, 0, 0, 0, 0, 0}}, new object[] {new VariantValue(new long[] { 1, 2}), new byte[] {2, 97, 120, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2}, new byte[] {2, 97, 120, 0, 16, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0}}, @@ -380,8 +379,8 @@ public static IEnumerable ReadVariantValueTestData new byte[] {1, 118, 0, 2, 97, 118, 0, 0, 8, 0, 0, 0, 1, 105, 0, 0, 1, 0, 0, 0}}, // v -> av / v -> v / v -> i new object[] {VariantValue.CreateVariant(new VariantValue(VariantValueType.Variant, new VariantValue[] { VariantValue.CreateVariant(1) })), - new byte[] {1, 118, 0, 2, 97, 118, 0, 0, 0, 0, 0, 9, 1, 118, 0, 1, 105, 0, 0, 0, 0, 0, 0, 1}, - new byte[] {1, 118, 0, 2, 97, 118, 0, 0, 9, 0, 0, 0, 1, 118, 0, 1, 105, 0, 0, 0, 1, 0, 0, 0}}, + new byte[] {1, 118, 0, 2, 97, 118, 0, 0, 0, 0, 0, 12, 1, 118, 0, 1, 105, 0, 0, 0, 0, 0, 0, 1}, + new byte[] {1, 118, 0, 2, 97, 118, 0, 0, 12, 0, 0, 0, 1, 118, 0, 1, 105, 0, 0, 0, 1, 0, 0, 0}}, // v -> a{sv} // 0: v -> i // 1: v -> v / v -> i diff --git a/test/Tmds.DBus.Protocol.Tests/WriterTests.cs b/test/Tmds.DBus.Protocol.Tests/WriterTests.cs index aec47701..3f9b94bf 100644 --- a/test/Tmds.DBus.Protocol.Tests/WriterTests.cs +++ b/test/Tmds.DBus.Protocol.Tests/WriterTests.cs @@ -199,53 +199,14 @@ public static IEnumerable WriteIntrospectionXmlTestData } } - public static IEnumerable WriteVariantAsObjectTestData + [Theory, MemberData(nameof(WriteVariantValueAsVariantTestData))] + public void WriteVariantValueAsVariant(VariantValue value, byte[] bigEndianData, byte[] littleEndianData) { - get - { - var myDictionary = new Dictionary - { - { 1, "one" }, - { 2, "two" } - }; - return new[] - { - new object[] {true, new byte[] {1, 98, 0, 0, 0, 0, 0, 1}, - new byte[] {1, 98, 0, 0, 1, 0, 0, 0}}, - new object[] {(byte)5, new byte[] {1, 121, 0, 5}, - new byte[] {1, 121, 0, 5}}, - new object[] {(short)0x0102, new byte[] {1, 110, 0, 0, 1, 2}, - new byte[] {1, 110, 0, 0, 2, 1}}, - new object[] {0x01020304, new byte[] {1, 105, 0, 0, 1, 2, 3, 4}, - new byte[] {1, 105, 0, 0, 4, 3, 2, 1}}, - new object[] {0x0102030405060708, new byte[] {1, 120, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8}, - new byte[] {1, 120, 0, 0, 0, 0, 0, 0, 8, 7, 6, 5, 4, 3, 2, 1}}, - new object[] {1.0, new byte[] {1, 100, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0}, - new byte[] {1, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 63}}, - new object[] {(ushort)0x0102, new byte[] {1, 113, 0, 0, 1, 2}, - new byte[] {1, 113, 0, 0, 2, 1}}, - new object[] {(uint)0x01020304, new byte[] {1, 117, 0, 0, 1, 2, 3, 4}, - new byte[] {1, 117, 0, 0, 4, 3, 2, 1}}, - new object[] {(ulong)0x0102030405060708, new byte[] {1, 116, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8}, - new byte[] {1, 116, 0, 0, 0, 0, 0, 0, 8, 7, 6, 5, 4, 3, 2, 1}}, - new object[] {"hw", new byte[] {1, 115, 0, 0, 0, 0, 0, 2, 104, 119, 0}, - new byte[] {1, 115, 0, 0, 2, 0, 0, 0, 104, 119, 0}}, - new object[] {new ObjectPath("/a/b"), new byte[] {1, 111, 0, 0, 0, 0, 0, 4, 47, 97, 47, 98, 0}, - new byte[] {1, 111, 0, 0, 4, 0, 0, 0, 47, 97, 47, 98, 0}}, - new object[] {new Signature("sis"u8), new byte[] {1, 103, 0, 3, 115, 105, 115, 0}, - new byte[] {1, 103, 0, 3, 115, 105, 115, 0}}, - new object[] {new long[] { 1, 2}, new byte[] {2, 97, 120, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2}, - new byte[] {2, 97, 120, 0, 16, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0}}, - new object[] {new ValueTuple { Item1 = 1, Item2 = "hw" }, new byte[] {4, 40, 120, 115, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 104, 119, 0}, - new byte[] {4, 40, 120, 115, 41, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 104, 119, 0}}, - new object[] {myDictionary, new byte[] {5, 97, 123, 121, 115, 125, 0, 0, 0, 0, 0, 28, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 111, 110, 101, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 116, 119, 111, 0}, - new byte[] {5, 97, 123, 121, 115, 125, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 111, 110, 101, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 116, 119, 111, 0}}, - new object[] {((byte)1, (byte)2, (byte)3, (byte)4, (byte)5, (byte)6, (byte)7, (byte)8), new byte[] {10, 40, 121, 121, 121, 121, 121, 121, 121, 121, 41, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8}, - new byte[] {10, 40, 121, 121, 121, 121, 121, 121, 121, 121, 41, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8}}, - }; - } + TestWrite(value, (ref MessageWriter writer, VariantValue value) => writer.WriteVariant(value), alignment: 0, bigEndianData, littleEndianData); } + public static IEnumerable WriteVariantValueAsVariantTestData + => ReaderTests.ReadVariantValueTestData; public static IEnumerable WriteVariantAsVariantTestData