Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VariantValue: support constructing variant values. #316

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
{
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 All @@ -12,4 +29,4 @@

public static implicit operator ObjectPath(string value) => new ObjectPath(value);

public Variant AsVariant() => new Variant(this);

Check warning on line 32 in src/Tmds.DBus.Protocol/ObjectPath.cs

View workflow job for this annotation

GitHub Actions / build

'Variant' is obsolete: 'Variant will be removed. Use the VariantValue type instead.'

Check warning on line 32 in src/Tmds.DBus.Protocol/ObjectPath.cs

View workflow job for this annotation

GitHub Actions / build

'Variant' is obsolete: 'Variant will be removed. Use the VariantValue type instead.'
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
Loading