93 changed files with 13643 additions and 4 deletions
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
@ -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) |
|||
{ } |
|||
} |
|||
@ -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; } |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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) |
|||
{ } |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
|
|||
public abstract class ConnectionOptions |
|||
{ |
|||
internal ConnectionOptions() |
|||
{ } |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
|
|||
static class Constants |
|||
{ |
|||
public const int StackAllocByteThreshold = 512; |
|||
public const int StackAllocCharThreshold = StackAllocByteThreshold / 2; |
|||
} |
|||
File diff suppressed because it is too large
@ -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(); |
|||
} |
|||
@ -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; } |
|||
} |
|||
@ -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', |
|||
} |
|||
@ -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(); |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
public class DisconnectedException : Exception |
|||
{ |
|||
internal DisconnectedException(Exception innerException) : base(innerException.Message, innerException) |
|||
{ } |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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"; |
|||
} |
|||
@ -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; |
|||
@ -0,0 +1,6 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
|
|||
public interface IDBusWritable |
|||
{ |
|||
void WriteTo(ref MessageWriter writer); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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();
|
|||
} |
|||
@ -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(); |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
|
|||
[Flags] |
|||
public enum MessageFlags : byte |
|||
{ |
|||
None = 0, |
|||
NoReplyExpected = 1, |
|||
NoAutoStart = 2, |
|||
AllowInteractiveAuthorization = 4 |
|||
} |
|||
@ -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 |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
|
|||
public enum MessageType : byte |
|||
{ |
|||
MethodCall = 1, |
|||
MethodReturn = 2, |
|||
Error = 3, |
|||
Signal = 4 |
|||
} |
|||
@ -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)); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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)); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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)); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
|
|||
public ref partial struct MessageWriter |
|||
{ |
|||
public void WriteVariant(Variant value) |
|||
{ |
|||
value.WriteTo(ref this); |
|||
} |
|||
} |
|||
@ -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)!; |
|||
} |
|||
} |
|||
} |
|||
@ -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}"); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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()); |
|||
} |
|||
} |
|||
@ -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
|
|||
@ -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
|
|||
@ -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); |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
|
|||
[Flags] |
|||
public enum ObserverFlags |
|||
{ |
|||
None = 0, |
|||
EmitOnConnectionDispose = 1, |
|||
EmitOnObserverDispose = 2, |
|||
NoSubscribe = 4, |
|||
|
|||
EmitOnDispose = EmitOnConnectionDispose | EmitOnObserverDispose, |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
@ -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
|
|||
@ -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
|
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
@ -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
|
|||
@ -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
|
|||
@ -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
|
|||
@ -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
|
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
|
|||
public class ProtocolException : Exception |
|||
{ |
|||
public ProtocolException(string message) : base(message) |
|||
{ } |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -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}"); |
|||
} |
|||
} |
|||
@ -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>())); |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace Tmds.DBus.Protocol; |
|||
|
|||
public ref partial struct Reader |
|||
{ |
|||
[RequiresUnreferencedCode(Strings.UseNonObjectReadVariantValue)] |
|||
[Obsolete(Strings.UseNonObjectReadVariantValueObsolete)] |
|||
public object ReadVariant() => Read<object>(); |
|||
} |
|||
@ -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 |
|||
}; |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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}"; |
|||
} |
|||
@ -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(); |
|||
} |
|||
@ -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)}'."); |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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}"); |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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}."); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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…
Reference in new issue