Skip to content

Commit

Permalink
VariantValue: support constructing variant values. (#316)
Browse files Browse the repository at this point in the history
The VariantValue type was created as a representation of a
variant that is read from the bus.

For writing variants, a separate type ('Variant') exists.

This PR makes it possible to compose variant values using
VariantValue. These values can then be written to the bus.

The UX of VariantValue when dealing with composite values
(Dictionaries, Arrays of Arrays/Dictionaries/Structs)
is clunkier than Variant (which offers a strongly-typed API).
Such composite values are not expected to be the common case.

The benefit of using VariantValue over Variant is that
values read from the bus as VariantValue can now be also
written back.
  • Loading branch information
tmds authored Nov 13, 2024
1 parent 874e6b2 commit 6a641dd
Show file tree
Hide file tree
Showing 10 changed files with 1,028 additions and 313 deletions.
50 changes: 35 additions & 15 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,28 +281,48 @@ Note that `VariantValue` is a small struct, there is no need to pass it by refer

### Writing a variant

For writing variants, the value must be stored in a `Variant` struct and passed to `Writer.WriteVariant(Variant)`.
For writing variants, the value must be stored in a `VariantValue` struct and passed to `Writer.WriteVariant(VariantValue)`.

Basic types have implicit conversion to `Variant`.
Simple types have implicit conversion to `Variant`.

```cs
Variant v1 = (byte)1;
Variant v2 = "string";
Variant v3 = new ObjectPath("/path");
VariantValue v1 = (byte)1;
VariantValue v2 = "string";
VariantValue v3 = new ObjectPath("/path");
```

For composite variant values, the libraries `Struct`/`Array`/`Dict` classes must be used.
They can also be constructed using a static method:

```cs
Variant v4 = Struct.Create((byte)1, Struct.Create("string", "string"));
Variant v5 = new Dict<byte, Variant>()
{
{ 1, Struct.Create(1, 2) },
{ 2, "string" },
};
Variant v6 = new Array<int>() { 1, 2 };
VariantValue v1 = VariantValue.Byte(1);
VariantValue v2 = VariantValue.String("string");
VariantValue v3 = VariantValue.ObjectPath("/path");
```

Structs can be created using the static `Struct` method:

```cs
VariantValue v1 = VariantValue.Struct("string", 5);
```

As shown in the previous examples, the composite types support nesting.
Arrays can be created using the static `Array` method.

Note that the `Variant` struct is a small struct, there is no need to pass it by reference.
For simple types, the C# array can be passed as the argument:
```cs
VariantValue v = VariantValue.Array(new int[] { 1, 2, 3 })
```

For arrays that hold other arrays, dictionaries, or structs, the item signature must be specified and then the items as a `VariantValue[]`:

```cs
// Array that holds structs of (byte, string).
VariantValue v = VariantValue.Array("(ys)"u8, new [] { VariantValue.Struct((byte)1, "one"), VariantValue.Struct((byte)1, "two") });
```

For dictionaries, the `Dictionary` method can be used. It accepts the key type, the value signature, and then the pairs as a `KeyValuePair<VariantValue, VariantValue>[]`.

```cs
// This example shows how to convert a strongly typed .NET Dictionary to a VariantValue.
Dictionary<byte, int> dict = ...;
VariantValue v = VariantValue.Dictionary(DBusType.Byte, "i"u8, dict.Select(pair => KeyValuePair.Create((VariantValue)pair.Key, (VariantValue)pair.Value)).ToArray());
```
19 changes: 18 additions & 1 deletion src/Tmds.DBus.Protocol/ObjectPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,24 @@ public struct ObjectPath
{
private string _value;

public ObjectPath(string value) => _value = value;
public ObjectPath(string value)
{
_value = value;
ThrowIfEmpty();
}

internal void ThrowIfEmpty()
{
if (_value is null || _value.Length == 0)
{
ThrowEmptyException();
}
}

private void ThrowEmptyException()
{
throw new ArgumentException($"{nameof(ObjectPath)} is empty.");
}

public override string ToString() => _value ?? "";

Expand Down
7 changes: 7 additions & 0 deletions src/Tmds.DBus.Protocol/ProtocolConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ static class ProtocolConstants
public static ReadOnlySpan<byte> SignatureSignature => new byte[] { (byte)'g' };
public static ReadOnlySpan<byte> VariantSignature => new byte[] { (byte)'v' };

private static ReadOnlySpan<byte> SingleTypes => new byte[] { (byte)'y', (byte)'b', (byte)'n', (byte)'q', (byte)'i', (byte)'u', (byte)'x', (byte)'t', (byte)'d', (byte)'h', (byte)'s', (byte)'o', (byte)'g', (byte)'v' };

public static bool IsSingleCompleteType(byte b)
{
return SingleTypes.IndexOf(b) != -1;
}


[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetTypeAlignment(DBusType type)
Expand Down
20 changes: 11 additions & 9 deletions src/Tmds.DBus.Protocol/Reader.Variant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan<byte> in
items.Add(new KeyValuePair<VariantValue, VariantValue>(key, value));
}
ReadOnlySpan<byte> valueSignature = itemSignature.Slice(2, itemSignature.Length - 3);
return new VariantValue(ToVariantValueType(keyType), ToVariantValueType(valueType), VariantValue.GetSignatureObject(items.Count, valueSignature), items.ToArray(), nesting);
return new VariantValue(ToVariantValueType(keyType, keyInnerSignature), ToVariantValueType(valueType, valueInnerSignature), VariantValue.GetSignatureObject(items.Count, valueSignature), items.ToArray(), nesting);
}
else
{
Expand Down Expand Up @@ -136,7 +136,7 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan<byte> in
: ReadTypeAsVariantValue(type, innerSignature, nesting: 0);
items.Add(value);
}
return new VariantValue(ToVariantValueType(type), VariantValue.GetSignatureObject(items.Count, itemSignature), items.ToArray(), nesting);
return new VariantValue(ToVariantValueType(type, innerSignature), VariantValue.GetSignatureObject(items.Count, itemSignature), items.ToArray(), nesting);
}
}
case DBusType.Struct:
Expand All @@ -148,11 +148,6 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan<byte> in
int i = 0;
while (sigReader.TryRead(out type, out innerSignature))
{
if (i > VariantValue.MaxStructFields)
{
VariantValue.ThrowMaxStructFieldsExceeded();
}
variantMask <<= 1;
VariantValue value;
if (type == DBusType.Variant)
{
Expand Down Expand Up @@ -182,6 +177,13 @@ private void ThrowInvalidSignature(string message)
throw new ProtocolException(message);
}

private static VariantValueType ToVariantValueType(DBusType type)
=> (VariantValueType)type;
private static VariantValueType ToVariantValueType(DBusType type, ReadOnlySpan<byte> innerSignature)
{
VariantValueType rv = (VariantValueType)type;
if (rv == VariantValueType.Array && innerSignature[0] == (byte)DBusType.DictEntry)
{
rv = VariantValueType.Dictionary;
}
return rv;
}
}
16 changes: 16 additions & 0 deletions src/Tmds.DBus.Protocol/Signature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ public struct Signature
{
private byte[]? _value;

// note: C# compiler treats these as static data.
public static ReadOnlySpan<byte> Byte => new byte[] { (byte)'y' };
public static ReadOnlySpan<byte> Boolean => new byte[] { (byte)'b' };
public static ReadOnlySpan<byte> Int16 => new byte[] { (byte)'n' };
public static ReadOnlySpan<byte> UInt16 => new byte[] { (byte)'q' };
public static ReadOnlySpan<byte> Int32 => new byte[] { (byte)'i' };
public static ReadOnlySpan<byte> UInt32 => new byte[] { (byte)'u' };
public static ReadOnlySpan<byte> Int64 => new byte[] { (byte)'x' };
public static ReadOnlySpan<byte> UInt64 => new byte[] { (byte)'t' };
public static ReadOnlySpan<byte> Double => new byte[] { (byte)'d' };
public static ReadOnlySpan<byte> UnixFd => new byte[] { (byte)'h' };
public static ReadOnlySpan<byte> String => new byte[] { (byte)'s' };
public static ReadOnlySpan<byte> ObjectPath => new byte[] { (byte)'o' };
public static ReadOnlySpan<byte> Sig => new byte[] { (byte)'g' }; // Name can not be the same as enclosing type.
public static ReadOnlySpan<byte> Variant => new byte[] { (byte)'v' };

internal byte[] Data => _value ?? Array.Empty<byte>();

[Obsolete("Use the constructor that accepts a ReadOnlySpan.")]
Expand Down
3 changes: 3 additions & 0 deletions src/Tmds.DBus.Protocol/Variant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ namespace Tmds.DBus.Protocol;
// DynamicallyAccessedMemberTypes.PublicParameterlessConstructor.
#pragma warning disable IL2091

#if !DEBUG
[Obsolete($"{nameof(Variant)} will be removed. Use the {nameof(VariantValue)} type instead.")]
#endif
public readonly struct Variant
{
private static readonly object Int64Type = DBusType.Int64;
Expand Down
Loading

0 comments on commit 6a641dd

Please sign in to comment.