Skip to content

Commit

Permalink
MessageWriter: support writing VariantValue.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmds committed Nov 10, 2024
1 parent 84a32de commit fa5f79c
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 67 deletions.
16 changes: 8 additions & 8 deletions src/Tmds.DBus.Protocol/MessageWriter.Basic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ public ref partial struct MessageWriter

public void WriteDouble(double value) => WritePrimitiveCore<double>(value, DBusType.Double);

public void WriteString(ReadOnlySpan<byte> value) => WriteStringCore(value);
public void WriteString(scoped ReadOnlySpan<byte> value) => WriteStringCore(value);

public void WriteString(string value) => WriteStringCore(value);

public void WriteSignature(Signature value)
=> WriteSignature(value.Data);

public void WriteSignature(ReadOnlySpan<byte> value)
public void WriteSignature(scoped ReadOnlySpan<byte> value)
{
int length = value.Length;
WriteByte((byte)length);
Expand All @@ -48,7 +48,7 @@ public void WriteSignature(string s)
WriteByte(0);
}

public void WriteObjectPath(ReadOnlySpan<byte> value) => WriteStringCore(value);
public void WriteObjectPath(scoped ReadOnlySpan<byte> value) => WriteStringCore(value);

public void WriteObjectPath(string value) => WriteStringCore(value);

Expand Down Expand Up @@ -108,19 +108,19 @@ public void WriteVariantDouble(double value)
WriteDouble(value);
}

public void WriteVariantString(ReadOnlySpan<byte> value)
public void WriteVariantString(scoped ReadOnlySpan<byte> value)
{
WriteSignature(ProtocolConstants.StringSignature);
WriteString(value);
}

public void WriteVariantSignature(ReadOnlySpan<byte> value)
public void WriteVariantSignature(scoped ReadOnlySpan<byte> value)
{
WriteSignature(ProtocolConstants.SignatureSignature);
WriteSignature(value);
}

public void WriteVariantObjectPath(ReadOnlySpan<byte> value)
public void WriteVariantObjectPath(scoped ReadOnlySpan<byte> value)
{
WriteSignature(ProtocolConstants.ObjectPathSignature);
WriteObjectPath(value);
Expand All @@ -144,7 +144,7 @@ public void WriteVariantObjectPath(string value)
WriteObjectPath(value);
}

private void WriteStringCore(ReadOnlySpan<byte> span)
private void WriteStringCore(scoped ReadOnlySpan<byte> span)
{
int length = span.Length;
WriteUInt32((uint)length);
Expand Down Expand Up @@ -174,7 +174,7 @@ private void WritePrimitiveCore<T>(T value, DBusType type)
Advance(length);
}

private int WriteRaw(ReadOnlySpan<byte> data)
private int WriteRaw(scoped ReadOnlySpan<byte> data)
{
int totalLength = data.Length;
if (totalLength <= MaxSizeHint)
Expand Down
5 changes: 5 additions & 0 deletions src/Tmds.DBus.Protocol/MessageWriter.Variant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ public void WriteVariant(Variant value)
{
value.WriteTo(ref this);
}

public void WriteVariant(VariantValue value)
{
value.WriteVariantTo(ref this);
}
}
1 change: 1 addition & 0 deletions src/Tmds.DBus.Protocol/ProtocolConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ static class ProtocolConstants
public static ReadOnlySpan<byte> StringSignature => new byte[] { (byte)'s' };
public static ReadOnlySpan<byte> ObjectPathSignature => new byte[] { (byte)'o' };
public static ReadOnlySpan<byte> SignatureSignature => new byte[] { (byte)'g' };
public static ReadOnlySpan<byte> VariantSignature => new byte[] { (byte)'v' };


[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
180 changes: 175 additions & 5 deletions src/Tmds.DBus.Protocol/VariantValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1122,22 +1122,192 @@ internal string Signature
get
{
Span<byte> 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<Microsoft.Win32.SafeHandles.SafeFileHandle>() ?? 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<VariantValue, VariantValue>[])!;
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<byte> span = stackalloc byte[ProtocolConstants.MaxSignatureLength];
ReadOnlySpan<byte> signature = GetSignature(type, span);
writer.WriteSignature(signature);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ReadOnlySpan<byte> GetSignature(scoped Span<byte> buffer)
private ReadOnlySpan<byte> GetSignature(VariantValueType type, Span<byte> 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<byte> signature)
=> AppendTypeSignature(Type, signature);

private int AppendTypeSignature(VariantValueType type, Span<byte> signature)
{
VariantValueType type = Type;
switch (type)
{
case VariantValueType.Invalid:
Expand Down
19 changes: 9 additions & 10 deletions test/Tmds.DBus.Protocol.Tests/ReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -256,8 +252,8 @@ public static IEnumerable<object[]> 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[]
Expand Down Expand Up @@ -303,6 +299,9 @@ public static IEnumerable<object[]> 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}},
Expand Down Expand Up @@ -380,8 +379,8 @@ public static IEnumerable<object[]> 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
Expand Down
49 changes: 5 additions & 44 deletions test/Tmds.DBus.Protocol.Tests/WriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,53 +199,14 @@ public static IEnumerable<object[]> WriteIntrospectionXmlTestData
}
}

public static IEnumerable<object[]> WriteVariantAsObjectTestData
[Theory, MemberData(nameof(WriteVariantValueAsVariantTestData))]
public void WriteVariantValueAsVariant(VariantValue value, byte[] bigEndianData, byte[] littleEndianData)
{
get
{
var myDictionary = new Dictionary<byte, string>
{
{ 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<long, string> { 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<object[]> WriteVariantValueAsVariantTestData
=> ReaderTests.ReadVariantValueTestData;


public static IEnumerable<object[]> WriteVariantAsVariantTestData
Expand Down

0 comments on commit fa5f79c

Please sign in to comment.