Browse Source

add tmds.dbus.protocol

feature/linux-automation-backup
Jumar Macato 2 years ago
parent
commit
4cbea1b48a
  1. 3
      Avalonia.Desktop.slnf
  2. 7
      Avalonia.sln
  3. 4
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  4. 17
      src/Linux/Tmds.DBus.Protocol/ActionException.cs
  5. 220
      src/Linux/Tmds.DBus.Protocol/Address.cs
  6. 191
      src/Linux/Tmds.DBus.Protocol/AddressReader.cs
  7. 88
      src/Linux/Tmds.DBus.Protocol/Array.cs
  8. 36
      src/Linux/Tmds.DBus.Protocol/ClientConnectionOptions.cs
  9. 19
      src/Linux/Tmds.DBus.Protocol/ClientSetupResult.cs
  10. 17
      src/Linux/Tmds.DBus.Protocol/CloseSafeHandle.cs
  11. 10
      src/Linux/Tmds.DBus.Protocol/ConnectException.cs
  12. 112
      src/Linux/Tmds.DBus.Protocol/Connection.DBus.cs
  13. 335
      src/Linux/Tmds.DBus.Protocol/Connection.cs
  14. 7
      src/Linux/Tmds.DBus.Protocol/ConnectionOptions.cs
  15. 7
      src/Linux/Tmds.DBus.Protocol/Constants.cs
  16. 1290
      src/Linux/Tmds.DBus.Protocol/DBusConnection.cs
  17. 50
      src/Linux/Tmds.DBus.Protocol/DBusEnvironment.cs
  18. 15
      src/Linux/Tmds.DBus.Protocol/DBusException.cs
  19. 23
      src/Linux/Tmds.DBus.Protocol/DBusType.cs
  20. 89
      src/Linux/Tmds.DBus.Protocol/Dict.cs
  21. 6
      src/Linux/Tmds.DBus.Protocol/DisconnectedException.cs
  22. 18
      src/Linux/Tmds.DBus.Protocol/DisposableMessage.cs
  23. 14
      src/Linux/Tmds.DBus.Protocol/Feature.cs
  24. 14
      src/Linux/Tmds.DBus.Protocol/GlobalUsings.cs
  25. 6
      src/Linux/Tmds.DBus.Protocol/IDBusWritable.cs
  26. 14
      src/Linux/Tmds.DBus.Protocol/IMessageStream.cs
  27. 13
      src/Linux/Tmds.DBus.Protocol/IMethodHandler.cs
  28. 9
      src/Linux/Tmds.DBus.Protocol/IMethodHandlerDictionary.cs
  29. 51
      src/Linux/Tmds.DBus.Protocol/IntrospectionXml.cs
  30. 120
      src/Linux/Tmds.DBus.Protocol/MatchRule.cs
  31. 242
      src/Linux/Tmds.DBus.Protocol/Message.cs
  32. 43
      src/Linux/Tmds.DBus.Protocol/MessageBuffer.cs
  33. 42
      src/Linux/Tmds.DBus.Protocol/MessageBufferPool.cs
  34. 10
      src/Linux/Tmds.DBus.Protocol/MessageFlags.cs
  35. 14
      src/Linux/Tmds.DBus.Protocol/MessageHeader.cs
  36. 27
      src/Linux/Tmds.DBus.Protocol/MessagePool.cs
  37. 403
      src/Linux/Tmds.DBus.Protocol/MessageStream.cs
  38. 9
      src/Linux/Tmds.DBus.Protocol/MessageType.cs
  39. 271
      src/Linux/Tmds.DBus.Protocol/MessageWriter.Array.cs
  40. 234
      src/Linux/Tmds.DBus.Protocol/MessageWriter.Basic.cs
  41. 128
      src/Linux/Tmds.DBus.Protocol/MessageWriter.Dictionary.cs
  42. 28
      src/Linux/Tmds.DBus.Protocol/MessageWriter.Handle.cs
  43. 215
      src/Linux/Tmds.DBus.Protocol/MessageWriter.Header.cs
  44. 75
      src/Linux/Tmds.DBus.Protocol/MessageWriter.IntrospectionXml.cs
  45. 299
      src/Linux/Tmds.DBus.Protocol/MessageWriter.Struct.cs
  46. 77
      src/Linux/Tmds.DBus.Protocol/MessageWriter.Variant.Dynamic.cs
  47. 9
      src/Linux/Tmds.DBus.Protocol/MessageWriter.Variant.cs
  48. 994
      src/Linux/Tmds.DBus.Protocol/MessageWriter.WriteT.Dynamic.cs
  49. 82
      src/Linux/Tmds.DBus.Protocol/MessageWriter.WriteT.cs
  50. 193
      src/Linux/Tmds.DBus.Protocol/MessageWriter.cs
  51. 97
      src/Linux/Tmds.DBus.Protocol/MethodContext.cs
  52. 214
      src/Linux/Tmds.DBus.Protocol/Netstandard2_0Extensions.cs
  53. 53
      src/Linux/Tmds.DBus.Protocol/Netstandard2_1Extensions.cs
  54. 16
      src/Linux/Tmds.DBus.Protocol/ObjectPath.cs
  55. 12
      src/Linux/Tmds.DBus.Protocol/ObserverFlags.cs
  56. 311
      src/Linux/Tmds.DBus.Protocol/PathNodeDictionary.cs
  57. 19
      src/Linux/Tmds.DBus.Protocol/PlatformDetection.cs
  58. 25
      src/Linux/Tmds.DBus.Protocol/Polyfill/DynamicallyAccessedMemberTypes.cs
  59. 30
      src/Linux/Tmds.DBus.Protocol/Polyfill/DynamicallyAccessedMembersAttribute.cs
  60. 530
      src/Linux/Tmds.DBus.Protocol/Polyfill/Nerdbank.Streams.Sequence.cs
  61. 144
      src/Linux/Tmds.DBus.Protocol/Polyfill/NullableAttributes.cs
  62. 23
      src/Linux/Tmds.DBus.Protocol/Polyfill/RequiresUnreferencedCodeAttribute.cs
  63. 417
      src/Linux/Tmds.DBus.Protocol/Polyfill/SequenceReader.cs
  64. 194
      src/Linux/Tmds.DBus.Protocol/Polyfill/SequenceReaderExtensions.cs
  65. 32
      src/Linux/Tmds.DBus.Protocol/Polyfill/UnconditionalSuppressMessageAttribute.cs
  66. 86
      src/Linux/Tmds.DBus.Protocol/ProtocolConstants.cs
  67. 7
      src/Linux/Tmds.DBus.Protocol/ProtocolException.cs
  68. 166
      src/Linux/Tmds.DBus.Protocol/Reader.Array.cs
  69. 132
      src/Linux/Tmds.DBus.Protocol/Reader.Basic.cs
  70. 48
      src/Linux/Tmds.DBus.Protocol/Reader.Dictionary.cs
  71. 36
      src/Linux/Tmds.DBus.Protocol/Reader.Handle.cs
  72. 1045
      src/Linux/Tmds.DBus.Protocol/Reader.ReadT.Dynamic.cs
  73. 77
      src/Linux/Tmds.DBus.Protocol/Reader.ReadT.cs
  74. 309
      src/Linux/Tmds.DBus.Protocol/Reader.Struct.cs
  75. 8
      src/Linux/Tmds.DBus.Protocol/Reader.Variant.Dynamic.cs
  76. 161
      src/Linux/Tmds.DBus.Protocol/Reader.Variant.cs
  77. 90
      src/Linux/Tmds.DBus.Protocol/Reader.cs
  78. 12
      src/Linux/Tmds.DBus.Protocol/Signature.cs
  79. 271
      src/Linux/Tmds.DBus.Protocol/SignatureReader.cs
  80. 232
      src/Linux/Tmds.DBus.Protocol/SocketExtensions.cs
  81. 24
      src/Linux/Tmds.DBus.Protocol/StringBuilderExtensions.cs
  82. 29
      src/Linux/Tmds.DBus.Protocol/Strings.cs
  83. 469
      src/Linux/Tmds.DBus.Protocol/Struct.cs
  84. 32
      src/Linux/Tmds.DBus.Protocol/ThrowHelper.cs
  85. 22
      src/Linux/Tmds.DBus.Protocol/Tmds.DBus.Protocol.csproj
  86. 134
      src/Linux/Tmds.DBus.Protocol/TypeModel.Dynamic.cs
  87. 359
      src/Linux/Tmds.DBus.Protocol/TypeModel.cs
  88. 244
      src/Linux/Tmds.DBus.Protocol/UnixFdCollection.cs
  89. 20
      src/Linux/Tmds.DBus.Protocol/Utf8Span.cs
  90. 451
      src/Linux/Tmds.DBus.Protocol/Variant.cs
  91. 41
      src/Linux/Tmds.DBus.Protocol/VariantExtensions.cs
  92. 795
      src/Linux/Tmds.DBus.Protocol/VariantValue.cs
  93. 30
      src/Linux/Tmds.DBus.Protocol/VariantValueType.cs

3
Avalonia.Desktop.slnf

@ -30,15 +30,16 @@
"src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj",
"src\\Avalonia.Native\\Avalonia.Native.csproj",
"src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",
"src\\Avalonia.Vulkan\\Avalonia.Vulkan.csproj",
"src\\Avalonia.ReactiveUI\\Avalonia.ReactiveUI.csproj",
"src\\Avalonia.Remote.Protocol\\Avalonia.Remote.Protocol.csproj",
"src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
"src\\Avalonia.Vulkan\\Avalonia.Vulkan.csproj",
"src\\Avalonia.X11\\Avalonia.X11.csproj",
"src\\Headless\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
"src\\Headless\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj",
"src\\Linux\\Tmds.DBus.Protocol\\Tmds.DBus.Protocol.csproj",
"src\\Linux\\Tmds.DBus.SourceGenerator\\Tmds.DBus.SourceGenerator.csproj",
"src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj",
"src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",

7
Avalonia.sln

@ -304,6 +304,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.RenderTests.WpfCom
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tmds.DBus.SourceGenerator", "src\Linux\Tmds.DBus.SourceGenerator\Tmds.DBus.SourceGenerator.csproj", "{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tmds.DBus.Protocol", "src\Linux\Tmds.DBus.Protocol\Tmds.DBus.Protocol.csproj", "{9A7672D7-77B9-4D50-AF22-6C5049C4712B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -708,6 +710,10 @@ Global
{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB}.Release|Any CPU.Build.0 = Release|Any CPU
{9A7672D7-77B9-4D50-AF22-6C5049C4712B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A7672D7-77B9-4D50-AF22-6C5049C4712B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A7672D7-77B9-4D50-AF22-6C5049C4712B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A7672D7-77B9-4D50-AF22-6C5049C4712B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -796,6 +802,7 @@ Global
{DA5F1FF9-4259-4C54-B443-85CFA226EE6A} = {9CCA131B-DE95-4D44-8788-C3CAE28574CD}
{9AE1B827-21AC-4063-AB22-C8804B7F931E} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{12AE6CBC-C0A1-4BEF-AED6-81E566AAE7EB} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{9A7672D7-77B9-4D50-AF22-6C5049C4712B} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

4
src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj

@ -13,12 +13,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Tmds.DBus.Protocol" Version="0.18.0" />
<ProjectReference Include="../Linux/Tmds.DBus.SourceGenerator/Tmds.DBus.SourceGenerator.csproj"
PrivateAssets="all"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="DBusGeneratorMode" />
<ProjectReference Include="..\Linux\Tmds.DBus.Protocol\Tmds.DBus.Protocol.csproj" />
</ItemGroup>
<ItemGroup>
@ -51,8 +51,6 @@
<!-- AT-SPI2 -->
<AdditionalFiles Include="DBusXml/org.a11y.Bus.xml" DBusGeneratorMode="Proxy" />
<AdditionalFiles Include="DBusXml/org.a11y.atspi.accessible.root.xml" DBusGeneratorMode="Handler" />
</ItemGroup>

17
src/Linux/Tmds.DBus.Protocol/ActionException.cs

@ -0,0 +1,17 @@
namespace Tmds.DBus.Protocol;
public static class ActionException
{
// Exception used when the IDisposable returned by AddMatchAsync gets disposed.
public static bool IsObserverDisposed(Exception exception)
=> object.ReferenceEquals(exception, DBusConnection.ObserverDisposedException);
// Exception used when the Connection gets disposed.
public static bool IsConnectionDisposed(Exception exception)
// note: Connection.DisposedException is only ever used as an InnerException of DisconnectedException,
// so we directly check for that.
=> object.ReferenceEquals(exception?.InnerException, Connection.DisposedException);
public static bool IsDisposed(Exception exception)
=> IsObserverDisposed(exception) || IsConnectionDisposed(exception);
}

220
src/Linux/Tmds.DBus.Protocol/Address.cs

@ -0,0 +1,220 @@
using System.IO.MemoryMappedFiles;
namespace Tmds.DBus.Protocol;
public static class Address
{
private static bool _systemAddressResolved = false;
private static string? _systemAddress = null;
private static bool _sessionAddressResolved = false;
private static string? _sessionAddress = null;
public static string? System
{
get
{
if (_systemAddressResolved)
{
return _systemAddress;
}
_systemAddress = Environment.GetEnvironmentVariable("DBUS_SYSTEM_BUS_ADDRESS");
if (string.IsNullOrEmpty(_systemAddress) && !PlatformDetection.IsWindows())
{
_systemAddress = "unix:path=/var/run/dbus/system_bus_socket";
}
_systemAddressResolved = true;
return _systemAddress;
}
}
public static string? Session
{
get
{
if (_sessionAddressResolved)
{
return _sessionAddress;
}
_sessionAddress = Environment.GetEnvironmentVariable("DBUS_SESSION_BUS_ADDRESS");
if (string.IsNullOrEmpty(_sessionAddress))
{
if (PlatformDetection.IsWindows())
{
_sessionAddress = GetSessionBusAddressFromSharedMemory();
}
else
{
_sessionAddress = GetSessionBusAddressFromX11();
}
}
_sessionAddressResolved = true;
return _sessionAddress;
}
}
private static string? GetSessionBusAddressFromX11()
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DISPLAY")))
{
var display = XOpenDisplay(null);
if (display == IntPtr.Zero)
{
return null;
}
string username;
unsafe
{
const int BufLen = 1024;
byte* stackBuf = stackalloc byte[BufLen];
Passwd passwd;
IntPtr result;
getpwuid_r(getuid(), out passwd, stackBuf, BufLen, out result);
if (result != IntPtr.Zero)
{
username = Marshal.PtrToStringAnsi(passwd.Name)!;
}
else
{
return null;
}
}
var machineId = DBusEnvironment.MachineId.Replace("-", string.Empty);
var selectionName = $"_DBUS_SESSION_BUS_SELECTION_{username}_{machineId}";
var selectionAtom = XInternAtom(display, selectionName, false);
if (selectionAtom == IntPtr.Zero)
{
return null;
}
var owner = XGetSelectionOwner(display, selectionAtom);
if (owner == IntPtr.Zero)
{
return null;
}
var addressAtom = XInternAtom(display, "_DBUS_SESSION_BUS_ADDRESS", false);
if (addressAtom == IntPtr.Zero)
{
return null;
}
IntPtr actualReturnType;
IntPtr actualReturnFormat;
IntPtr nrItemsReturned;
IntPtr bytesAfterReturn;
IntPtr propReturn;
int rv = XGetWindowProperty(display, owner, addressAtom, 0, 1024, false, (IntPtr)31 /* XA_STRING */,
out actualReturnType, out actualReturnFormat, out nrItemsReturned, out bytesAfterReturn, out propReturn);
string? address = rv == 0 ? Marshal.PtrToStringAnsi(propReturn) : null;
if (propReturn != IntPtr.Zero)
{
XFree(propReturn);
}
XCloseDisplay(display);
return address;
}
else
{
return null;
}
}
private static string? GetSessionBusAddressFromSharedMemory()
{
string? result = ReadSharedMemoryString("DBusDaemonAddressInfo", 255);
if (string.IsNullOrEmpty(result))
{
result = ReadSharedMemoryString("DBusDaemonAddressInfoDebug", 255);
}
return result;
}
private static string? ReadSharedMemoryString(string id, long maxlen = -1)
{
if (!PlatformDetection.IsWindows())
{
return null;
}
MemoryMappedFile? shmem;
try
{
shmem = MemoryMappedFile.OpenExisting(id);
}
catch
{
shmem = null;
}
if (shmem == null)
{
return null;
}
MemoryMappedViewStream s = shmem.CreateViewStream();
long len = s.Length;
if (maxlen >= 0 && len > maxlen)
{
len = maxlen;
}
if (len == 0)
{
return string.Empty;
}
if (len > int.MaxValue)
{
len = int.MaxValue;
}
byte[] bytes = new byte[len];
int count = s.Read(bytes, 0, (int)len);
if (count <= 0)
{
return string.Empty;
}
count = 0;
while (count < len && bytes[count] != 0)
{
count++;
}
return Encoding.UTF8.GetString(bytes, 0, count);
}
struct Passwd
{
public IntPtr Name;
public IntPtr Password;
public uint UserID;
public uint GroupID;
public IntPtr UserInfo;
public IntPtr HomeDir;
public IntPtr Shell;
}
[DllImport("libc")]
private static extern unsafe int getpwuid_r(uint uid, out Passwd pwd, byte* buf, int bufLen, out IntPtr result);
[DllImport("libc")]
private static extern uint getuid();
[DllImport("libX11")]
private static extern IntPtr XOpenDisplay(string? name);
[DllImport("libX11")]
private static extern int XCloseDisplay(IntPtr display);
[DllImport("libX11")]
private static extern IntPtr XInternAtom(IntPtr display, string atom_name, bool only_if_exists);
[DllImport("libX11")]
private static extern int XGetWindowProperty(IntPtr display, IntPtr w, IntPtr property,
int long_offset, int long_length, bool delete, IntPtr req_type,
out IntPtr actual_type_return, out IntPtr actual_format_return,
out IntPtr nitems_return, out IntPtr bytes_after_return, out IntPtr prop_return);
[DllImport("libX11")]
private static extern int XFree(IntPtr data);
[DllImport("libX11")]
private static extern IntPtr XGetSelectionOwner(IntPtr display, IntPtr Atom);
}

191
src/Linux/Tmds.DBus.Protocol/AddressReader.cs

@ -0,0 +1,191 @@
namespace Tmds.DBus.Protocol;
static class AddressParser
{
public struct AddressEntry
{
internal string String { get; }
internal int Offset { get; }
internal int Count { get; }
internal AddressEntry(string s, int offset, int count) =>
(String, Offset, Count) = (s, offset, count);
internal ReadOnlySpan<char> AsSpan() => String.AsSpan(Offset, Count);
public override string ToString() => AsSpan().AsString();
}
public static bool TryGetNextEntry(string addresses, ref AddressEntry address)
{
int offset = address.String is null ? 0 : address.Offset + address.Count + 1;
if (offset >= addresses.Length - 1)
{
return false;
}
ReadOnlySpan<char> span = addresses.AsSpan().Slice(offset);
int length = span.IndexOf(';');
if (length == -1)
{
length = span.Length;
}
address = new AddressEntry(addresses, offset, length);
return true;
}
public static bool IsType(AddressEntry address, string type)
{
ReadOnlySpan<char> span = address.AsSpan();
return span.Length > type.Length && span[type.Length] == ':' && span.StartsWith(type.AsSpan());
}
public static void ParseTcpProperties(AddressEntry address, out string host, out int? port, out Guid guid)
{
host = null!;
port = null;
guid = default;
ReadOnlySpan<char> properties = GetProperties(address);
while (TryParseProperty(ref properties, out ReadOnlySpan<char> key, out ReadOnlySpan<char> value))
{
if (key.SequenceEqual("host".AsSpan()))
{
host = Unescape(value);
}
else if (key.SequenceEqual("port".AsSpan()))
{
port = int.Parse(Unescape(value));
}
else if (key.SequenceEqual("guid".AsSpan()))
{
guid = Guid.ParseExact(Unescape(value), "N");
}
}
if (host is null)
{
host = "localhost";
}
}
public static void ParseUnixProperties(AddressEntry address, out string path, out Guid guid)
{
path = null!;
bool isAbstract = false;
guid = default;
ReadOnlySpan<char> properties = GetProperties(address);
while (TryParseProperty(ref properties, out ReadOnlySpan<char> key, out ReadOnlySpan<char> value))
{
if (key.SequenceEqual("path".AsSpan()))
{
path = Unescape(value);
}
else if (key.SequenceEqual("abstract".AsSpan()))
{
isAbstract = true;
path = Unescape(value);
}
else if (key.SequenceEqual("guid".AsSpan()))
{
guid = Guid.ParseExact(Unescape(value), "N");
}
}
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("path");
}
if (isAbstract)
{
path = (char)'\0' + path;
}
}
private static ReadOnlySpan<char> GetProperties(AddressEntry address)
{
ReadOnlySpan<char> span = address.AsSpan();
int colonPos = span.IndexOf(':');
if (colonPos == -1)
{
throw new FormatException("No colon found.");
}
return span.Slice(colonPos + 1);
}
public static bool TryParseProperty(ref ReadOnlySpan<char> properties, out ReadOnlySpan<char> key, out ReadOnlySpan<char> value)
{
if (properties.Length == 0)
{
key = default;
value = default;
return false;
}
int end = properties.IndexOf(',');
ReadOnlySpan<char> property;
if (end == -1)
{
property = properties;
properties = default;
}
else
{
property = properties.Slice(0, end);
properties = properties.Slice(end + 1);
}
int equalPos = property.IndexOf('=');
if (equalPos == -1)
{
throw new FormatException("No equals sign found.");
}
key = property.Slice(0, equalPos);
value = property.Slice(equalPos + 1);
return true;
}
private static string Unescape(ReadOnlySpan<char> value)
{
if (!value.Contains("%".AsSpan(), StringComparison.Ordinal))
{
return value.AsString();
}
Span<char> unescaped = stackalloc char[Constants.StackAllocCharThreshold];
int pos = 0;
for (int i = 0; i < value.Length;)
{
char c = value[i++];
if (c != '%')
{
unescaped[pos++] = c;
}
else if (i + 2 < value.Length)
{
int a = FromHexChar(value[i++]);
int b = FromHexChar(value[i++]);
if (a == -1 || b == -1)
{
throw new FormatException("Invalid hex char.");
}
unescaped[pos++] = (char)((a << 4) + b);
}
else
{
throw new FormatException("Escape sequence is too short.");
}
}
return unescaped.Slice(0, pos).AsString();
static int FromHexChar(char c)
{
if (c >= '0' && c <= '9')
{
return c - '0';
}
if (c >= 'A' && c <= 'F')
{
return c - 'A' + 10;
}
if (c >= 'a' && c <= 'f')
{
return c - 'a' + 10;
}
return -1;
}
}
}

88
src/Linux/Tmds.DBus.Protocol/Array.cs

@ -0,0 +1,88 @@
using System.Collections;
namespace Tmds.DBus.Protocol;
// Using obsolete generic write members
#pragma warning disable CS0618
public sealed class Array<T> : IDBusWritable, IList<T>
where T : notnull
{
private readonly List<T> _values;
public Array() :
this(new List<T>())
{ }
public Array(int capacity) :
this(new List<T>(capacity))
{ }
public Array(IEnumerable<T> collection) :
this(new List<T>(collection))
{ }
private Array(List<T> values)
{
TypeModel.EnsureSupportedVariantType<T>();
_values = values;
}
public void Add(T item)
=> _values.Add(item);
public void Clear()
=> _values.Clear();
public int Count => _values.Count;
bool ICollection<T>.IsReadOnly
=> false;
public T this[int index]
{
get => _values[index];
set => _values[index] = value;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
=> _values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> _values.GetEnumerator();
public int IndexOf(T item)
=> _values.IndexOf(item);
public void Insert(int index, T item)
=> _values.Insert(index, item);
public void RemoveAt(int index)
=> _values.RemoveAt(index);
public bool Contains(T item)
=> _values.Contains(item);
public void CopyTo(T[] array, int arrayIndex)
=> _values.CopyTo(array, arrayIndex);
public bool Remove(T item)
=> _values.Remove(item);
public Variant AsVariant()
=> Variant.FromArray(this);
public static implicit operator Variant(Array<T> value)
=> value.AsVariant();
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
{
#if NET5_0_OR_GREATER
Span<T> span = CollectionsMarshal.AsSpan(_values);
writer.WriteArray<T>(span);
#else
writer.WriteArray(_values);
#endif
}
}

36
src/Linux/Tmds.DBus.Protocol/ClientConnectionOptions.cs

@ -0,0 +1,36 @@
namespace Tmds.DBus.Protocol;
public class ClientConnectionOptions : ConnectionOptions
{
private string _address;
public ClientConnectionOptions(string address)
{
if (address == null)
throw new ArgumentNullException(nameof(address));
_address = address;
}
protected ClientConnectionOptions()
{
_address = string.Empty;
}
public bool AutoConnect { get; set; }
internal bool IsShared { get; set; }
protected internal virtual ValueTask<ClientSetupResult> SetupAsync(CancellationToken cancellationToken)
{
return new ValueTask<ClientSetupResult>(
new ClientSetupResult(_address)
{
SupportsFdPassing = true,
UserId = DBusEnvironment.UserId,
MachineId = DBusEnvironment.MachineId
});
}
protected internal virtual void Teardown(object? token)
{ }
}

19
src/Linux/Tmds.DBus.Protocol/ClientSetupResult.cs

@ -0,0 +1,19 @@
namespace Tmds.DBus.Protocol;
public class ClientSetupResult
{
public ClientSetupResult(string address)
{
ConnectionAddress = address ?? throw new ArgumentNullException(nameof(address));
}
public string ConnectionAddress { get; }
public object? TeardownToken { get; set; }
public string? UserId { get; set; }
public string? MachineId { get; set; }
public bool SupportsFdPassing { get; set; }
}

17
src/Linux/Tmds.DBus.Protocol/CloseSafeHandle.cs

@ -0,0 +1,17 @@
namespace Tmds.DBus.Protocol;
sealed class CloseSafeHandle : SafeHandle
{
public CloseSafeHandle() :
base(new IntPtr(-1), ownsHandle: true)
{ }
public override bool IsInvalid
=> handle == new IntPtr(-1);
protected override bool ReleaseHandle()
=> close(handle.ToInt32()) == 0;
[DllImport("libc", SetLastError = true)]
internal static extern int close(int fd);
}

10
src/Linux/Tmds.DBus.Protocol/ConnectException.cs

@ -0,0 +1,10 @@
namespace Tmds.DBus.Protocol;
public class ConnectException : Exception
{
public ConnectException(string message) : base(message)
{ }
public ConnectException(string message, Exception innerException) : base(message, innerException)
{ }
}

112
src/Linux/Tmds.DBus.Protocol/Connection.DBus.cs

@ -0,0 +1,112 @@
using System.Threading.Channels;
namespace Tmds.DBus.Protocol;
public partial class Connection
{
public const string DBusObjectPath = "/org/freedesktop/DBus";
public const string DBusServiceName = "org.freedesktop.DBus";
public const string DBusInterface = "org.freedesktop.DBus";
public Task<string[]> ListServicesAsync()
{
return CallMethodAsync(CreateMessage(), (Message m, object? s) => m.GetBodyReader().ReadArrayOfString());
MessageBuffer CreateMessage()
{
using var writer = GetMessageWriter();
writer.WriteMethodCallHeader(
destination: DBusServiceName,
path: DBusObjectPath,
@interface: DBusInterface,
member: "ListNames");
return writer.CreateMessage();
}
}
public Task<string[]> ListActivatableServicesAsync()
{
return CallMethodAsync(CreateMessage(), (Message m, object? s) => m.GetBodyReader().ReadArrayOfString());
MessageBuffer CreateMessage()
{
using var writer = GetMessageWriter();
writer.WriteMethodCallHeader(
destination: DBusServiceName,
path: DBusObjectPath,
@interface: DBusInterface,
member: "ListActivatableNames");
return writer.CreateMessage();
}
}
public async Task BecomeMonitorAsync(Action<Exception?, DisposableMessage> handler, IEnumerable<MatchRule>? rules = null)
{
if (_connectionOptions.IsShared)
{
throw new InvalidOperationException("Cannot become monitor on a shared connection.");
}
DBusConnection connection = await ConnectCoreAsync().ConfigureAwait(false);
await connection.BecomeMonitorAsync(handler, rules).ConfigureAwait(false);
}
public static async IAsyncEnumerable<DisposableMessage> MonitorBusAsync(string address, IEnumerable<MatchRule>? rules = null, [EnumeratorCancellation]CancellationToken ct = default)
{
ct.ThrowIfCancellationRequested();
var channel = Channel.CreateUnbounded<DisposableMessage>(
new UnboundedChannelOptions()
{
AllowSynchronousContinuations = true,
SingleReader = true,
SingleWriter = true,
}
);
using var connection = new Connection(address);
using CancellationTokenRegistration ctr =
#if NETCOREAPP3_1_OR_GREATER
ct.UnsafeRegister(c => ((Connection)c!).Dispose(), connection);
#else
ct.Register(c => ((Connection)c!).Dispose(), connection);
#endif
try
{
await connection.ConnectAsync().ConfigureAwait(false);
await connection.BecomeMonitorAsync(
(Exception? ex, DisposableMessage message) =>
{
if (ex is not null)
{
if (ct.IsCancellationRequested)
{
ex = new OperationCanceledException(ct);
}
channel.Writer.TryComplete(ex);
return;
}
if (!channel.Writer.TryWrite(message))
{
message.Dispose();
}
},
rules
).ConfigureAwait(false);
}
catch
{
ct.ThrowIfCancellationRequested();
throw;
}
while (await channel.Reader.WaitToReadAsync().ConfigureAwait(false))
{
if (channel.Reader.TryRead(out DisposableMessage msg))
{
yield return msg;
}
}
}
}

335
src/Linux/Tmds.DBus.Protocol/Connection.cs

@ -0,0 +1,335 @@
namespace Tmds.DBus.Protocol;
public delegate T MessageValueReader<T>(Message message, object? state);
public partial class Connection : IDisposable
{
internal static readonly Exception DisposedException = new ObjectDisposedException(typeof(Connection).FullName);
private static Connection? s_systemConnection;
private static Connection? s_sessionConnection;
public static Connection System => s_systemConnection ?? CreateConnection(ref s_systemConnection, Address.System);
public static Connection Session => s_sessionConnection ?? CreateConnection(ref s_sessionConnection, Address.Session);
public string? UniqueName => GetConnection().UniqueName;
enum ConnectionState
{
Created,
Connecting,
Connected,
Disconnected
}
private readonly object _gate = new object();
private readonly ClientConnectionOptions _connectionOptions;
private DBusConnection? _connection;
private CancellationTokenSource? _connectCts;
private Task<DBusConnection>? _connectingTask;
private ClientSetupResult? _setupResult;
private ConnectionState _state;
private bool _disposed;
private int _nextSerial;
public Connection(string address) :
this(new ClientConnectionOptions(address))
{ }
public Connection(ConnectionOptions connectionOptions)
{
if (connectionOptions == null)
throw new ArgumentNullException(nameof(connectionOptions));
_connectionOptions = (ClientConnectionOptions)connectionOptions;
}
// For tests.
internal void Connect(IMessageStream stream)
{
_connection = new DBusConnection(this, DBusEnvironment.MachineId);
_connection.Connect(stream);
_state = ConnectionState.Connected;
}
public async ValueTask ConnectAsync()
{
await ConnectCoreAsync(explicitConnect: true).ConfigureAwait(false);
}
private ValueTask<DBusConnection> ConnectCoreAsync(bool explicitConnect = false)
{
lock (_gate)
{
ThrowHelper.ThrowIfDisposed(_disposed, this);
ConnectionState state = _state;
if (state == ConnectionState.Connected)
{
return new ValueTask<DBusConnection>(_connection!);
}
if (!_connectionOptions.AutoConnect)
{
DBusConnection? connection = _connection;
if (!explicitConnect && _state == ConnectionState.Disconnected && connection is not null)
{
throw new DisconnectedException(connection.DisconnectReason);
}
if (!explicitConnect || _state != ConnectionState.Created)
{
throw new InvalidOperationException("Can only connect once using an explicit call.");
}
}
if (state == ConnectionState.Connecting)
{
return new ValueTask<DBusConnection>(_connectingTask!);
}
_state = ConnectionState.Connecting;
_connectingTask = DoConnectAsync();
return new ValueTask<DBusConnection>(_connectingTask);
}
}
private async Task<DBusConnection> DoConnectAsync()
{
Debug.Assert(Monitor.IsEntered(_gate));
DBusConnection? connection = null;
try
{
_connectCts = new();
_setupResult = await _connectionOptions.SetupAsync(_connectCts.Token).ConfigureAwait(false);
connection = _connection = new DBusConnection(this, _setupResult.MachineId ?? DBusEnvironment.MachineId);
await connection.ConnectAsync(_setupResult.ConnectionAddress, _setupResult.UserId, _setupResult.SupportsFdPassing, _connectCts.Token).ConfigureAwait(false);
lock (_gate)
{
ThrowHelper.ThrowIfDisposed(_disposed, this);
if (_connection == connection && _state == ConnectionState.Connecting)
{
_connectingTask = null;
_connectCts = null;
_state = ConnectionState.Connected;
}
else
{
throw new DisconnectedException(connection.DisconnectReason);
}
}
return connection;
}
catch (Exception exception)
{
Disconnect(exception, connection);
// Prefer throwing ObjectDisposedException.
ThrowHelper.ThrowIfDisposed(_disposed, this);
// Throw DisconnectedException or ConnectException.
if (exception is DisconnectedException || exception is ConnectException)
{
throw;
}
else
{
throw new ConnectException(exception.Message, exception);
}
}
}
public void Dispose()
{
lock (_gate)
{
if (_disposed)
{
return;
}
_disposed = true;
}
Disconnect(DisposedException);
}
internal void Disconnect(Exception disconnectReason, DBusConnection? trigger = null)
{
DBusConnection? connection;
ClientSetupResult? setupResult;
CancellationTokenSource? connectCts;
lock (_gate)
{
if (trigger is not null && trigger != _connection)
{
// Already disconnected from this stream.
return;
}
ConnectionState state = _state;
if (state == ConnectionState.Disconnected)
{
return;
}
_state = ConnectionState.Disconnected;
connection = _connection;
setupResult = _setupResult;
connectCts = _connectCts;
_connectingTask = null;
_setupResult = null;
_connectCts = null;
if (connection is not null)
{
connection.DisconnectReason = disconnectReason;
}
}
connectCts?.Cancel();
connection?.Dispose();
if (setupResult != null)
{
_connectionOptions.Teardown(setupResult.TeardownToken);
}
}
public async Task CallMethodAsync(MessageBuffer message)
{
DBusConnection connection;
try
{
connection = await ConnectCoreAsync().ConfigureAwait(false);
}
catch
{
message.ReturnToPool();
throw;
}
await connection.CallMethodAsync(message).ConfigureAwait(false);
}
public async Task<T> CallMethodAsync<T>(MessageBuffer message, MessageValueReader<T> reader, object? readerState = null)
{
DBusConnection connection;
try
{
connection = await ConnectCoreAsync().ConfigureAwait(false);
}
catch
{
message.ReturnToPool();
throw;
}
return await connection.CallMethodAsync(message, reader, readerState).ConfigureAwait(false);
}
[Obsolete("Use an overload that accepts ObserverFlags.")]
public ValueTask<IDisposable> AddMatchAsync<T>(MatchRule rule, MessageValueReader<T> reader, Action<Exception?, T, object?, object?> handler, object? readerState = null, object? handlerState = null, bool emitOnCapturedContext = true, bool subscribe = true)
=> AddMatchAsync(rule, reader, handler, readerState, handlerState, emitOnCapturedContext, ObserverFlags.EmitOnDispose | (!subscribe ? ObserverFlags.NoSubscribe : default));
public ValueTask<IDisposable> AddMatchAsync<T>(MatchRule rule, MessageValueReader<T> reader, Action<Exception?, T, object?, object?> handler, ObserverFlags flags, object? readerState = null, object? handlerState = null, bool emitOnCapturedContext = true)
=> AddMatchAsync(rule, reader, handler, readerState, handlerState, emitOnCapturedContext, flags);
public ValueTask<IDisposable> AddMatchAsync<T>(MatchRule rule, MessageValueReader<T> reader, Action<Exception?, T, object?, object?> handler, object? readerState, object? handlerState, bool emitOnCapturedContext, ObserverFlags flags)
=> AddMatchAsync(rule, reader, handler, readerState, handlerState, emitOnCapturedContext ? SynchronizationContext.Current : null, flags);
public async ValueTask<IDisposable> AddMatchAsync<T>(MatchRule rule, MessageValueReader<T> reader, Action<Exception?, T, object?, object?> handler, object? readerState , object? handlerState, SynchronizationContext? synchronizationContext, ObserverFlags flags)
{
DBusConnection connection = await ConnectCoreAsync().ConfigureAwait(false);
return await connection.AddMatchAsync(synchronizationContext, rule, reader, handler, readerState, handlerState, flags).ConfigureAwait(false);
}
public void AddMethodHandler(IMethodHandler methodHandler)
=> UpdateMethodHandlers((dictionary, handler) => dictionary.AddMethodHandler(handler), methodHandler);
public void AddMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers)
=> UpdateMethodHandlers((dictionary, handlers) => dictionary.AddMethodHandlers(handlers), methodHandlers);
public void RemoveMethodHandler(string path)
=> UpdateMethodHandlers((dictionary, path) => dictionary.RemoveMethodHandler(path), path);
public void RemoveMethodHandlers(IEnumerable<string> paths)
=> UpdateMethodHandlers((dictionary, paths) => dictionary.RemoveMethodHandlers(paths), paths);
private void UpdateMethodHandlers<T>(Action<IMethodHandlerDictionary, T> update, T state)
=> GetConnection().UpdateMethodHandlers(update, state);
private static Connection CreateConnection(ref Connection? field, string? address)
{
address = address ?? "unix:";
var connection = Volatile.Read(ref field);
if (connection is not null)
{
return connection;
}
var newConnection = new Connection(new ClientConnectionOptions(address) { AutoConnect = true, IsShared = true });
connection = Interlocked.CompareExchange(ref field, newConnection, null);
if (connection != null)
{
newConnection.Dispose();
return connection;
}
return newConnection;
}
public MessageWriter GetMessageWriter() => new MessageWriter(MessageBufferPool.Shared, GetNextSerial());
public bool TrySendMessage(MessageBuffer message)
{
DBusConnection? connection = GetConnection(ifConnected: true);
if (connection is null)
{
message.ReturnToPool();
return false;
}
connection.SendMessage(message);
return true;
}
public Task<Exception?> DisconnectedAsync()
{
DBusConnection connection = GetConnection();
return connection.DisconnectedAsync();
}
private DBusConnection GetConnection() => GetConnection(ifConnected: false)!;
private DBusConnection? GetConnection(bool ifConnected)
{
lock (_gate)
{
ThrowHelper.ThrowIfDisposed(_disposed, this);
if (_connectionOptions.AutoConnect)
{
throw new InvalidOperationException("Method cannot be used on autoconnect connections.");
}
ConnectionState state = _state;
if (state == ConnectionState.Created ||
state == ConnectionState.Connecting)
{
throw new InvalidOperationException("Connect before using this method.");
}
if (ifConnected && state != ConnectionState.Connected)
{
return null;
}
return _connection;
}
}
internal uint GetNextSerial() => (uint)Interlocked.Increment(ref _nextSerial);
}

7
src/Linux/Tmds.DBus.Protocol/ConnectionOptions.cs

@ -0,0 +1,7 @@
namespace Tmds.DBus.Protocol;
public abstract class ConnectionOptions
{
internal ConnectionOptions()
{ }
}

7
src/Linux/Tmds.DBus.Protocol/Constants.cs

@ -0,0 +1,7 @@
namespace Tmds.DBus.Protocol;
static class Constants
{
public const int StackAllocByteThreshold = 512;
public const int StackAllocCharThreshold = StackAllocByteThreshold / 2;
}

1290
src/Linux/Tmds.DBus.Protocol/DBusConnection.cs

File diff suppressed because it is too large

50
src/Linux/Tmds.DBus.Protocol/DBusEnvironment.cs

@ -0,0 +1,50 @@
namespace Tmds.DBus.Protocol;
static class DBusEnvironment
{
public static string? UserId
{
get
{
if (PlatformDetection.IsWindows())
{
#if NET6_0_OR_GREATER
return System.Security.Principal.WindowsIdentity.GetCurrent().User?.Value;
#else
throw new NotSupportedException("Cannot determine Windows UserId. You must manually assign it.");
#endif
}
else
{
return geteuid().ToString();
}
}
}
private static string? _machineId;
public static string MachineId
{
get
{
if (_machineId == null)
{
const string MachineUuidPath = @"/var/lib/dbus/machine-id";
if (File.Exists(MachineUuidPath))
{
_machineId = Guid.Parse(File.ReadAllText(MachineUuidPath).Substring(0, 32)).ToString("N");
}
else
{
_machineId = Guid.Empty.ToString("N");
}
}
return _machineId;
}
}
[DllImport("libc")]
internal static extern uint geteuid();
}

15
src/Linux/Tmds.DBus.Protocol/DBusException.cs

@ -0,0 +1,15 @@
namespace Tmds.DBus.Protocol;
public class DBusException : Exception
{
public DBusException(string errorName, string errorMessage) :
base($"{errorName}: {errorMessage}")
{
ErrorName = errorName;
ErrorMessage = errorMessage;
}
public string ErrorName { get; }
public string ErrorMessage { get; }
}

23
src/Linux/Tmds.DBus.Protocol/DBusType.cs

@ -0,0 +1,23 @@
namespace Tmds.DBus.Protocol;
public enum DBusType : byte
{
Invalid = 0,
Byte = (byte)'y',
Bool = (byte)'b',
Int16 = (byte)'n',
UInt16 = (byte)'q',
Int32 = (byte)'i',
UInt32 = (byte)'u',
Int64 = (byte)'x',
UInt64 = (byte)'t',
Double = (byte)'d',
String = (byte)'s',
ObjectPath = (byte)'o',
Signature = (byte)'g',
Array = (byte)'a',
Struct = (byte)'(',
Variant = (byte)'v',
DictEntry = (byte)'{',
UnixFd = (byte)'h',
}

89
src/Linux/Tmds.DBus.Protocol/Dict.cs

@ -0,0 +1,89 @@
using System.Collections;
namespace Tmds.DBus.Protocol;
// Using obsolete generic write members
#pragma warning disable CS0618
public sealed class Dict<TKey, TValue> : IDBusWritable, IDictionary<TKey, TValue>
where TKey : notnull
where TValue : notnull
{
private readonly Dictionary<TKey, TValue> _dict;
public Dict() :
this(new Dictionary<TKey, TValue>())
{ }
public Dict(IDictionary<TKey, TValue> dictionary) :
this(new Dictionary<TKey, TValue>(dictionary))
{ }
private Dict(Dictionary<TKey, TValue> value)
{
TypeModel.EnsureSupportedVariantType<TKey>();
TypeModel.EnsureSupportedVariantType<TValue>();
_dict = value;
}
public Variant AsVariant() => Variant.FromDict(this);
public static implicit operator Variant(Dict<TKey, TValue> value)
=> value.AsVariant();
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteDictionary<TKey, TValue>(_dict);
ICollection<TKey> IDictionary<TKey, TValue>.Keys => _dict.Keys;
ICollection<TValue> IDictionary<TKey, TValue>.Values => _dict.Values;
public int Count => _dict.Count;
public TValue this[TKey key]
{
get => _dict[key];
set => _dict[key] = value;
}
public void Add(TKey key, TValue value)
=> _dict.Add(key, value);
public bool ContainsKey(TKey key)
=> _dict.ContainsKey(key);
public bool Remove(TKey key)
=> _dict.Remove(key);
public bool TryGetValue(TKey key,
#if NET
[MaybeNullWhen(false)]
#endif
out TValue value)
=> _dict.TryGetValue(key, out value);
public void Clear()
=> _dict.Clear();
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dict).Add(item);
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dict).Contains(item);
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dict).CopyTo(array, arrayIndex);
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dict).Remove(item);
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
=> _dict.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> _dict.GetEnumerator();
}

6
src/Linux/Tmds.DBus.Protocol/DisconnectedException.cs

@ -0,0 +1,6 @@
namespace Tmds.DBus.Protocol;
public class DisconnectedException : Exception
{
internal DisconnectedException(Exception innerException) : base(innerException.Message, innerException)
{ }
}

18
src/Linux/Tmds.DBus.Protocol/DisposableMessage.cs

@ -0,0 +1,18 @@
namespace Tmds.DBus.Protocol;
public struct DisposableMessage : IDisposable
{
private Message? _message;
internal DisposableMessage(Message? message)
=> _message = message;
public Message Message
=> _message ?? throw new ObjectDisposedException(typeof(Message).FullName);
public void Dispose()
{
_message?.ReturnToPool();
_message = null;
}
}

14
src/Linux/Tmds.DBus.Protocol/Feature.cs

@ -0,0 +1,14 @@
namespace Tmds.DBus.Protocol;
static class Feature
{
public static bool IsDynamicCodeEnabled =>
#if NETSTANDARD2_0
true
#else
System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported
#endif
&& EnableDynamicCode;
private static readonly bool EnableDynamicCode = Environment.GetEnvironmentVariable("TMDS_DBUS_PROTOCOL_DYNAMIC_CODE") != "0";
}

14
src/Linux/Tmds.DBus.Protocol/GlobalUsings.cs

@ -0,0 +1,14 @@
global using System;
global using System.Buffers;
global using System.Buffers.Binary;
global using System.Collections.Generic;
global using System.Diagnostics;
global using System.Diagnostics.CodeAnalysis;
global using System.IO;
global using System.Linq;
global using System.Runtime.CompilerServices;
global using System.Runtime.InteropServices;
global using System.Text;
global using System.Threading;
global using System.Threading.Tasks;
global using Nerdbank.Streams;

6
src/Linux/Tmds.DBus.Protocol/IDBusWritable.cs

@ -0,0 +1,6 @@
namespace Tmds.DBus.Protocol;
public interface IDBusWritable
{
void WriteTo(ref MessageWriter writer);
}

14
src/Linux/Tmds.DBus.Protocol/IMessageStream.cs

@ -0,0 +1,14 @@
namespace Tmds.DBus.Protocol;
interface IMessageStream
{
public delegate void MessageReceivedHandler<T>(Exception? closeReason, Message message, T state);
void ReceiveMessages<T>(MessageReceivedHandler<T> handler, T state);
ValueTask<bool> TrySendMessageAsync(MessageBuffer message);
void BecomeMonitor();
void Close(Exception closeReason);
}

13
src/Linux/Tmds.DBus.Protocol/IMethodHandler.cs

@ -0,0 +1,13 @@
namespace Tmds.DBus.Protocol;
public interface IMethodHandler
{
// Path that is handled by this method handler.
string Path { get; }
// The message argument is only valid during the call. It must not be stored to extend its lifetime.
ValueTask HandleMethodAsync(MethodContext context);
// Controls whether to wait for the handler method to finish executing before reading more messages.
bool RunMethodHandlerSynchronously(Message message);
}

9
src/Linux/Tmds.DBus.Protocol/IMethodHandlerDictionary.cs

@ -0,0 +1,9 @@
namespace Tmds.DBus.Protocol;
interface IMethodHandlerDictionary
{
void AddMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers);
void AddMethodHandler(IMethodHandler methodHandler);
void RemoveMethodHandler(string path);
void RemoveMethodHandlers(IEnumerable<string> paths);
}

51
src/Linux/Tmds.DBus.Protocol/IntrospectionXml.cs

@ -0,0 +1,51 @@
namespace Tmds.DBus.Protocol;
public static class IntrospectionXml
{
public static ReadOnlyMemory<byte> DBusProperties { get; } =
"""
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg direction="in" type="s"/>
<arg direction="in" type="s"/>
<arg direction="out" type="v"/>
</method>
<method name="GetAll">
<arg direction="in" type="s"/>
<arg direction="out" type="a{sv}"/>
</method>
<method name="Set">
<arg direction="in" type="s"/>
<arg direction="in" type="s"/>
<arg direction="in" type="v"/>
</method>
<signal name="PropertiesChanged">
<arg type="s" name="interface_name"/>
<arg type="a{sv}" name="changed_properties"/>
<arg type="as" name="invalidated_properties"/>
</signal>
</interface>
"""u8.ToArray();
public static ReadOnlyMemory<byte> DBusIntrospectable { get; } =
"""
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg type="s" name="xml_data" direction="out"/>
</method>
</interface>
"""u8.ToArray();
public static ReadOnlyMemory<byte> DBusPeer { get; } =
"""
<interface name="org.freedesktop.DBus.Peer">
<method name="Ping"/>
<method name="GetMachineId">
<arg type="s" name="machine_uuid" direction="out"/>
</method>
</interface>
"""u8.ToArray();
}

120
src/Linux/Tmds.DBus.Protocol/MatchRule.cs

@ -0,0 +1,120 @@
namespace Tmds.DBus.Protocol;
struct MatchRuleData
{
public MessageType? MessageType { get; set; }
public string? Sender { get; set; }
public string? Interface { get; set; }
public string? Member { get; set; }
public string? Path { get; set; }
public string? PathNamespace { get; set; }
public string? Destination { get; set; }
public string? Arg0 { get; set; }
public string? Arg0Path { get; set; }
public string? Arg0Namespace { get; set; }
public string GetRuleString()
{
var sb = new StringBuilder(); // TODO (perf): pool
if (MessageType.HasValue)
{
string? typeMatch = MessageType switch
{
Protocol.MessageType.MethodCall => "type=method_call",
Protocol.MessageType.MethodReturn => "type=method_return",
Protocol.MessageType.Error => "type=error",
Protocol.MessageType.Signal => "type=signal",
_ => null
};
if (typeMatch is not null)
{
sb.Append(typeMatch);
}
}
Append(sb, "sender", Sender);
Append(sb, "interface", Interface);
Append(sb, "member", Member);
Append(sb, "path", Path);
Append(sb, "pathNamespace", PathNamespace);
Append(sb, "destination", Destination);
Append(sb, "arg0", Arg0);
Append(sb, "arg0Path", Arg0Path);
Append(sb, "arg0Namespace", Arg0Namespace);
return sb.ToString();
static void Append(StringBuilder sb, string key, string? value)
{
if (value is null)
{
return;
}
sb.Append($"{(sb.Length > 0 ? ',' : "")}{key}=");
bool quoting = false;
ReadOnlySpan<char> span = value.AsSpan();
while (!span.IsEmpty)
{
int specialPos = span.IndexOfAny((ReadOnlySpan<char>)new char[] { ',', '\'' });
if (specialPos == -1)
{
sb.Append(span);
break;
}
bool isComma = span[specialPos] == ',';
if (isComma && !quoting ||
!isComma && quoting)
{
sb.Append("'");
quoting = !quoting;
}
sb.Append(span.Slice(0, specialPos + (isComma ? 1 : 0)));
if (!isComma)
{
sb.Append("\\'");
}
span = span.Slice(specialPos + 1);
}
if (quoting)
{
sb.Append("'");
quoting = false;
}
}
}
}
public sealed class MatchRule
{
private MatchRuleData _data;
internal MatchRuleData Data => _data;
public MessageType? Type { get => _data.MessageType; set => _data.MessageType = value; }
public string? Sender { get => _data.Sender; set => _data.Sender = value; }
public string? Interface { get => _data.Interface; set => _data.Interface = value; }
public string? Member { get => _data.Member; set => _data.Member = value; }
public string? Path { get => _data.Path; set => _data.Path = value; }
public string? PathNamespace { get => _data.PathNamespace; set => _data.PathNamespace = value; }
public string? Destination { get => _data.Destination; set => _data.Destination = value; }
public string? Arg0 { get => _data.Arg0; set => _data.Arg0 = value; }
public string? Arg0Path { get => _data.Arg0Path; set => _data.Arg0Path = value; }
public string? Arg0Namespace { get => _data.Arg0Namespace; set => _data.Arg0Namespace = value; }
public override string ToString() => _data.GetRuleString();
}

242
src/Linux/Tmds.DBus.Protocol/Message.cs

@ -0,0 +1,242 @@
namespace Tmds.DBus.Protocol;
public sealed class Message
{
private const int HeaderFieldsLengthOffset = 12;
private readonly MessagePool _pool;
private readonly Sequence<byte> _data;
private UnixFdCollection? _handles;
private ReadOnlySequence<byte> _body;
public bool IsBigEndian { get; private set; }
public uint Serial { get; private set; }
public MessageFlags MessageFlags { get; private set; }
public MessageType MessageType { get; private set; }
public uint? ReplySerial { get; private set; }
public int UnixFdCount { get; private set; }
private HeaderBuffer _path;
private HeaderBuffer _interface;
private HeaderBuffer _member;
private HeaderBuffer _errorName;
private HeaderBuffer _destination;
private HeaderBuffer _sender;
private HeaderBuffer _signature;
public string? PathAsString => _path.ToString();
public string? InterfaceAsString => _interface.ToString();
public string? MemberAsString => _member.ToString();
public string? ErrorNameAsString => _errorName.ToString();
public string? DestinationAsString => _destination.ToString();
public string? SenderAsString => _sender.ToString();
public string? SignatureAsString => _signature.ToString();
public ReadOnlySpan<byte> Path => _path.Span;
public ReadOnlySpan<byte> Interface => _interface.Span;
public ReadOnlySpan<byte> Member => _member.Span;
public ReadOnlySpan<byte> ErrorName => _errorName.Span;
public ReadOnlySpan<byte> Destination => _destination.Span;
public ReadOnlySpan<byte> Sender => _sender.Span;
public ReadOnlySpan<byte> Signature => _signature.Span;
public bool PathIsSet => _path.IsSet;
public bool InterfaceIsSet => _interface.IsSet;
public bool MemberIsSet => _member.IsSet;
public bool ErrorNameIsSet => _errorName.IsSet;
public bool DestinationIsSet => _destination.IsSet;
public bool SenderIsSet => _sender.IsSet;
public bool SignatureIsSet => _signature.IsSet;
struct HeaderBuffer
{
private byte[] _buffer;
private int _length;
private string? _string;
public Span<byte> Span => new Span<byte>(_buffer, 0, Math.Max(_length, 0));
public void Set(ReadOnlySpan<byte> data)
{
_string = null;
if (_buffer is null || data.Length > _buffer.Length)
{
_buffer = new byte[data.Length];
}
data.CopyTo(_buffer);
_length = data.Length;
}
public void Clear()
{
_length = -1;
_string = null;
}
public override string? ToString()
{
return _length == -1 ? null : _string ??= Encoding.UTF8.GetString(Span);
}
public bool IsSet => _length != -1;
}
public Reader GetBodyReader() => new Reader(IsBigEndian, _body, _handles, UnixFdCount);
internal Message(MessagePool messagePool, Sequence<byte> sequence)
{
_pool = messagePool;
_data = sequence;
ClearHeaders();
}
internal void ReturnToPool()
{
_data.Reset();
ClearHeaders();
_handles?.DisposeHandles();
_pool.Return(this);
}
private void ClearHeaders()
{
ReplySerial = null;
UnixFdCount = 0;
_path.Clear();
_interface.Clear();
_member.Clear();
_errorName.Clear();
_destination.Clear();
_sender.Clear();
_signature.Clear();
}
internal static Message? TryReadMessage(MessagePool messagePool, ref ReadOnlySequence<byte> sequence, UnixFdCollection? handles = null, bool isMonitor = false)
{
SequenceReader<byte> seqReader = new(sequence);
if (!seqReader.TryRead(out byte endianness) ||
!seqReader.TryRead(out byte msgType) ||
!seqReader.TryRead(out byte flags) ||
!seqReader.TryRead(out byte version))
{
return null;
}
if (version != 1)
{
throw new NotSupportedException();
}
bool isBigEndian = endianness == 'B';
if (!TryReadUInt32(ref seqReader, isBigEndian, out uint bodyLength) ||
!TryReadUInt32(ref seqReader, isBigEndian, out uint serial) ||
!TryReadUInt32(ref seqReader, isBigEndian, out uint headerFieldLength))
{
return null;
}
headerFieldLength = (uint)ProtocolConstants.Align((int)headerFieldLength, DBusType.Struct);
long totalLength = seqReader.Consumed + headerFieldLength + bodyLength;
if (sequence.Length < totalLength)
{
return null;
}
// Copy data so it has a lifetime independent of the source sequence.
var message = messagePool.Rent();
Sequence<byte> dst = message._data;
do
{
ReadOnlySpan<byte> srcSpan = sequence.First.Span;
int length = (int)Math.Min(totalLength, srcSpan.Length);
Span<byte> dstSpan = dst.GetSpan(0);
length = Math.Min(length, dstSpan.Length);
srcSpan.Slice(0, length).CopyTo(dstSpan);
dst.Advance(length);
sequence = sequence.Slice(length);
totalLength -= length;
} while (totalLength > 0);
message.IsBigEndian = isBigEndian;
message.Serial = serial;
message.MessageType = (MessageType)msgType;
message.MessageFlags = (MessageFlags)flags;
message.ParseHeader(handles, isMonitor);
return message;
static bool TryReadUInt32(ref SequenceReader<byte> seqReader, bool isBigEndian, out uint value)
{
int v;
bool rv = (isBigEndian && seqReader.TryReadBigEndian(out v) || seqReader.TryReadLittleEndian(out v));
value = (uint)v;
return rv;
}
}
private void ParseHeader(UnixFdCollection? handles, bool isMonitor)
{
var reader = new Reader(IsBigEndian, _data.AsReadOnlySequence);
reader.Advance(HeaderFieldsLengthOffset);
ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct);
while (reader.HasNext(headersEnd))
{
MessageHeader hdrType = (MessageHeader)reader.ReadByte();
ReadOnlySpan<byte> sig = reader.ReadSignature();
switch (hdrType)
{
case MessageHeader.Path:
_path.Set(reader.ReadObjectPathAsSpan());
break;
case MessageHeader.Interface:
_interface.Set(reader.ReadStringAsSpan());
break;
case MessageHeader.Member:
_member.Set(reader.ReadStringAsSpan());
break;
case MessageHeader.ErrorName:
_errorName.Set(reader.ReadStringAsSpan());
break;
case MessageHeader.ReplySerial:
ReplySerial = reader.ReadUInt32();
break;
case MessageHeader.Destination:
_destination.Set(reader.ReadStringAsSpan());
break;
case MessageHeader.Sender:
_sender.Set(reader.ReadStringAsSpan());
break;
case MessageHeader.Signature:
_signature.Set(reader.ReadSignature());
break;
case MessageHeader.UnixFds:
UnixFdCount = (int)reader.ReadUInt32();
if (UnixFdCount > 0 && !isMonitor)
{
if (handles is null || UnixFdCount > handles.Count)
{
throw new ProtocolException("Received less handles than UNIX_FDS.");
}
if (_handles is null)
{
_handles = new(handles.IsRawHandleCollection);
}
handles.MoveTo(_handles, UnixFdCount);
}
break;
default:
throw new NotSupportedException();
}
}
reader.AlignStruct();
_body = reader.UnreadSequence;
}
}

43
src/Linux/Tmds.DBus.Protocol/MessageBuffer.cs

@ -0,0 +1,43 @@
namespace Tmds.DBus.Protocol;
public sealed class MessageBuffer : IDisposable
{
private readonly MessageBufferPool _messagePool;
private readonly Sequence<byte> _data;
internal uint Serial { get; private set; }
internal MessageFlags MessageFlags { get; private set; }
internal UnixFdCollection? Handles { get; private set; }
internal MessageBuffer(MessageBufferPool messagePool, Sequence<byte> sequence)
{
_messagePool = messagePool;
_data = sequence;
}
internal void Init(uint serial, MessageFlags flags, UnixFdCollection? handles)
{
Serial = serial;
MessageFlags = flags;
Handles = handles;
}
public void Dispose() => ReturnToPool();
internal void ReturnToPool()
{
_data.Reset();
Handles?.DisposeHandles();
Handles = null;
_messagePool.Return(this);
}
// For writing data.
internal Sequence<byte> Sequence => _data;
// For reading data.
internal ReadOnlySequence<byte> AsReadOnlySequence() => _data.AsReadOnlySequence;
}

42
src/Linux/Tmds.DBus.Protocol/MessageBufferPool.cs

@ -0,0 +1,42 @@
namespace Tmds.DBus.Protocol;
class MessageBufferPool
{
private const int MinimumSpanLength = 512;
public static readonly MessageBufferPool Shared = new MessageBufferPool(Environment.ProcessorCount * 2);
private readonly int _maxSize;
private readonly Stack<MessageBuffer> _pool = new Stack<MessageBuffer>();
internal MessageBufferPool(int maxSize)
{
_maxSize = maxSize;
}
public MessageBuffer Rent()
{
lock (_pool)
{
if (_pool.Count > 0)
{
return _pool.Pop();
}
}
var sequence = new Sequence<byte>(ArrayPool<byte>.Shared) { MinimumSpanLength = MinimumSpanLength };
return new MessageBuffer(this, sequence);
}
internal void Return(MessageBuffer value)
{
lock (_pool)
{
if (_pool.Count < _maxSize)
{
_pool.Push(value);
}
}
}
}

10
src/Linux/Tmds.DBus.Protocol/MessageFlags.cs

@ -0,0 +1,10 @@
namespace Tmds.DBus.Protocol;
[Flags]
public enum MessageFlags : byte
{
None = 0,
NoReplyExpected = 1,
NoAutoStart = 2,
AllowInteractiveAuthorization = 4
}

14
src/Linux/Tmds.DBus.Protocol/MessageHeader.cs

@ -0,0 +1,14 @@
namespace Tmds.DBus.Protocol;
internal enum MessageHeader : byte
{
Path = 1,
Interface = 2,
Member = 3,
ErrorName = 4,
ReplySerial = 5,
Destination = 6,
Sender = 7,
Signature = 8,
UnixFds = 9
}

27
src/Linux/Tmds.DBus.Protocol/MessagePool.cs

@ -0,0 +1,27 @@
namespace Tmds.DBus.Protocol;
class MessagePool
{
private const int MinimumSpanLength = 512;
private Message? _pooled = null; // Pool a single message.
public Message Rent()
{
Message? rent = Interlocked.Exchange(ref _pooled, null);
if (rent is not null)
{
return rent;
}
var sequence = new Sequence<byte>(ArrayPool<byte>.Shared) { MinimumSpanLength = MinimumSpanLength };
return new Message(this, sequence);
}
internal void Return(Message value)
{
Volatile.Write(ref _pooled, value);
}
}

403
src/Linux/Tmds.DBus.Protocol/MessageStream.cs

@ -0,0 +1,403 @@
using System.IO.Pipelines;
using System.Net.Sockets;
using System.Threading.Channels;
namespace Tmds.DBus.Protocol;
#pragma warning disable VSTHRD100 // Avoid "async void" methods
class MessageStream : IMessageStream
{
private static readonly ReadOnlyMemory<byte> OneByteArray = new[] { (byte)0 };
private readonly Socket _socket;
private UnixFdCollection? _fdCollection;
private bool _supportsFdPassing;
private readonly MessagePool _messagePool;
// Messages going out.
private readonly ChannelReader<MessageBuffer> _messageReader;
private readonly ChannelWriter<MessageBuffer> _messageWriter;
// Bytes coming in.
private readonly PipeWriter _pipeWriter;
private readonly PipeReader _pipeReader;
private Exception? _completionException;
private bool _isMonitor;
public MessageStream(Socket socket)
{
_socket = socket;
Channel<MessageBuffer> channel = Channel.CreateUnbounded<MessageBuffer>(new UnboundedChannelOptions
{
AllowSynchronousContinuations = true,
SingleReader = true,
SingleWriter = false
});
_messageReader = channel.Reader;
_messageWriter = channel.Writer;
var pipe = new Pipe(new PipeOptions(useSynchronizationContext: false));
_pipeReader = pipe.Reader;
_pipeWriter = pipe.Writer;
_messagePool = new();
}
public void BecomeMonitor()
{
_isMonitor = true;
}
private async void ReadFromSocketIntoPipe()
{
var writer = _pipeWriter;
Exception? exception = null;
try
{
while (true)
{
Memory<byte> memory = writer.GetMemory(1024);
int bytesRead = await _socket.ReceiveAsync(memory, _fdCollection).ConfigureAwait(false);
if (bytesRead == 0)
{
throw new IOException("Connection closed by peer");
}
writer.Advance(bytesRead);
await writer.FlushAsync().ConfigureAwait(false);
}
}
catch (Exception e)
{
exception = e;
}
writer.Complete(exception);
}
private async void ReadMessagesIntoSocket()
{
while (true)
{
if (!await _messageReader.WaitToReadAsync().ConfigureAwait(false))
{
// No more messages will be coming.
return;
}
var message = await _messageReader.ReadAsync().ConfigureAwait(false);
try
{
IReadOnlyList<SafeHandle>? handles = _supportsFdPassing ? message.Handles : null;
var buffer = message.AsReadOnlySequence();
if (buffer.IsSingleSegment)
{
await _socket.SendAsync(buffer.First, handles).ConfigureAwait(false);
}
else
{
SequencePosition position = buffer.Start;
while (buffer.TryGet(ref position, out ReadOnlyMemory<byte> memory))
{
await _socket.SendAsync(memory, handles).ConfigureAwait(false);
handles = null;
}
}
}
catch (Exception exception)
{
Close(exception);
return;
}
finally
{
message.ReturnToPool();
}
}
}
public async void ReceiveMessages<T>(IMessageStream.MessageReceivedHandler<T> handler, T state)
{
var reader = _pipeReader;
try
{
while (true)
{
ReadResult result = await reader.ReadAsync().ConfigureAwait(false);
ReadOnlySequence<byte> buffer = result.Buffer;
ReadMessages(ref buffer, handler, state);
reader.AdvanceTo(buffer.Start, buffer.End);
}
}
catch (Exception exception)
{
exception = CloseCore(exception);
OnException(exception, handler, state);
}
finally
{
_fdCollection?.Dispose();
}
void ReadMessages<TState>(ref ReadOnlySequence<byte> buffer, IMessageStream.MessageReceivedHandler<TState> handler, TState state)
{
Message? message;
while ((message = Message.TryReadMessage(_messagePool, ref buffer, _fdCollection, _isMonitor)) != null)
{
handler(closeReason: null, message, state);
}
}
static void OnException(Exception exception, IMessageStream.MessageReceivedHandler<T> handler, T state)
{
handler(exception, message: null!, state);
}
}
private struct AuthenticationResult
{
public bool IsAuthenticated;
public bool SupportsFdPassing;
public Guid Guid;
}
public async ValueTask DoClientAuthAsync(Guid guid, string? userId, bool supportsFdPassing)
{
ReadFromSocketIntoPipe();
// send 1 byte
await _socket.SendAsync(OneByteArray, SocketFlags.None).ConfigureAwait(false);
// auth
var authenticationResult = await SendAuthCommandsAsync(userId, supportsFdPassing).ConfigureAwait(false);
_supportsFdPassing = authenticationResult.SupportsFdPassing;
if (_supportsFdPassing)
{
_fdCollection = new();
}
if (guid != Guid.Empty)
{
if (guid != authenticationResult.Guid)
{
throw new ConnectException("Authentication failure: Unexpected GUID");
}
}
ReadMessagesIntoSocket();
}
private async ValueTask<AuthenticationResult> SendAuthCommandsAsync(string? userId, bool supportsFdPassing)
{
AuthenticationResult result;
if (userId is not null)
{
string command = CreateAuthExternalCommand(userId);
result = await SendAuthCommandAsync(command, supportsFdPassing).ConfigureAwait(false);
if (result.IsAuthenticated)
{
return result;
}
}
result = await SendAuthCommandAsync("AUTH ANONYMOUS\r\n", supportsFdPassing).ConfigureAwait(false);
if (result.IsAuthenticated)
{
return result;
}
throw new ConnectException("Authentication failure");
}
private static string CreateAuthExternalCommand(string userId)
{
const string AuthExternal = "AUTH EXTERNAL ";
const string hexchars = "0123456789abcdef";
#if NETSTANDARD2_0
StringBuilder sb = new();
sb.Append(AuthExternal);
for (int i = 0; i < userId.Length; i++)
{
byte b = (byte)userId[i];
sb.Append(hexchars[(int)(b >> 4)]);
sb.Append(hexchars[(int)(b & 0xF)]);
}
sb.Append("\r\n");
return sb.ToString();
#else
return string.Create<string>(
length: AuthExternal.Length + userId.Length * 2 + 2, userId,
static (Span<char> span, string userId) =>
{
AuthExternal.AsSpan().CopyTo(span);
span = span.Slice(AuthExternal.Length);
for (int i = 0; i < userId.Length; i++)
{
byte b = (byte)userId[i];
span[i * 2] = hexchars[(int)(b >> 4)];
span[i * 2 + 1] = hexchars[(int)(b & 0xF)];
}
span = span.Slice(userId.Length * 2);
span[0] = '\r';
span[1] = '\n';
});
#endif
}
private async ValueTask<AuthenticationResult> SendAuthCommandAsync(string command, bool supportsFdPassing)
{
byte[] lineBuffer = ArrayPool<byte>.Shared.Rent(512);
try
{
AuthenticationResult result = default(AuthenticationResult);
await WriteAsync(command, lineBuffer).ConfigureAwait(false);
int lineLength = await ReadLineAsync(lineBuffer).ConfigureAwait(false);
if (StartsWithAscii(lineBuffer, lineLength, "OK"))
{
result.IsAuthenticated = true;
result.Guid = ParseGuid(lineBuffer, lineLength);
if (supportsFdPassing)
{
await WriteAsync("NEGOTIATE_UNIX_FD\r\n", lineBuffer).ConfigureAwait(false);
lineLength = await ReadLineAsync(lineBuffer).ConfigureAwait(false);
result.SupportsFdPassing = StartsWithAscii(lineBuffer, lineLength, "AGREE_UNIX_FD");
}
await WriteAsync("BEGIN\r\n", lineBuffer).ConfigureAwait(false);
return result;
}
else if (StartsWithAscii(lineBuffer, lineLength, "REJECTED"))
{
return result;
}
else
{
await WriteAsync("ERROR\r\n", lineBuffer).ConfigureAwait(false);
return result;
}
}
finally
{
ArrayPool<byte>.Shared.Return(lineBuffer);
}
static bool StartsWithAscii(byte[] line, int length, string expected)
{
if (length < expected.Length)
{
return false;
}
for (int i = 0; i < expected.Length; i++)
{
if (line[i] != expected[i])
{
return false;
}
}
return true;
}
static Guid ParseGuid(byte[] line, int length)
{
Span<byte> span = new Span<byte>(line, 0, length);
int spaceIndex = span.IndexOf((byte)' ');
if (spaceIndex == -1)
{
return Guid.Empty;
}
span = span.Slice(spaceIndex + 1);
spaceIndex = span.IndexOf((byte)' ');
if (spaceIndex != -1)
{
span = span.Slice(0, spaceIndex);
}
Span<char> charBuffer = stackalloc char[span.Length]; // TODO (low prio): check length
for (int i = 0; i < span.Length; i++)
{
// TODO (low prio): validate char
charBuffer[i] = (char)span[i];
}
#if NETSTANDARD2_0
return Guid.ParseExact(charBuffer.AsString(), "N");
#else
return Guid.ParseExact(charBuffer, "N");
#endif
}
}
private async ValueTask WriteAsync(string message, Memory<byte> lineBuffer)
{
int length = Encoding.ASCII.GetBytes(message.AsSpan(), lineBuffer.Span);
lineBuffer = lineBuffer.Slice(0, length);
await _socket.SendAsync(lineBuffer, SocketFlags.None).ConfigureAwait(false);
}
private async ValueTask<int> ReadLineAsync(Memory<byte> lineBuffer)
{
var reader = _pipeReader;
while (true)
{
ReadResult result = await reader.ReadAsync().ConfigureAwait(false);
ReadOnlySequence<byte> buffer = result.Buffer;
// TODO (low prio): check length.
SequencePosition? position = buffer.PositionOf((byte)'\n');
if (!position.HasValue)
{
reader.AdvanceTo(buffer.Start, buffer.End);
continue;
}
int length = CopyBuffer(buffer.Slice(0, position.Value), lineBuffer);
reader.AdvanceTo(buffer.GetPosition(1, position.Value));
return length;
}
int CopyBuffer(ReadOnlySequence<byte> src, Memory<byte> dst)
{
Span<byte> span = dst.Span;
src.CopyTo(span);
span = span.Slice(0, (int)src.Length);
if (!span.EndsWith((ReadOnlySpan<byte>)new byte[] { (byte)'\r' }))
{
throw new ProtocolException("Authentication messages from server must end with '\\r\\n'.");
}
if (span.Length == 1)
{
throw new ProtocolException("Received empty authentication message from server.");
}
return span.Length - 1;
}
}
public async ValueTask<bool> TrySendMessageAsync(MessageBuffer message)
{
while (await _messageWriter.WaitToWriteAsync().ConfigureAwait(false))
{
if (_messageWriter.TryWrite(message))
return true;
}
return false;
}
public void Close(Exception closeReason) => CloseCore(closeReason);
private Exception CloseCore(Exception closeReason)
{
Exception? previous = Interlocked.CompareExchange(ref _completionException, closeReason, null);
if (previous is null)
{
_socket?.Dispose();
_messageWriter.Complete();
}
return previous ?? closeReason;
}
}

9
src/Linux/Tmds.DBus.Protocol/MessageType.cs

@ -0,0 +1,9 @@
namespace Tmds.DBus.Protocol;
public enum MessageType : byte
{
MethodCall = 1,
MethodReturn = 2,
Error = 3,
Signal = 4
}

271
src/Linux/Tmds.DBus.Protocol/MessageWriter.Array.cs

@ -0,0 +1,271 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
public void WriteArray(byte[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<byte> value)
=> WriteArrayOfNumeric(value);
public void WriteArray(IEnumerable<byte> value)
=> WriteArrayOfT(value);
public void WriteArray(short[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<short> value)
=> WriteArrayOfNumeric(value);
public void WriteArray(IEnumerable<short> value)
=> WriteArrayOfT(value);
public void WriteArray(ushort[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<ushort> value)
=> WriteArrayOfNumeric(value);
public void WriteArray(IEnumerable<ushort> value)
=> WriteArrayOfT(value);
public void WriteArray(int[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<int> value)
=> WriteArrayOfNumeric(value);
public void WriteArray(IEnumerable<int> value)
=> WriteArrayOfT(value);
public void WriteArray(uint[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<uint> value)
=> WriteArrayOfNumeric(value);
public void WriteArray(IEnumerable<uint> value)
=> WriteArrayOfT(value);
public void WriteArray(long[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<long> value)
=> WriteArrayOfNumeric(value);
public void WriteArray(IEnumerable<long> value)
=> WriteArrayOfT(value);
public void WriteArray(ulong[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<ulong> value)
=> WriteArrayOfNumeric(value);
public void WriteArray(IEnumerable<ulong> value)
=> WriteArrayOfT(value);
public void WriteArray(double[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<double> value)
=> WriteArrayOfNumeric(value);
public void WriteArray(IEnumerable<double> value)
=> WriteArrayOfT(value);
public void WriteArray(string[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<string> value)
=> WriteArrayOfT(value);
public void WriteArray(IEnumerable<string> value)
=> WriteArrayOfT(value);
public void WriteArray(Signature[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<Signature> value)
=> WriteArrayOfT(value);
public void WriteArray(IEnumerable<Signature> value)
=> WriteArrayOfT(value);
public void WriteArray(ObjectPath[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<ObjectPath> value)
=> WriteArrayOfT(value);
public void WriteArray(IEnumerable<ObjectPath> value)
=> WriteArrayOfT(value);
public void WriteArray(Variant[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<Variant> value)
=> WriteArrayOfT(value);
public void WriteArray(IEnumerable<Variant> value)
=> WriteArrayOfT(value);
public void WriteArray(SafeHandle[] value)
=> WriteArray(value.AsSpan());
public void WriteArray(ReadOnlySpan<SafeHandle> value)
=> WriteArrayOfT(value);
public void WriteArray(IEnumerable<SafeHandle> value)
=> WriteArrayOfT(value);
[RequiresUnreferencedCode(Strings.UseNonGenericWriteArray)]
[Obsolete(Strings.UseNonGenericWriteArrayObsolete)]
public void WriteArray<T>(IEnumerable<T> value)
where T : notnull
{
ArrayStart arrayStart = WriteArrayStart(TypeModel.GetTypeAlignment<T>());
foreach (var item in value)
{
Write<T>(item);
}
WriteArrayEnd(arrayStart);
}
internal void WriteArray<T>(ReadOnlySpan<T> value)
where T : notnull
{
#if NET || NETSTANDARD2_1_OR_GREATER
if (typeof(T) == typeof(byte))
{
ReadOnlySpan<byte> span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(value)), value.Length);
WriteArray(span);
}
else if (typeof(T) == typeof(short))
{
ReadOnlySpan<short> span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, short>(ref MemoryMarshal.GetReference(value)), value.Length);
WriteArray(span);
}
else if (typeof(T) == typeof(ushort))
{
ReadOnlySpan<ushort> span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, ushort>(ref MemoryMarshal.GetReference(value)), value.Length);
WriteArray(span);
}
else if (typeof(T) == typeof(int))
{
ReadOnlySpan<int> span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, int>(ref MemoryMarshal.GetReference(value)), value.Length);
WriteArray(span);
}
else if (typeof(T) == typeof(uint))
{
ReadOnlySpan<uint> span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, uint>(ref MemoryMarshal.GetReference(value)), value.Length);
WriteArray(span);
}
else if (typeof(T) == typeof(long))
{
ReadOnlySpan<long> span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, long>(ref MemoryMarshal.GetReference(value)), value.Length);
WriteArray(span);
}
else if (typeof(T) == typeof(ulong))
{
ReadOnlySpan<ulong> span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, ulong>(ref MemoryMarshal.GetReference(value)), value.Length);
WriteArray(span);
}
else if (typeof(T) == typeof(double))
{
ReadOnlySpan<double> span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, double>(ref MemoryMarshal.GetReference(value)), value.Length);
WriteArray(span);
}
else
#endif
{
WriteArrayOfT<T>(value);
}
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteArray)]
[Obsolete(Strings.UseNonGenericWriteArrayObsolete)]
public void WriteArray<T>(T[] value)
where T : notnull
{
if (typeof(T) == typeof(byte))
{
WriteArray((byte[])(object)value);
}
else if (typeof(T) == typeof(short))
{
WriteArray((short[])(object)value);
}
else if (typeof(T) == typeof(ushort))
{
WriteArray((ushort[])(object)value);
}
else if (typeof(T) == typeof(int))
{
WriteArray((int[])(object)value);
}
else if (typeof(T) == typeof(uint))
{
WriteArray((uint[])(object)value);
}
else if (typeof(T) == typeof(long))
{
WriteArray((long[])(object)value);
}
else if (typeof(T) == typeof(ulong))
{
WriteArray((ulong[])(object)value);
}
else if (typeof(T) == typeof(double))
{
WriteArray((double[])(object)value);
}
else
{
WriteArrayOfT<T>(value.AsSpan());
}
}
private unsafe void WriteArrayOfNumeric<T>(ReadOnlySpan<T> value) where T : unmanaged
{
WriteInt32(value.Length * sizeof(T));
if (sizeof(T) > 4)
{
WritePadding(sizeof(T));
}
WriteRaw(MemoryMarshal.AsBytes(value));
}
private void WriteArrayOfT<T>(ReadOnlySpan<T> value)
where T : notnull
{
ArrayStart arrayStart = WriteArrayStart(TypeModel.GetTypeAlignment<T>());
foreach (var item in value)
{
Write<T>(item);
}
WriteArrayEnd(arrayStart);
}
private void WriteArrayOfT<T>(IEnumerable<T> value)
where T : notnull
{
if (value is T[] array)
{
WriteArrayOfT<T>(array.AsSpan());
return;
}
ArrayStart arrayStart = WriteArrayStart(TypeModel.GetTypeAlignment<T>());
foreach (var item in value)
{
Write<T>(item);
}
WriteArrayEnd(arrayStart);
}
private static void WriteArraySignature<T>(ref MessageWriter writer) where T : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Array<T>>(buffer));
}
}

234
src/Linux/Tmds.DBus.Protocol/MessageWriter.Basic.cs

@ -0,0 +1,234 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
private const int MaxSizeHint = 4096;
public void WriteBool(bool value) => WriteUInt32(value ? 1u : 0u);
public void WriteByte(byte value) => WritePrimitiveCore<byte>(value, DBusType.Byte);
public void WriteInt16(short value) => WritePrimitiveCore<Int16>(value, DBusType.Int16);
public void WriteUInt16(ushort value) => WritePrimitiveCore<UInt16>(value, DBusType.UInt16);
public void WriteInt32(int value) => WritePrimitiveCore<Int32>(value, DBusType.Int32);
public void WriteUInt32(uint value) => WritePrimitiveCore<UInt32>(value, DBusType.UInt32);
public void WriteInt64(long value) => WritePrimitiveCore<Int64>(value, DBusType.Int64);
public void WriteUInt64(ulong value) => WritePrimitiveCore<UInt64>(value, DBusType.UInt64);
public void WriteDouble(double value) => WritePrimitiveCore<double>(value, DBusType.Double);
public void WriteString(Utf8Span value) => WriteStringCore(value);
public void WriteString(string value) => WriteStringCore(value);
public void WriteSignature(Utf8Span value)
{
ReadOnlySpan<byte> span = value;
int length = span.Length;
WriteByte((byte)length);
var dst = GetSpan(length);
span.CopyTo(dst);
Advance(length);
WriteByte((byte)0);
}
public void WriteSignature(string s)
{
Span<byte> lengthSpan = GetSpan(1);
Advance(1);
int bytesWritten = WriteRaw(s);
lengthSpan[0] = (byte)bytesWritten;
WriteByte(0);
}
public void WriteObjectPath(Utf8Span value) => WriteStringCore(value);
public void WriteObjectPath(string value) => WriteStringCore(value);
public void WriteVariantBool(bool value)
{
WriteSignature(ProtocolConstants.BooleanSignature);
WriteBool(value);
}
public void WriteVariantByte(byte value)
{
WriteSignature(ProtocolConstants.ByteSignature);
WriteByte(value);
}
public void WriteVariantInt16(short value)
{
WriteSignature(ProtocolConstants.Int16Signature);
WriteInt16(value);
}
public void WriteVariantUInt16(ushort value)
{
WriteSignature(ProtocolConstants.UInt16Signature);
WriteUInt16(value);
}
public void WriteVariantInt32(int value)
{
WriteSignature(ProtocolConstants.Int32Signature);
WriteInt32(value);
}
public void WriteVariantUInt32(uint value)
{
WriteSignature(ProtocolConstants.UInt32Signature);
WriteUInt32(value);
}
public void WriteVariantInt64(long value)
{
WriteSignature(ProtocolConstants.Int64Signature);
WriteInt64(value);
}
public void WriteVariantUInt64(ulong value)
{
WriteSignature(ProtocolConstants.UInt64Signature);
WriteUInt64(value);
}
public void WriteVariantDouble(double value)
{
WriteSignature(ProtocolConstants.DoubleSignature);
WriteDouble(value);
}
public void WriteVariantString(Utf8Span value)
{
WriteSignature(ProtocolConstants.StringSignature);
WriteString(value);
}
public void WriteVariantSignature(Utf8Span value)
{
WriteSignature(ProtocolConstants.SignatureSignature);
WriteSignature(value);
}
public void WriteVariantObjectPath(Utf8Span value)
{
WriteSignature(ProtocolConstants.ObjectPathSignature);
WriteObjectPath(value);
}
public void WriteVariantString(string value)
{
WriteSignature(ProtocolConstants.StringSignature);
WriteString(value);
}
public void WriteVariantSignature(string value)
{
WriteSignature(ProtocolConstants.SignatureSignature);
WriteSignature(value);
}
public void WriteVariantObjectPath(string value)
{
WriteSignature(ProtocolConstants.ObjectPathSignature);
WriteObjectPath(value);
}
private void WriteStringCore(ReadOnlySpan<byte> span)
{
int length = span.Length;
WriteUInt32((uint)length);
var dst = GetSpan(length);
span.CopyTo(dst);
Advance(length);
WriteByte((byte)0);
}
private void WriteStringCore(string s)
{
WritePadding(DBusType.UInt32);
Span<byte> lengthSpan = GetSpan(4);
Advance(4);
int bytesWritten = WriteRaw(s);
Unsafe.WriteUnaligned<uint>(ref MemoryMarshal.GetReference(lengthSpan), (uint)bytesWritten);
WriteByte(0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WritePrimitiveCore<T>(T value, DBusType type)
{
WritePadding(type);
int length = ProtocolConstants.GetFixedTypeLength(type);
var span = GetSpan(length);
Unsafe.WriteUnaligned<T>(ref MemoryMarshal.GetReference(span), value);
Advance(length);
}
private int WriteRaw(ReadOnlySpan<byte> data)
{
int totalLength = data.Length;
if (totalLength <= MaxSizeHint)
{
var dst = GetSpan(totalLength);
data.CopyTo(dst);
Advance(totalLength);
return totalLength;
}
else
{
while (!data.IsEmpty)
{
var dst = GetSpan(1);
int length = Math.Min(data.Length, dst.Length);
data.Slice(0, length).CopyTo(dst);
Advance(length);
data = data.Slice(length);
}
return totalLength;
}
}
private int WriteRaw(string data)
{
const int MaxUtf8BytesPerChar = 3;
if (data.Length <= MaxSizeHint / MaxUtf8BytesPerChar)
{
ReadOnlySpan<char> chars = data.AsSpan();
int byteCount = Encoding.UTF8.GetByteCount(chars);
var dst = GetSpan(byteCount);
byteCount = Encoding.UTF8.GetBytes(data.AsSpan(), dst);
Advance(byteCount);
return byteCount;
}
else
{
ReadOnlySpan<char> chars = data.AsSpan();
Encoder encoder = Encoding.UTF8.GetEncoder();
int totalLength = 0;
do
{
Debug.Assert(!chars.IsEmpty);
var dst = GetSpan(MaxUtf8BytesPerChar);
encoder.Convert(chars, dst, flush: true, out int charsUsed, out int bytesUsed, out bool completed);
Advance(bytesUsed);
totalLength += bytesUsed;
if (completed)
{
return totalLength;
}
chars = chars.Slice(charsUsed);
} while (true);
}
}
}

128
src/Linux/Tmds.DBus.Protocol/MessageWriter.Dictionary.cs

@ -0,0 +1,128 @@
namespace Tmds.DBus.Protocol;
// Using obsolete generic write members
#pragma warning disable CS0618
public ref partial struct MessageWriter
{
public ArrayStart WriteDictionaryStart()
=> WriteArrayStart(DBusType.Struct);
public void WriteDictionaryEnd(ArrayStart start)
=> WriteArrayEnd(start);
public void WriteDictionaryEntryStart()
=> WriteStructureStart();
// Write method for the common 'a{sv}' type.
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // It's safe to call WriteDictionary with these types.
public void WriteDictionary(IEnumerable<KeyValuePair<string, Variant>> value)
=> WriteDictionary<string, Variant>(value);
// Write method for the common 'a{sv}' type.
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // It's safe to call WriteDictionary with these types.
public void WriteDictionary(KeyValuePair<string, Variant>[] value)
=> WriteDictionary<string, Variant>(value);
// Write method for the common 'a{sv}' type.
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // It's safe to call WriteDictionary with these types.
public void WriteDictionary(Dictionary<string, Variant> value)
=> WriteDictionary<string, Variant>(value);
[RequiresUnreferencedCode(Strings.UseNonGenericWriteDictionary)]
[Obsolete(Strings.UseNonGenericWriteDictionaryObsolete)]
public void WriteDictionary<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> value)
where TKey : notnull
where TValue : notnull
{
ArrayStart arrayStart = WriteDictionaryStart();
foreach (var item in value)
{
WriteDictionaryEntryStart();
Write<TKey>(item.Key);
Write<TValue>(item.Value);
}
WriteDictionaryEnd(arrayStart);
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteDictionary)]
[Obsolete(Strings.UseNonGenericWriteDictionaryObsolete)]
public void WriteDictionary<TKey, TValue>(KeyValuePair<TKey, TValue>[] value)
where TKey : notnull
where TValue : notnull
{
ArrayStart arrayStart = WriteDictionaryStart();
foreach (var item in value)
{
WriteDictionaryEntryStart();
Write<TKey>(item.Key);
Write<TValue>(item.Value);
}
WriteDictionaryEnd(arrayStart);
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteDictionary)]
[Obsolete(Strings.UseNonGenericWriteDictionaryObsolete)]
public void WriteDictionary<TKey, TValue>(Dictionary<TKey, TValue> value)
where TKey : notnull
where TValue : notnull
{
ArrayStart arrayStart = WriteDictionaryStart();
foreach (var item in value)
{
WriteDictionaryEntryStart();
Write<TKey>(item.Key);
Write<TValue>(item.Value);
}
WriteDictionaryEnd(arrayStart);
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteVariantDictionary)]
[Obsolete(Strings.UseNonGenericWriteVariantDictionaryObsolete)]
public void WriteVariantDictionary<TKey, TValue>(IEnumerable<KeyValuePair<TKey, TValue>> value)
where TKey : notnull
where TValue : notnull
{
WriteDictionarySignature<TKey, TValue>(ref this);
WriteDictionary(value);
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteVariantDictionary)]
[Obsolete(Strings.UseNonGenericWriteVariantDictionaryObsolete)]
public void WriteVariantDictionary<TKey, TValue>(KeyValuePair<TKey, TValue>[] value)
where TKey : notnull
where TValue : notnull
{
WriteDictionarySignature<TKey, TValue>(ref this);
WriteDictionary(value);
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteVariantDictionary)]
[Obsolete(Strings.UseNonGenericWriteVariantDictionaryObsolete)]
public void WriteVariantDictionary<TKey, TValue>(Dictionary<TKey, TValue> value)
where TKey : notnull
where TValue : notnull
{
WriteDictionarySignature<TKey, TValue>(ref this);
WriteDictionary(value);
}
// This method writes a Dictionary without using generics at the 'cost' of boxing.
// private void WriteDictionary(IDictionary value)
// {
// ArrayStart arrayStart = WriteDictionaryStart();
// foreach (System.Collections.DictionaryEntry de in dictionary)
// {
// WriteDictionaryEntryStart();
// Write(de.Key, asVariant: keyType == typeof(object));
// Write(de.Value, asVariant: valueType == typeof(object));
// }
// WriteDictionaryEnd(ref arrayStart);
// }
private static void WriteDictionarySignature<TKey, TValue>(ref MessageWriter writer) where TKey : notnull where TValue : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Dict<TKey, TValue>>(buffer));
}
}

28
src/Linux/Tmds.DBus.Protocol/MessageWriter.Handle.cs

@ -0,0 +1,28 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
public void WriteHandle(SafeHandle value)
{
int idx = HandleCount;
AddHandle(value);
WriteInt32(idx);
}
public void WriteVariantHandle(SafeHandle value)
{
WriteSignature(ProtocolConstants.UnixFdSignature);
WriteHandle(value);
}
private int HandleCount => _handles?.Count ?? 0;
private void AddHandle(SafeHandle handle)
{
if (_handles is null)
{
_handles = new(isRawHandleCollection: false);
}
_handles.AddHandle(handle);
}
}

215
src/Linux/Tmds.DBus.Protocol/MessageWriter.Header.cs

@ -0,0 +1,215 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
public void WriteMethodCallHeader(
string? destination = null,
string? path = null,
string? @interface = null,
string? member = null,
string? signature = null,
MessageFlags flags = MessageFlags.None)
{
ArrayStart start = WriteHeaderStart(MessageType.MethodCall, flags);
// Path.
if (path is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Path);
WriteVariantObjectPath(path);
}
// Interface.
if (@interface is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Interface);
WriteVariantString(@interface);
}
// Member.
if (member is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Member);
WriteVariantString(member);
}
// Destination.
if (destination is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Destination);
WriteVariantString(destination);
}
// Signature.
if (signature is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Signature);
WriteVariantSignature(signature);
}
WriteHeaderEnd(start);
}
public void WriteMethodReturnHeader(
uint replySerial,
Utf8Span destination = default,
string? signature = null)
{
ArrayStart start = WriteHeaderStart(MessageType.MethodReturn, MessageFlags.None);
// ReplySerial
WriteStructureStart();
WriteByte((byte)MessageHeader.ReplySerial);
WriteVariantUInt32(replySerial);
// Destination.
if (!destination.IsEmpty)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Destination);
WriteVariantString(destination);
}
// Signature.
if (signature is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Signature);
WriteVariantSignature(signature);
}
WriteHeaderEnd(start);
}
public void WriteError(
uint replySerial,
ReadOnlySpan<byte> destination = default,
string? errorName = null,
string? errorMsg = null)
{
ArrayStart start = WriteHeaderStart(MessageType.Error, MessageFlags.None);
// ReplySerial
WriteStructureStart();
WriteByte((byte)MessageHeader.ReplySerial);
WriteVariantUInt32(replySerial);
// Destination.
if (!destination.IsEmpty)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Destination);
WriteVariantString(destination);
}
// Error.
if (errorName is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.ErrorName);
WriteVariantString(errorName);
}
// Signature.
if (errorMsg is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Signature);
WriteVariantSignature(ProtocolConstants.StringSignature);
}
WriteHeaderEnd(start);
if (errorMsg is not null)
{
WriteString(errorMsg);
}
}
public void WriteSignalHeader(
string? destination = null,
string? path = null,
string? @interface = null,
string? member = null,
string? signature = null)
{
ArrayStart start = WriteHeaderStart(MessageType.Signal, MessageFlags.None);
// Path.
if (path is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Path);
WriteVariantObjectPath(path);
}
// Interface.
if (@interface is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Interface);
WriteVariantString(@interface);
}
// Member.
if (member is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Member);
WriteVariantString(member);
}
// Destination.
if (destination is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Destination);
WriteVariantString(destination);
}
// Signature.
if (signature is not null)
{
WriteStructureStart();
WriteByte((byte)MessageHeader.Signature);
WriteVariantSignature(signature);
}
WriteHeaderEnd(start);
}
private void WriteHeaderEnd(ArrayStart start)
{
WriteArrayEnd(start);
WritePadding(DBusType.Struct);
}
private ArrayStart WriteHeaderStart(MessageType type, MessageFlags flags)
{
_flags = flags;
WriteByte(BitConverter.IsLittleEndian ? (byte)'l' : (byte)'B'); // endianness
WriteByte((byte)type);
WriteByte((byte)flags);
WriteByte((byte)1); // version
WriteUInt32((uint)0); // length placeholder
Debug.Assert(_offset == LengthOffset + 4);
WriteUInt32(_serial);
Debug.Assert(_offset == SerialOffset + 4);
// headers
ArrayStart start = WriteArrayStart(DBusType.Struct);
// UnixFds
WriteStructureStart();
WriteByte((byte)MessageHeader.UnixFds);
WriteVariantUInt32(0); // unix fd length placeholder
Debug.Assert(_offset == UnixFdLengthOffset + 4);
return start;
}
}

75
src/Linux/Tmds.DBus.Protocol/MessageWriter.IntrospectionXml.cs

@ -0,0 +1,75 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
private ReadOnlySpan<byte> IntrospectionHeader =>
"""
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
"""u8;
private ReadOnlySpan<byte> IntrospectionFooter =>
"""
</node>
"""u8;
private ReadOnlySpan<byte> NodeNameStart =>
"""
<node name="
"""u8;
private ReadOnlySpan<byte> NodeNameEnd =>
"""
"/>
"""u8;
public void WriteIntrospectionXml(scoped ReadOnlySpan<ReadOnlyMemory<byte>> interfaceXmls, IEnumerable<string> childNames)
=> WriteIntrospectionXml(interfaceXmls, baseInterfaceXmls: default, childNames: default,
childNamesEnumerable: childNames ?? throw new ArgumentNullException(nameof(childNames)));
internal void WriteIntrospectionXml(
scoped ReadOnlySpan<ReadOnlyMemory<byte>> interfaceXmls,
scoped ReadOnlySpan<ReadOnlyMemory<byte>> baseInterfaceXmls,
scoped ReadOnlySpan<string> childNames,
IEnumerable<string>? childNamesEnumerable)
{
WritePadding(DBusType.UInt32);
Span<byte> lengthSpan = GetSpan(4);
Advance(4);
int bytesWritten = 0;
bytesWritten += WriteRaw(IntrospectionHeader);
foreach (var xml in interfaceXmls)
{
bytesWritten += WriteRaw(xml.Span);
}
foreach (var xml in baseInterfaceXmls)
{
bytesWritten += WriteRaw(xml.Span);
}
// D-Bus names only consist of '[A-Z][a-z][0-9]_'.
// We don't need to any escaping for use as an XML attribute value.
foreach (var childName in childNames)
{
bytesWritten += WriteRaw(NodeNameStart);
bytesWritten += WriteRaw(childName);
bytesWritten += WriteRaw(NodeNameEnd);
}
if (childNamesEnumerable is not null)
{
foreach (var childName in childNamesEnumerable)
{
bytesWritten += WriteRaw(NodeNameStart);
bytesWritten += WriteRaw(childName);
bytesWritten += WriteRaw(NodeNameEnd);
}
}
bytesWritten += WriteRaw(IntrospectionFooter);
Unsafe.WriteUnaligned<uint>(ref MemoryMarshal.GetReference(lengthSpan), (uint)bytesWritten);
WriteByte(0);
}
}

299
src/Linux/Tmds.DBus.Protocol/MessageWriter.Struct.cs

@ -0,0 +1,299 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1>(ValueTuple<T1> value)
where T1 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
}
private static void WriteStructSignature<T1>(ref MessageWriter writer)
where T1 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1>>(buffer));
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1, T2>((T1, T2) value)
where T1 : notnull
where T2 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
Write<T2>(value.Item2);
}
private static void WriteStructSignature<T1, T2>(ref MessageWriter writer)
where T1 : notnull
where T2 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1, T2>>(buffer));
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1, T2, T3>((T1, T2, T3) value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
Write<T2>(value.Item2);
Write<T3>(value.Item3);
}
private static void WriteStructSignature<T1, T2, T3>(ref MessageWriter writer)
where T1 : notnull
where T2 : notnull
where T3 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1, T2, T3>>(buffer));
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1, T2, T3, T4>((T1, T2, T3, T4) value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
Write<T2>(value.Item2);
Write<T3>(value.Item3);
Write<T4>(value.Item4);
}
private static void WriteStructSignature<T1, T2, T3, T4>(ref MessageWriter writer)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1, T2, T3, T4>>(buffer));
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1, T2, T3, T4, T5>((T1, T2, T3, T4, T5) value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
Write<T2>(value.Item2);
Write<T3>(value.Item3);
Write<T4>(value.Item4);
Write<T5>(value.Item5);
}
private static void WriteStructSignature<T1, T2, T3, T4, T5>(ref MessageWriter writer)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5>>(buffer));
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1, T2, T3, T4, T5, T6>((T1, T2, T3, T4, T5, T6) value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
Write<T2>(value.Item2);
Write<T3>(value.Item3);
Write<T4>(value.Item4);
Write<T5>(value.Item5);
Write<T6>(value.Item6);
}
private static void WriteStructSignature<T1, T2, T3, T4, T5, T6>(ref MessageWriter writer)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6>>(buffer));
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1, T2, T3, T4, T5, T6, T7>((T1, T2, T3, T4, T5, T6, T7) value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
Write<T2>(value.Item2);
Write<T3>(value.Item3);
Write<T4>(value.Item4);
Write<T5>(value.Item5);
Write<T6>(value.Item6);
Write<T7>(value.Item7);
}
private static void WriteStructSignature<T1, T2, T3, T4, T5, T6, T7>(ref MessageWriter writer)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6, T7>>(buffer));
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1, T2, T3, T4, T5, T6, T7, T8>((T1, T2, T3, T4, T5, T6, T7, T8) value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
Write<T2>(value.Item2);
Write<T3>(value.Item3);
Write<T4>(value.Item4);
Write<T5>(value.Item5);
Write<T6>(value.Item6);
Write<T7>(value.Item7);
Write<T8>(value.Rest.Item1);
}
private static void WriteStructSignature<T1, T2, T3, T4, T5, T6, T7, T8>(ref MessageWriter writer)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6, T7, T8>>(buffer));
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1, T2, T3, T4, T5, T6, T7, T8, T9>((T1, T2, T3, T4, T5, T6, T7, T8, T9) value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
Write<T2>(value.Item2);
Write<T3>(value.Item3);
Write<T4>(value.Item4);
Write<T5>(value.Item5);
Write<T6>(value.Item6);
Write<T7>(value.Item7);
Write<T8>(value.Rest.Item1);
Write<T9>(value.Rest.Item2);
}
private static void WriteStructSignature<T1, T2, T3, T4, T5, T6, T7, T8, T9>(ref MessageWriter writer)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9>>(buffer));
}
[RequiresUnreferencedCode(Strings.UseNonGenericWriteStruct)]
[Obsolete(Strings.UseNonGenericWriteStructObsolete)]
public void WriteStruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>((T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
where T10 : notnull
{
WriteStructureStart();
Write<T1>(value.Item1);
Write<T2>(value.Item2);
Write<T3>(value.Item3);
Write<T4>(value.Item4);
Write<T5>(value.Item5);
Write<T6>(value.Item6);
Write<T7>(value.Item7);
Write<T8>(value.Rest.Item1);
Write<T9>(value.Rest.Item2);
Write<T10>(value.Rest.Item3);
}
private static void WriteStructSignature<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(ref MessageWriter writer)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
where T10 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
writer.WriteSignature(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>>(buffer));
}
}

77
src/Linux/Tmds.DBus.Protocol/MessageWriter.Variant.Dynamic.cs

@ -0,0 +1,77 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
[RequiresUnreferencedCode(Strings.UseNonObjectWriteVariant)]
[Obsolete(Strings.UseNonObjectWriteVariantObsolete)]
public void WriteVariant(object value)
{
Type type = value.GetType();
if (type == typeof(byte))
{
WriteVariantByte((byte)value);
return;
}
else if (type == typeof(bool))
{
WriteVariantBool((bool)value);
return;
}
else if (type == typeof(short))
{
WriteVariantInt16((short)value);
return;
}
else if (type == typeof(ushort))
{
WriteVariantUInt16((ushort)value);
return;
}
else if (type == typeof(int))
{
WriteVariantInt32((int)value);
return;
}
else if (type == typeof(uint))
{
WriteVariantUInt32((uint)value);
return;
}
else if (type == typeof(long))
{
WriteVariantInt64((long)value);
return;
}
else if (type == typeof(ulong))
{
WriteVariantUInt64((ulong)value);
return;
}
else if (type == typeof(double))
{
WriteVariantDouble((double)value);
return;
}
else if (type == typeof(string))
{
WriteVariantString((string)value);
return;
}
else if (type == typeof(ObjectPath))
{
WriteVariantObjectPath(value.ToString()!);
return;
}
else if (type == typeof(Signature))
{
WriteVariantSignature(value.ToString()!);
return;
}
else
{
var typeWriter = TypeWriters.GetTypeWriter(type);
typeWriter.WriteVariant(ref this, value);
}
}
}

9
src/Linux/Tmds.DBus.Protocol/MessageWriter.Variant.cs

@ -0,0 +1,9 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
public void WriteVariant(Variant value)
{
value.WriteTo(ref this);
}
}

994
src/Linux/Tmds.DBus.Protocol/MessageWriter.WriteT.Dynamic.cs

@ -0,0 +1,994 @@
namespace Tmds.DBus.Protocol;
// Code in this file is not trimmer friendly.
#pragma warning disable IL3050
#pragma warning disable IL2026
// Using obsolete generic write members
#pragma warning disable CS0618
public ref partial struct MessageWriter
{
interface ITypeWriter
{
void WriteVariant(ref MessageWriter writer, object value);
}
interface ITypeWriter<in T> : ITypeWriter
{
void Write(ref MessageWriter writer, T value);
}
private void WriteDynamic<T>(T value) where T : notnull
{
if (typeof(T) == typeof(object))
{
WriteVariant((object)value);
return;
}
var typeWriter = (ITypeWriter<T>)TypeWriters.GetTypeWriter(typeof(T));
typeWriter.Write(ref this, value);
}
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddArrayTypeWriter<T>()
where T : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddDictionaryTypeWriter<TKey, TValue>()
where TKey : notnull
where TValue : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1>()
where T1 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1>()
where T1 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1, T2>()
where T1 : notnull
where T2 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1, T2>()
where T1 : notnull
where T2 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1, T2, T3>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1, T2, T3>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1, T2, T3, T4>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1, T2, T3, T4>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1, T2, T3, T4, T5>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1, T2, T3, T4, T5>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1, T2, T3, T4, T5, T6>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1, T2, T3, T4, T5, T6>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8, T9>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8, T9>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddValueTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
where T10 : notnull
{ }
[Obsolete(Strings.AddTypeWriterMethodObsolete)]
public static void AddTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
where T10 : notnull
{ }
static class TypeWriters
{
private static readonly Dictionary<Type, ITypeWriter> _typeWriters = new();
public static ITypeWriter GetTypeWriter(Type type)
{
lock (_typeWriters)
{
if (_typeWriters.TryGetValue(type, out ITypeWriter? writer))
{
return writer;
}
writer = CreateWriterForType(type);
_typeWriters.Add(type, writer);
return writer;
}
}
private static ITypeWriter CreateWriterForType(Type type)
{
// Struct (ValueTuple)
if (type.IsGenericType && type.FullName!.StartsWith("System.ValueTuple"))
{
switch (type.GenericTypeArguments.Length)
{
case 1: return CreateValueTupleTypeWriter(type.GenericTypeArguments[0]);
case 2:
return CreateValueTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1]);
case 3:
return CreateValueTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2]);
case 4:
return CreateValueTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3]);
case 5:
return CreateValueTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4]);
case 6:
return CreateValueTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5]);
case 7:
return CreateValueTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5],
type.GenericTypeArguments[6]);
case 8:
switch (type.GenericTypeArguments[7].GenericTypeArguments.Length)
{
case 1:
return CreateValueTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5],
type.GenericTypeArguments[6],
type.GenericTypeArguments[7].GenericTypeArguments[0]);
case 2:
return CreateValueTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5],
type.GenericTypeArguments[6],
type.GenericTypeArguments[7].GenericTypeArguments[0],
type.GenericTypeArguments[7].GenericTypeArguments[1]);
case 3:
return CreateValueTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5],
type.GenericTypeArguments[6],
type.GenericTypeArguments[7].GenericTypeArguments[0],
type.GenericTypeArguments[7].GenericTypeArguments[1],
type.GenericTypeArguments[7].GenericTypeArguments[2]);
}
break;
}
}
// Struct (ValueTuple)
if (type.IsGenericType && type.FullName!.StartsWith("System.Tuple"))
{
switch (type.GenericTypeArguments.Length)
{
case 1: return CreateTupleTypeWriter(type.GenericTypeArguments[0]);
case 2:
return CreateTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1]);
case 3:
return CreateTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2]);
case 4:
return CreateTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3]);
case 5:
return CreateTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4]);
case 6:
return CreateTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5]);
case 7:
return CreateTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5],
type.GenericTypeArguments[6]);
case 8:
switch (type.GenericTypeArguments[7].GenericTypeArguments.Length)
{
case 1:
return CreateTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5],
type.GenericTypeArguments[6],
type.GenericTypeArguments[7].GenericTypeArguments[0]);
case 2:
return CreateTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5],
type.GenericTypeArguments[6],
type.GenericTypeArguments[7].GenericTypeArguments[0],
type.GenericTypeArguments[7].GenericTypeArguments[1]);
case 3:
return CreateTupleTypeWriter(type.GenericTypeArguments[0],
type.GenericTypeArguments[1],
type.GenericTypeArguments[2],
type.GenericTypeArguments[3],
type.GenericTypeArguments[4],
type.GenericTypeArguments[5],
type.GenericTypeArguments[6],
type.GenericTypeArguments[7].GenericTypeArguments[0],
type.GenericTypeArguments[7].GenericTypeArguments[1],
type.GenericTypeArguments[7].GenericTypeArguments[2]);
}
break;
}
}
// Array/Dictionary type (IEnumerable<>/IEnumerable<KeyValuePair<,>>)
Type? extractedType = TypeModel.ExtractGenericInterface(type, typeof(IEnumerable<>));
if (extractedType != null)
{
if (_typeWriters.TryGetValue(extractedType, out ITypeWriter? writer))
{
return writer;
}
Type elementType = extractedType.GenericTypeArguments[0];
if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{
Type keyType = elementType.GenericTypeArguments[0];
Type valueType = elementType.GenericTypeArguments[1];
writer = CreateDictionaryTypeWriter(keyType, valueType);
}
else
{
writer = CreateArrayTypeWriter(elementType);
}
if (type != extractedType)
{
_typeWriters.Add(extractedType, writer);
}
return writer;
}
ThrowNotSupportedType(type);
return default!;
}
sealed class ArrayTypeWriter<T> : ITypeWriter<IEnumerable<T>>
where T : notnull
{
public void Write(ref MessageWriter writer, IEnumerable<T> value)
{
writer.WriteArray(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteArraySignature<T>(ref writer);
writer.WriteArray((IEnumerable<T>)value);
}
}
private static ITypeWriter CreateArrayTypeWriter(Type elementType)
{
Type writerType = typeof(ArrayTypeWriter<>).MakeGenericType(new[] { elementType });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class DictionaryTypeWriter<TKey, TValue> : ITypeWriter<IEnumerable<KeyValuePair<TKey, TValue>>>
where TKey : notnull
where TValue : notnull
{
public void Write(ref MessageWriter writer, IEnumerable<KeyValuePair<TKey, TValue>> value)
{
writer.WriteDictionary(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteDictionarySignature<TKey, TValue>(ref writer);
writer.WriteDictionary((IEnumerable<KeyValuePair<TKey, TValue>>)value);
}
}
private static ITypeWriter CreateDictionaryTypeWriter(Type keyType, Type valueType)
{
Type writerType = typeof(DictionaryTypeWriter<,>).MakeGenericType(new[] { keyType, valueType });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1> : ITypeWriter<ValueTuple<T1>>
where T1 : notnull
{
public void Write(ref MessageWriter writer, ValueTuple<T1> value)
{
writer.WriteStruct<T1>(new ValueTuple<T1>(value.Item1));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1>(ref writer);
Write(ref writer, (ValueTuple<T1>)value);
}
}
sealed class TupleTypeWriter<T1> : ITypeWriter<Tuple<T1>>
where T1 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1> value)
{
writer.WriteStruct<T1>(new ValueTuple<T1>(value.Item1));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1>(ref writer);
Write(ref writer, (Tuple<T1>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1)
{
Type writerType = typeof(ValueTupleTypeWriter<>).MakeGenericType(new[] { type1 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1)
{
Type writerType = typeof(TupleTypeWriter<>).MakeGenericType(new[] { type1 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1, T2> : ITypeWriter<ValueTuple<T1, T2>>
where T1 : notnull
where T2 : notnull
{
public void Write(ref MessageWriter writer, ValueTuple<T1, T2> value)
{
writer.WriteStruct<T1, T2>(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2>(ref writer);
Write(ref writer, (ValueTuple<T1, T2>)value);
}
}
sealed class TupleTypeWriter<T1, T2> : ITypeWriter<Tuple<T1, T2>>
where T1 : notnull
where T2 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1, T2> value)
{
writer.WriteStruct<T1, T2>((value.Item1, value.Item2));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2>(ref writer);
Write(ref writer, (Tuple<T1, T2>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1, Type type2)
{
Type writerType = typeof(ValueTupleTypeWriter<,>).MakeGenericType(new[] { type1, type2 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1, Type type2)
{
Type writerType = typeof(TupleTypeWriter<,>).MakeGenericType(new[] { type1, type2 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1, T2, T3> : ITypeWriter<ValueTuple<T1, T2, T3>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
{
public void Write(ref MessageWriter writer, ValueTuple<T1, T2, T3> value)
{
writer.WriteStruct<T1, T2, T3>(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3>(ref writer);
Write(ref writer, (ValueTuple<T1, T2, T3>)value);
}
}
sealed class TupleTypeWriter<T1, T2, T3> : ITypeWriter<Tuple<T1, T2, T3>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1, T2, T3> value)
{
writer.WriteStruct<T1, T2, T3>((value.Item1, value.Item2, value.Item3));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3>(ref writer);
Write(ref writer, (Tuple<T1, T2, T3>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1, Type type2, Type type3)
{
Type writerType = typeof(ValueTupleTypeWriter<,,>).MakeGenericType(new[] { type1, type2, type3 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1, Type type2, Type type3)
{
Type writerType = typeof(TupleTypeWriter<,,>).MakeGenericType(new[] { type1, type2, type3 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1, T2, T3, T4> : ITypeWriter<ValueTuple<T1, T2, T3, T4>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{
public void Write(ref MessageWriter writer, ValueTuple<T1, T2, T3, T4> value)
{
writer.WriteStruct<T1, T2, T3, T4>(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4>(ref writer);
Write(ref writer, (ValueTuple<T1, T2, T3, T4>)value);
}
}
sealed class TupleTypeWriter<T1, T2, T3, T4> : ITypeWriter<Tuple<T1, T2, T3, T4>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1, T2, T3, T4> value)
{
writer.WriteStruct<T1, T2, T3, T4>((value.Item1, value.Item2, value.Item3, value.Item4));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4>(ref writer);
Write(ref writer, (Tuple<T1, T2, T3, T4>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1, Type type2, Type type3, Type type4)
{
Type writerType = typeof(ValueTupleTypeWriter<,,,>).MakeGenericType(new[] { type1, type2, type3, type4 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1, Type type2, Type type3, Type type4)
{
Type writerType = typeof(TupleTypeWriter<,,,>).MakeGenericType(new[] { type1, type2, type3, type4 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1, T2, T3, T4, T5> : ITypeWriter<ValueTuple<T1, T2, T3, T4, T5>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
{
public void Write(ref MessageWriter writer, ValueTuple<T1, T2, T3, T4, T5> value)
{
writer.WriteStruct<T1, T2, T3, T4, T5>(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5>(ref writer);
Write(ref writer, (ValueTuple<T1, T2, T3, T4, T5>)value);
}
}
sealed class TupleTypeWriter<T1, T2, T3, T4, T5> : ITypeWriter<Tuple<T1, T2, T3, T4, T5>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1, T2, T3, T4, T5> value)
{
writer.WriteStruct<T1, T2, T3, T4, T5>((value.Item1, value.Item2, value.Item3, value.Item4, value.Item5));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5>(ref writer);
Write(ref writer, (Tuple<T1, T2, T3, T4, T5>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5)
{
Type writerType = typeof(ValueTupleTypeWriter<,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5)
{
Type writerType = typeof(TupleTypeWriter<,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1, T2, T3, T4, T5, T6> : ITypeWriter<ValueTuple<T1, T2, T3, T4, T5, T6>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
{
public void Write(ref MessageWriter writer, ValueTuple<T1, T2, T3, T4, T5, T6> value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6>(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6>(ref writer);
Write(ref writer, (ValueTuple<T1, T2, T3, T4, T5, T6>)value);
}
}
sealed class TupleTypeWriter<T1, T2, T3, T4, T5, T6> : ITypeWriter<Tuple<T1, T2, T3, T4, T5, T6>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1, T2, T3, T4, T5, T6> value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6>((value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6>(ref writer);
Write(ref writer, (Tuple<T1, T2, T3, T4, T5, T6>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6)
{
Type writerType = typeof(ValueTupleTypeWriter<,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6)
{
Type writerType = typeof(TupleTypeWriter<,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7> : ITypeWriter<ValueTuple<T1, T2, T3, T4, T5, T6, T7>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
{
public void Write(ref MessageWriter writer, ValueTuple<T1, T2, T3, T4, T5, T6, T7> value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6, T7>(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6, T7>(ref writer);
Write(ref writer, (ValueTuple<T1, T2, T3, T4, T5, T6, T7>)value);
}
}
sealed class TupleTypeWriter<T1, T2, T3, T4, T5, T6, T7> : ITypeWriter<Tuple<T1, T2, T3, T4, T5, T6, T7>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1, T2, T3, T4, T5, T6, T7> value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6, T7>((value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6, T7>(ref writer);
Write(ref writer, (Tuple<T1, T2, T3, T4, T5, T6, T7>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6, Type type7)
{
Type writerType = typeof(ValueTupleTypeWriter<,,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6, type7 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6, Type type7)
{
Type writerType = typeof(TupleTypeWriter<,,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6, type7 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8> : ITypeWriter<ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
{
public void Write(ref MessageWriter writer, (T1, T2, T3, T4, T5, T6, T7, T8) value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6, T7, T8>(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6, T7, T8>(ref writer);
Write(ref writer, (ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>>)value);
}
}
sealed class TupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8> : ITypeWriter<Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>> value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6, T7, T8>((value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7, value.Rest.Item1));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6, T7, T8>(ref writer);
Write(ref writer, (Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6, Type type7, Type type8)
{
Type writerType = typeof(ValueTupleTypeWriter<,,,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6, type7, type8 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6, Type type7, Type type8)
{
Type writerType = typeof(TupleTypeWriter<,,,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6, type7, type8 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8, T9> : ITypeWriter<ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9>>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
{
public void Write(ref MessageWriter writer, (T1, T2, T3, T4, T5, T6, T7, T8, T9) value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6, T7, T8, T9>(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6, T7, T8, T9>(ref writer);
Write(ref writer, (ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9>>)value);
}
}
sealed class TupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8, T9> : ITypeWriter<Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9>>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9>> value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6, T7, T8, T9>((value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7, value.Rest.Item1, value.Rest.Item2));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6, T7, T8, T9>(ref writer);
Write(ref writer, (Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9>>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6, Type type7, Type type8, Type type9)
{
Type writerType = typeof(ValueTupleTypeWriter<,,,,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6, type7, type8 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6, Type type7, Type type8, Type type9)
{
Type writerType = typeof(TupleTypeWriter<,,,,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6, type7, type8, type9 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
sealed class ValueTupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : ITypeWriter<ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10>>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
where T10 : notnull
{
public void Write(ref MessageWriter writer, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(value);
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(ref writer);
Write(ref writer, (ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10>>)value);
}
}
sealed class TupleTypeWriter<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : ITypeWriter<Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10>>>
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
where T10 : notnull
{
public void Write(ref MessageWriter writer, Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10>> value)
{
writer.WriteStruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>((value.Item1, value.Item2, value.Item3, value.Item4, value.Item5, value.Item6, value.Item7, value.Rest.Item1, value.Rest.Item2, value.Rest.Item3));
}
public void WriteVariant(ref MessageWriter writer, object value)
{
WriteStructSignature<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(ref writer);
Write(ref writer, (Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10>>)value);
}
}
private static ITypeWriter CreateValueTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6, Type type7, Type type8, Type type9, Type type10)
{
Type writerType = typeof(ValueTupleTypeWriter<,,,,,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6, type7, type8, type10 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
private static ITypeWriter CreateTupleTypeWriter(Type type1, Type type2, Type type3, Type type4, Type type5, Type type6, Type type7, Type type8, Type type9, Type type10)
{
Type writerType = typeof(TupleTypeWriter<,,,,,,,,,>).MakeGenericType(new[] { type1, type2, type3, type4, type5, type6, type7, type8, type9, type10 });
return (ITypeWriter)Activator.CreateInstance(writerType)!;
}
}
}

82
src/Linux/Tmds.DBus.Protocol/MessageWriter.WriteT.cs

@ -0,0 +1,82 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Write<T>(T value) where T : notnull
{
if (typeof(T) == typeof(byte))
{
WriteByte((byte)(object)value);
}
else if (typeof(T) == typeof(bool))
{
WriteBool((bool)(object)value);
}
else if (typeof(T) == typeof(short))
{
WriteInt16((short)(object)value);
}
else if (typeof(T) == typeof(ushort))
{
WriteUInt16((ushort)(object)value);
}
else if (typeof(T) == typeof(int))
{
WriteInt32((int)(object)value);
}
else if (typeof(T) == typeof(uint))
{
WriteUInt32((uint)(object)value);
}
else if (typeof(T) == typeof(long))
{
WriteInt64((long)(object)value);
}
else if (typeof(T) == typeof(ulong))
{
WriteUInt64((ulong)(object)value);
}
else if (typeof(T) == typeof(double))
{
WriteDouble((double)(object)value);
}
else if (typeof(T) == typeof(string))
{
WriteString((string)(object)value);
}
else if (typeof(T) == typeof(ObjectPath))
{
WriteString(((ObjectPath)(object)value).ToString());
}
else if (typeof(T) == typeof(Signature))
{
WriteSignature(((Signature)(object)value).ToString());
}
else if (typeof(T) == typeof(Variant))
{
((Variant)(object)value).WriteTo(ref this);
}
else if (typeof(T).IsAssignableTo(typeof(SafeHandle)))
{
WriteHandle((SafeHandle)(object)value);
}
else if (typeof(T).IsAssignableTo(typeof(IDBusWritable)))
{
(value as IDBusWritable)!.WriteTo(ref this);
}
else if (Feature.IsDynamicCodeEnabled)
{
WriteDynamic<T>(value);
}
else
{
ThrowNotSupportedType(typeof(T));
}
}
private static void ThrowNotSupportedType(Type type)
{
throw new NotSupportedException($"Cannot write type {type.FullName}");
}
}

193
src/Linux/Tmds.DBus.Protocol/MessageWriter.cs

@ -0,0 +1,193 @@
namespace Tmds.DBus.Protocol;
public ref partial struct MessageWriter
{
private const int LengthOffset = 4;
private const int SerialOffset = 8;
private const int HeaderFieldsLengthOffset = 12;
private const int UnixFdLengthOffset = 20;
private MessageBuffer _message;
private Sequence<byte> _data;
private UnixFdCollection? _handles;
private readonly uint _serial;
private MessageFlags _flags;
private Span<byte> _firstSpan;
private Span<byte> _span;
private int _offset;
private int _buffered;
public MessageBuffer CreateMessage()
{
Flush();
Span<byte> span = _firstSpan;
// Length
uint headerFieldsLength = Unsafe.ReadUnaligned<uint>(ref MemoryMarshal.GetReference(span.Slice(HeaderFieldsLengthOffset)));
uint pad = headerFieldsLength % 8;
if (pad != 0)
{
headerFieldsLength += (8 - pad);
}
uint length = (uint)_data.Length // Total length
- headerFieldsLength // Header fields
- 4 // Header fields length
- (uint)HeaderFieldsLengthOffset; // Preceeding header fields
Unsafe.WriteUnaligned<uint>(ref MemoryMarshal.GetReference(span.Slice(LengthOffset)), length);
// UnixFdLength
Unsafe.WriteUnaligned<uint>(ref MemoryMarshal.GetReference(span.Slice(UnixFdLengthOffset)), (uint)HandleCount);
uint serial = _serial;
MessageFlags flags = _flags;
ReadOnlySequence<byte> data = _data;
UnixFdCollection? handles = _handles;
var message = _message;
_message = null!;
_handles = null;
_data = null!;
message.Init(serial, flags, handles);
return message;
}
internal MessageWriter(MessageBufferPool messagePool, uint serial)
{
_message = messagePool.Rent();
_data = _message.Sequence;
_handles = null;
_flags = default;
_offset = 0;
_buffered = 0;
_serial = serial;
_firstSpan = _span = _data.GetSpan(sizeHint: 0);
}
public ArrayStart WriteArrayStart(DBusType elementType)
{
// Array length.
WritePadding(DBusType.UInt32);
Span<byte> lengthSpan = GetSpan(4);
Advance(4);
WritePadding(elementType);
return new ArrayStart(lengthSpan, _offset);
}
public void WriteArrayEnd(ArrayStart start)
{
start.WriteLength(_offset);
}
public void WriteStructureStart()
{
WritePadding(DBusType.Struct);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Advance(int count)
{
_buffered += count;
_offset += count;
_span = _span.Slice(count);
}
private void WritePadding(DBusType type)
{
int pad = ProtocolConstants.GetPadding(_offset, type);
if (pad != 0)
{
GetSpan(pad).Slice(0, pad).Fill(0);
Advance(pad);
}
}
private void WritePadding(int alignment)
{
int pad = ProtocolConstants.GetPadding(_offset, alignment);
if (pad != 0)
{
GetSpan(pad).Slice(0, pad).Fill(0);
Advance(pad);
}
}
private Span<byte> GetSpan(int sizeHint)
{
Ensure(sizeHint);
return _span;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Ensure(int count = 1)
{
if (_span.Length < count)
{
EnsureMore(count);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void EnsureMore(int count = 0)
{
if (_buffered > 0)
{
Flush();
}
_span = _data.GetSpan(count);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Flush()
{
var buffered = _buffered;
if (buffered > 0)
{
_buffered = 0;
_data.Advance(buffered);
_span = default;
}
}
public void Dispose()
{
_message?.ReturnToPool();
_handles?.Dispose();
_message = null!;
_data = null!;
_handles = null!;
}
// For Tests.
internal ReadOnlySequence<byte> AsReadOnlySequence()
{
Flush();
return _data.AsReadOnlySequence;
}
// For Tests.
internal UnixFdCollection? Handles => _handles;
}
public ref struct ArrayStart
{
private Span<byte> _span;
private int _offset;
internal ArrayStart(Span<byte> lengthSpan, int offset)
{
_span = lengthSpan;
_offset = offset;
}
internal void WriteLength(int offset)
{
uint length = (uint)(offset - _offset);
Unsafe.WriteUnaligned<uint>(ref MemoryMarshal.GetReference(_span), length);
}
}

97
src/Linux/Tmds.DBus.Protocol/MethodContext.cs

@ -0,0 +1,97 @@
namespace Tmds.DBus.Protocol;
public class MethodContext
{
internal MethodContext(Connection connection, Message request, CancellationToken requestAborted)
{
Connection = connection;
Request = request;
RequestAborted = requestAborted;
}
public Message Request { get; }
public Connection Connection { get; }
public CancellationToken RequestAborted { get; }
public bool ReplySent { get; private set; }
public bool NoReplyExpected => (Request.MessageFlags & MessageFlags.NoReplyExpected) != 0;
public bool IsDBusIntrospectRequest { get; internal set; }
internal List<string>? IntrospectChildNameList { get; set; }
public MessageWriter CreateReplyWriter(string? signature)
{
var writer = Connection.GetMessageWriter();
writer.WriteMethodReturnHeader(
replySerial: Request.Serial,
destination: Request.Sender,
signature: signature
);
return writer;
}
public void Reply(MessageBuffer message)
{
if (ReplySent || NoReplyExpected)
{
message.Dispose();
if (ReplySent)
{
throw new InvalidOperationException("A reply has already been sent.");
}
}
ReplySent = true;
Connection.TrySendMessage(message);
}
public void ReplyError(string? errorName = null,
string? errorMsg = null)
{
using var writer = Connection.GetMessageWriter();
writer.WriteError(
replySerial: Request.Serial,
destination: Request.Sender,
errorName: errorName,
errorMsg: errorMsg
);
Reply(writer.CreateMessage());
}
public void ReplyIntrospectXml(ReadOnlySpan<ReadOnlyMemory<byte>> interfaceXmls)
{
if (!IsDBusIntrospectRequest)
{
throw new InvalidOperationException($"Can not reply with introspection XML when {nameof(IsDBusIntrospectRequest)} is false.");
}
using var writer = Connection.GetMessageWriter();
writer.WriteMethodReturnHeader(
replySerial: Request.Serial,
destination: Request.Sender,
signature: "s"
);
// Add the Peer and Introspectable interfaces.
// Tools like D-Feet will list the paths separately as soon as there is an interface.
// We add the base interfaces only for the paths that we want to show up.
// Those are paths that have other interfaces, paths that are leaves.
bool includeBaseInterfaces = !interfaceXmls.IsEmpty || IntrospectChildNameList is null || IntrospectChildNameList.Count == 0;
ReadOnlySpan<ReadOnlyMemory<byte>> baseInterfaceXmls = includeBaseInterfaces ? [ IntrospectionXml.DBusIntrospectable, IntrospectionXml.DBusPeer ] : [ ];
// Add the child names.
#if NET5_0_OR_GREATER
ReadOnlySpan<string> childNames = CollectionsMarshal.AsSpan(IntrospectChildNameList);
IEnumerable<string>? childNamesEnumerable = null;
#else
ReadOnlySpan<string> childNames = default;
IEnumerable<string>? childNamesEnumerable = IntrospectChildNameList;
#endif
writer.WriteIntrospectionXml(interfaceXmls, baseInterfaceXmls, childNames, childNamesEnumerable);
Reply(writer.CreateMessage());
}
}

214
src/Linux/Tmds.DBus.Protocol/Netstandard2_0Extensions.cs

@ -0,0 +1,214 @@
using System.Net;
using System.Net.Sockets;
namespace Tmds.DBus.Protocol;
#if NETSTANDARD2_0
static partial class NetstandardExtensions
{
public static bool Remove<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, out TValue value)
{
if (dictionary.TryGetValue(key, out value))
{
dictionary.Remove(key);
return true;
}
return false;
}
public static unsafe int GetBytes(this Encoding encoding, ReadOnlySpan<char> chars, Span<byte> bytes)
{
fixed (char* pChars = &GetNonNullPinnableReference(chars))
fixed (byte* pBytes = &GetNonNullPinnableReference(bytes))
{
return encoding.GetBytes(pChars, chars.Length, pBytes, bytes.Length);
}
}
public static unsafe int GetChars(this Encoding encoding, ReadOnlySpan<byte> bytes, Span<char> chars)
{
fixed (char* pChars = &GetNonNullPinnableReference(chars))
fixed (byte* pBytes = &GetNonNullPinnableReference(bytes))
{
return encoding.GetChars(pBytes, bytes.Length, pChars, chars.Length);
}
}
public static unsafe string GetString(this Encoding encoding, ReadOnlySpan<byte> bytes)
{
fixed (byte* pBytes = &GetNonNullPinnableReference(bytes))
{
return encoding.GetString(pBytes, bytes.Length);
}
}
public static unsafe int GetCharCount(this Encoding encoding, ReadOnlySpan<byte> bytes)
{
fixed (byte* pBytes = &GetNonNullPinnableReference(bytes))
{
return encoding.GetCharCount(pBytes, bytes.Length);
}
}
public static unsafe int GetByteCount(this Encoding encoding, ReadOnlySpan<char> chars)
{
fixed (char* pChars = &GetNonNullPinnableReference(chars))
{
return encoding.GetByteCount(pChars, chars.Length);
}
}
public static unsafe int GetByteCount(this Encoder encoder, ReadOnlySpan<char> chars, bool flush)
{
fixed (char* pChars = &GetNonNullPinnableReference(chars))
{
return encoder.GetByteCount(pChars, chars.Length, flush);
}
}
public static unsafe void Convert(this Encoder encoder, ReadOnlySpan<char> chars, Span<byte> bytes, bool flush, out int charsUsed, out int bytesUsed, out bool completed)
{
fixed (char* pChars = &GetNonNullPinnableReference(chars))
fixed (byte* pBytes = &GetNonNullPinnableReference(bytes))
{
encoder.Convert(pChars, chars.Length, pBytes, bytes.Length, flush, out charsUsed, out bytesUsed, out completed);
}
}
public static unsafe void Append(this StringBuilder sb, ReadOnlySpan<char> value)
{
fixed (char* ptr = value)
{
sb.Append(ptr, value.Length);
}
}
public static unsafe string AsString(this ReadOnlySpan<char> chars)
{
fixed (char* ptr = chars)
{
return new string(ptr, 0, chars.Length);
}
}
public static unsafe string AsString(this Span<char> chars)
=> AsString((ReadOnlySpan<char>)chars);
public static async ValueTask<int> ReceiveAsync(this Socket socket, Memory<byte> buffer, SocketFlags socketFlags)
{
if (MemoryMarshal.TryGetArray((ReadOnlyMemory<byte>)buffer, out var segment))
return await SocketTaskExtensions.ReceiveAsync(socket, segment, socketFlags).ConfigureAwait(false);
throw new NotSupportedException();
}
public static async ValueTask<int> SendAsync(this Socket socket, ReadOnlyMemory<byte> buffer, SocketFlags socketFlags)
{
if (MemoryMarshal.TryGetArray(buffer, out var segment))
return await SocketTaskExtensions.SendAsync(socket, segment, socketFlags).ConfigureAwait(false);
throw new NotSupportedException();
}
/// <summary>
/// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to fake non-null pointer. Such a reference can be used
/// for pinning but must never be dereferenced. This is useful for interop with methods that do not accept null pointers for zero-sized buffers.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ref T GetNonNullPinnableReference<T>(Span<T> span) => ref (span.Length != 0) ? ref MemoryMarshal.GetReference(span) : ref Unsafe.AsRef<T>((void*)1);
/// <summary>
/// Returns a reference to the 0th element of the ReadOnlySpan. If the ReadOnlySpan is empty, returns a reference to fake non-null pointer. Such a reference
/// can be used for pinning but must never be dereferenced. This is useful for interop with methods that do not accept null pointers for zero-sized buffers.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe ref T GetNonNullPinnableReference<T>(ReadOnlySpan<T> span) => ref (span.Length != 0) ? ref MemoryMarshal.GetReference(span) : ref Unsafe.AsRef<T>((void*)1);
}
internal sealed class UnixDomainSocketEndPoint : EndPoint
{
private const AddressFamily EndPointAddressFamily = AddressFamily.Unix;
private static readonly Encoding s_pathEncoding = Encoding.UTF8;
private const int s_nativePathOffset = 2;
private readonly string _path;
private readonly byte[] _encodedPath;
public UnixDomainSocketEndPoint(string path)
{
if (path == null)
{
throw new ArgumentNullException(nameof(path));
}
_path = path;
_encodedPath = s_pathEncoding.GetBytes(_path);
if (path.Length == 0)
{
throw new ArgumentOutOfRangeException(
nameof(path), path,
string.Format("The path '{0}' is of an invalid length for use with domain sockets on this platform. The length must be at least 1 characters.", path));
}
}
internal UnixDomainSocketEndPoint(SocketAddress socketAddress)
{
if (socketAddress == null)
{
throw new ArgumentNullException(nameof(socketAddress));
}
if (socketAddress.Family != EndPointAddressFamily)
{
throw new ArgumentOutOfRangeException(nameof(socketAddress));
}
if (socketAddress.Size > s_nativePathOffset)
{
_encodedPath = new byte[socketAddress.Size - s_nativePathOffset];
for (int i = 0; i < _encodedPath.Length; i++)
{
_encodedPath[i] = socketAddress[s_nativePathOffset + i];
}
_path = s_pathEncoding.GetString(_encodedPath, 0, _encodedPath.Length);
}
else
{
_encodedPath = Array.Empty<byte>();
_path = string.Empty;
}
}
public override SocketAddress Serialize()
{
var result = new SocketAddress(AddressFamily.Unix, _encodedPath.Length + s_nativePathOffset);
for (int index = 0; index < _encodedPath.Length; index++)
{
result[s_nativePathOffset + index] = _encodedPath[index];
}
return result;
}
public override EndPoint Create(SocketAddress socketAddress) => new UnixDomainSocketEndPoint(socketAddress);
public override AddressFamily AddressFamily => EndPointAddressFamily;
public string Path => _path;
public override string ToString() => _path;
}
#else
static partial class NetstandardExtensions
{
public static string AsString(this ReadOnlySpan<char> chars)
=> new string(chars);
public static unsafe string AsString(this Span<char> chars)
=> AsString((ReadOnlySpan<char>)chars);
}
#endif

53
src/Linux/Tmds.DBus.Protocol/Netstandard2_1Extensions.cs

@ -0,0 +1,53 @@
using System.Net;
using System.Net.Sockets;
using System.Reflection;
namespace Tmds.DBus.Protocol;
#if NETSTANDARD2_0 || NETSTANDARD2_1
static partial class NetstandardExtensions
{
private static PropertyInfo s_safehandleProperty = typeof(Socket).GetTypeInfo().GetDeclaredProperty("SafeHandle");
private const int MaxInputElementsPerIteration = 1 * 1024 * 1024;
public static bool IsAssignableTo(this Type type, Type? targetType)
=> targetType?.IsAssignableFrom(type) ?? false;
public static SafeHandle GetSafeHandle(this Socket socket)
{
if (s_safehandleProperty != null)
{
return (SafeHandle)s_safehandleProperty.GetValue(socket, null);
}
ThrowHelper.ThrowNotSupportedException();
return null!;
}
public static async Task ConnectAsync(this Socket socket, EndPoint remoteEP, CancellationToken cancellationToken)
{
using var ctr = cancellationToken.Register(state => ((Socket)state!).Dispose(), socket, useSynchronizationContext: false);
try
{
await Task.Factory.FromAsync(
(targetEndPoint, callback, state) => ((Socket)state).BeginConnect(targetEndPoint, callback, state),
asyncResult => ((Socket)asyncResult.AsyncState).EndConnect(asyncResult),
remoteEP,
state: socket).ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
cancellationToken.ThrowIfCancellationRequested();
throw;
}
}
}
#else
static partial class NetstandardExtensions
{
public static SafeHandle GetSafeHandle(this Socket socket)
=> socket.SafeHandle;
}
#endif

16
src/Linux/Tmds.DBus.Protocol/ObjectPath.cs

@ -0,0 +1,16 @@
namespace Tmds.DBus.Protocol;
public struct ObjectPath
{
private string _value;
public ObjectPath(string value) => _value = value;
public override string ToString() => _value ?? "";
public static implicit operator string(ObjectPath value) => value._value;
public static implicit operator ObjectPath(string value) => new ObjectPath(value);
public Variant AsVariant() => new Variant(this);
}

12
src/Linux/Tmds.DBus.Protocol/ObserverFlags.cs

@ -0,0 +1,12 @@
namespace Tmds.DBus.Protocol;
[Flags]
public enum ObserverFlags
{
None = 0,
EmitOnConnectionDispose = 1,
EmitOnObserverDispose = 2,
NoSubscribe = 4,
EmitOnDispose = EmitOnConnectionDispose | EmitOnObserverDispose,
}

311
src/Linux/Tmds.DBus.Protocol/PathNodeDictionary.cs

@ -0,0 +1,311 @@
namespace Tmds.DBus.Protocol;
sealed class PathNode
{
// _childNames is null when there are no child names
// a string if there is a single child name
// a List<string> List<string>.Count child names
private object? _childNames;
public IMethodHandler? MethodHandler;
public PathNode? Parent { get; set; }
public int ChildNameCount =>
_childNames switch
{
null => 0,
string => 1,
var list => ((List<string>)list).Count
};
public void ClearChildNames()
{
Debug.Assert(ChildNameCount == 1, "Method isn't expected to be called unless there is 1 child name.");
if (_childNames is List<string> list)
{
list.Clear();
}
else
{
_childNames = null;
}
}
public void RemoveChildName(string name)
{
Debug.Assert(ChildNameCount > 1, "Caller is expected to call ClearChildNames instead.");
var list = (List<string>)_childNames!;
list.Remove(name);
}
public void AddChildName(string value)
{
if (_childNames is null)
{
_childNames = value;
}
else if (_childNames is string first)
{
_childNames = new List<string>() { first, value };
}
else
{
((List<string>)_childNames).Add(value);
}
}
public void CopyChildNamesTo(MethodContext methodContext)
{
Debug.Assert(methodContext.IntrospectChildNameList is null || methodContext.IntrospectChildNameList.Count == 0);
if (_childNames is null)
{
return;
}
methodContext.IntrospectChildNameList ??= new();
if (_childNames is string s)
{
methodContext.IntrospectChildNameList.Add(s);
}
else
{
methodContext.IntrospectChildNameList.AddRange((List<string>)_childNames);
}
}
}
sealed class PathNodeDictionary : IMethodHandlerDictionary
{
private readonly Dictionary<string, PathNode> _dictionary = new();
public bool TryGetValue(string path, [NotNullWhen(true)]out PathNode? pathNode)
=> _dictionary.TryGetValue(path, out pathNode);
// For tests:
public PathNode this[string path]
=> _dictionary[path];
public int Count
=> _dictionary.Count;
public void AddMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers)
{
if (methodHandlers is null)
{
throw new ArgumentNullException(nameof(methodHandlers));
}
int registeredCount = 0;
try
{
for (int i = 0; i < methodHandlers.Count; i++)
{
IMethodHandler methodHandler = methodHandlers[i] ?? throw new ArgumentNullException("methodHandler");
AddMethodHandler(methodHandler);
registeredCount++;
}
}
catch
{
RemoveMethodHandlers(methodHandlers, registeredCount);
throw;
}
}
private PathNode GetOrCreateNode(string path)
{
#if NET6_0_OR_GREATER
ref PathNode? node = ref CollectionsMarshal.GetValueRefOrAddDefault(_dictionary, path, out bool exists);
if (exists)
{
return node!;
}
PathNode newNode = new PathNode();
node = newNode;
#else
if (_dictionary.TryGetValue(path, out PathNode? node))
{
return node;
}
PathNode newNode = new PathNode();
_dictionary.Add(path, newNode);
#endif
string? parentPath = GetParentPath(path);
if (parentPath is not null)
{
PathNode parent = GetOrCreateNode(parentPath);
newNode.Parent = parent;
parent.AddChildName(GetChildName(path));
}
return newNode;
}
private static string? GetParentPath(string path)
{
if (path.Length == 1)
{
return null;
}
int index = path.LastIndexOf('/');
Debug.Assert(index != -1);
// When index == 0, return '/'.
index = Math.Max(index, 1);
return path.Substring(0, index);
}
private static string GetChildName(string path)
{
int index = path.LastIndexOf('/');
return path.Substring(index + 1);
}
private void RemoveMethodHandlers(IReadOnlyList<IMethodHandler> methodHandlers, int count)
{
// We start by (optimistically) removing all nodes (assuming they form a tree that is pruned).
// If there are nodes that are still needed to serve as parent nodes, we'll add them back at the end.
(string Path, PathNode Node)[] nodes = new (string, PathNode)[count];
int j = 0;
for (int i = 0; i < count; i++)
{
string path = methodHandlers[i].Path;
if (_dictionary.Remove(path, out PathNode? node))
{
nodes[j++] = (path, node);
node.MethodHandler = null;
}
}
count = j; j = 0;
// Reverse sort by path length to remove leaves before parents.
Array.Sort(nodes, 0, count, RemoveKeyComparerInstance);
for (int i = 0; i < count; i++)
{
var node = nodes[i];
if (node.Node.ChildNameCount == 0)
{
RemoveFromParent(node.Path, node.Node);
}
else
{
nodes[j++] = node;
}
}
count = j; j = 0;
// Add back the nodes that serve as parent nodes.
for (int i = 0; i < count; i++)
{
var node = nodes[i];
_dictionary[node.Path] = node.Node;
}
}
private void RemoveFromParent(string path, PathNode node)
{
PathNode? parent = node.Parent;
if (parent is null)
{
return;
}
Debug.Assert(parent.ChildNameCount >= 1, "node is expected to be a known child");
if (parent.ChildNameCount == 1) // We're the only child.
{
if (parent.MethodHandler is not null)
{
// Parent is still needed for the MethodHandler.
parent.ClearChildNames();
}
else
{
// Suppress netstandard2.0 nullability warnings around NetstandardExtensions.Remove.
#if NETSTANDARD2_0
#pragma warning disable CS8620
#pragma warning disable CS8604
#endif
// Parent is no longer needed.
string parentPath = GetParentPath(path)!;
Debug.Assert(parentPath is not null);
_dictionary.Remove(parentPath, out PathNode? parentNode);
Debug.Assert(parentNode is not null);
RemoveFromParent(parentPath, parentNode);
#if NETSTANDARD2_0
#pragma warning restore CS8620
#pragma warning restore CS8604
#endif
}
}
else
{
string childName = GetChildName(path);
parent.RemoveChildName(childName);
}
}
public void AddMethodHandler(IMethodHandler methodHandler)
{
string path = methodHandler.Path ?? throw new ArgumentNullException(nameof(methodHandler.Path));
// Validate the path starts with '/' and has no empty sections.
// GetParentPath relies on this.
if (path[0] != '/' || path.IndexOf("//", StringComparison.Ordinal) != -1)
{
throw new FormatException($"The path '{path}' is not valid.");
}
PathNode node = GetOrCreateNode(path);
if (node.MethodHandler is not null)
{
throw new InvalidOperationException($"A method handler is already registered for the path '{path}'.");
}
node.MethodHandler = methodHandler;
}
public void RemoveMethodHandler(string path)
{
if (path is null)
{
throw new ArgumentNullException(nameof(path));
}
if (_dictionary.Remove(path, out PathNode? node))
{
if (node.ChildNameCount > 0)
{
// Node is still needed for its children.
node.MethodHandler = null;
_dictionary.Add(path, node);
}
else
{
RemoveFromParent(path, node);
}
}
}
public void RemoveMethodHandlers(IEnumerable<string> paths)
{
if (paths is null)
{
throw new ArgumentNullException(nameof(paths));
}
foreach (var path in paths)
{
RemoveMethodHandler(path);
}
}
private static readonly RemoveKeyComparer RemoveKeyComparerInstance = new();
sealed class RemoveKeyComparer : IComparer<(string Path, PathNode Node)>
{
public int Compare((string Path, PathNode Node) x, (string Path, PathNode Node) y)
=> x.Path.Length - y.Path.Length;
}
}

19
src/Linux/Tmds.DBus.Protocol/PlatformDetection.cs

@ -0,0 +1,19 @@
namespace Tmds.DBus.Protocol;
#if NET6_0_OR_GREATER
using System.Runtime.Versioning;
#endif
static class PlatformDetection
{
#if NET6_0_OR_GREATER
[SupportedOSPlatformGuard("windows")]
#endif
public static bool IsWindows() =>
#if NET6_0_OR_GREATER
// IsWindows is marked with the NonVersionable attribute.
// This allows R2R to inline it and eliminate platform-specific branches.
OperatingSystem.IsWindows();
#else
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
#endif
}

25
src/Linux/Tmds.DBus.Protocol/Polyfill/DynamicallyAccessedMemberTypes.cs

@ -0,0 +1,25 @@
#if !NET5_0_OR_GREATER
namespace System.Diagnostics.CodeAnalysis;
[Flags]
enum DynamicallyAccessedMemberTypes
{
None = 0,
PublicParameterlessConstructor = 0x0001,
PublicConstructors = 0x0002 | PublicParameterlessConstructor,
NonPublicConstructors = 0x0004,
PublicMethods = 0x0008,
NonPublicMethods = 0x0010,
PublicFields = 0x0020,
NonPublicFields = 0x0040,
PublicNestedTypes = 0x0080,
NonPublicNestedTypes = 0x0100,
PublicProperties = 0x0200,
NonPublicProperties = 0x0400,
PublicEvents = 0x0800,
NonPublicEvents = 0x1000,
Interfaces = 0x2000,
All = ~None
}
#endif

30
src/Linux/Tmds.DBus.Protocol/Polyfill/DynamicallyAccessedMembersAttribute.cs

@ -0,0 +1,30 @@
#if !NET5_0_OR_GREATER
namespace System.Diagnostics.CodeAnalysis;
using Targets = AttributeTargets;
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
[AttributeUsage(
validOn: Targets.Class |
Targets.Field |
Targets.GenericParameter |
Targets.Interface |
Targets.Method |
Targets.Parameter |
Targets.Property |
Targets.ReturnValue |
Targets.Struct,
Inherited = false)]
sealed class DynamicallyAccessedMembersAttribute :
Attribute
{
public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) =>
MemberTypes = memberTypes;
public DynamicallyAccessedMemberTypes MemberTypes { get; }
}
#endif

530
src/Linux/Tmds.DBus.Protocol/Polyfill/Nerdbank.Streams.Sequence.cs

@ -0,0 +1,530 @@
// Copyright (c) Andrew Arnott. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Nerdbank.Streams
{
using System;
using System.Buffers;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
/// <summary>
/// Manages a sequence of elements, readily castable as a <see cref="ReadOnlySequence{T}"/>.
/// </summary>
/// <typeparam name="T">The type of element stored by the sequence.</typeparam>
/// <remarks>
/// Instance members are not thread-safe.
/// </remarks>
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
internal class Sequence<T> : IBufferWriter<T>, IDisposable
{
private const int MaximumAutoGrowSize = 32 * 1024;
private static readonly int DefaultLengthFromArrayPool = 1 + (4095 / Unsafe.SizeOf<T>());
private static readonly ReadOnlySequence<T> Empty = new ReadOnlySequence<T>(SequenceSegment.Empty, 0, SequenceSegment.Empty, 0);
private readonly Stack<SequenceSegment> segmentPool = new Stack<SequenceSegment>();
private readonly MemoryPool<T>? memoryPool;
private readonly ArrayPool<T>? arrayPool;
private SequenceSegment? first;
private SequenceSegment? last;
/// <summary>
/// Initializes a new instance of the <see cref="Sequence{T}"/> class
/// that uses a private <see cref="ArrayPool{T}"/> for recycling arrays.
/// </summary>
public Sequence()
: this(ArrayPool<T>.Create())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Sequence{T}"/> class.
/// </summary>
/// <param name="memoryPool">The pool to use for recycling backing arrays.</param>
public Sequence(MemoryPool<T> memoryPool)
{
this.memoryPool = memoryPool ?? throw new ArgumentNullException(nameof(memoryPool));
}
/// <summary>
/// Initializes a new instance of the <see cref="Sequence{T}"/> class.
/// </summary>
/// <param name="arrayPool">The pool to use for recycling backing arrays.</param>
public Sequence(ArrayPool<T> arrayPool)
{
this.arrayPool = arrayPool ?? throw new ArgumentNullException(nameof(arrayPool));
}
/// <summary>
/// Gets or sets the minimum length for any array allocated as a segment in the sequence.
/// Any non-positive value allows the pool to determine the length of the array.
/// </summary>
/// <value>The default value is 0.</value>
/// <remarks>
/// <para>
/// Each time <see cref="GetSpan(int)"/> or <see cref="GetMemory(int)"/> is called,
/// previously allocated memory is used if it is large enough to satisfy the length demand.
/// If new memory must be allocated, the argument to one of these methods typically dictate
/// the length of array to allocate. When the caller uses very small values (just enough for its immediate need)
/// but the high level scenario can predict that a large amount of memory will be ultimately required,
/// it can be advisable to set this property to a value such that just a few larger arrays are allocated
/// instead of many small ones.
/// </para>
/// <para>
/// The <see cref="MemoryPool{T}"/> in use may itself have a minimum array length as well,
/// in which case the higher of the two minimums dictate the minimum array size that will be allocated.
/// </para>
/// <para>
/// If <see cref="AutoIncreaseMinimumSpanLength"/> is <see langword="true"/>, this value may be automatically increased as the length of a sequence grows.
/// </para>
/// </remarks>
public int MinimumSpanLength { get; set; } = 0;
/// <summary>
/// Gets or sets a value indicating whether the <see cref="MinimumSpanLength"/> should be
/// intelligently increased as the length of the sequence grows.
/// </summary>
/// <remarks>
/// This can help prevent long sequences made up of many very small arrays.
/// </remarks>
public bool AutoIncreaseMinimumSpanLength { get; set; } = true;
/// <summary>
/// Gets this sequence expressed as a <see cref="ReadOnlySequence{T}"/>.
/// </summary>
/// <returns>A read only sequence representing the data in this object.</returns>
public ReadOnlySequence<T> AsReadOnlySequence => this;
/// <summary>
/// Gets the length of the sequence.
/// </summary>
public long Length => this.AsReadOnlySequence.Length;
/// <summary>
/// Gets the value to display in a debugger datatip.
/// </summary>
private string DebuggerDisplay => $"Length: {this.AsReadOnlySequence.Length}";
/// <summary>
/// Expresses this sequence as a <see cref="ReadOnlySequence{T}"/>.
/// </summary>
/// <param name="sequence">The sequence to convert.</param>
public static implicit operator ReadOnlySequence<T>(Sequence<T> sequence)
{
return sequence.first is { } first && sequence.last is { } last
? new ReadOnlySequence<T>(first, first.Start, last, last!.End)
: Empty;
}
/// <summary>
/// Removes all elements from the sequence from its beginning to the specified position,
/// considering that data to have been fully processed.
/// </summary>
/// <param name="position">
/// The position of the first element that has not yet been processed.
/// This is typically <see cref="ReadOnlySequence{T}.End"/> after reading all elements from that instance.
/// </param>
public void AdvanceTo(SequencePosition position)
{
var firstSegment = (SequenceSegment?)position.GetObject();
if (firstSegment == null)
{
// Emulate PipeReader behavior which is to just return for default(SequencePosition)
return;
}
if (ReferenceEquals(firstSegment, SequenceSegment.Empty) && this.Length == 0)
{
// We were called with our own empty buffer segment.
return;
}
int firstIndex = position.GetInteger();
// Before making any mutations, confirm that the block specified belongs to this sequence.
Sequence<T>.SequenceSegment? current = this.first;
while (current != firstSegment && current != null)
{
current = current.Next;
}
if (current == null)
throw new ArgumentException("Position does not represent a valid position in this sequence.",
nameof(position));
// Also confirm that the position is not a prior position in the block.
if (firstIndex < current.Start)
throw new ArgumentException("Position must not be earlier than current position.", nameof(position));
// Now repeat the loop, performing the mutations.
current = this.first;
while (current != firstSegment)
{
current = this.RecycleAndGetNext(current!);
}
firstSegment.AdvanceTo(firstIndex);
this.first = firstSegment.Length == 0 ? this.RecycleAndGetNext(firstSegment) : firstSegment;
if (this.first == null)
{
this.last = null;
}
}
/// <summary>
/// Advances the sequence to include the specified number of elements initialized into memory
/// returned by a prior call to <see cref="GetMemory(int)"/>.
/// </summary>
/// <param name="count">The number of elements written into memory.</param>
public void Advance(int count)
{
SequenceSegment? last = this.last;
if(last==null)
throw new InvalidOperationException("Cannot advance before acquiring memory.");
last.Advance(count);
this.ConsiderMinimumSizeIncrease();
}
/// <summary>
/// Gets writable memory that can be initialized and added to the sequence via a subsequent call to <see cref="Advance(int)"/>.
/// </summary>
/// <param name="sizeHint">The size of the memory required, or 0 to just get a convenient (non-empty) buffer.</param>
/// <returns>The requested memory.</returns>
public Memory<T> GetMemory(int sizeHint) => this.GetSegment(sizeHint).RemainingMemory;
/// <summary>
/// Gets writable memory that can be initialized and added to the sequence via a subsequent call to <see cref="Advance(int)"/>.
/// </summary>
/// <param name="sizeHint">The size of the memory required, or 0 to just get a convenient (non-empty) buffer.</param>
/// <returns>The requested memory.</returns>
public Span<T> GetSpan(int sizeHint) => this.GetSegment(sizeHint).RemainingSpan;
/// <summary>
/// Adds an existing memory location to this sequence without copying.
/// </summary>
/// <param name="memory">The memory to add.</param>
/// <remarks>
/// This *may* leave significant slack space in a previously allocated block if calls to <see cref="Append(ReadOnlyMemory{T})"/>
/// follow calls to <see cref="GetMemory(int)"/> or <see cref="GetSpan(int)"/>.
/// </remarks>
public void Append(ReadOnlyMemory<T> memory)
{
if (memory.Length > 0)
{
Sequence<T>.SequenceSegment? segment = this.segmentPool.Count > 0 ? this.segmentPool.Pop() : new SequenceSegment();
segment.AssignForeign(memory);
this.Append(segment);
}
}
/// <summary>
/// Clears the entire sequence, recycles associated memory into pools,
/// and resets this instance for reuse.
/// This invalidates any <see cref="ReadOnlySequence{T}"/> previously produced by this instance.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public void Dispose() => this.Reset();
/// <summary>
/// Clears the entire sequence and recycles associated memory into pools.
/// This invalidates any <see cref="ReadOnlySequence{T}"/> previously produced by this instance.
/// </summary>
public void Reset()
{
Sequence<T>.SequenceSegment? current = this.first;
while (current != null)
{
current = this.RecycleAndGetNext(current);
}
this.first = this.last = null;
}
private SequenceSegment GetSegment(int sizeHint)
{
if (sizeHint < 0)
throw new ArgumentOutOfRangeException(nameof(sizeHint));
int? minBufferSize = null;
if (sizeHint == 0)
{
if (this.last == null || this.last.WritableBytes == 0)
{
// We're going to need more memory. Take whatever size the pool wants to give us.
minBufferSize = -1;
}
}
else
{
if (this.last == null || this.last.WritableBytes < sizeHint)
{
minBufferSize = Math.Max(this.MinimumSpanLength, sizeHint);
}
}
if (minBufferSize.HasValue)
{
Sequence<T>.SequenceSegment? segment = this.segmentPool.Count > 0 ? this.segmentPool.Pop() : new SequenceSegment();
if (this.arrayPool != null)
{
segment.Assign(this.arrayPool.Rent(minBufferSize.Value == -1 ? DefaultLengthFromArrayPool : minBufferSize.Value));
}
else
{
segment.Assign(this.memoryPool!.Rent(minBufferSize.Value));
}
this.Append(segment);
}
return this.last!;
}
private void Append(SequenceSegment segment)
{
if (this.last == null)
{
this.first = this.last = segment;
}
else
{
if (this.last.Length > 0)
{
// Add a new block.
this.last.SetNext(segment);
}
else
{
// The last block is completely unused. Replace it instead of appending to it.
Sequence<T>.SequenceSegment? current = this.first;
if (this.first != this.last)
{
while (current!.Next != this.last)
{
current = current.Next;
}
}
else
{
this.first = segment;
}
current!.SetNext(segment);
this.RecycleAndGetNext(this.last);
}
this.last = segment;
}
}
private SequenceSegment? RecycleAndGetNext(SequenceSegment segment)
{
Sequence<T>.SequenceSegment? recycledSegment = segment;
Sequence<T>.SequenceSegment? nextSegment = segment.Next;
recycledSegment.ResetMemory(this.arrayPool);
this.segmentPool.Push(recycledSegment);
return nextSegment;
}
private void ConsiderMinimumSizeIncrease()
{
if (this.AutoIncreaseMinimumSpanLength && this.MinimumSpanLength < MaximumAutoGrowSize)
{
int autoSize = Math.Min(MaximumAutoGrowSize, (int)Math.Min(int.MaxValue, this.Length / 2));
if (this.MinimumSpanLength < autoSize)
{
this.MinimumSpanLength = autoSize;
}
}
}
private class SequenceSegment : ReadOnlySequenceSegment<T>
{
internal static readonly SequenceSegment Empty = new SequenceSegment();
/// <summary>
/// A value indicating whether the element may contain references (and thus must be cleared).
/// </summary>
private static readonly bool MayContainReferences = !typeof(T).GetTypeInfo().IsPrimitive;
#pragma warning disable SA1011 // Closing square brackets should be spaced correctly
/// <summary>
/// Gets the backing array, when using an <see cref="ArrayPool{T}"/> instead of a <see cref="MemoryPool{T}"/>.
/// </summary>
private T[]? array;
#pragma warning restore SA1011 // Closing square brackets should be spaced correctly
/// <summary>
/// Gets the position within <see cref="ReadOnlySequenceSegment{T}.Memory"/> where the data starts.
/// </summary>
/// <remarks>This may be nonzero as a result of calling <see cref="Sequence{T}.AdvanceTo(SequencePosition)"/>.</remarks>
internal int Start { get; private set; }
/// <summary>
/// Gets the position within <see cref="ReadOnlySequenceSegment{T}.Memory"/> where the data ends.
/// </summary>
internal int End { get; private set; }
/// <summary>
/// Gets the tail of memory that has not yet been committed.
/// </summary>
internal Memory<T> RemainingMemory => this.AvailableMemory.Slice(this.End);
/// <summary>
/// Gets the tail of memory that has not yet been committed.
/// </summary>
internal Span<T> RemainingSpan => this.AvailableMemory.Span.Slice(this.End);
/// <summary>
/// Gets the tracker for the underlying array for this segment, which can be used to recycle the array when we're disposed of.
/// Will be <see langword="null"/> if using an array pool, in which case the memory is held by <see cref="array"/>.
/// </summary>
internal IMemoryOwner<T>? MemoryOwner { get; private set; }
/// <summary>
/// Gets the full memory owned by the <see cref="MemoryOwner"/>.
/// </summary>
internal Memory<T> AvailableMemory => this.array ?? this.MemoryOwner?.Memory ?? default;
/// <summary>
/// Gets the number of elements that are committed in this segment.
/// </summary>
internal int Length => this.End - this.Start;
/// <summary>
/// Gets the amount of writable bytes in this segment.
/// It is the amount of bytes between <see cref="Length"/> and <see cref="End"/>.
/// </summary>
internal int WritableBytes => this.AvailableMemory.Length - this.End;
/// <summary>
/// Gets or sets the next segment in the singly linked list of segments.
/// </summary>
internal new SequenceSegment? Next
{
get => (SequenceSegment?)base.Next;
set => base.Next = value;
}
/// <summary>
/// Gets a value indicating whether this segment refers to memory that came from outside and that we cannot write to nor recycle.
/// </summary>
internal bool IsForeignMemory => this.array == null && this.MemoryOwner == null;
/// <summary>
/// Assigns this (recyclable) segment a new area in memory.
/// </summary>
/// <param name="memoryOwner">The memory and a means to recycle it.</param>
internal void Assign(IMemoryOwner<T> memoryOwner)
{
this.MemoryOwner = memoryOwner;
this.Memory = memoryOwner.Memory;
}
/// <summary>
/// Assigns this (recyclable) segment a new area in memory.
/// </summary>
/// <param name="array">An array drawn from an <see cref="ArrayPool{T}"/>.</param>
internal void Assign(T[] array)
{
this.array = array;
this.Memory = array;
}
/// <summary>
/// Assigns this (recyclable) segment a new area in memory.
/// </summary>
/// <param name="memory">A memory block obtained from outside, that we do not own and should not recycle.</param>
internal void AssignForeign(ReadOnlyMemory<T> memory)
{
this.Memory = memory;
this.End = memory.Length;
}
/// <summary>
/// Clears all fields in preparation to recycle this instance.
/// </summary>
internal void ResetMemory(ArrayPool<T>? arrayPool)
{
this.ClearReferences(this.Start, this.End - this.Start);
this.Memory = default;
this.Next = null;
this.RunningIndex = 0;
this.Start = 0;
this.End = 0;
if (this.array != null)
{
arrayPool!.Return(this.array);
this.array = null;
}
else
{
this.MemoryOwner?.Dispose();
this.MemoryOwner = null;
}
}
/// <summary>
/// Adds a new segment after this one.
/// </summary>
/// <param name="segment">The next segment in the linked list.</param>
internal void SetNext(SequenceSegment segment)
{
this.Next = segment;
segment.RunningIndex = this.RunningIndex + this.Start + this.Length;
// Trim any slack on this segment.
if (!this.IsForeignMemory)
{
// When setting Memory, we start with index 0 instead of this.Start because
// the first segment has an explicit index set anyway,
// and we don't want to double-count it here.
this.Memory = this.AvailableMemory.Slice(0, this.Start + this.Length);
}
}
/// <summary>
/// Commits more elements as written in this segment.
/// </summary>
/// <param name="count">The number of elements written.</param>
internal void Advance(int count)
{
if (!(count >= 0 && this.End + count <= this.Memory.Length))
throw new ArgumentOutOfRangeException(nameof(count));
this.End += count;
}
/// <summary>
/// Removes some elements from the start of this segment.
/// </summary>
/// <param name="offset">The number of elements to ignore from the start of the underlying array.</param>
internal void AdvanceTo(int offset)
{
Debug.Assert(offset >= this.Start, "Trying to rewind.");
this.ClearReferences(this.Start, offset - this.Start);
this.Start = offset;
}
private void ClearReferences(int startIndex, int length)
{
// Clear the array to allow the objects to be GC'd.
// Reference types need to be cleared. Value types can be structs with reference type members too, so clear everything.
if (MayContainReferences)
{
this.AvailableMemory.Span.Slice(startIndex, length).Clear();
}
}
}
}
}

144
src/Linux/Tmds.DBus.Protocol/Polyfill/NullableAttributes.cs

@ -0,0 +1,144 @@
namespace System.Diagnostics.CodeAnalysis
{
#if NETSTANDARD2_0
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
internal sealed class AllowNullAttribute : Attribute { }
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
internal sealed class DisallowNullAttribute : Attribute { }
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
internal sealed class MaybeNullAttribute : Attribute { }
/// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
internal sealed class NotNullAttribute : Attribute { }
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class MaybeNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter may be null.
/// </param>
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
internal sealed class NotNullIfNotNullAttribute : Attribute
{
/// <summary>Initializes the attribute with the associated parameter name.</summary>
/// <param name="parameterName">
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
/// </param>
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
/// <summary>Gets the associated parameter name.</summary>
public string ParameterName { get; }
}
/// <summary>Applied to a method that will never return under any circumstance.</summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
internal sealed class DoesNotReturnAttribute : Attribute { }
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
internal sealed class DoesNotReturnIfAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified parameter value.</summary>
/// <param name="parameterValue">
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
/// the associated parameter matches this value.
/// </param>
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
/// <summary>Gets the condition parameter value.</summary>
public bool ParameterValue { get; }
}
#endif
#if !NETCOREAPP || NETCOREAPP3_1
/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values.</summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
internal sealed class MemberNotNullAttribute : Attribute
{
/// <summary>Initializes the attribute with a field or property member.</summary>
/// <param name="member">
/// The field or property member that is promised to be not-null.
/// </param>
public MemberNotNullAttribute(string member) => Members = new[] { member };
/// <summary>Initializes the attribute with the list of field and property members.</summary>
/// <param name="members">
/// The list of field and property members that are promised to be not-null.
/// </param>
public MemberNotNullAttribute(params string[] members) => Members = members;
/// <summary>Gets field or property member names.</summary>
public string[] Members { get; }
}
/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.</summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
internal sealed class MemberNotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
/// <param name="member">
/// The field or property member that is promised to be not-null.
/// </param>
public MemberNotNullWhenAttribute(bool returnValue, string member)
{
ReturnValue = returnValue;
Members = new[] { member };
}
/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
/// <param name="members">
/// The list of field and property members that are promised to be not-null.
/// </param>
public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
{
ReturnValue = returnValue;
Members = members;
}
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
/// <summary>Gets field or property member names.</summary>
public string[] Members { get; }
}
#endif
}

23
src/Linux/Tmds.DBus.Protocol/Polyfill/RequiresUnreferencedCodeAttribute.cs

@ -0,0 +1,23 @@
#if !NET5_0_OR_GREATER
namespace System.Diagnostics.CodeAnalysis;
[System.AttributeUsage(
System.AttributeTargets.Method |
System.AttributeTargets.Constructor |
System.AttributeTargets.Class, Inherited = false)]
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[System.Diagnostics.Conditional("MULTI_TARGETING_SUPPORT_ATTRIBUTES")]
internal sealed class RequiresUnreferencedCodeAttribute : System.Attribute
{
public RequiresUnreferencedCodeAttribute(string message)
{
Message = message;
}
public string Message { get; }
public string? Url { get; set; }
}
#endif

417
src/Linux/Tmds.DBus.Protocol/Polyfill/SequenceReader.cs

@ -0,0 +1,417 @@
// <auto-generated /> // Copied from https://github.com/dotnet/runtime/raw/cf5b231fcbea483df3b081939b422adfb6fd486a/src/libraries/System.Memory/src/System/Buffers/SequenceReader.cs
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
#if NETSTANDARD2_0
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace System.Buffers
{
/// <summary>
/// Provides methods for reading binary and text data out of a <see cref="ReadOnlySequence{T}"/> with a focus on performance and minimal or zero heap allocations.
/// </summary>
/// <typeparam name="T">The type of element stored by the <see cref="ReadOnlySequence{T}"/>.</typeparam>
internal ref partial struct SequenceReader<T> where T : unmanaged, IEquatable<T>
{
private SequencePosition _currentPosition;
private SequencePosition _nextPosition;
private bool _moreData;
private readonly long _length;
/// <summary>
/// Create a <see cref="SequenceReader{T}"/> over the given <see cref="ReadOnlySequence{T}"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SequenceReader(ReadOnlySequence<T> sequence)
{
CurrentSpanIndex = 0;
Consumed = 0;
Sequence = sequence;
_currentPosition = sequence.Start;
_length = -1;
var first = sequence.First.Span;
_nextPosition = sequence.GetPosition(first.Length);
CurrentSpan = first;
_moreData = first.Length > 0;
if (!_moreData && !sequence.IsSingleSegment)
{
_moreData = true;
GetNextSpan();
}
}
/// <summary>
/// True when there is no more data in the <see cref="Sequence"/>.
/// </summary>
public readonly bool End => !_moreData;
/// <summary>
/// The underlying <see cref="ReadOnlySequence{T}"/> for the reader.
/// </summary>
public readonly ReadOnlySequence<T> Sequence { get; }
/// <summary>
/// The current position in the <see cref="Sequence"/>.
/// </summary>
public readonly SequencePosition Position
=> Sequence.GetPosition(CurrentSpanIndex, _currentPosition);
/// <summary>
/// The current segment in the <see cref="Sequence"/> as a span.
/// </summary>
public ReadOnlySpan<T> CurrentSpan { readonly get; private set; }
/// <summary>
/// The index in the <see cref="CurrentSpan"/>.
/// </summary>
public int CurrentSpanIndex { readonly get; private set; }
/// <summary>
/// The unread portion of the <see cref="CurrentSpan"/>.
/// </summary>
public readonly ReadOnlySpan<T> UnreadSpan
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => CurrentSpan.Slice(CurrentSpanIndex);
}
/// <summary>
/// The total number of <typeparamref name="T"/>'s processed by the reader.
/// </summary>
public long Consumed { readonly get; private set; }
/// <summary>
/// Remaining <typeparamref name="T"/>'s in the reader's <see cref="Sequence"/>.
/// </summary>
public readonly long Remaining => Length - Consumed;
/// <summary>
/// Count of <typeparamref name="T"/> in the reader's <see cref="Sequence"/>.
/// </summary>
public readonly long Length
{
get
{
if (_length < 0)
{
// Cast-away readonly to initialize lazy field
Unsafe.AsRef(_length) = Sequence.Length;
}
return _length;
}
}
/// <summary>
/// Peeks at the next value without advancing the reader.
/// </summary>
/// <param name="value">The next value or default if at the end.</param>
/// <returns>False if at the end of the reader.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryPeek(out T value)
{
if (_moreData)
{
value = CurrentSpan[CurrentSpanIndex];
return true;
}
else
{
value = default;
return false;
}
}
/// <summary>
/// Read the next value and advance the reader.
/// </summary>
/// <param name="value">The next value or default if at the end.</param>
/// <returns>False if at the end of the reader.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryRead(out T value)
{
if (End)
{
value = default;
return false;
}
value = CurrentSpan[CurrentSpanIndex];
CurrentSpanIndex++;
Consumed++;
if (CurrentSpanIndex >= CurrentSpan.Length)
{
GetNextSpan();
}
return true;
}
/// <summary>
/// Move the reader back the specified number of items.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if trying to rewind a negative amount or more than <see cref="Consumed"/>.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Rewind(long count)
{
if ((ulong)count > (ulong)Consumed)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count));
}
Consumed -= count;
if (CurrentSpanIndex >= count)
{
CurrentSpanIndex -= (int)count;
_moreData = true;
}
else
{
// Current segment doesn't have enough data, scan backward through segments
RetreatToPreviousSpan(Consumed);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void RetreatToPreviousSpan(long consumed)
{
ResetReader();
Advance(consumed);
}
private void ResetReader()
{
CurrentSpanIndex = 0;
Consumed = 0;
_currentPosition = Sequence.Start;
_nextPosition = _currentPosition;
if (Sequence.TryGet(ref _nextPosition, out ReadOnlyMemory<T> memory, advance: true))
{
_moreData = true;
if (memory.Length == 0)
{
CurrentSpan = default;
// No data in the first span, move to one with data
GetNextSpan();
}
else
{
CurrentSpan = memory.Span;
}
}
else
{
// No data in any spans and at end of sequence
_moreData = false;
CurrentSpan = default;
}
}
/// <summary>
/// Get the next segment with available data, if any.
/// </summary>
private void GetNextSpan()
{
if (!Sequence.IsSingleSegment)
{
SequencePosition previousNextPosition = _nextPosition;
while (Sequence.TryGet(ref _nextPosition, out ReadOnlyMemory<T> memory, advance: true))
{
_currentPosition = previousNextPosition;
if (memory.Length > 0)
{
CurrentSpan = memory.Span;
CurrentSpanIndex = 0;
return;
}
else
{
CurrentSpan = default;
CurrentSpanIndex = 0;
previousNextPosition = _nextPosition;
}
}
}
_moreData = false;
}
/// <summary>
/// Move the reader ahead the specified number of items.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(long count)
{
const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000);
if ((count & TooBigOrNegative) == 0 && CurrentSpan.Length - CurrentSpanIndex > (int)count)
{
CurrentSpanIndex += (int)count;
Consumed += count;
}
else
{
// Can't satisfy from the current span
AdvanceToNextSpan(count);
}
}
/// <summary>
/// Unchecked helper to avoid unnecessary checks where you know count is valid.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void AdvanceCurrentSpan(long count)
{
Debug.Assert(count >= 0);
Consumed += count;
CurrentSpanIndex += (int)count;
if (CurrentSpanIndex >= CurrentSpan.Length)
GetNextSpan();
}
/// <summary>
/// Only call this helper if you know that you are advancing in the current span
/// with valid count and there is no need to fetch the next one.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void AdvanceWithinSpan(long count)
{
Debug.Assert(count >= 0);
Consumed += count;
CurrentSpanIndex += (int)count;
Debug.Assert(CurrentSpanIndex < CurrentSpan.Length);
}
private void AdvanceToNextSpan(long count)
{
if (count < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count));
}
Consumed += count;
while (_moreData)
{
int remaining = CurrentSpan.Length - CurrentSpanIndex;
if (remaining > count)
{
CurrentSpanIndex += (int)count;
count = 0;
break;
}
// As there may not be any further segments we need to
// push the current index to the end of the span.
CurrentSpanIndex += remaining;
count -= remaining;
Debug.Assert(count >= 0);
GetNextSpan();
if (count == 0)
{
break;
}
}
if (count != 0)
{
// Not enough data left- adjust for where we actually ended and throw
Consumed -= count;
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count));
}
}
/// <summary>
/// Copies data from the current <see cref="Position"/> to the given <paramref name="destination"/> span if there
/// is enough data to fill it.
/// </summary>
/// <remarks>
/// This API is used to copy a fixed amount of data out of the sequence if possible. It does not advance
/// the reader. To look ahead for a specific stream of data <see cref="IsNext(ReadOnlySpan{T}, bool)"/> can be used.
/// </remarks>
/// <param name="destination">Destination span to copy to.</param>
/// <returns>True if there is enough data to completely fill the <paramref name="destination"/> span.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryCopyTo(Span<T> destination)
{
// This API doesn't advance to facilitate conditional advancement based on the data returned.
// We don't provide an advance option to allow easier utilizing of stack allocated destination spans.
// (Because we can make this method readonly we can guarantee that we won't capture the span.)
ReadOnlySpan<T> firstSpan = UnreadSpan;
if (firstSpan.Length >= destination.Length)
{
firstSpan.Slice(0, destination.Length).CopyTo(destination);
return true;
}
// Not enough in the current span to satisfy the request, fall through to the slow path
return TryCopyMultisegment(destination);
}
internal readonly bool TryCopyMultisegment(Span<T> destination)
{
// If we don't have enough to fill the requested buffer, return false
if (Remaining < destination.Length)
return false;
ReadOnlySpan<T> firstSpan = UnreadSpan;
Debug.Assert(firstSpan.Length < destination.Length);
firstSpan.CopyTo(destination);
int copied = firstSpan.Length;
SequencePosition next = _nextPosition;
while (Sequence.TryGet(ref next, out ReadOnlyMemory<T> nextSegment, true))
{
if (nextSegment.Length > 0)
{
ReadOnlySpan<T> nextSpan = nextSegment.Span;
int toCopy = Math.Min(nextSpan.Length, destination.Length - copied);
nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied));
copied += toCopy;
if (copied >= destination.Length)
{
break;
}
}
}
return true;
}
static class ThrowHelper
{
public static void ThrowArgumentOutOfRangeException(string name) => throw new ArgumentOutOfRangeException(name);
}
}
}
#else
using System.Buffers;
using System.Runtime.CompilerServices;
#pragma warning disable RS0026
#pragma warning disable RS0016
#pragma warning disable RS0041
[assembly: TypeForwardedTo(typeof(SequenceReader<>))]
#pragma warning restore RS0041
#pragma warning restore RS0016
#pragma warning restore RS0026
#endif

194
src/Linux/Tmds.DBus.Protocol/Polyfill/SequenceReaderExtensions.cs

@ -0,0 +1,194 @@
// <auto-generated /> // Copied from https://raw.githubusercontent.com/dotnet/runtime/cf5b231fcbea483df3b081939b422adfb6fd486a/src/libraries/System.Memory/src/System/Buffers/SequenceReaderExtensions.Binary.cs
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
#if NETSTANDARD2_0
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Buffers
{
/// <summary>
/// Provides extended functionality for the <see cref="SequenceReader{T}"/> class that allows reading of endian specific numeric values from binary data.
/// </summary>
internal static partial class SequenceReaderExtensions
{
/// <summary>
/// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
/// structs- see remarks for full details.
/// </summary>
/// <remarks>
/// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
/// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
/// overloads such as <see cref="TryReadLittleEndian(ref SequenceReader{byte}, out short)"/>
/// </remarks>
/// <returns>
/// True if successful. <paramref name="value"/> will be default if failed (due to lack of space).
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe bool TryRead<T>(ref this SequenceReader<byte> reader, out T value) where T : unmanaged
{
ReadOnlySpan<byte> span = reader.UnreadSpan;
if (span.Length < sizeof(T))
return TryReadMultisegment(ref reader, out value);
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));
reader.Advance(sizeof(T));
return true;
}
private static unsafe bool TryReadMultisegment<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged
{
Debug.Assert(reader.UnreadSpan.Length < sizeof(T));
// Not enough data in the current segment, try to peek for the data we need.
T buffer = default;
Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T));
if (!reader.TryCopyTo(tempSpan))
{
value = default;
return false;
}
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));
reader.Advance(sizeof(T));
return true;
}
/// <summary>
/// Reads an <see cref="short"/> as little endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="short"/>.</returns>
public static bool TryReadLittleEndian(ref this SequenceReader<byte> reader, out short value)
{
if (BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
/// <summary>
/// Reads an <see cref="short"/> as big endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="short"/>.</returns>
public static bool TryReadBigEndian(ref this SequenceReader<byte> reader, out short value)
{
if (!BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
private static bool TryReadReverseEndianness(ref SequenceReader<byte> reader, out short value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
/// <summary>
/// Reads an <see cref="int"/> as little endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="int"/>.</returns>
public static bool TryReadLittleEndian(ref this SequenceReader<byte> reader, out int value)
{
if (BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
/// <summary>
/// Reads an <see cref="int"/> as big endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="int"/>.</returns>
public static bool TryReadBigEndian(ref this SequenceReader<byte> reader, out int value)
{
if (!BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
private static bool TryReadReverseEndianness(ref SequenceReader<byte> reader, out int value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
/// <summary>
/// Reads an <see cref="long"/> as little endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="long"/>.</returns>
public static bool TryReadLittleEndian(ref this SequenceReader<byte> reader, out long value)
{
if (BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
/// <summary>
/// Reads an <see cref="long"/> as big endian.
/// </summary>
/// <returns>False if there wasn't enough data for an <see cref="long"/>.</returns>
public static bool TryReadBigEndian(ref this SequenceReader<byte> reader, out long value)
{
if (!BitConverter.IsLittleEndian)
{
return reader.TryRead(out value);
}
return TryReadReverseEndianness(ref reader, out value);
}
private static bool TryReadReverseEndianness(ref SequenceReader<byte> reader, out long value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
}
}
#else
using System.Buffers;
using System.Runtime.CompilerServices;
#pragma warning disable RS0016
#pragma warning disable RS0041
[assembly: TypeForwardedTo(typeof(SequenceReaderExtensions))]
#pragma warning restore RS0041
#pragma warning restore RS0016
#endif

32
src/Linux/Tmds.DBus.Protocol/Polyfill/UnconditionalSuppressMessageAttribute.cs

@ -0,0 +1,32 @@
#if !NET5_0_OR_GREATER
namespace System.Diagnostics.CodeAnalysis;
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
[AttributeUsage(
validOn: AttributeTargets.All,
Inherited = false,
AllowMultiple = true)]
sealed class UnconditionalSuppressMessageAttribute :
Attribute
{
public UnconditionalSuppressMessageAttribute(string category, string checkId)
{
Category = category;
CheckId = checkId;
}
public string Category { get; }
public string CheckId { get; }
public string? Scope { get; set; }
public string? Target { get; set; }
public string? MessageId { get; set; }
public string? Justification { get; set; }
}
#endif

86
src/Linux/Tmds.DBus.Protocol/ProtocolConstants.cs

@ -0,0 +1,86 @@
namespace Tmds.DBus.Protocol;
static class ProtocolConstants
{
public const int MaxSignatureLength = 256;
// note: C# compiler treats these as static data.
public static ReadOnlySpan<byte> ByteSignature => new byte[] { (byte)'y' };
public static ReadOnlySpan<byte> BooleanSignature => new byte[] { (byte)'b' };
public static ReadOnlySpan<byte> Int16Signature => new byte[] { (byte)'n' };
public static ReadOnlySpan<byte> UInt16Signature => new byte[] { (byte)'q' };
public static ReadOnlySpan<byte> Int32Signature => new byte[] { (byte)'i' };
public static ReadOnlySpan<byte> UInt32Signature => new byte[] { (byte)'u' };
public static ReadOnlySpan<byte> Int64Signature => new byte[] { (byte)'x' };
public static ReadOnlySpan<byte> UInt64Signature => new byte[] { (byte)'t' };
public static ReadOnlySpan<byte> DoubleSignature => new byte[] { (byte)'d' };
public static ReadOnlySpan<byte> UnixFdSignature => new byte[] { (byte)'h' };
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' };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetTypeAlignment(DBusType type)
{
switch (type)
{
case DBusType.Byte: return 1;
case DBusType.Bool: return 4;
case DBusType.Int16: return 2;
case DBusType.UInt16: return 2;
case DBusType.Int32: return 4;
case DBusType.UInt32: return 4;
case DBusType.Int64: return 8;
case DBusType.UInt64: return 8;
case DBusType.Double: return 8;
case DBusType.String: return 4;
case DBusType.ObjectPath: return 4;
case DBusType.Signature: return 4;
case DBusType.Array: return 4;
case DBusType.Struct: return 8;
case DBusType.Variant: return 1;
case DBusType.DictEntry: return 8;
case DBusType.UnixFd: return 4;
default: return 1;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetFixedTypeLength(DBusType type)
{
switch (type)
{
case DBusType.Byte: return 1;
case DBusType.Bool: return 4;
case DBusType.Int16: return 2;
case DBusType.UInt16: return 2;
case DBusType.Int32: return 4;
case DBusType.UInt32: return 4;
case DBusType.Int64: return 8;
case DBusType.UInt64: return 8;
case DBusType.Double: return 8;
case DBusType.UnixFd: return 4;
default: return 0;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Align(int offset, DBusType type)
{
return offset + GetPadding(offset, type);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetPadding(int offset, DBusType type)
{
int alignment = GetTypeAlignment(type);
return GetPadding(offset ,alignment);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetPadding(int offset, int alignment)
{
return (~offset + 1) & (alignment - 1);
}
}

7
src/Linux/Tmds.DBus.Protocol/ProtocolException.cs

@ -0,0 +1,7 @@
namespace Tmds.DBus.Protocol;
public class ProtocolException : Exception
{
public ProtocolException(string message) : base(message)
{ }
}

166
src/Linux/Tmds.DBus.Protocol/Reader.Array.cs

@ -0,0 +1,166 @@
namespace Tmds.DBus.Protocol;
public ref partial struct Reader
{
public byte[] ReadArrayOfByte()
=> ReadArrayOfNumeric<byte>();
public bool[] ReadArrayOfBool()
=> ReadArrayOfT<bool>();
public short[] ReadArrayOfInt16()
=> ReadArrayOfNumeric<short>();
public ushort[] ReadArrayOfUInt16()
=> ReadArrayOfNumeric<ushort>();
public int[] ReadArrayOfInt32()
=> ReadArrayOfNumeric<int>();
public uint[] ReadArrayOfUInt32()
=> ReadArrayOfNumeric<uint>();
public long[] ReadArrayOfInt64()
=> ReadArrayOfNumeric<long>();
public ulong[] ReadArrayOfUInt64()
=> ReadArrayOfNumeric<ulong>();
public double[] ReadArrayOfDouble()
=> ReadArrayOfNumeric<double>();
public string[] ReadArrayOfString()
=> ReadArrayOfT<string>();
public ObjectPath[] ReadArrayOfObjectPath()
=> ReadArrayOfT<ObjectPath>();
public Signature[] ReadArrayOfSignature()
=> ReadArrayOfT<Signature>();
public VariantValue[] ReadArrayOfVariantValue()
=> ReadArrayOfT<VariantValue>();
public T[] ReadArrayOfHandle<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>() where T : SafeHandle
=> ReadArrayOfT<T>();
[RequiresUnreferencedCode(Strings.UseNonGenericReadArray)]
[Obsolete(Strings.UseNonGenericReadArrayObsolete)]
public T[] ReadArray<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>()
{
if (typeof(T) == typeof(byte))
{
return (T[])(object)ReadArrayOfNumeric<byte>();
}
else if (typeof(T) == typeof(short))
{
return (T[])(object)ReadArrayOfNumeric<short>();
}
else if (typeof(T) == typeof(ushort))
{
return (T[])(object)ReadArrayOfNumeric<ushort>();
}
else if (typeof(T) == typeof(int))
{
return (T[])(object)ReadArrayOfNumeric<int>();
}
else if (typeof(T) == typeof(uint))
{
return (T[])(object)ReadArrayOfNumeric<uint>();
}
else if (typeof(T) == typeof(long))
{
return (T[])(object)ReadArrayOfNumeric<long>();
}
else if (typeof(T) == typeof(ulong))
{
return (T[])(object)ReadArrayOfNumeric<ulong>();
}
else if (typeof(T) == typeof(double))
{
return (T[])(object)ReadArrayOfNumeric<double>();
}
else
{
return ReadArrayOfT<T>();
}
}
private T[] ReadArrayOfT<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>()
{
List<T> items = new();
ArrayEnd arrayEnd = ReadArrayStart(TypeModel.GetTypeAlignment<T>());
while (HasNext(arrayEnd))
{
items.Add(Read<T>());
}
return items.ToArray();
}
private unsafe T[] ReadArrayOfNumeric<T>() where T : unmanaged
{
int length = ReadInt32();
if (sizeof(T) > 4)
{
AlignReader(sizeof(T));
}
T[] array = new T[length / sizeof(T)];
bool dataRead = _reader.TryCopyTo(MemoryMarshal.AsBytes(array.AsSpan()));
if (!dataRead)
{
ThrowHelper.ThrowIndexOutOfRange();
}
_reader.Advance(sizeof(T) * array.Length);
if (sizeof(T) > 1 && ReverseEndianness)
{
#if NET8_0_OR_GREATER
if (sizeof(T) == 2)
{
var span = MemoryMarshal.Cast<T, short>(array.AsSpan());
BinaryPrimitives.ReverseEndianness(span, span);
}
else if (sizeof(T) == 4)
{
var span = MemoryMarshal.Cast<T, int>(array.AsSpan());
BinaryPrimitives.ReverseEndianness(span, span);
}
else if (sizeof(T) == 8)
{
Span<long> span = MemoryMarshal.Cast<T, long>(array.AsSpan());
BinaryPrimitives.ReverseEndianness(span, span);
}
#else
Span<T> span = array.AsSpan();
for (int i = 0; i < span.Length; i++)
{
if (sizeof(T) == 2)
{
span[i] = (T)(object)BinaryPrimitives.ReverseEndianness((short)(object)span[i]);
}
else if (sizeof(T) == 4)
{
span[i] = (T)(object)BinaryPrimitives.ReverseEndianness((int)(object)span[i]);
}
else if (typeof(T) == typeof(double))
{
span[i] = (T)(object)ReverseDoubleEndianness((double)(object)span[i]);
}
else if (sizeof(T) == 8)
{
span[i] = (T)(object)BinaryPrimitives.ReverseEndianness((long)(object)span[i]);
}
}
#endif
}
return array;
#if !NET8_0_OR_GREATER
static double ReverseDoubleEndianness(double d)
{
long l = *(long*)&d;
l = BinaryPrimitives.ReverseEndianness(l);
return *(double*)&d;
}
#endif
}
}

132
src/Linux/Tmds.DBus.Protocol/Reader.Basic.cs

@ -0,0 +1,132 @@
namespace Tmds.DBus.Protocol;
public ref partial struct Reader
{
public byte ReadByte()
{
if (!_reader.TryRead(out byte b))
{
ThrowHelper.ThrowIndexOutOfRange();
}
return b;
}
public bool ReadBool()
{
return ReadInt32() != 0;
}
public ushort ReadUInt16()
=> (ushort)ReadInt16();
public short ReadInt16()
{
AlignReader(DBusType.Int16);
bool dataRead = _isBigEndian ? _reader.TryReadBigEndian(out short rv) : _reader.TryReadLittleEndian(out rv);
if (!dataRead)
{
ThrowHelper.ThrowIndexOutOfRange();
}
return rv;
}
public uint ReadUInt32()
=> (uint)ReadInt32();
public int ReadInt32()
{
AlignReader(DBusType.Int32);
bool dataRead = _isBigEndian ? _reader.TryReadBigEndian(out int rv) : _reader.TryReadLittleEndian(out rv);
if (!dataRead)
{
ThrowHelper.ThrowIndexOutOfRange();
}
return rv;
}
public ulong ReadUInt64()
=> (ulong)ReadInt64();
public long ReadInt64()
{
AlignReader(DBusType.Int64);
bool dataRead = _isBigEndian ? _reader.TryReadBigEndian(out long rv) : _reader.TryReadLittleEndian(out rv);
if (!dataRead)
{
ThrowHelper.ThrowIndexOutOfRange();
}
return rv;
}
public unsafe double ReadDouble()
{
double value;
*(long*)&value = ReadInt64();
return value;
}
public Utf8Span ReadSignature()
{
int length = ReadByte();
return ReadSpan(length);
}
public void ReadSignature(string expected)
{
ReadOnlySpan<byte> signature = ReadSignature().Span;
if (signature.Length != expected.Length)
{
ThrowHelper.ThrowUnexpectedSignature(signature, expected);
}
for (int i = 0; i < signature.Length; i++)
{
if (signature[i] != expected[i])
{
ThrowHelper.ThrowUnexpectedSignature(signature, expected);
}
}
}
public Utf8Span ReadObjectPathAsSpan() => ReadSpan();
public ObjectPath ReadObjectPath() => new ObjectPath(ReadString());
public ObjectPath ReadObjectPathAsString() => ReadString();
public Utf8Span ReadStringAsSpan() => ReadSpan();
public string ReadString() => Encoding.UTF8.GetString(ReadSpan());
public Signature ReadSignatureAsSignature() => new Signature(ReadSignature().ToString());
public string ReadSignatureAsString() => ReadSignature().ToString();
private ReadOnlySpan<byte> ReadSpan()
{
int length = (int)ReadUInt32();
return ReadSpan(length);
}
private ReadOnlySpan<byte> ReadSpan(int length)
{
var span = _reader.UnreadSpan;
if (span.Length >= length)
{
_reader.Advance(length + 1);
return span.Slice(0, length);
}
else
{
var buffer = new byte[length];
if (!_reader.TryCopyTo(buffer))
{
ThrowHelper.ThrowIndexOutOfRange();
}
_reader.Advance(length + 1);
return new ReadOnlySpan<byte>(buffer);
}
}
private bool ReverseEndianness
=> BitConverter.IsLittleEndian != !_isBigEndian;
}

48
src/Linux/Tmds.DBus.Protocol/Reader.Dictionary.cs

@ -0,0 +1,48 @@
namespace Tmds.DBus.Protocol;
// Using obsolete generic read members
#pragma warning disable CS0618
public ref partial struct Reader
{
public ArrayEnd ReadDictionaryStart()
=> ReadArrayStart(DBusType.Struct);
// Read method for the common 'a{sv}' type.
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // It's safe to call ReadDictionary with these types.
public Dictionary<string, VariantValue> ReadDictionaryOfStringToVariantValue()
=> ReadDictionary<string, VariantValue>();
[RequiresUnreferencedCode(Strings.UseNonGenericReadDictionary)]
[Obsolete(Strings.UseNonGenericReadDictionaryObsolete)]
public Dictionary<TKey, TValue> ReadDictionary
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]TKey,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]TValue
>
()
where TKey : notnull
where TValue : notnull
=> ReadDictionary<TKey, TValue>(new Dictionary<TKey, TValue>());
internal Dictionary<TKey, TValue> ReadDictionary
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]TKey,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]TValue
>
(Dictionary<TKey, TValue> dictionary)
where TKey : notnull
where TValue : notnull
{
ArrayEnd dictEnd = ReadDictionaryStart();
while (HasNext(dictEnd))
{
var key = Read<TKey>();
var value = Read<TValue>();
// Use the indexer to avoid throwing if the key is present multiple times.
dictionary[key] = value;
}
return dictionary;
}
}

36
src/Linux/Tmds.DBus.Protocol/Reader.Handle.cs

@ -0,0 +1,36 @@
namespace Tmds.DBus.Protocol;
public ref partial struct Reader
{
public T? ReadHandle<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>() where T : SafeHandle
=> ReadHandleGeneric<T>();
internal T? ReadHandleGeneric<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>()
{
int idx = (int)ReadUInt32();
if (idx >= _handleCount)
{
throw new IndexOutOfRangeException();
}
if (_handles is not null)
{
return _handles.ReadHandleGeneric<T>(idx);
}
return default(T);
}
// note: The handle is still owned (i.e. Disposed) by the Message.
public IntPtr ReadHandleRaw()
{
int idx = (int)ReadUInt32();
if (idx >= _handleCount)
{
throw new IndexOutOfRangeException();
}
if (_handles is not null)
{
return _handles.ReadHandleRaw(idx);
}
return new IntPtr(-1);
}
}

1045
src/Linux/Tmds.DBus.Protocol/Reader.ReadT.Dynamic.cs

File diff suppressed because it is too large

77
src/Linux/Tmds.DBus.Protocol/Reader.ReadT.cs

@ -0,0 +1,77 @@
namespace Tmds.DBus.Protocol;
public ref partial struct Reader
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal T Read<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>()
{
if (typeof(T) == typeof(byte))
{
return (T)(object)ReadByte();
}
else if (typeof(T) == typeof(bool))
{
return (T)(object)ReadBool();
}
else if (typeof(T) == typeof(short))
{
return (T)(object)ReadInt16();
}
else if (typeof(T) == typeof(ushort))
{
return (T)(object)ReadUInt16();
}
else if (typeof(T) == typeof(int))
{
return (T)(object)ReadInt32();
}
else if (typeof(T) == typeof(uint))
{
return (T)(object)ReadUInt32();
}
else if (typeof(T) == typeof(long))
{
return (T)(object)ReadInt64();
}
else if (typeof(T) == typeof(ulong))
{
return (T)(object)ReadUInt64();
}
else if (typeof(T) == typeof(double))
{
return (T)(object)ReadDouble();
}
else if (typeof(T) == typeof(string))
{
return (T)(object)ReadString();
}
else if (typeof(T) == typeof(ObjectPath))
{
return (T)(object)ReadObjectPath();
}
else if (typeof(T) == typeof(Signature))
{
return (T)(object)ReadSignatureAsSignature();
}
else if (typeof(T).IsAssignableTo(typeof(SafeHandle)))
{
return (T)(object)ReadHandleGeneric<T>()!;
}
else if (typeof(T) == typeof(VariantValue))
{
return (T)(object)ReadVariantValue();
}
else if (Feature.IsDynamicCodeEnabled)
{
return ReadDynamic<T>();
}
ThrowNotSupportedType(typeof(T));
return default!;
}
private static void ThrowNotSupportedType(Type type)
{
throw new NotSupportedException($"Cannot read type {type.FullName}");
}
}

309
src/Linux/Tmds.DBus.Protocol/Reader.Struct.cs

@ -0,0 +1,309 @@
namespace Tmds.DBus.Protocol;
public ref partial struct Reader
{
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1
>()
{
AlignStruct();
return ValueTuple.Create(Read<T1>());
}
private Tuple<T1> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1
>()
{
AlignStruct();
return Tuple.Create(Read<T1>());
}
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1, T2> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2
>()
where T1 : notnull
where T2 : notnull
{
AlignStruct();
return ValueTuple.Create(Read<T1>(), Read<T2>());
}
private Tuple<T1, T2> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2
>()
where T1 : notnull
where T2 : notnull
{
AlignStruct();
return Tuple.Create(Read<T1>(), Read<T2>());
}
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1, T2, T3> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3
>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
{
AlignStruct();
return ValueTuple.Create(Read<T1>(), Read<T2>(), Read<T3>());
}
private Tuple<T1, T2, T3> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3
>()
{
AlignStruct();
return Tuple.Create(Read<T1>(), Read<T2>(), Read<T3>());
}
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1, T2, T3, T4> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4
>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{
AlignStruct();
return ValueTuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>());
}
private Tuple<T1, T2, T3, T4> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4
>()
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{
AlignStruct();
return Tuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>());
}
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1, T2, T3, T4, T5> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5
>()
{
AlignStruct();
return ValueTuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>());
}
private Tuple<T1, T2, T3, T4, T5> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5
>()
{
AlignStruct();
return Tuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>());
}
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1, T2, T3, T4, T5, T6> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6
>()
{
AlignStruct();
return ValueTuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>());
}
private Tuple<T1, T2, T3, T4, T5, T6> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6
>()
{
AlignStruct();
return Tuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>());
}
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1, T2, T3, T4, T5, T6, T7> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T7
>()
{
AlignStruct();
return ValueTuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>(), Read<T7>());
}
private Tuple<T1, T2, T3, T4, T5, T6, T7> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T7
>()
{
AlignStruct();
return Tuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>(), Read<T7>());
}
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8>> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T7,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T8
>()
{
AlignStruct();
return ValueTuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>(), Read<T7>(), Read<T8>());
}
private Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8>> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T7,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T8
>()
{
AlignStruct();
return Tuple.Create(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>(), Read<T7>(), Read<T8>());
}
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9>> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T7,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T8,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T9
>()
{
AlignStruct();
return (Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>(), Read<T7>(), Read<T8>(), Read<T9>());
}
private Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9>> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T7,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T8,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T9
>()
{
AlignStruct();
return new Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9>>(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>(), Read<T7>(), Tuple.Create(Read<T8>(), Read<T9>()));
}
[RequiresUnreferencedCode(Strings.UseNonGenericReadStruct)]
[Obsolete(Strings.UseNonGenericReadStructObsolete)]
public ValueTuple<T1, T2, T3, T4, T5, T6, T7, ValueTuple<T8, T9, T10>> ReadStruct
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T7,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T8,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T9,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T10
>()
{
AlignStruct();
return (Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>(), Read<T7>(), Read<T8>(), Read<T9>(), Read<T10>());
}
private Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10>> ReadStructAsTuple
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T1,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T2,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T3,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T4,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T5,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T6,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T7,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T8,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T9,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T10
>()
{
AlignStruct();
return new Tuple<T1, T2, T3, T4, T5, T6, T7, Tuple<T8, T9, T10>>(Read<T1>(), Read<T2>(), Read<T3>(), Read<T4>(), Read<T5>(), Read<T6>(), Read<T7>(), Tuple.Create(Read<T8>(), Read<T9>(), Read<T10>()));
}
}

8
src/Linux/Tmds.DBus.Protocol/Reader.Variant.Dynamic.cs

@ -0,0 +1,8 @@
namespace Tmds.DBus.Protocol;
public ref partial struct Reader
{
[RequiresUnreferencedCode(Strings.UseNonObjectReadVariantValue)]
[Obsolete(Strings.UseNonObjectReadVariantValueObsolete)]
public object ReadVariant() => Read<object>();
}

161
src/Linux/Tmds.DBus.Protocol/Reader.Variant.cs

@ -0,0 +1,161 @@
namespace Tmds.DBus.Protocol;
public ref partial struct Reader
{
public VariantValue ReadVariantValue()
{
Utf8Span signature = ReadSignature();
SignatureReader sigReader = new(signature);
if (!sigReader.TryRead(out DBusType type, out ReadOnlySpan<byte> innerSignature))
{
ThrowInvalidSignature($"Invalid variant signature: {signature.ToString()}");
}
return ReadTypeAsVariantValue(type, innerSignature);
}
private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan<byte> innerSignature)
{
SignatureReader sigReader;
switch (type)
{
case DBusType.Byte:
return new VariantValue(ReadByte());
case DBusType.Bool:
return new VariantValue(ReadBool());
case DBusType.Int16:
return new VariantValue(ReadInt16());
case DBusType.UInt16:
return new VariantValue(ReadUInt16());
case DBusType.Int32:
return new VariantValue(ReadInt32());
case DBusType.UInt32:
return new VariantValue(ReadUInt32());
case DBusType.Int64:
return new VariantValue(ReadInt64());
case DBusType.UInt64:
return new VariantValue(ReadUInt64());
case DBusType.Double:
return new VariantValue(ReadDouble());
case DBusType.String:
return new VariantValue(ReadString());
case DBusType.ObjectPath:
return new VariantValue(ReadObjectPath());
case DBusType.Signature:
return new VariantValue(ReadSignatureAsSignature());
case DBusType.UnixFd:
int idx = (int)ReadUInt32();
return new VariantValue(_handles, idx);
case DBusType.Variant:
return ReadVariantValue();
case DBusType.Array:
sigReader = new(innerSignature);
if (!sigReader.TryRead(out type, out innerSignature))
{
ThrowInvalidSignature("Signature is missing array item type.");
}
bool isDictionary = type == DBusType.DictEntry;
if (isDictionary)
{
sigReader = new(innerSignature);
DBusType valueType = default;
ReadOnlySpan<byte> valueInnerSignature = default;
if (!sigReader.TryRead(out DBusType keyType, out ReadOnlySpan<byte> keyInnerSignature) ||
!sigReader.TryRead(out valueType, out valueInnerSignature))
{
ThrowInvalidSignature("Signature is missing dict entry types.");
}
List<KeyValuePair<VariantValue, VariantValue>> items = new();
ArrayEnd arrayEnd = ReadArrayStart(type);
while (HasNext(arrayEnd))
{
AlignStruct();
VariantValue key = ReadTypeAsVariantValue(keyType, keyInnerSignature);
VariantValue value = ReadTypeAsVariantValue(valueType, valueInnerSignature);
items.Add(new KeyValuePair<VariantValue, VariantValue>(key, value));
}
return new VariantValue(ToVariantValueType(keyType), ToVariantValueType(valueType), items.ToArray());
}
else
{
if (type == DBusType.Byte)
{
return new VariantValue(ReadArrayOfByte());
}
else if (type == DBusType.Int16)
{
return new VariantValue(ReadArrayOfInt16());
}
else if (type == DBusType.UInt16)
{
return new VariantValue(ReadArrayOfUInt16());
}
else if (type == DBusType.Int32)
{
return new VariantValue(ReadArrayOfInt32());
}
else if (type == DBusType.UInt32)
{
return new VariantValue(ReadArrayOfUInt32());
}
else if (type == DBusType.Int64)
{
return new VariantValue(ReadArrayOfInt64());
}
else if (type == DBusType.UInt64)
{
return new VariantValue(ReadArrayOfUInt64());
}
else if (type == DBusType.Double)
{
return new VariantValue(ReadArrayOfDouble());
}
else if (type == DBusType.String ||
type == DBusType.ObjectPath)
{
return new VariantValue(ToVariantValueType(type), ReadArrayOfString());
}
else
{
List<VariantValue> items = new();
ArrayEnd arrayEnd = ReadArrayStart(type);
while (HasNext(arrayEnd))
{
VariantValue value = ReadTypeAsVariantValue(type, innerSignature);
items.Add(value);
}
return new VariantValue(ToVariantValueType(type), items.ToArray());
}
}
case DBusType.Struct:
{
AlignStruct();
sigReader = new(innerSignature);
List<VariantValue> items = new();
while (sigReader.TryRead(out type, out innerSignature))
{
VariantValue value = ReadTypeAsVariantValue(type, innerSignature);
items.Add(value);
}
return new VariantValue(items.ToArray());
}
case DBusType.DictEntry: // Already handled under DBusType.Array.
default:
// note: the SignatureReader maps all unknown types to DBusType.Invalid
// so we won't see the actual character that caused it to fail.
ThrowInvalidSignature($"Unexpected type in signature: {type}.");
return default;
}
}
private void ThrowInvalidSignature(string message)
{
throw new ProtocolException(message);
}
private static VariantValueType ToVariantValueType(DBusType type)
=> type switch
{
DBusType.Variant => VariantValueType.VariantValue,
_ => (VariantValueType)type
};
}

90
src/Linux/Tmds.DBus.Protocol/Reader.cs

@ -0,0 +1,90 @@
[assembly: InternalsVisibleTo("Tmds.DBus.Protocol.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010071a8770f460cce31df0feb6f94b328aebd55bffeb5c69504593df097fdd9b29586dbd155419031834411c8919516cc565dee6b813c033676218496edcbe7939c0dd1f919f3d1a228ebe83b05a3bbdbae53ce11bcf4c04a42d8df1a83c2d06cb4ebb0b447e3963f48a1ca968996f3f0db8ab0e840a89d0a5d5a237e2f09189ed3")]
namespace Tmds.DBus.Protocol;
public ref partial struct Reader
{
private delegate object ValueReader(ref Reader reader);
private readonly bool _isBigEndian;
private readonly UnixFdCollection? _handles;
private readonly int _handleCount;
private SequenceReader<byte> _reader;
internal ReadOnlySequence<byte> UnreadSequence => _reader.Sequence.Slice(_reader.Position);
internal void Advance(long count) => _reader.Advance(count);
internal Reader(bool isBigEndian, ReadOnlySequence<byte> sequence) : this(isBigEndian, sequence, handles: null, 0) { }
internal Reader(bool isBigEndian, ReadOnlySequence<byte> sequence, UnixFdCollection? handles, int handleCount)
{
_reader = new(sequence);
_isBigEndian = isBigEndian;
_handles = handles;
_handleCount = handleCount;
}
public void AlignStruct() => AlignReader(DBusType.Struct);
private void AlignReader(DBusType type)
{
long pad = ProtocolConstants.GetPadding((int)_reader.Consumed, type);
if (pad != 0)
{
_reader.Advance(pad);
}
}
private void AlignReader(int alignment)
{
long pad = ProtocolConstants.GetPadding((int)_reader.Consumed, alignment);
if (pad != 0)
{
_reader.Advance(pad);
}
}
public ArrayEnd ReadArrayStart(DBusType elementType)
{
uint arrayLength = ReadUInt32();
AlignReader(elementType);
int endOfArray = (int)(_reader.Consumed + arrayLength);
return new ArrayEnd(elementType, endOfArray);
}
public bool HasNext(ArrayEnd iterator)
{
int consumed = (int)_reader.Consumed;
int nextElement = ProtocolConstants.Align(consumed, iterator.Type);
if (nextElement >= iterator.EndOfArray)
{
return false;
}
int advance = nextElement - consumed;
if (advance != 0)
{
_reader.Advance(advance);
}
return true;
}
public void SkipTo(ArrayEnd end)
{
int advance = end.EndOfArray - (int)_reader.Consumed;
_reader.Advance(advance);
}
}
public ref struct ArrayEnd
{
internal readonly DBusType Type;
internal readonly int EndOfArray;
internal ArrayEnd(DBusType type, int endOfArray)
{
Type = type;
EndOfArray = endOfArray;
}
}

12
src/Linux/Tmds.DBus.Protocol/Signature.cs

@ -0,0 +1,12 @@
namespace Tmds.DBus.Protocol;
public struct Signature
{
private string _value;
public Signature(string value) => _value = value;
public override string ToString() => _value ?? "";
public Variant AsVariant() => new Variant(this);
}

271
src/Linux/Tmds.DBus.Protocol/SignatureReader.cs

@ -0,0 +1,271 @@
[assembly: InternalsVisibleTo("dotnet-dbus, PublicKey=002400000480000094000000060200000024000052534131000400000100010071a8770f460cce31df0feb6f94b328aebd55bffeb5c69504593df097fdd9b29586dbd155419031834411c8919516cc565dee6b813c033676218496edcbe7939c0dd1f919f3d1a228ebe83b05a3bbdbae53ce11bcf4c04a42d8df1a83c2d06cb4ebb0b447e3963f48a1ca968996f3f0db8ab0e840a89d0a5d5a237e2f09189ed3")]
namespace Tmds.DBus.Protocol;
public ref struct SignatureReader
{
private ReadOnlySpan<byte> _signature;
public ReadOnlySpan<byte> Signature => _signature;
public SignatureReader(ReadOnlySpan<byte> signature)
{
_signature = signature;
}
public bool TryRead(out DBusType type, out ReadOnlySpan<byte> innerSignature)
{
innerSignature = default;
if (_signature.IsEmpty)
{
type = DBusType.Invalid;
return false;
}
type = ReadSingleType(_signature, out int length);
if (length > 1)
{
switch (type)
{
case DBusType.Array:
innerSignature = _signature.Slice(1, length - 1);
break;
case DBusType.Struct:
case DBusType.DictEntry:
innerSignature = _signature.Slice(1, length - 2);
break;
}
}
_signature = _signature.Slice(length);
return true;
}
private static DBusType ReadSingleType(ReadOnlySpan<byte> signature, out int length)
{
length = 0;
if (signature.IsEmpty)
{
return DBusType.Invalid;
}
DBusType type = (DBusType)signature[0];
if (IsBasicType(type))
{
length = 1;
}
else if (type == DBusType.Variant)
{
length = 1;
}
else if (type == DBusType.Array)
{
if (ReadSingleType(signature.Slice(1), out int elementLength) != DBusType.Invalid)
{
type = DBusType.Array;
length = elementLength + 1;
}
else
{
type = DBusType.Invalid;
}
}
else if (type == DBusType.Struct)
{
length = DetermineLength(signature.Slice(1), (byte)'(', (byte)')');
if (length == 0)
{
type = DBusType.Invalid;
}
}
else if (type == DBusType.DictEntry)
{
length = DetermineLength(signature.Slice(1), (byte)'{', (byte)'}');
if (length < 4 ||
!IsBasicType((DBusType)signature[1]) ||
ReadSingleType(signature.Slice(2), out int valueTypeLength) == DBusType.Invalid ||
length != valueTypeLength + 3)
{
type = DBusType.Invalid;
}
}
else
{
type = DBusType.Invalid;
}
return type;
}
private static int DetermineLength(ReadOnlySpan<byte> span, byte startChar, byte endChar)
{
int length = 1;
int count = 1;
do
{
int offset = span.IndexOfAny(startChar, endChar);
if (offset == -1)
{
return 0;
}
if (span[offset] == startChar)
{
count++;
}
else
{
count--;
}
length += offset + 1;
span = span.Slice(offset + 1);
} while (count > 0);
return length;
}
private static bool IsBasicType(DBusType type)
{
return BasicTypes.IndexOf((byte)type) != -1;
}
private static ReadOnlySpan<byte> BasicTypes => new byte[] {
(byte)DBusType.Byte,
(byte)DBusType.Bool,
(byte)DBusType.Int16,
(byte)DBusType.UInt16,
(byte)DBusType.Int32,
(byte)DBusType.UInt32,
(byte)DBusType.Int64,
(byte)DBusType.UInt64,
(byte)DBusType.Double,
(byte)DBusType.String,
(byte)DBusType.ObjectPath,
(byte)DBusType.Signature,
(byte)DBusType.UnixFd };
private static ReadOnlySpan<byte> ReadSingleType(ref ReadOnlySpan<byte> signature)
{
if (signature.Length == 0)
{
return default;
}
int length;
DBusType type = (DBusType)signature[0];
if (type == DBusType.Struct)
{
length = DetermineLength(signature.Slice(1), (byte)'(', (byte)')');
}
else if (type == DBusType.DictEntry)
{
length = DetermineLength(signature.Slice(1), (byte)'{', (byte)'}');
}
else if (type == DBusType.Array)
{
ReadOnlySpan<byte> remainder = signature.Slice(1);
length = 1 + ReadSingleType(ref remainder).Length;
}
else
{
length = 1;
}
ReadOnlySpan<byte> rv = signature.Slice(0, length);
signature = signature.Slice(length);
return rv;
}
internal static T Transform<T>(ReadOnlySpan<byte> signature, Func<DBusType, T[], T> map)
{
DBusType dbusType = signature.Length == 0 ? DBusType.Invalid : (DBusType)signature[0];
if (dbusType == DBusType.Array)
{
if ((DBusType)signature[1] == DBusType.DictEntry)
{
signature = signature.Slice(2);
ReadOnlySpan<byte> keySignature = ReadSingleType(ref signature);
ReadOnlySpan<byte> valueSignature = ReadSingleType(ref signature);
signature = signature.Slice(1);
T keyType = Transform(keySignature, map);
T valueType = Transform(valueSignature, map);
return map(DBusType.DictEntry, new[] { keyType, valueType });
}
else
{
signature = signature.Slice(1);
T elementType = Transform(signature, map);
signature = signature.Slice(1);
return map(DBusType.Array, new[] { elementType });
}
}
else if (dbusType == DBusType.Struct)
{
signature = signature.Slice(1, signature.Length - 2);
int typeCount = CountTypes(signature);
T[] innerTypes = new T[typeCount];
for (int i = 0; i < innerTypes.Length; i++)
{
ReadOnlySpan<byte> innerTypeSignature = ReadSingleType(ref signature);
innerTypes[i] = Transform(innerTypeSignature, map);
}
return map(DBusType.Struct, innerTypes);
}
return map(dbusType, Array.Empty<T>());
}
// Counts the number of single types in a signature.
private static int CountTypes(ReadOnlySpan<byte> signature)
{
if (signature.Length == 0)
{
return 0;
}
if (signature.Length == 1)
{
return 1;
}
DBusType type = (DBusType)signature[0];
signature = signature.Slice(1);
if (type == DBusType.Struct)
{
ReadToEnd(ref signature, (byte)'(', (byte)')');
}
else if (type == DBusType.DictEntry)
{
ReadToEnd(ref signature, (byte)'{', (byte)'}');
}
return (type == DBusType.Array ? 0 : 1) + CountTypes(signature);
static void ReadToEnd(ref ReadOnlySpan<byte> span, byte startChar, byte endChar)
{
int count = 1;
do
{
int offset = span.IndexOfAny(startChar, endChar);
if (span[offset] == startChar)
{
count++;
}
else
{
count--;
}
span = span.Slice(offset + 1);
} while (count > 0);
}
}
}

232
src/Linux/Tmds.DBus.Protocol/SocketExtensions.cs

@ -0,0 +1,232 @@
using System.Net.Sockets;
namespace Tmds.DBus.Protocol;
using SizeT = System.UIntPtr;
using SSizeT = System.IntPtr;
static class SocketExtensions
{
public static ValueTask<int> ReceiveAsync(this Socket socket, Memory<byte> memory, UnixFdCollection? fdCollection)
{
if (fdCollection is null)
{
return socket.ReceiveAsync(memory, SocketFlags.None);
}
else
{
return socket.ReceiveWithHandlesAsync(memory, fdCollection);
}
}
private async static ValueTask<int> ReceiveWithHandlesAsync(this Socket socket, Memory<byte> memory, UnixFdCollection fdCollection)
{
while (true)
{
await socket.ReceiveAsync(new Memory<byte>(), SocketFlags.None).ConfigureAwait(false);
int rv = recvmsg(socket, memory, fdCollection);
if (rv >= 0)
{
return rv;
}
else
{
int errno = Marshal.GetLastWin32Error();
if (errno == EAGAIN || errno == EINTR)
{
continue;
}
throw new SocketException(errno);
}
}
}
public static ValueTask SendAsync(this Socket socket, ReadOnlyMemory<byte> buffer, IReadOnlyList<SafeHandle>? handles)
{
if (handles is null || handles.Count == 0)
{
return SendAsync(socket, buffer);
}
else
{
return socket.SendAsyncWithHandlesAsync(buffer, handles);
}
}
private static async ValueTask SendAsync(this Socket socket, ReadOnlyMemory<byte> buffer)
{
while (buffer.Length > 0)
{
int sent = await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
buffer = buffer.Slice(sent);
}
}
private static ValueTask SendAsyncWithHandlesAsync(this Socket socket, ReadOnlyMemory<byte> buffer, IReadOnlyList<SafeHandle> handles)
{
socket.Blocking = false;
do
{
int rv = sendmsg(socket, buffer, handles);
if (rv > 0)
{
if (buffer.Length == rv)
{
return default;
}
return SendAsync(socket, buffer.Slice(rv));
}
else
{
int errno = Marshal.GetLastWin32Error();
if (errno == EINTR)
{
continue;
}
// TODO (low prio): handle EAGAIN.
return new ValueTask(Task.FromException(new SocketException(errno)));
}
} while (true);
}
private static unsafe int sendmsg(Socket socket, ReadOnlyMemory<byte> buffer, IReadOnlyList<SafeHandle> handles)
{
fixed (byte* ptr = buffer.Span)
{
IOVector* iovs = stackalloc IOVector[1];
iovs[0].Base = ptr;
iovs[0].Length = (SizeT)buffer.Length;
Msghdr msg = new Msghdr();
msg.msg_iov = iovs;
msg.msg_iovlen = (SizeT)1;
var fdm = new cmsg_fd();
int size = sizeof(Cmsghdr) + 4 * handles.Count;
msg.msg_control = &fdm;
msg.msg_controllen = (SizeT)size;
fdm.hdr.cmsg_len = (SizeT)size;
fdm.hdr.cmsg_level = SOL_SOCKET;
fdm.hdr.cmsg_type = SCM_RIGHTS;
SafeHandle handle = socket.GetSafeHandle();
int handleRefsAdded = 0;
bool refAdded = false;
try
{
handle.DangerousAddRef(ref refAdded);
for (int i = 0, j = 0; i < handles.Count; i++)
{
bool added = false;
SafeHandle h = handles[i];
h.DangerousAddRef(ref added);
handleRefsAdded++;
fdm.fds[j++] = h.DangerousGetHandle().ToInt32();
}
return (int)sendmsg(handle.DangerousGetHandle().ToInt32(), new IntPtr(&msg), 0);
}
finally
{
for (int i = 0; i < handleRefsAdded; i++)
{
SafeHandle h = handles[i];
h.DangerousRelease();
}
if (refAdded)
handle.DangerousRelease();
}
}
}
private static unsafe int recvmsg(Socket socket, Memory<byte> buffer, UnixFdCollection handles)
{
fixed (byte* buf = buffer.Span)
{
IOVector iov = new IOVector();
iov.Base = buf;
iov.Length = (SizeT)buffer.Length;
Msghdr msg = new Msghdr();
msg.msg_iov = &iov;
msg.msg_iovlen = (SizeT)1;
cmsg_fd cm = new cmsg_fd();
msg.msg_control = &cm;
msg.msg_controllen = (SizeT)sizeof(cmsg_fd);
var handle = socket.GetSafeHandle();
bool refAdded = false;
try
{
handle.DangerousAddRef(ref refAdded);
int rv = (int)recvmsg(handle.DangerousGetHandle().ToInt32(), new IntPtr(&msg), 0);
if (rv >= 0)
{
if (cm.hdr.cmsg_level == SOL_SOCKET && cm.hdr.cmsg_type == SCM_RIGHTS)
{
int msgFdCount = ((int)cm.hdr.cmsg_len - sizeof(Cmsghdr)) / sizeof(int);
for (int i = 0; i < msgFdCount; i++)
{
handles.AddHandle(new IntPtr(cm.fds[i]));
}
}
}
return rv;
}
finally
{
if (refAdded)
handle.DangerousRelease();
}
}
}
const int SOL_SOCKET = 1;
const int EINTR = 4;
//const int EBADF = 9;
static readonly int EAGAIN = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 35 : 11;
const int SCM_RIGHTS = 1;
private unsafe struct Msghdr
{
public IntPtr msg_name; //optional address
public uint msg_namelen; //size of address
public IOVector* msg_iov; //scatter/gather array
public SizeT msg_iovlen; //# elements in msg_iov
public void* msg_control; //ancillary data, see below
public SizeT msg_controllen; //ancillary data buffer len
public int msg_flags; //flags on received message
}
private unsafe struct IOVector
{
public void* Base;
public SizeT Length;
}
private struct Cmsghdr
{
public SizeT cmsg_len; //data byte count, including header
public int cmsg_level; //originating protocol
public int cmsg_type; //protocol-specific type
}
private unsafe struct cmsg_fd
{
public Cmsghdr hdr;
public fixed int fds[64];
}
[DllImport("libc", SetLastError = true)]
public static extern SSizeT sendmsg(int sockfd, IntPtr msg, int flags);
[DllImport("libc", SetLastError = true)]
public static extern SSizeT recvmsg(int sockfd, IntPtr msg, int flags);
}

24
src/Linux/Tmds.DBus.Protocol/StringBuilderExtensions.cs

@ -0,0 +1,24 @@
namespace Tmds.DBus.Protocol;
static class StringBuilderExtensions
{
public static void AppendUTF8(this StringBuilder sb, ReadOnlySpan<byte> value)
{
char[]? valueArray = null;
int length = Encoding.UTF8.GetCharCount(value);
Span<char> charBuffer = length <= Constants.StackAllocCharThreshold ?
stackalloc char[length] :
(valueArray = ArrayPool<char>.Shared.Rent(length));
int charsWritten = Encoding.UTF8.GetChars(value, charBuffer);
sb.Append(charBuffer.Slice(0, charsWritten));
if (valueArray is not null)
{
ArrayPool<char>.Shared.Return(valueArray);
}
}
}

29
src/Linux/Tmds.DBus.Protocol/Strings.cs

@ -0,0 +1,29 @@
namespace Tmds.DBus.Protocol;
static class Strings
{
public const string AddTypeReaderMethodObsolete = "AddTypeReader methods are obsolete. Remove the call to this method.";
public const string AddTypeWriterMethodObsolete = "AddTypeWriter methods are obsolete. Remove the call to this method.";
public const string UseNonGenericWriteArray = $"Use a non-generic overload of '{nameof(MessageWriter.WriteArray)}' if it exists for the item type, and otherwise write out the elements separately surrounded by a call to '{nameof(MessageWriter.WriteArrayStart)}' and '{nameof(MessageWriter.WriteArrayEnd)}'.";
public const string UseNonGenericReadArray = $"Use a '{nameof(Reader.ReadArray)}Of*' method if it exists for the item type, and otherwise read out the elements in a while loop using '{nameof(Reader.ReadArrayStart)}' and '{nameof(Reader.HasNext)}'.";
public const string UseNonGenericReadDictionary = $"Read the dictionary by calling '{nameof(Reader.ReadDictionaryStart)} and reading the key-value pairs in a while loop using '{nameof(Reader.HasNext)}'.";
public const string UseNonGenericWriteDictionary = $"Write the dictionary by calling '{nameof(MessageWriter.WriteDictionaryStart)}', for each element call '{nameof(MessageWriter.WriteDictionaryEntryStart)}', write the key and value. Complete the dictionary writing by calling '{nameof(MessageWriter.WriteDictionaryEnd)}'.";
public const string UseNonGenericWriteVariantDictionary = $"Write the signature using '{nameof(MessageWriter.WriteSignature)}', then write the dictionary by calling '{nameof(MessageWriter.WriteDictionaryStart)}', for each element call '{nameof(MessageWriter.WriteDictionaryEntryStart)}', write the key and value. Complete the dictionary writing by calling '{nameof(MessageWriter.WriteDictionaryEnd)}'.";
public const string UseNonGenericReadStruct = $"Read the struct by calling '{nameof(Reader.AlignStruct)}' and then reading all the struct fields.";
public const string UseNonGenericWriteStruct = $"Write the struct by calling '{nameof(MessageWriter.WriteStructureStart)}' and then writing all the struct fields.";
public const string UseNonObjectWriteVariant = $"Use the overload of '{nameof(MessageWriter.WriteVariant)}' that accepts a '{nameof(Variant)}' instead.";
public const string UseNonObjectReadVariantValue = $"Use '{nameof(Reader.ReadVariantValue)}' instead.";
private const string MethodIsNotCompatibleWithTrimmingNativeAot = "Method is not compatible with trimming/NativeAOT.";
public const string UseNonGenericWriteArrayObsolete = $"{MethodIsNotCompatibleWithTrimmingNativeAot} {UseNonGenericWriteArray}";
public const string UseNonGenericReadArrayObsolete = $"{MethodIsNotCompatibleWithTrimmingNativeAot} {UseNonGenericReadArray}";
public const string UseNonGenericReadDictionaryObsolete = $"{MethodIsNotCompatibleWithTrimmingNativeAot} {UseNonGenericReadDictionary}";
public const string UseNonGenericWriteDictionaryObsolete = $"{MethodIsNotCompatibleWithTrimmingNativeAot} {UseNonGenericWriteDictionary}";
public const string UseNonGenericWriteVariantDictionaryObsolete = $"{MethodIsNotCompatibleWithTrimmingNativeAot} {UseNonGenericWriteVariantDictionary}";
public const string UseNonGenericReadStructObsolete = $"{MethodIsNotCompatibleWithTrimmingNativeAot} {UseNonGenericReadStruct}";
public const string UseNonGenericWriteStructObsolete = $"{MethodIsNotCompatibleWithTrimmingNativeAot} {UseNonGenericWriteStruct}";
public const string UseNonObjectWriteVariantObsolete = $"{MethodIsNotCompatibleWithTrimmingNativeAot} {UseNonObjectWriteVariant}";
public const string UseNonObjectReadVariantValueObsolete = $"{MethodIsNotCompatibleWithTrimmingNativeAot} {UseNonObjectReadVariantValue}";
}

469
src/Linux/Tmds.DBus.Protocol/Struct.cs

@ -0,0 +1,469 @@
namespace Tmds.DBus.Protocol;
// Using obsolete generic write members
#pragma warning disable CS0618
public static class Struct
{
public static Struct<T1> Create<T1>(T1 item1)
where T1 : notnull
=> new Struct<T1>(item1);
public static Struct<T1, T2> Create<T1,T2>(T1 item1, T2 item2)
where T1 : notnull
where T2 : notnull
=> new Struct<T1, T2>(item1, item2);
public static Struct<T1, T2, T3> Create<T1,T2,T3>(T1 item1, T2 item2, T3 item3)
where T1 : notnull
where T2 : notnull
where T3 : notnull
=> new Struct<T1, T2, T3>(item1, item2, item3);
public static Struct<T1, T2, T3, T4> Create<T1,T2,T3,T4>(T1 item1, T2 item2, T3 item3, T4 item4)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
=> new Struct<T1, T2, T3, T4>(item1, item2, item3, item4);
public static Struct<T1, T2, T3, T4, T5> Create<T1,T2,T3,T4,T5>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
=> new Struct<T1, T2, T3, T4, T5>(item1, item2, item3, item4, item5);
public static Struct<T1, T2, T3, T4, T5, T6> Create<T1,T2,T3,T4,T5,T6>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
=> new Struct<T1, T2, T3, T4, T5, T6>(item1, item2, item3, item4, item5, item6);
public static Struct<T1, T2, T3, T4, T5, T6, T7> Create<T1,T2,T3,T4,T5,T6,T7>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
=> new Struct<T1, T2, T3, T4, T5, T6, T7>(item1, item2, item3, item4, item5, item6, item7);
public static Struct<T1, T2, T3, T4, T5, T6, T7, T8> Create<T1,T2,T3,T4,T5,T6,T7,T8>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
=> new Struct<T1, T2, T3, T4, T5, T6, T7, T8>(item1, item2, item3, item4, item5, item6, item7, item8);
public static Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9> Create<T1,T2,T3,T4,T5,T6,T7,T8,T9>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
=> new Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9>(item1, item2, item3, item4, item5, item6, item7, item8, item9);
public static Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> Create<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10>(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9, T10 item10)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
where T10 : notnull
=> new Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10);
}
public sealed class Struct<T1> : IDBusWritable
where T1 : notnull
{
public T1 Item1;
public Struct(T1 item1)
{
TypeModel.EnsureSupportedVariantType<T1>();
Item1 = item1;
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private ValueTuple<T1> ToValueTuple()
=> new ValueTuple<T1>(Item1);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1> value)
=> value.AsVariant();
}
public sealed class Struct<T1,T2> : IDBusWritable
where T1 : notnull
where T2 : notnull
{
public T1 Item1;
public T2 Item2;
public Struct(T1 item1, T2 item2)
{
TypeModel.EnsureSupportedVariantType<T1>();
TypeModel.EnsureSupportedVariantType<T2>();
(Item1, Item2) = (item1, item2);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private (T1, T2) ToValueTuple()
=> (Item1, Item2);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1, T2> value)
=> value.AsVariant();
}
public sealed class Struct<T1,T2,T3> : IDBusWritable
where T1 : notnull
where T2 : notnull
where T3 : notnull
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public Struct(T1 item1, T2 item2, T3 item3)
{
TypeModel.EnsureSupportedVariantType<T1>();
TypeModel.EnsureSupportedVariantType<T2>();
TypeModel.EnsureSupportedVariantType<T3>();
(Item1, Item2, Item3) = (item1, item2, item3);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private (T1, T2, T3) ToValueTuple()
=> (Item1, Item2, Item3);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1, T2, T3> value)
=> value.AsVariant();
}
public sealed class Struct<T1,T2,T3,T4> : IDBusWritable
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public T4 Item4;
public Struct(T1 item1, T2 item2, T3 item3, T4 item4)
{
TypeModel.EnsureSupportedVariantType<T1>();
TypeModel.EnsureSupportedVariantType<T2>();
TypeModel.EnsureSupportedVariantType<T3>();
TypeModel.EnsureSupportedVariantType<T4>();
(Item1, Item2, Item3, Item4) = (item1, item2, item3, item4);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private (T1, T2, T3, T4) ToValueTuple()
=> (Item1, Item2, Item3, Item4);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1, T2, T3, T4> value)
=> value.AsVariant();
}
public sealed class Struct<T1,T2,T3,T4,T5> : IDBusWritable
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public T4 Item4;
public T5 Item5;
public Struct(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5)
{
TypeModel.EnsureSupportedVariantType<T1>();
TypeModel.EnsureSupportedVariantType<T2>();
TypeModel.EnsureSupportedVariantType<T3>();
TypeModel.EnsureSupportedVariantType<T4>();
TypeModel.EnsureSupportedVariantType<T5>();
(Item1, Item2, Item3, Item4, Item5) = (item1, item2, item3, item4, item5);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private (T1, T2, T3, T4, T5) ToValueTuple()
=> (Item1, Item2, Item3, Item4, Item5);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1, T2, T3, T4, T5> value)
=> value.AsVariant();
}
public sealed class Struct<T1,T2,T3,T4,T5,T6> : IDBusWritable
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public T4 Item4;
public T5 Item5;
public T6 Item6;
public Struct(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6)
{
TypeModel.EnsureSupportedVariantType<T1>();
TypeModel.EnsureSupportedVariantType<T2>();
TypeModel.EnsureSupportedVariantType<T3>();
TypeModel.EnsureSupportedVariantType<T4>();
TypeModel.EnsureSupportedVariantType<T5>();
TypeModel.EnsureSupportedVariantType<T6>();
(Item1, Item2, Item3, Item4, Item5, Item6) = (item1, item2, item3, item4, item5, item6);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private (T1, T2, T3, T4, T5, T6) ToValueTuple()
=> (Item1, Item2, Item3, Item4, Item5, Item6);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1, T2, T3, T4, T5, T6> value)
=> value.AsVariant();
}
public sealed class Struct<T1,T2,T3,T4,T5,T6,T7> : IDBusWritable
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public T4 Item4;
public T5 Item5;
public T6 Item6;
public T7 Item7;
public Struct(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7)
{
TypeModel.EnsureSupportedVariantType<T1>();
TypeModel.EnsureSupportedVariantType<T2>();
TypeModel.EnsureSupportedVariantType<T3>();
TypeModel.EnsureSupportedVariantType<T4>();
TypeModel.EnsureSupportedVariantType<T5>();
TypeModel.EnsureSupportedVariantType<T6>();
TypeModel.EnsureSupportedVariantType<T7>();
(Item1, Item2, Item3, Item4, Item5, Item6, Item7) = (item1, item2, item3, item4, item5, item6, item7);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private (T1, T2, T3, T4, T5, T6, T7) ToValueTuple()
=> (Item1, Item2, Item3, Item4, Item5, Item6, Item7);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1, T2, T3, T4, T5, T6, T7> value)
=> value.AsVariant();
}
public sealed class Struct<T1,T2,T3,T4,T5,T6,T7,T8> : IDBusWritable
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public T4 Item4;
public T5 Item5;
public T6 Item6;
public T7 Item7;
public T8 Item8;
public Struct(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8)
{
TypeModel.EnsureSupportedVariantType<T1>();
TypeModel.EnsureSupportedVariantType<T2>();
TypeModel.EnsureSupportedVariantType<T3>();
TypeModel.EnsureSupportedVariantType<T4>();
TypeModel.EnsureSupportedVariantType<T5>();
TypeModel.EnsureSupportedVariantType<T6>();
TypeModel.EnsureSupportedVariantType<T7>();
TypeModel.EnsureSupportedVariantType<T8>();
(Item1, Item2, Item3, Item4, Item5, Item6, Item7, Item8) = (item1, item2, item3, item4, item5, item6, item7, item8);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private (T1, T2, T3, T4, T5, T6, T7, T8) ToValueTuple()
=> (Item1, Item2, Item3, Item4, Item5, Item6, Item7, Item8);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1, T2, T3, T4, T5, T6, T7, T8> value)
=> value.AsVariant();
}
public sealed class Struct<T1,T2,T3,T4,T5,T6,T7,T8,T9> : IDBusWritable
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public T4 Item4;
public T5 Item5;
public T6 Item6;
public T7 Item7;
public T8 Item8;
public T9 Item9;
public Struct(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9)
{
TypeModel.EnsureSupportedVariantType<T1>();
TypeModel.EnsureSupportedVariantType<T2>();
TypeModel.EnsureSupportedVariantType<T3>();
TypeModel.EnsureSupportedVariantType<T4>();
TypeModel.EnsureSupportedVariantType<T5>();
TypeModel.EnsureSupportedVariantType<T6>();
TypeModel.EnsureSupportedVariantType<T7>();
TypeModel.EnsureSupportedVariantType<T8>();
TypeModel.EnsureSupportedVariantType<T9>();
(Item1, Item2, Item3, Item4, Item5, Item6, Item7, Item8, Item9) = (item1, item2, item3, item4, item5, item6, item7, item8, item9);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private (T1, T2, T3, T4, T5, T6, T7, T8, T9) ToValueTuple()
=> (Item1, Item2, Item3, Item4, Item5, Item6, Item7, Item8, Item9);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9> value)
=> value.AsVariant();
}
public sealed class Struct<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10> : IDBusWritable
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
where T10 : notnull
{
public T1 Item1;
public T2 Item2;
public T3 Item3;
public T4 Item4;
public T5 Item5;
public T6 Item6;
public T7 Item7;
public T8 Item8;
public T9 Item9;
public T10 Item10;
public Struct(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8, T9 item9, T10 item10)
{
TypeModel.EnsureSupportedVariantType<T1>();
TypeModel.EnsureSupportedVariantType<T2>();
TypeModel.EnsureSupportedVariantType<T3>();
TypeModel.EnsureSupportedVariantType<T4>();
TypeModel.EnsureSupportedVariantType<T5>();
TypeModel.EnsureSupportedVariantType<T6>();
TypeModel.EnsureSupportedVariantType<T7>();
TypeModel.EnsureSupportedVariantType<T8>();
TypeModel.EnsureSupportedVariantType<T9>();
TypeModel.EnsureSupportedVariantType<T10>();
(Item1, Item2, Item3, Item4, Item5, Item6, Item7, Item8, Item9, Item10) = (item1, item2, item3, item4, item5, item6, item7, item8, item9, item10);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026")] // this is a supported variant type.
void IDBusWritable.WriteTo(ref MessageWriter writer)
=> writer.WriteStruct(ToValueTuple());
private (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) ToValueTuple()
=> (Item1, Item2, Item3, Item4, Item5, Item6, Item7, Item8, Item9, Item10);
public Variant AsVariant()
=> Variant.FromStruct(this);
public static implicit operator Variant(Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> value)
=> value.AsVariant();
}

32
src/Linux/Tmds.DBus.Protocol/ThrowHelper.cs

@ -0,0 +1,32 @@
namespace Tmds.DBus.Protocol;
static class ThrowHelper
{
public static void ThrowIfDisposed(bool condition, object instance)
{
if (condition)
{
ThrowObjectDisposedException(instance);
}
}
private static void ThrowObjectDisposedException(object instance)
{
throw new ObjectDisposedException(instance?.GetType().FullName);
}
public static void ThrowIndexOutOfRange()
{
throw new IndexOutOfRangeException();
}
public static void ThrowNotSupportedException()
{
throw new NotSupportedException();
}
internal static void ThrowUnexpectedSignature(ReadOnlySpan<byte> signature, string expected)
{
throw new ProtocolException($"Expected signature '{expected}' does not match actual signature '{Encoding.UTF8.GetString(signature)}'.");
}
}

22
src/Linux/Tmds.DBus.Protocol/Tmds.DBus.Protocol.csproj

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks);netstandard2.0</TargetFrameworks>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="../../Shared/IsExternalInit.cs" Link="IsExternalInit.cs"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.IO.Pipelines" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETStandard'">
<PackageReference Include="System.Threading.Channels" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
</ItemGroup>
</Project>

134
src/Linux/Tmds.DBus.Protocol/TypeModel.Dynamic.cs

@ -0,0 +1,134 @@
namespace Tmds.DBus.Protocol;
// Code in this file is not trimmer friendly.
#pragma warning disable IL3050
#pragma warning disable IL2070
static partial class TypeModel
{
private static DBusType GetTypeAlignmentDynamic<T>()
{
if (typeof(T).IsArray)
{
return DBusType.Array;
}
else if (ExtractGenericInterface(typeof(T), typeof(System.Collections.Generic.IEnumerable<>)) != null)
{
return DBusType.Array;
}
else
{
return DBusType.Struct;
}
}
private static int AppendTypeSignatureDynamic(Type type, Span<byte> signature)
{
Type? extractedType;
if (type == typeof(object))
{
signature[0] = (byte)DBusType.Variant;
return 1;
}
else if (type.IsArray)
{
int bytesWritten = 0;
signature[bytesWritten++] = (byte)DBusType.Array;
bytesWritten += AppendTypeSignature(type.GetElementType()!, signature.Slice(bytesWritten));
return bytesWritten;
}
else if (type.FullName!.StartsWith("System.ValueTuple"))
{
int bytesWritten = 0;
signature[bytesWritten++] = (byte)'(';
Type[] typeArguments = type.GenericTypeArguments;
do
{
for (int i = 0; i < typeArguments.Length; i++)
{
if (i == 7)
{
break;
}
bytesWritten += AppendTypeSignature(typeArguments[i], signature.Slice(bytesWritten));
}
if (typeArguments.Length == 8)
{
typeArguments = typeArguments[7].GenericTypeArguments;
}
else
{
break;
}
} while (true);
signature[bytesWritten++] = (byte)')';
return bytesWritten;
}
else if ((extractedType = TypeModel.ExtractGenericInterface(type, typeof(IDictionary<,>))) != null)
{
int bytesWritten = 0;
signature[bytesWritten++] = (byte)'a';
signature[bytesWritten++] = (byte)'{';
bytesWritten += AppendTypeSignature(extractedType.GenericTypeArguments[0], signature.Slice(bytesWritten));
bytesWritten += AppendTypeSignature(extractedType.GenericTypeArguments[1], signature.Slice(bytesWritten));
signature[bytesWritten++] = (byte)'}';
return bytesWritten;
}
ThrowNotSupportedType(type);
return 0;
}
public static Type? ExtractGenericInterface(Type queryType, Type interfaceType)
{
if (IsGenericInstantiation(queryType, interfaceType))
{
return queryType;
}
return GetGenericInstantiation(queryType, interfaceType);
}
private static bool IsGenericInstantiation(Type candidate, Type interfaceType)
{
return
candidate.IsGenericType &&
candidate.GetGenericTypeDefinition() == interfaceType;
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070")]
private static Type? GetGenericInstantiation(Type queryType, Type interfaceType)
{
Type? bestMatch = null;
var interfaces = queryType.GetInterfaces();
foreach (var @interface in interfaces)
{
if (IsGenericInstantiation(@interface, interfaceType))
{
if (bestMatch == null)
{
bestMatch = @interface;
}
else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0)
{
bestMatch = @interface;
}
}
}
if (bestMatch != null)
{
return bestMatch;
}
var baseType = queryType?.BaseType;
if (baseType == null)
{
return null;
}
else
{
return GetGenericInstantiation(baseType, interfaceType);
}
}
}

359
src/Linux/Tmds.DBus.Protocol/TypeModel.cs

@ -0,0 +1,359 @@
namespace Tmds.DBus.Protocol;
static partial class TypeModel
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DBusType GetTypeAlignment<T>()
{
// TODO (perf): add caching.
if (typeof(T) == typeof(byte))
{
return DBusType.Byte;
}
else if (typeof(T) == typeof(bool))
{
return DBusType.Bool;
}
else if (typeof(T) == typeof(short))
{
return DBusType.Int16;
}
else if (typeof(T) == typeof(ushort))
{
return DBusType.UInt16;
}
else if (typeof(T) == typeof(int))
{
return DBusType.Int32;
}
else if (typeof(T) == typeof(uint))
{
return DBusType.UInt32;
}
else if (typeof(T) == typeof(long))
{
return DBusType.Int64;
}
else if (typeof(T) == typeof(ulong))
{
return DBusType.UInt64;
}
else if (typeof(T) == typeof(double))
{
return DBusType.Double;
}
else if (typeof(T) == typeof(string))
{
return DBusType.String;
}
else if (typeof(T) == typeof(ObjectPath))
{
return DBusType.ObjectPath;
}
else if (typeof(T) == typeof(Signature))
{
return DBusType.Signature;
}
else if (typeof(T) == typeof(Variant))
{
return DBusType.Variant;
}
else if (typeof(T).IsConstructedGenericType)
{
Type type = typeof(T).GetGenericTypeDefinition();
if (type == typeof(Dict<,>))
{
return DBusType.Array;
}
else if (type == typeof(Array<>))
{
return DBusType.Array;
}
else if (type == typeof(Struct<>) ||
type == typeof(Struct<,>) ||
type == typeof(Struct<,,>) ||
type == typeof(Struct<,,,>) ||
type == typeof(Struct<,,,,>) ||
type == typeof(Struct<,,,,,>) ||
type == typeof(Struct<,,,,,,>) ||
type == typeof(Struct<,,,,,,,>) ||
type == typeof(Struct<,,,,,,,,>) ||
type == typeof(Struct<,,,,,,,,,>))
{
return DBusType.Struct;
}
}
else if (typeof(T).IsAssignableTo(typeof(SafeHandle)))
{
return DBusType.UnixFd;
}
else if (Feature.IsDynamicCodeEnabled)
{
return GetTypeAlignmentDynamic<T>();
}
ThrowNotSupportedType(typeof(T));
return default;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnsureSupportedVariantType<T>()
{
if (typeof(T) == typeof(byte))
{ }
else if (typeof(T) == typeof(bool))
{ }
else if (typeof(T) == typeof(short))
{ }
else if (typeof(T) == typeof(ushort))
{ }
else if (typeof(T) == typeof(int))
{ }
else if (typeof(T) == typeof(uint))
{ }
else if (typeof(T) == typeof(long))
{ }
else if (typeof(T) == typeof(ulong))
{ }
else if (typeof(T) == typeof(double))
{ }
else if (typeof(T) == typeof(string))
{ }
else if (typeof(T) == typeof(ObjectPath))
{ }
else if (typeof(T) == typeof(Signature))
{ }
else if (typeof(T) == typeof(Variant))
{ }
else if (typeof(T).IsConstructedGenericType)
{
Type type = typeof(T).GetGenericTypeDefinition();
if (type == typeof(Dict<,>) ||
type == typeof(Array<>) ||
type == typeof(Struct<>) ||
type == typeof(Struct<,>) ||
type == typeof(Struct<,,>) ||
type == typeof(Struct<,,,>) ||
type == typeof(Struct<,,,,>) ||
type == typeof(Struct<,,,,,>) ||
type == typeof(Struct<,,,,,,>) ||
type == typeof(Struct<,,,,,,,>) ||
type == typeof(Struct<,,,,,,,,>) ||
type == typeof(Struct<,,,,,,,,,>))
{
foreach (var innerType in type.GenericTypeArguments)
{
EnsureSupportedVariantType(innerType);
}
}
else
{
ThrowNotSupportedType(typeof(T));
}
}
else if (typeof(T).IsAssignableTo(typeof(SafeHandle)))
{ }
else
{
ThrowNotSupportedType(typeof(T));
}
}
private static void EnsureSupportedVariantType(Type type)
{
if (type == typeof(byte))
{ }
else if (type == typeof(bool))
{ }
else if (type == typeof(short))
{ }
else if (type == typeof(ushort))
{ }
else if (type == typeof(int))
{ }
else if (type == typeof(uint))
{ }
else if (type == typeof(long))
{ }
else if (type == typeof(ulong))
{ }
else if (type == typeof(double))
{ }
else if (type == typeof(string))
{ }
else if (type == typeof(ObjectPath))
{ }
else if (type == typeof(Signature))
{ }
else if (type == typeof(Variant))
{ }
else if (type.IsConstructedGenericType)
{
Type typeDefinition = type.GetGenericTypeDefinition();
if (typeDefinition == typeof(Dict<,>) ||
typeDefinition == typeof(Array<>) ||
typeDefinition == typeof(Struct<>) ||
typeDefinition == typeof(Struct<,>) ||
typeDefinition == typeof(Struct<,,>) ||
typeDefinition == typeof(Struct<,,,>) ||
typeDefinition == typeof(Struct<,,,,>) ||
typeDefinition == typeof(Struct<,,,,,>) ||
typeDefinition == typeof(Struct<,,,,,,>) ||
typeDefinition == typeof(Struct<,,,,,,,>) ||
typeDefinition == typeof(Struct<,,,,,,,,>) ||
typeDefinition == typeof(Struct<,,,,,,,,,>))
{
foreach (var innerType in typeDefinition.GenericTypeArguments)
{
EnsureSupportedVariantType(innerType);
}
}
else
{
ThrowNotSupportedType(type);
}
}
else if (type.IsAssignableTo(typeof(SafeHandle)))
{ }
else
{
ThrowNotSupportedType(type);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Utf8Span GetSignature<T>(scoped Span<byte> buffer)
{
Debug.Assert(buffer.Length >= ProtocolConstants.MaxSignatureLength);
int bytesWritten = AppendTypeSignature(typeof(T), buffer);
return new Utf8Span(buffer.Slice(0, bytesWritten).ToArray());
}
private static int AppendTypeSignature(Type type, Span<byte> signature)
{
if (type == typeof(byte))
{
signature[0] = (byte)DBusType.Byte;
return 1;
}
else if (type == typeof(bool))
{
signature[0] = (byte)DBusType.Bool;
return 1;
}
else if (type == typeof(short))
{
signature[0] = (byte)DBusType.Int16;
return 1;
}
else if (type == typeof(ushort))
{
signature[0] = (byte)DBusType.UInt16;
return 1;
}
else if (type == typeof(int))
{
signature[0] = (byte)DBusType.Int32;
return 1;
}
else if (type == typeof(uint))
{
signature[0] = (byte)DBusType.UInt32;
return 1;
}
else if (type == typeof(long))
{
signature[0] = (byte)DBusType.Int64;
return 1;
}
else if (type == typeof(ulong))
{
signature[0] = (byte)DBusType.UInt64;
return 1;
}
else if (type == typeof(double))
{
signature[0] = (byte)DBusType.Double;
return 1;
}
else if (type == typeof(string))
{
signature[0] = (byte)DBusType.String;
return 1;
}
else if (type == typeof(ObjectPath))
{
signature[0] = (byte)DBusType.ObjectPath;
return 1;
}
else if (type == typeof(Signature))
{
signature[0] = (byte)DBusType.Signature;
return 1;
}
else if (type == typeof(Variant))
{
signature[0] = (byte)DBusType.Variant;
return 1;
}
else if (type.IsConstructedGenericType)
{
Type genericTypeDefinition = type.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(Dict<,>))
{
int length = 0;
signature[length++] = (byte)'a';
signature[length++] = (byte)'{';
length += AppendTypeSignature(type.GenericTypeArguments[0], signature.Slice(length));
length += AppendTypeSignature(type.GenericTypeArguments[1], signature.Slice(length));
signature[length++] = (byte)'}';
return length;
}
else if (genericTypeDefinition == typeof(Array<>))
{
int length = 0;
signature[length++] = (byte)'a';
length += AppendTypeSignature(type.GenericTypeArguments[0], signature.Slice(length));
return length;
}
else if (genericTypeDefinition == typeof(Struct<>) ||
genericTypeDefinition == typeof(Struct<,>) ||
genericTypeDefinition == typeof(Struct<,,>) ||
genericTypeDefinition == typeof(Struct<,,,>) ||
genericTypeDefinition == typeof(Struct<,,,,>) ||
genericTypeDefinition == typeof(Struct<,,,,,>) ||
genericTypeDefinition == typeof(Struct<,,,,,,>) ||
genericTypeDefinition == typeof(Struct<,,,,,,,>) ||
genericTypeDefinition == typeof(Struct<,,,,,,,,>) ||
genericTypeDefinition == typeof(Struct<,,,,,,,,,>))
{
int length = 0;
signature[length++] = (byte)'(';
foreach (var innerType in type.GenericTypeArguments)
{
length += AppendTypeSignature(innerType, signature.Slice(length));
}
signature[length++] = (byte)')';
return length;
}
}
else if (type.IsAssignableTo(typeof(SafeHandle)))
{
signature[0] = (byte)DBusType.UnixFd;
return 1;
}
else if (Feature.IsDynamicCodeEnabled)
{
return AppendTypeSignatureDynamic(type, signature);
}
ThrowNotSupportedType(type);
return 0;
}
private static void ThrowNotSupportedType(Type type)
{
throw new NotSupportedException($"Unsupported type {type.FullName}");
}
}

244
src/Linux/Tmds.DBus.Protocol/UnixFdCollection.cs

@ -0,0 +1,244 @@
using System.Collections;
namespace Tmds.DBus.Protocol;
sealed class UnixFdCollection : IReadOnlyList<SafeHandle>, IDisposable
{
private IntPtr InvalidRawHandle => new IntPtr(-1);
private readonly List<(SafeHandle? Handle, bool CanRead)>? _handles;
private readonly List<(IntPtr RawHandle, bool CanRead)>? _rawHandles;
// The gate guards someone removing handles while the UnixFdCollection gets disposed by the message.
// We don't need to lock it while adding handles, or reading them to send them.
private readonly object _gate;
private bool _disposed;
internal bool IsRawHandleCollection => _rawHandles is not null;
internal UnixFdCollection(bool isRawHandleCollection = true)
{
if (isRawHandleCollection)
{
_gate = _rawHandles = new();
}
else
{
_gate = _handles = new();
}
}
internal int AddHandle(IntPtr handle)
{
_rawHandles!.Add((handle, true));
return _rawHandles.Count - 1;
}
internal void AddHandle(SafeHandle handle)
{
if (handle is null)
{
throw new ArgumentNullException(nameof(handle));
}
_handles!.Add((handle, true));
}
public int Count => _rawHandles is not null ? _rawHandles.Count : _handles!.Count;
// Used to get the file descriptors to send them over the socket.
public SafeHandle this[int index] => _handles![index].Handle!;
// We remain responsible for disposing the handle.
public IntPtr ReadHandleRaw(int index)
{
lock (_gate)
{
if (_disposed)
{
ThrowDisposed();
}
if (_rawHandles is not null)
{
(IntPtr rawHandle, bool CanRead) = _rawHandles[index];
if (!CanRead)
{
ThrowHandleAlreadyRead();
}
// Handle can no longer be read, but we are still responible for disposing it.
_rawHandles[index] = (rawHandle, false);
return rawHandle;
}
else
{
Debug.Assert(_handles is not null);
(SafeHandle? handle, bool CanRead) = _handles![index];
if (!CanRead)
{
ThrowHandleAlreadyRead();
}
// Handle can no longer be read, but we are still responible for disposing it.
_handles[index] = (handle, false);
return handle!.DangerousGetHandle();
}
}
}
private void ThrowHandleAlreadyRead()
{
throw new InvalidOperationException("The handle was already read.");
}
private void ThrowDisposed()
{
throw new ObjectDisposedException(typeof(UnixFdCollection).FullName);
}
public T? ReadHandle<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>(int index) where T : SafeHandle
=> ReadHandleGeneric<T>(index);
// The caller of this method owns the handle and is responsible for Disposing it.
internal T? ReadHandleGeneric<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>(int index)
{
lock (_gate)
{
if (_disposed)
{
ThrowDisposed();
}
if (_rawHandles is not null)
{
(IntPtr rawHandle, bool CanRead) = _rawHandles[index];
if (!CanRead)
{
ThrowHandleAlreadyRead();
}
#if NET6_0_OR_GREATER
SafeHandle handle = (Activator.CreateInstance<T>() as SafeHandle)!;
Marshal.InitHandle(handle, rawHandle);
#else
SafeHandle? handle = (SafeHandle?)Activator.CreateInstance(typeof(T), new object[] { rawHandle, true });
#endif
_rawHandles[index] = (InvalidRawHandle, false);
return (T?)(object?)handle;
}
else
{
Debug.Assert(_handles is not null);
(SafeHandle? handle, bool CanRead) = _handles![index];
if (!CanRead)
{
ThrowHandleAlreadyRead();
}
if (handle is not T)
{
throw new ArgumentException($"Requested handle type {typeof(T).FullName} does not matched stored type {handle?.GetType().FullName}.");
}
_handles[index] = (null, false);
return (T)(object)handle;
}
}
}
public IEnumerator<SafeHandle> GetEnumerator()
{
throw new NotSupportedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotSupportedException();
}
public void DisposeHandles(int count = -1)
{
if (count != 0)
{
DisposeHandles(true, count);
}
}
public void Dispose()
{
lock (_gate)
{
if (_disposed)
{
return;
}
_disposed = true;
DisposeHandles(true);
}
}
~UnixFdCollection()
{
DisposeHandles(false);
}
private void DisposeHandles(bool disposing, int count = -1)
{
if (count == -1)
{
count = Count;
}
if (disposing)
{
if (_handles is not null)
{
for (int i = 0; i < count; i++)
{
var handle = _handles[i];
if (handle.Handle is not null)
{
handle.Handle.Dispose();
}
}
_handles.RemoveRange(0, count);
}
}
else
{
if (_rawHandles is not null)
{
for (int i = 0; i < count; i++)
{
var handle = _rawHandles[i];
if (handle.RawHandle != InvalidRawHandle)
{
close(handle.RawHandle.ToInt32());
}
}
_rawHandles.RemoveRange(0, count);
}
}
}
[DllImport("libc")]
private static extern void close(int fd);
internal void MoveTo(UnixFdCollection handles, int count)
{
if (handles.IsRawHandleCollection != IsRawHandleCollection)
{
throw new ArgumentException("Handle collections are not compatible.");
}
if (handles.IsRawHandleCollection)
{
for (int i = 0; i < count; i++)
{
handles._rawHandles!.Add(_rawHandles![i]);
}
_rawHandles!.RemoveRange(0, count);
}
else
{
for (int i = 0; i < count; i++)
{
handles._handles!.Add(_handles![i]);
}
_handles!.RemoveRange(0, count);
}
}
}

20
src/Linux/Tmds.DBus.Protocol/Utf8Span.cs

@ -0,0 +1,20 @@
namespace Tmds.DBus.Protocol;
public ref struct Utf8Span
{
private ReadOnlySpan<byte> _buffer;
public ReadOnlySpan<byte> Span => _buffer;
public bool IsEmpty => _buffer.IsEmpty;
public Utf8Span(ReadOnlySpan<byte> value) => _buffer = value;
public static implicit operator Utf8Span(ReadOnlySpan<byte> value) => new Utf8Span(value);
public static implicit operator Utf8Span(Span<byte> value) => new Utf8Span(value);
public static implicit operator ReadOnlySpan<byte>(Utf8Span value) => value._buffer;
public override string ToString() => Encoding.UTF8.GetString(_buffer);
}

451
src/Linux/Tmds.DBus.Protocol/Variant.cs

@ -0,0 +1,451 @@
namespace Tmds.DBus.Protocol;
// This type is for writing so we don't need to add
// DynamicallyAccessedMemberTypes.PublicParameterlessConstructor.
#pragma warning disable IL2091
public readonly struct Variant
{
private static readonly object Int64Type = DBusType.Int64;
private static readonly object UInt64Type = DBusType.UInt64;
private static readonly object DoubleType = DBusType.Double;
private readonly object? _o;
private readonly long _l;
private const int TypeShift = 8 * 7;
//private const int SignatureFirstShift = 8 * 6;
private const long StripTypeMask = ~(0xffL << TypeShift);
private DBusType Type
=> DetermineType();
public Variant(byte value)
{
_l = value | ((long)DBusType.Byte << TypeShift);
_o = null;
}
public Variant(bool value)
{
_l = (value ? 1L : 0) | ((long)DBusType.Bool << TypeShift);
_o = null;
}
public Variant(short value)
{
_l = (ushort)value | ((long)DBusType.Int16 << TypeShift);
_o = null;
}
public Variant(ushort value)
{
_l = value | ((long)DBusType.UInt16 << TypeShift);
_o = null;
}
public Variant(int value)
{
_l = (uint)value | ((long)DBusType.Int32 << TypeShift);
_o = null;
}
public Variant(uint value)
{
_l = value | ((long)DBusType.UInt32 << TypeShift);
_o = null;
}
public Variant(long value)
{
_l = value;
_o = Int64Type;
}
public Variant(ulong value)
{
_l = (long)value;
_o = UInt64Type;
}
internal unsafe Variant(double value)
{
_l = *(long*)&value;
_o = DoubleType;
}
public Variant(string value)
{
_l = (long)DBusType.String << TypeShift;
_o = value ?? throw new ArgumentNullException(nameof(value));
}
public Variant(ObjectPath value)
{
_l = (long)DBusType.ObjectPath << TypeShift;
string s = value.ToString();
if (s.Length == 0)
{
throw new ArgumentException(nameof(value));
}
_o = s;
}
public Variant(Signature value)
{
_l = (long)DBusType.Signature << TypeShift;
string s = value.ToString();
if (s.Length == 0)
{
throw new ArgumentException(nameof(value));
}
_o = s;
}
public Variant(SafeHandle value)
{
_l = (long)DBusType.UnixFd << TypeShift;
_o = value ?? throw new ArgumentNullException(nameof(value));
}
public static implicit operator Variant(byte value)
=> new Variant(value);
public static implicit operator Variant(bool value)
=> new Variant(value);
public static implicit operator Variant(short value)
=> new Variant(value);
public static implicit operator Variant(ushort value)
=> new Variant(value);
public static implicit operator Variant(int value)
=> new Variant(value);
public static implicit operator Variant(uint value)
=> new Variant(value);
public static implicit operator Variant(long value)
=> new Variant(value);
public static implicit operator Variant(ulong value)
=> new Variant(value);
public static implicit operator Variant(double value)
=> new Variant(value);
public static implicit operator Variant(string value)
=> new Variant(value);
public static implicit operator Variant(ObjectPath value)
=> new Variant(value);
public static implicit operator Variant(Signature value)
=> new Variant(value);
public static implicit operator Variant(SafeHandle value)
=> new Variant(value);
public static Variant FromArray<T>(Array<T> value) where T : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Array<T>>(buffer), value);
}
public static Variant FromDict<TKey, TValue>(Dict<TKey, TValue> value)
where TKey : notnull
where TValue : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Dict<TKey, TValue>>(buffer), value);
}
public static Variant FromStruct<T1>(Struct<T1> value)
where T1 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1>>(buffer), value);
}
public static Variant FromStruct<T1, T2>(Struct<T1, T2> value)
where T1 : notnull
where T2 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1, T2>>(buffer), value);
}
public static Variant FromStruct<T1, T2, T3>(Struct<T1, T2, T3> value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1, T2, T3>>(buffer), value);
}
public static Variant FromStruct<T1, T2, T3, T4>(Struct<T1, T2, T3, T4> value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1, T2, T3, T4>>(buffer), value);
}
public static Variant FromStruct<T1, T2, T3, T4, T5>(Struct<T1, T2, T3, T4, T5> value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5>>(buffer), value);
}
public static Variant FromStruct<T1, T2, T3, T4, T5, T6>(Struct<T1, T2, T3, T4, T5, T6> value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6>>(buffer), value);
}
public static Variant FromStruct<T1, T2, T3, T4, T5, T6, T7>(Struct<T1, T2, T3, T4, T5, T6, T7> value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6, T7>>(buffer), value);
}
public static Variant FromStruct<T1, T2, T3, T4, T5, T6, T7, T8>(Struct<T1, T2, T3, T4, T5, T6, T7, T8> value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6, T7, T8>>(buffer), value);
}
public static Variant FromStruct<T1, T2, T3, T4, T5, T6, T7, T8, T9>(Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9> value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9>>(buffer), value);
}
public static Variant FromStruct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> value)
where T1 : notnull
where T2 : notnull
where T3 : notnull
where T4 : notnull
where T5 : notnull
where T6 : notnull
where T7 : notnull
where T8 : notnull
where T9 : notnull
where T10 : notnull
{
Span<byte> buffer = stackalloc byte[ProtocolConstants.MaxSignatureLength];
return new Variant(TypeModel.GetSignature<Struct<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>>(buffer), value);
}
// Dictionary, Struct, Array.
private unsafe Variant(Utf8Span signature, IDBusWritable value)
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
// Store the signature in the long if it is large enough.
if (signature.Span.Length <= 8)
{
long l = 0;
Span<byte> span = new Span<byte>(&l, 8);
signature.Span.CopyTo(span);
if (BitConverter.IsLittleEndian)
{
l = BinaryPrimitives.ReverseEndianness(l);
}
_l = l;
_o = value;
}
else
{
_l = (long)signature.Span[0] << TypeShift;
_o = new ValueTuple<byte[], IDBusWritable>(signature.Span.ToArray(), value);
}
}
private byte GetByte()
{
DebugAssertTypeIs(DBusType.Byte);
return (byte)(_l & StripTypeMask);
}
private bool GetBool()
{
DebugAssertTypeIs(DBusType.Bool);
return (_l & StripTypeMask) != 0;
}
private short GetInt16()
{
DebugAssertTypeIs(DBusType.Int16);
return (short)(_l & StripTypeMask);
}
private ushort GetUInt16()
{
DebugAssertTypeIs(DBusType.UInt16);
return (ushort)(_l & StripTypeMask);
}
private int GetInt32()
{
DebugAssertTypeIs(DBusType.Int32);
return (int)(_l & StripTypeMask);
}
private uint GetUInt32()
{
DebugAssertTypeIs(DBusType.UInt32);
return (uint)(_l & StripTypeMask);
}
private long GetInt64()
{
DebugAssertTypeIs(DBusType.Int64);
return _l;
}
private ulong GetUInt64()
{
DebugAssertTypeIs(DBusType.UInt64);
return (ulong)(_l);
}
private unsafe double GetDouble()
{
DebugAssertTypeIs(DBusType.Double);
double value;
*(long*)&value = _l;
return value;
}
private string GetString()
{
DebugAssertTypeIs(DBusType.String);
return (_o as string)!;
}
private string GetObjectPath()
{
DebugAssertTypeIs(DBusType.ObjectPath);
return (_o as string)!;
}
private string GetSignature()
{
DebugAssertTypeIs(DBusType.Signature);
return (_o as string)!;
}
private SafeHandle GetUnixFd()
{
DebugAssertTypeIs(DBusType.UnixFd);
return (_o as SafeHandle)!;
}
private void DebugAssertTypeIs(DBusType expected)
{
Debug.Assert(Type == expected);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private DBusType DetermineType()
{
// For most types, we store the DBusType in the highest byte of the long.
// Except for some types, like Int64, for which we store the value allocation free
// in the long, and use the object field to store the type.
DBusType type = (DBusType)(_l >> TypeShift);
if (_o is not null)
{
if (_o.GetType() == typeof(DBusType))
{
type = (DBusType)_o;
}
}
return type;
}
internal unsafe void WriteTo(ref MessageWriter writer)
{
switch (Type)
{
case DBusType.Byte:
writer.WriteVariantByte(GetByte());
break;
case DBusType.Bool:
writer.WriteVariantBool(GetBool());
break;
case DBusType.Int16:
writer.WriteVariantInt16(GetInt16());
break;
case DBusType.UInt16:
writer.WriteVariantUInt16(GetUInt16());
break;
case DBusType.Int32:
writer.WriteVariantInt32(GetInt32());
break;
case DBusType.UInt32:
writer.WriteVariantUInt32(GetUInt32());
break;
case DBusType.Int64:
writer.WriteVariantInt64(GetInt64());
break;
case DBusType.UInt64:
writer.WriteVariantUInt64(GetUInt64());
break;
case DBusType.Double:
writer.WriteVariantDouble(GetDouble());
break;
case DBusType.String:
writer.WriteVariantString(GetString());
break;
case DBusType.ObjectPath:
writer.WriteVariantObjectPath(GetObjectPath());
break;
case DBusType.Signature:
writer.WriteVariantSignature(GetSignature());
break;
case DBusType.UnixFd:
writer.WriteVariantHandle(GetUnixFd());
break;
case DBusType.Array:
case DBusType.Struct:
Utf8Span signature;
IDBusWritable writable;
if ((_l << 8) == 0)
{
// The signature is stored in the object.
var o = (ValueTuple<byte[], IDBusWritable>)_o!;
signature = new Utf8Span(o.Item1);
writable = o.Item2;
}
else
{
// The signature is stored in _l.
long l = _l;
if (BitConverter.IsLittleEndian)
{
l = BinaryPrimitives.ReverseEndianness(l);
}
Span<byte> span = new Span<byte>(&l, 8);
int length = span.IndexOf((byte)0);
if (length == -1)
{
length = 8;
}
signature = new Utf8Span(span.Slice(0, length));
writable = (_o as IDBusWritable)!;
}
writer.WriteSignature(signature);
writable.WriteTo(ref writer);
break;
default:
throw new InvalidOperationException($"Cannot write Variant of type {Type}.");
}
}
}

41
src/Linux/Tmds.DBus.Protocol/VariantExtensions.cs

@ -0,0 +1,41 @@
namespace Tmds.DBus.Protocol;
// This type is for writing so we don't need to add
// DynamicallyAccessedMemberTypes.PublicParameterlessConstructor.
#pragma warning disable IL2091
public static class VariantExtensions
{
public static Variant AsVariant(this byte value)
=> new Variant(value);
public static Variant AsVariant(this bool value)
=> new Variant(value);
public static Variant AsVariant(this short value)
=> new Variant(value);
public static Variant AsVariant(this ushort value)
=> new Variant(value);
public static Variant AsVariant(this int value)
=> new Variant(value);
public static Variant AsVariant(this uint value)
=> new Variant(value);
public static Variant AsVariant(this long value)
=> new Variant(value);
public static Variant AsVariant(this ulong value)
=> new Variant(value);
public static Variant AsVariant(this double value)
=> new Variant(value);
public static Variant AsVariant(this string value)
=> new Variant(value);
public static Variant AsVariant(this SafeHandle value)
=> new Variant(value);
}

795
src/Linux/Tmds.DBus.Protocol/VariantValue.cs

@ -0,0 +1,795 @@
namespace Tmds.DBus.Protocol;
public readonly struct VariantValue : IEquatable<VariantValue>
{
private static readonly object Int64Type = VariantValueType.Int64;
private static readonly object UInt64Type = VariantValueType.UInt64;
private static readonly object DoubleType = VariantValueType.Double;
private readonly object? _o;
private readonly long _l;
private const int TypeShift = 8 * 7;
private const int ArrayItemTypeShift = 8 * 0;
private const int DictionaryKeyTypeShift = 8 * 0;
private const int DictionaryValueTypeShift = 8 * 1;
private const long StripTypeMask = ~(0xffL << TypeShift);
private const long ArrayOfByte = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.Byte << ArrayItemTypeShift);
private const long ArrayOfInt16 = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.Int16 << ArrayItemTypeShift);
private const long ArrayOfUInt16 = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.UInt16 << ArrayItemTypeShift);
private const long ArrayOfInt32 = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.Int32 << ArrayItemTypeShift);
private const long ArrayOfUInt32 = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.UInt32 << ArrayItemTypeShift);
private const long ArrayOfInt64 = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.Int64 << ArrayItemTypeShift);
private const long ArrayOfUInt64 = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.UInt64 << ArrayItemTypeShift);
private const long ArrayOfDouble = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.Double << ArrayItemTypeShift);
private const long ArrayOfString = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.String << ArrayItemTypeShift);
private const long ArrayOfObjectPath = ((long)VariantValueType.Array << TypeShift) | ((long)VariantValueType.ObjectPath << ArrayItemTypeShift);
public VariantValueType Type
=> DetermineType();
internal VariantValue(byte value)
{
_l = value | ((long)VariantValueType.Byte << TypeShift);
_o = null;
}
internal VariantValue(bool value)
{
_l = (value ? 1L : 0) | ((long)VariantValueType.Bool << TypeShift);
_o = null;
}
internal VariantValue(short value)
{
_l = (ushort)value | ((long)VariantValueType.Int16 << TypeShift);
_o = null;
}
internal VariantValue(ushort value)
{
_l = value | ((long)VariantValueType.UInt16 << TypeShift);
_o = null;
}
internal VariantValue(int value)
{
_l = (uint)value | ((long)VariantValueType.Int32 << TypeShift);
_o = null;
}
internal VariantValue(uint value)
{
_l = value | ((long)VariantValueType.UInt32 << TypeShift);
_o = null;
}
internal VariantValue(long value)
{
_l = value;
_o = Int64Type;
}
internal VariantValue(ulong value)
{
_l = (long)value;
_o = UInt64Type;
}
internal unsafe VariantValue(double value)
{
_l = *(long*)&value;
_o = DoubleType;
}
internal VariantValue(string value)
{
_l = (long)VariantValueType.String << TypeShift;
_o = value ?? throw new ArgumentNullException(nameof(value));
}
internal VariantValue(ObjectPath value)
{
_l = (long)VariantValueType.ObjectPath << TypeShift;
string s = value.ToString();
if (s.Length == 0)
{
throw new ArgumentException(nameof(value));
}
_o = s;
}
internal VariantValue(Signature value)
{
_l = (long)VariantValueType.Signature << TypeShift;
string s = value.ToString();
if (s.Length == 0)
{
throw new ArgumentException(nameof(value));
}
_o = s;
}
// Array
internal VariantValue(VariantValueType itemType, VariantValue[] items)
{
Debug.Assert(
itemType != VariantValueType.Byte &&
itemType != VariantValueType.Int16 &&
itemType != VariantValueType.UInt16 &&
itemType != VariantValueType.Int32 &&
itemType != VariantValueType.UInt32 &&
itemType != VariantValueType.Int64 &&
itemType != VariantValueType.UInt64 &&
itemType != VariantValueType.Double
);
_l = ((long)VariantValueType.Array << TypeShift) |
((long)itemType << ArrayItemTypeShift);
_o = items;
}
internal VariantValue(VariantValueType itemType, string[] items)
{
Debug.Assert(itemType == VariantValueType.String || itemType == VariantValueType.ObjectPath);
_l = ((long)VariantValueType.Array << TypeShift) |
((long)itemType << ArrayItemTypeShift);
_o = items;
}
internal VariantValue(byte[] items)
{
_l = ArrayOfByte;
_o = items;
}
internal VariantValue(short[] items)
{
_l = ArrayOfInt16;
_o = items;
}
internal VariantValue(ushort[] items)
{
_l = ArrayOfUInt16;
_o = items;
}
internal VariantValue(int[] items)
{
_l = ArrayOfInt32;
_o = items;
}
internal VariantValue(uint[] items)
{
_l = ArrayOfUInt32;
_o = items;
}
internal VariantValue(long[] items)
{
_l = ArrayOfInt64;
_o = items;
}
internal VariantValue(ulong[] items)
{
_l = ArrayOfUInt64;
_o = items;
}
internal VariantValue(double[] items)
{
_l = ArrayOfDouble;
_o = items;
}
// Dictionary
internal VariantValue(VariantValueType keyType, VariantValueType valueType, KeyValuePair<VariantValue, VariantValue>[] pairs)
{
_l = ((long)VariantValueType.Dictionary << TypeShift) |
((long)keyType << DictionaryKeyTypeShift) |
((long)valueType << DictionaryValueTypeShift);
_o = pairs;
}
// Struct
internal VariantValue(VariantValue[] fields)
{
_l = ((long)VariantValueType.Struct << TypeShift);
_o = fields;
}
// UnixFd
internal VariantValue(UnixFdCollection? fdCollection, int index)
{
_l = (long)index | ((long)VariantValueType.UnixFd << TypeShift);
_o = fdCollection;
}
public byte GetByte()
{
EnsureTypeIs(VariantValueType.Byte);
return UnsafeGetByte();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte UnsafeGetByte()
{
return (byte)(_l & StripTypeMask);
}
public bool GetBool()
{
EnsureTypeIs(VariantValueType.Bool);
return UnsafeGetBool();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool UnsafeGetBool()
{
return (_l & StripTypeMask) != 0;
}
public short GetInt16()
{
EnsureTypeIs(VariantValueType.Int16);
return UnsafeGetInt16();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private short UnsafeGetInt16()
{
return (short)(_l & StripTypeMask);
}
public ushort GetUInt16()
{
EnsureTypeIs(VariantValueType.UInt16);
return UnsafeGetUInt16();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ushort UnsafeGetUInt16()
{
return (ushort)(_l & StripTypeMask);
}
public int GetInt32()
{
EnsureTypeIs(VariantValueType.Int32);
return UnsafeGetInt32();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int UnsafeGetInt32()
{
return (int)(_l & StripTypeMask);
}
public uint GetUInt32()
{
EnsureTypeIs(VariantValueType.UInt32);
return UnsafeGetUInt32();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private uint UnsafeGetUInt32()
{
return (uint)(_l & StripTypeMask);
}
public long GetInt64()
{
EnsureTypeIs(VariantValueType.Int64);
return UnsafeGetInt64();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private long UnsafeGetInt64()
{
return _l;
}
public ulong GetUInt64()
{
EnsureTypeIs(VariantValueType.UInt64);
return UnsafeGetUInt64();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ulong UnsafeGetUInt64()
{
return (ulong)(_l);
}
public string GetString()
{
EnsureTypeIs(VariantValueType.String);
return UnsafeGetString();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private string UnsafeGetString()
{
return (_o as string)!;
}
public string GetObjectPath()
{
EnsureTypeIs(VariantValueType.ObjectPath);
return UnsafeGetString();
}
public string GetSignature()
{
EnsureTypeIs(VariantValueType.Signature);
return UnsafeGetString();
}
public double GetDouble()
{
EnsureTypeIs(VariantValueType.Double);
return UnsafeGetDouble();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe double UnsafeGetDouble()
{
double value;
*(long*)&value = _l;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T UnsafeGet<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>()
{
if (typeof(T) == typeof(byte))
{
return (T)(object)UnsafeGetByte();
}
else if (typeof(T) == typeof(bool))
{
return (T)(object)UnsafeGetBool();
}
else if (typeof(T) == typeof(short))
{
return (T)(object)UnsafeGetInt16();
}
else if (typeof(T) == typeof(ushort))
{
return (T)(object)UnsafeGetUInt16();
}
else if (typeof(T) == typeof(int))
{
return (T)(object)UnsafeGetInt32();
}
else if (typeof(T) == typeof(uint))
{
return (T)(object)UnsafeGetUInt32();
}
else if (typeof(T) == typeof(long))
{
return (T)(object)UnsafeGetInt64();
}
else if (typeof(T) == typeof(ulong))
{
return (T)(object)UnsafeGetUInt64();
}
else if (typeof(T) == typeof(double))
{
return (T)(object)UnsafeGetDouble();
}
else if (typeof(T) == typeof(string))
{
return (T)(object)UnsafeGetString();
}
else if (typeof(T) == typeof(VariantValue))
{
return (T)(object)this;
}
else if (typeof(T).IsAssignableTo(typeof(SafeHandle)))
{
return (T)(object)UnsafeReadHandle<T>()!;
}
ThrowCannotRetrieveAs(Type, typeof(T));
return default!;
}
public Dictionary<TKey, TValue> GetDictionary
<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]TKey,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]TValue
>
()
where TKey : notnull
where TValue : notnull
{
EnsureTypeIs(VariantValueType.Dictionary);
EnsureCanUnsafeGet<TKey>(KeyType);
EnsureCanUnsafeGet<TValue>(ValueType);
Dictionary<TKey, TValue> dict = new();
var pairs = (_o as KeyValuePair<VariantValue, VariantValue>[])!.AsSpan();
foreach (var pair in pairs)
{
dict[pair.Key.UnsafeGet<TKey>()] = pair.Value.UnsafeGet<TValue>();
}
return dict;
}
public T[] GetArray<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>()
where T : notnull
{
EnsureTypeIs(VariantValueType.Array);
EnsureCanUnsafeGet<T>(ItemType);
// Return the array by reference when we can.
// Don't bother to make a copy in case the caller mutates the data and
// calls GetArray again to retrieve the original data. It's an unlikely scenario.
if (typeof(T) == typeof(byte))
{
return (T[])(object)(_o as byte[])!;
}
else if (typeof(T) == typeof(short))
{
return (T[])(object)(_o as short[])!;
}
else if (typeof(T) == typeof(int))
{
return (T[])(object)(_o as int[])!;
}
else if (typeof(T) == typeof(long))
{
return (T[])(object)(_o as long[])!;
}
else if (typeof(T) == typeof(ushort))
{
return (T[])(object)(_o as ushort[])!;
}
else if (typeof(T) == typeof(uint))
{
return (T[])(object)(_o as uint[])!;
}
else if (typeof(T) == typeof(ulong))
{
return (T[])(object)(_o as ulong[])!;
}
else if (typeof(T) == typeof(double))
{
return (T[])(object)(_o as double[])!;
}
else if (typeof(T) == typeof(string))
{
return (T[])(object)(_o as string[])!;
}
else
{
var items = (_o as VariantValue[])!.AsSpan();
T[] array = new T[items.Length];
int i = 0;
foreach (var item in items)
{
array[i++] = item.UnsafeGet<T>();
}
return array;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnsureCanUnsafeGet<T>(VariantValueType type)
{
if (typeof(T) == typeof(byte))
{
EnsureTypeIs(type, VariantValueType.Byte);
}
else if (typeof(T) == typeof(bool))
{
EnsureTypeIs(type, VariantValueType.Bool);
}
else if (typeof(T) == typeof(short))
{
EnsureTypeIs(type, VariantValueType.Int16);
}
else if (typeof(T) == typeof(ushort))
{
EnsureTypeIs(type, VariantValueType.UInt16);
}
else if (typeof(T) == typeof(int))
{
EnsureTypeIs(type, VariantValueType.Int32);
}
else if (typeof(T) == typeof(uint))
{
EnsureTypeIs(type, VariantValueType.UInt32);
}
else if (typeof(T) == typeof(long))
{
EnsureTypeIs(type, VariantValueType.Int64);
}
else if (typeof(T) == typeof(ulong))
{
EnsureTypeIs(type, VariantValueType.UInt64);
}
else if (typeof(T) == typeof(double))
{
EnsureTypeIs(type, VariantValueType.Double);
}
else if (typeof(T) == typeof(string))
{
EnsureTypeIs(type, [ VariantValueType.String, VariantValueType.Signature, VariantValueType.ObjectPath ]);
}
else if (typeof(T) == typeof(VariantValue))
{ }
else if (typeof(T).IsAssignableTo(typeof(SafeHandle)))
{
EnsureTypeIs(type, VariantValueType.UnixFd);
}
else
{
ThrowCannotRetrieveAs(type, typeof(T));
}
}
public T? ReadHandle<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
#endif
T>() where T : SafeHandle
{
EnsureTypeIs(VariantValueType.UnixFd);
return UnsafeReadHandle<T>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T? UnsafeReadHandle<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>()
{
var handles = (UnixFdCollection?)_o;
if (handles is null)
{
return default;
}
int index = (int)_l;
return handles.ReadHandleGeneric<T>(index);
}
// Use for Array, Struct and Dictionary.
public int Count
{
get
{
Array? array = _o as Array;
return array?.Length ?? -1;
}
}
// Valid for Array, Struct.
public VariantValue GetItem(int i)
{
if (Type == VariantValueType.Array)
{
switch (_l)
{
case ArrayOfByte:
return new VariantValue((_o as byte[])![i]);
case ArrayOfInt16:
return new VariantValue((_o as short[])![i]);
case ArrayOfUInt16:
return new VariantValue((_o as ushort[])![i]);
case ArrayOfInt32:
return new VariantValue((_o as int[])![i]);
case ArrayOfUInt32:
return new VariantValue((_o as uint[])![i]);
case ArrayOfInt64:
return new VariantValue((_o as long[])![i]);
case ArrayOfUInt64:
return new VariantValue((_o as ulong[])![i]);
case ArrayOfDouble:
return new VariantValue((_o as double[])![i]);
case ArrayOfString:
case ArrayOfObjectPath:
return new VariantValue((_o as string[])![i]);
}
}
var values = _o as VariantValue[];
if (_o is null)
{
ThrowCannotRetrieveAs(Type, [VariantValueType.Array, VariantValueType.Struct]);
}
return values![i];
}
// Valid for Dictionary.
public KeyValuePair<VariantValue, VariantValue> GetDictionaryEntry(int i)
{
var values = _o as KeyValuePair<VariantValue, VariantValue>[];
if (_o is null)
{
ThrowCannotRetrieveAs(Type, VariantValueType.Dictionary);
}
return values![i];
}
// implicit conversion to VariantValue for basic D-Bus types (except Unix_FD).
public static implicit operator VariantValue(byte value)
=> new VariantValue(value);
public static implicit operator VariantValue(bool value)
=> new VariantValue(value);
public static implicit operator VariantValue(short value)
=> new VariantValue(value);
public static implicit operator VariantValue(ushort value)
=> new VariantValue(value);
public static implicit operator VariantValue(int value)
=> new VariantValue(value);
public static implicit operator VariantValue(uint value)
=> new VariantValue(value);
public static implicit operator VariantValue(long value)
=> new VariantValue(value);
public static implicit operator VariantValue(ulong value)
=> new VariantValue(value);
public static implicit operator VariantValue(double value)
=> new VariantValue(value);
public static implicit operator VariantValue(string value)
=> new VariantValue(value);
public static implicit operator VariantValue(ObjectPath value)
=> new VariantValue(value);
public static implicit operator VariantValue(Signature value)
=> new VariantValue(value);
public VariantValueType ItemType
=> DetermineInnerType(VariantValueType.Array, ArrayItemTypeShift);
public VariantValueType KeyType
=> DetermineInnerType(VariantValueType.Dictionary, DictionaryKeyTypeShift);
public VariantValueType ValueType
=> DetermineInnerType(VariantValueType.Dictionary, DictionaryValueTypeShift);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureTypeIs(VariantValueType expected)
=> EnsureTypeIs(Type, expected);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnsureTypeIs(VariantValueType actual, VariantValueType expected)
{
if (actual != expected)
{
ThrowCannotRetrieveAs(actual, expected);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnsureTypeIs(VariantValueType actual, VariantValueType[] expected)
{
if (Array.IndexOf<VariantValueType>(expected, actual) == -1)
{
ThrowCannotRetrieveAs(actual, expected);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private VariantValueType DetermineInnerType(VariantValueType outer, int typeShift)
{
VariantValueType type = DetermineType();
return type == outer ? (VariantValueType)((_l >> typeShift) & 0xff) : VariantValueType.Invalid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private VariantValueType DetermineType()
{
// For most types, we store the VariantValueType in the highest byte of the long.
// Except for some types, like Int64, for which we store the value allocation free
// in the long, and use the object field to store the type.
VariantValueType type = (VariantValueType)(_l >> TypeShift);
if (_o is not null)
{
if (_o.GetType() == typeof(VariantValueType))
{
type = (VariantValueType)_o;
}
}
return type;
}
private static void ThrowCannotRetrieveAs(VariantValueType from, VariantValueType to)
=> ThrowCannotRetrieveAs(from.ToString(), [ to.ToString() ]);
private static void ThrowCannotRetrieveAs(VariantValueType from, VariantValueType[] to)
=> ThrowCannotRetrieveAs(from.ToString(), to.Select(expected => expected.ToString()));
private static void ThrowCannotRetrieveAs(string from, string to)
=> ThrowCannotRetrieveAs(from, [ to ]);
private static void ThrowCannotRetrieveAs(VariantValueType from, Type to)
=> ThrowCannotRetrieveAs(from.ToString(), to.FullName ?? "?<Type>?");
private static void ThrowCannotRetrieveAs(string from, IEnumerable<string> to)
{
throw new InvalidOperationException($"Type {from} can not be retrieved as {string.Join("/", to)}.");
}
public override string ToString()
=> ToString(includeTypeSuffix: true);
public string ToString(bool includeTypeSuffix)
{
// This is implemented so something user-friendly shows in the debugger.
// By overriding the ToString method, it will also affect generic types like KeyValueType<TKey, TValue> that call ToString.
VariantValueType type = Type;
switch (type)
{
case VariantValueType.Byte:
return $"{GetByte()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.Bool:
return $"{GetBool()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.Int16:
return $"{GetInt16()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.UInt16:
return $"{GetUInt16()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.Int32:
return $"{GetInt32()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.UInt32:
return $"{GetUInt32()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.Int64:
return $"{GetInt64()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.UInt64:
return $"{GetUInt64()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.Double:
return $"{GetDouble()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.String:
return $"{GetString()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.ObjectPath:
return $"{GetObjectPath()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.Signature:
return $"{GetSignature()}{TypeSuffix(includeTypeSuffix, type)}";
case VariantValueType.Array:
return $"[{nameof(VariantValueType.Array)}<{ItemType}>, Count={Count}]";
case VariantValueType.Struct:
var values = (_o as VariantValue[]) ?? Array.Empty<VariantValue>();
return $"({
string.Join(", ", values.Select(v => v.ToString(includeTypeSuffix: false)))
}){(
!includeTypeSuffix ? ""
: $" [{nameof(VariantValueType.Struct)}]<{
string.Join(", ", values.Select(v => v.Type))
}>]")})";
case VariantValueType.Dictionary:
return $"[{nameof(VariantValueType.Dictionary)}<{KeyType}, {ValueType}>, Count={Count}]";
case VariantValueType.UnixFd:
return $"[{nameof(VariantValueType.UnixFd)}]";
case VariantValueType.Invalid:
return $"[{nameof(VariantValueType.Invalid)}]";
case VariantValueType.VariantValue: // note: No VariantValue returns this as its Type.
default:
return $"[?{Type}?]";
}
}
static string TypeSuffix(bool includeTypeSuffix, VariantValueType type)
=> includeTypeSuffix ? $" [{type}]" : "";
public static bool operator==(VariantValue lhs, VariantValue rhs)
=> lhs.Equals(rhs);
public static bool operator!=(VariantValue lhs, VariantValue rhs)
=> !lhs.Equals(rhs);
public override bool Equals(object? obj)
{
if (obj is not null && obj.GetType() == typeof(VariantValue))
{
return ((VariantValue)obj).Equals(this);
}
return false;
}
public override int GetHashCode()
{
#if NETSTANDARD2_0
return _l.GetHashCode() + 17 * (_o?.GetHashCode() ?? 0);
#else
return HashCode.Combine(_l, _o);
#endif
}
public bool Equals(VariantValue other)
{
if (_l == other._l && object.ReferenceEquals(_o, other._o))
{
return true;
}
VariantValueType type = Type;
if (type != other.Type)
{
return false;
}
switch (type)
{
case VariantValueType.String:
case VariantValueType.ObjectPath:
case VariantValueType.Signature:
return (_o as string)!.Equals(other._o as string, StringComparison.Ordinal);
}
// Always return false for composite types and handles.
return false;
}
}

30
src/Linux/Tmds.DBus.Protocol/VariantValueType.cs

@ -0,0 +1,30 @@
namespace Tmds.DBus.Protocol;
public enum VariantValueType
{
Invalid = 0,
// VariantValue is used for a variant for which we read the value
// and no longer track its signature.
VariantValue = 1,
// Match the DBusType values for easy conversion.
Byte = DBusType.Byte,
Bool = DBusType.Bool,
Int16 = DBusType.Int16,
UInt16 = DBusType.UInt16,
Int32 = DBusType.Int32,
UInt32 = DBusType.UInt32,
Int64 = DBusType.Int64,
UInt64 = DBusType.UInt64,
Double = DBusType.Double,
String = DBusType.String,
ObjectPath = DBusType.ObjectPath,
Signature = DBusType.Signature,
Array = DBusType.Array,
Struct = DBusType.Struct,
Dictionary = DBusType.DictEntry,
UnixFd = DBusType.UnixFd,
// We don't need this : variants are resolved into the VariantValue.
// Variant = DBusType.Variant,
}
Loading…
Cancel
Save