Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions src/Tmds.DBus.Protocol/Reader.Handle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,40 @@ public ref partial struct Reader
/// <summary>
/// Reads a Unix file descriptor handle.
/// </summary>
/// <typeparam name="T">The SafeHandle type to read.</typeparam>
/// <returns>The handle, or null if unavailable.</returns>
/// <typeparam name="T">The <see cref="SafeHandle"/> type to read.</typeparam>
/// <returns>The handle, or <see langword="null"/> if <typeparamref name="T"/> is <see cref="SkipSafeHandle"/> or if file descriptor passing is not supported.</returns>
/// <remarks>
/// A handle can only be read once.
/// To skip reading a handle, call <c>ReadHandle&lt;SkipSafeHandle&gt;()</c>, which will return <see langword="null"/> without consuming the underlying handle.
/// </remarks>
public T? ReadHandle<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>() where T : SafeHandle, new()
=> ReadHandleGeneric<T>();

internal T? ReadHandleGeneric<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>()
{
int idx = (int)ReadUInt32();
if (idx >= _handleCount)
if (_handles is null)
{
throw new IndexOutOfRangeException();
return default(T);
}
if (_handles is not null)
{
return _handles.ReadHandleGeneric<T>(idx);
}
return default(T);
return _handles.ReadHandleGeneric<T>(idx);
}

/// <summary>
/// Reads a Unix file descriptor handle as a raw IntPtr.
/// </summary>
/// <remarks>The handle is still owned (i.e. Disposed) by the <see cref="Message"/>.</remarks>
/// <remarks>
/// A handle can only be read once.
/// The handle is still owned (i.e. Disposed) by the <see cref="Message"/>.
/// To skip reading a handle, call <c>ReadHandle&lt;SkipSafeHandle&gt;()</c>, which will return <see langword="null"/> without consuming the underlying handle.
/// </remarks>
public IntPtr ReadHandleRaw()
{
int idx = (int)ReadUInt32();
if (idx >= _handleCount)
{
throw new IndexOutOfRangeException();
}
if (_handles is not null)
if (_handles is null)
{
return _handles.ReadHandleRaw(idx);
return new IntPtr(-1);
}
return new IntPtr(-1);
return _handles.ReadHandleRaw(idx);
}
}
32 changes: 32 additions & 0 deletions src/Tmds.DBus.Protocol/SkipSafeHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Tmds.DBus.Protocol;

/// <summary>
/// A SafeHandle that can be used to skip reading a Unix file descriptor handle.
/// </summary>
/// <remarks>
/// When this type is used with handle reading methods, the handle will not be read
/// and <see langword="null"/> will be returned instead. This allows skipping handles without consuming them.
/// </remarks>
public sealed class SkipSafeHandle : SafeHandle
{
/// <summary>
/// Initializes a new instance of the <see cref="SkipSafeHandle"/> class.
/// </summary>
public SkipSafeHandle() : base(new IntPtr(-1), false)
{
}

/// <summary>
/// Gets a value indicating whether the handle is invalid.
/// </summary>
public override bool IsInvalid => true;

/// <summary>
/// Releases the handle.
/// </summary>
/// <returns>Always returns true.</returns>
protected override bool ReleaseHandle()
{
return true;
}
}
8 changes: 8 additions & 0 deletions src/Tmds.DBus.Protocol/UnixFdCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ private void ThrowDisposed()
if (_rawHandles is not null)
{
(IntPtr rawHandle, bool CanRead) = _rawHandles[index];
if (typeof(T) == typeof(SkipSafeHandle))
{
return default;
}
if (!CanRead)
{
ThrowHandleAlreadyRead();
Expand All @@ -128,6 +132,10 @@ private void ThrowDisposed()
{
Debug.Assert(_handles is not null);
(SafeHandle? handle, bool CanRead) = _handles![index];
if (typeof(T) == typeof(SkipSafeHandle))
{
return default;
}
if (!CanRead)
{
ThrowHandleAlreadyRead();
Expand Down
7 changes: 6 additions & 1 deletion src/Tmds.DBus.Protocol/VariantValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,11 @@ private static void EnsureCanUnsafeGet<T>(VariantValueType type)
/// Reads a Unix file descriptor handle for a <see cref="VariantValueType.UnixFd"/> type.
/// </summary>
/// <typeparam name="T">The <see cref="SafeHandle"/> type to read.</typeparam>
/// <returns>The handle, or <see langword="null"/> if <typeparamref name="T"/> is <see cref="SkipSafeHandle"/> or if file descriptor passing is not supported.</returns>
/// <remarks>
/// A handle can only be read once.
/// To skip reading a handle, call <c>ReadHandle&lt;SkipSafeHandle&gt;()</c>, which will return <see langword="null"/> without consuming the underlying handle.
/// </remarks>
public T? ReadHandle<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Expand Down Expand Up @@ -2209,7 +2214,7 @@ private void WriteValueTo(ref MessageWriter writer, int nestingOffset)
SafeHandle? handle = UnsafeReadHandle<Microsoft.Win32.SafeHandles.SafeFileHandle>();
if (handle is null)
{
throw new InvalidOperationException("Handle already read");
throw new NotSupportedException("Handle not available.");
}
writer.WriteHandle(handle);
break;
Expand Down
72 changes: 72 additions & 0 deletions test/Tmds.DBus.Protocol.Tests/ReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,78 @@ public void ReadHandleRaw()
Assert.Equal(expected, handle);
}

[Fact]
public void ReadHandle_SkipSafeHandle_ReturnsNull()
{
byte handleIndex = 0;
IntPtr expected = new IntPtr(-3);
using UnixFdCollection fds = new UnixFdCollection(isRawHandleCollection: true);
fds.AddHandle(expected);
byte[] bigEndianData = new byte[] { 0, 0, 0, handleIndex };

Reader reader = new Reader(isBigEndian: true, new System.Buffers.ReadOnlySequence<byte>(bigEndianData), handles: fds, fds.Count);
var skipped = reader.ReadHandle<SkipSafeHandle>();

Assert.Null(skipped);
}

[Fact]
public void ReadHandle_AfterSkip_CanStillReadHandle()
{
byte handleIndex = 0;
IntPtr expected = new IntPtr(-3);
using UnixFdCollection fds = new UnixFdCollection(isRawHandleCollection: true);
fds.AddHandle(expected);
byte[] bigEndianData = new byte[] { 0, 0, 0, handleIndex, 0, 0, 0, handleIndex };

Reader reader = new Reader(isBigEndian: true, new System.Buffers.ReadOnlySequence<byte>(bigEndianData), handles: fds, fds.Count);

var skipped = reader.ReadHandle<SkipSafeHandle>();
Assert.Null(skipped);

using var handle = reader.ReadHandle<SafeFileHandle>();
Assert.NotNull(handle);
Assert.Equal(expected, handle.DangerousGetHandle());
}

[Fact]
public void ReadHandleRaw_AfterSkip_CanStillReadHandle()
{
byte handleIndex = 0;
IntPtr expected = new IntPtr(-3);
using UnixFdCollection fds = new UnixFdCollection(isRawHandleCollection: true);
fds.AddHandle(expected);
byte[] bigEndianData = new byte[] { 0, 0, 0, handleIndex, 0, 0, 0, handleIndex };

Reader reader = new Reader(isBigEndian: true, new System.Buffers.ReadOnlySequence<byte>(bigEndianData), handles: fds, fds.Count);

var skipped = reader.ReadHandle<SkipSafeHandle>();
Assert.Null(skipped);

IntPtr handle = reader.ReadHandleRaw();
Assert.Equal(expected, handle);
}

[Fact]
public void ReadHandle_WithNullHandles_ReturnsNull()
{
byte[] bigEndianData = new byte[] { 0, 0, 0, 0 };
Reader reader = new Reader(isBigEndian: true, new System.Buffers.ReadOnlySequence<byte>(bigEndianData), handles: null, handleCount: 0);

using var handle = reader.ReadHandle<SafeFileHandle>();
Assert.Null(handle);
}

[Fact]
public void ReadHandleRaw_WithNullHandles_ReturnsInvalidHandle()
{
byte[] bigEndianData = new byte[] { 0, 0, 0, 0 };
Reader reader = new Reader(isBigEndian: true, new System.Buffers.ReadOnlySequence<byte>(bigEndianData), handles: null, handleCount: 0);

IntPtr handle = reader.ReadHandleRaw();
Assert.Equal(new IntPtr(-1), handle);
}

public bool Equals(VariantValue lhs, VariantValue other)
{
if (lhs.GetDBusSignature() != other.GetDBusSignature())
Expand Down
33 changes: 33 additions & 0 deletions test/Tmds.DBus.Protocol.Tests/VariantValueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,39 @@ public void UnixFd(byte nesting)
Assert.Equal(-1, vv.Count);
}

[Fact]
public void UnixFd_SkipSafeHandle_ReturnsNull()
{
byte handleIndex = 0;
IntPtr expected = new IntPtr(-3);
using UnixFdCollection fds = new UnixFdCollection(isRawHandleCollection: true);
fds.AddHandle(expected);

var vv = new VariantValue(fds, handleIndex);
var skipped = vv.ReadHandle<SkipSafeHandle>();

Assert.Null(skipped);
}

[Fact]
public void UnixFd_AfterSkip_CanStillReadHandle()
{
byte handleIndex = 0;
IntPtr expected = new IntPtr(-3);
using UnixFdCollection fds = new UnixFdCollection(isRawHandleCollection: true);
fds.AddHandle(expected);

var vv1 = new VariantValue(fds, handleIndex);
var vv2 = new VariantValue(fds, handleIndex);

var skipped = vv1.ReadHandle<SkipSafeHandle>();
Assert.Null(skipped);

using var handle = vv2.ReadHandle<SafeFileHandle>();
Assert.NotNull(handle);
Assert.Equal(expected, handle.DangerousGetHandle());
}

private static VariantValue Nest(VariantValue vv, byte nesting)
{
for (int i = 0; i < nesting; i++)
Expand Down
Loading