Skip to content

Commit 4ae613c

Browse files
committed
Send a datagram with multiple buffers; Expose datagram lost suspect
1 parent fb3a6d1 commit 4ae613c

8 files changed

+129
-73
lines changed

QuicWithDatagram/Implementations/MsQuic/MsQuicConnection.cs

+83-45
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers;
45
using System.Diagnostics;
56
using System.Net.Quic.Implementations.MsQuic.Internal;
67
using System.Net.Security;
@@ -133,7 +134,7 @@ public void SetClosing()
133134
_closing = true;
134135
}
135136
}
136-
137+
137138
public CancellationTokenSource Abort = new();
138139

139140
public event QuicDatagramReceivedEventHandler? DatagramReceived;
@@ -526,22 +527,9 @@ private static uint HandleEventDatagramSendStateChanged(State state, ref Connect
526527
{
527528
var datagramState = connectionEvent.Data.DatagramSendStateChanged.State;
528529
GCHandle handle = GCHandle.FromIntPtr(connectionEvent.Data.DatagramSendStateChanged.ClientContext);
529-
if (handle.Target is TaskCompletionSource<QUIC_DATAGRAM_SEND_STATE> source)
530-
{
531-
switch (datagramState)
532-
{
533-
case QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_LOST_DISCARDED:
534-
case QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_ACKNOWLEDGED:
535-
case QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_ACKNOWLEDGED_SPURIOUS:
536-
case QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_CANCELED:
537-
source.TrySetResult(datagramState);
538-
break;
539-
default:
540-
break;
541-
}
542-
return MsQuicStatusCodes.Success;
543-
}
544-
return MsQuicStatusCodes.InvalidState;
530+
return handle.Target is Func<QUIC_DATAGRAM_SEND_STATE, bool> callback && callback(datagramState) ?
531+
MsQuicStatusCodes.Success :
532+
MsQuicStatusCodes.InvalidState;
545533
}
546534

547535
internal override async ValueTask<QuicStreamProvider> AcceptStreamAsync(CancellationToken cancellationToken = default)
@@ -967,43 +955,93 @@ internal override event QuicDatagramReceivedEventHandler? DatagramReceived
967955
remove => _state.DatagramReceived -= value;
968956
}
969957

970-
internal override async ValueTask<bool> SendDatagramAsync(ReadOnlyMemory<byte> buffer, bool priority)
958+
internal override Task<QuicDatagramSendingResult> SendDatagramAsync(ReadOnlyMemory<byte> buffer, bool priority)
971959
{
972-
TaskCompletionSource<QUIC_DATAGRAM_SEND_STATE> tcs = new();
973-
var tcsHandle = GCHandle.Alloc(tcs);
974-
using var handle = buffer.Pin();
975-
var quicBuffer = new QuicBuffer[1];
976-
quicBuffer[0].Length = (uint)buffer.Length;
977-
unsafe { quicBuffer[0].Buffer = (byte*)handle.Pointer; }
978-
var quicBufferHandle = GCHandle.Alloc(quicBuffer, GCHandleType.Pinned);
960+
return SendDatagramAsync(new ReadOnlySequence<byte>(buffer), priority);
961+
}
962+
963+
internal unsafe override Task<QuicDatagramSendingResult> SendDatagramAsync(ReadOnlySequence<byte> buffers, bool priority)
964+
{
965+
TaskCompletionSource<QuicDatagramSendingResult> sent = new();
966+
TaskCompletionSource completion = new(), lostSuspect = new();
967+
Func<QUIC_DATAGRAM_SEND_STATE, bool> callback = state => state switch
968+
{
969+
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SENT =>
970+
sent.TrySetResult(new(completion.Task, lostSuspect.Task)),
971+
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_LOST_SUSPECT =>
972+
lostSuspect.TrySetException(new TimeoutException()),
973+
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_LOST_DISCARDED =>
974+
completion.TrySetException(new TimeoutException()),
975+
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_ACKNOWLEDGED or
976+
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_ACKNOWLEDGED_SPURIOUS =>
977+
lostSuspect.TrySetResult() | completion.TrySetResult(),
978+
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_CANCELED =>
979+
sent.TrySetCanceled() | lostSuspect.TrySetCanceled() | completion.TrySetCanceled(),
980+
_ => false
981+
};
982+
var count = 0;
983+
foreach (var buffer in buffers)
984+
{
985+
++count;
986+
}
987+
var handles = new MemoryHandle[count];
988+
nint quicBuffers;
989+
var callbackHandle = GCHandle.Alloc(callback);
979990
try
980991
{
981-
unsafe
992+
quicBuffers = Marshal.AllocHGlobal(sizeof(QuicBuffer) * count);
993+
try
982994
{
983-
var status = MsQuicApi.Api.DatagramSendDelegate(
984-
_state.Handle,
985-
(QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(quicBuffer, 0),
986-
1,
987-
priority ? QUIC_SEND_FLAGS.DGRAM_PRIORITY : QUIC_SEND_FLAGS.NONE,
988-
(IntPtr)tcsHandle);
989-
QuicExceptionHelpers.ThrowIfFailed(status, "Failed to send a datagram to peer.");
995+
completion.Task.ContinueWith(task =>
996+
{
997+
foreach (var handle in handles)
998+
{
999+
using (handle) { }
1000+
}
1001+
Marshal.FreeHGlobal(quicBuffers);
1002+
callbackHandle.Free();
1003+
});
1004+
try
1005+
{
1006+
var pointer = (QuicBuffer*)quicBuffers;
1007+
count = 0;
1008+
foreach (var buffer in buffers)
1009+
{
1010+
var handle = buffer.Pin();
1011+
pointer[count].Length = (uint)buffer.Length;
1012+
pointer[count].Buffer = (byte*)handle.Pointer;
1013+
handles[count] = handle;
1014+
++count;
1015+
}
1016+
var status = MsQuicApi.Api.DatagramSendDelegate(
1017+
_state.Handle,
1018+
pointer,
1019+
(uint)count,
1020+
priority ? QUIC_SEND_FLAGS.DGRAM_PRIORITY : QUIC_SEND_FLAGS.NONE,
1021+
(IntPtr)callbackHandle);
1022+
QuicExceptionHelpers.ThrowIfFailed(status, "Failed to send a datagram to peer.");
1023+
}
1024+
catch
1025+
{
1026+
foreach (var handle in handles)
1027+
{
1028+
using (handle) { }
1029+
}
1030+
throw;
1031+
}
9901032
}
991-
using var abort = _state.Abort.Token.Register(() => tcs.SetException(ThrowHelper.GetConnectionAbortedException(_state.AbortErrorCode)));
992-
var state = await tcs.Task.ConfigureAwait(false);
993-
return state switch
1033+
catch
9941034
{
995-
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_ACKNOWLEDGED => true,
996-
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_ACKNOWLEDGED_SPURIOUS => false,
997-
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_CANCELED => throw new OperationCanceledException("Datagram send canceled."),
998-
QUIC_DATAGRAM_SEND_STATE.QUIC_DATAGRAM_SEND_LOST_DISCARDED => throw new QuicDatagramException("Datagram lost discarded.", (int)state),
999-
_ => throw new QuicDatagramException("Unknown datagram send state.", (int)state)
1000-
};
1035+
Marshal.FreeHGlobal(quicBuffers);
1036+
throw;
1037+
}
10011038
}
1002-
finally
1039+
catch (Exception ex)
10031040
{
1004-
quicBufferHandle.Free();
1005-
tcsHandle.Free();
1041+
callbackHandle.Free();
1042+
sent.SetException(ex);
10061043
}
1044+
return sent.Task;
10071045
}
10081046

10091047
private void ThrowIfDisposed()

QuicWithDatagram/Implementations/QuicConnectionProvider.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers;
45
using System.Threading;
56
using System.Threading.Tasks;
67

@@ -44,7 +45,9 @@ internal abstract class QuicConnectionProvider : IDisposable
4445

4546
internal abstract event QuicDatagramReceivedEventHandler DatagramReceived;
4647

47-
internal abstract ValueTask<bool> SendDatagramAsync(ReadOnlyMemory<byte> buffer, bool priority);
48+
internal abstract Task<QuicDatagramSendingResult> SendDatagramAsync(ReadOnlyMemory<byte> buffer, bool priority);
49+
50+
internal abstract Task<QuicDatagramSendingResult> SendDatagramAsync(ReadOnlySequence<byte> buffers, bool priority);
4851

4952
public abstract void Dispose();
5053
}

QuicWithDatagram/QuicConnection.cs

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers;
45
using System.Net.Quic.Implementations;
56
using System.Net.Security;
67
using System.Security.Cryptography.X509Certificates;
@@ -9,8 +10,6 @@
910

1011
namespace System.Net.Quic
1112
{
12-
public delegate void QuicDatagramReceivedEventHandler(object sender, ReadOnlySpan<byte> buffer);
13-
1413
public sealed class QuicConnection : IDisposable
1514
{
1615
private readonly QuicConnectionProvider _provider;
@@ -124,7 +123,13 @@ public event QuicDatagramReceivedEventHandler? DatagramReceived
124123
/// </summary>
125124
/// <param name="buffer">Payload of the datagram.</param>
126125
/// <param name="priority">Whether the datagram should be prioritized.</param>
127-
/// <returns>Whether the datagram is acknowledged in time.</returns>
128-
public ValueTask<bool> SendDatagramAsync(ReadOnlyMemory<byte> buffer, bool priority = false) => _provider.SendDatagramAsync(buffer, priority);
126+
public Task<QuicDatagramSendingResult> SendDatagramAsync(ReadOnlyMemory<byte> buffer, bool priority = false) => _provider.SendDatagramAsync(buffer, priority);
127+
128+
/// <summary>
129+
/// Sends a datagram.
130+
/// </summary>
131+
/// <param name="buffers">Payload of the datagram.</param>
132+
/// <param name="priority">Whether the datagram should be prioritized.</param>
133+
public Task<QuicDatagramSendingResult> SendDatagramAsync(ReadOnlySequence<byte> buffers, bool priority = false) => _provider.SendDatagramAsync(buffers, priority);
129134
}
130135
}

QuicWithDatagram/QuicDatagramException.cs

-16
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Net.Quic
5+
{
6+
public delegate void QuicDatagramReceivedEventHandler(object sender, ReadOnlySpan<byte> buffer);
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Net.Quic
5+
{
6+
public sealed class QuicDatagramSendingResult
7+
{
8+
public Task Completion { get; }
9+
10+
public Task LostSuspect { get; }
11+
12+
internal QuicDatagramSendingResult(Task completion, Task lostSuspect)
13+
{
14+
Completion = completion;
15+
LostSuspect = lostSuspect;
16+
}
17+
}
18+
}

QuicWithDatagram/QuicWithDatagram.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<Nullable>enable</Nullable>
99
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1010
<AssemblyName>FlyByWireless.Quic</AssemblyName>
11-
<VersionSuffix>alpha.3</VersionSuffix>
11+
<VersionSuffix>alpha.4</VersionSuffix>
1212
<Authors>WONG Tin Chi Timothy</Authors>
1313
<Product>MsQuic Wrapper with Datagram</Product>
1414
<Title>$(Product)</Title>

ref/QuicWithDatagram/System.Net.Quic.cs

+7-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public QuicClientConnectionOptions() { }
1414
public System.Net.EndPoint? RemoteEndPoint { get { throw null; } set { } }
1515
}
1616
public delegate void QuicDatagramReceivedEventHandler(object sender, ReadOnlySpan<byte> buffer);
17+
public sealed class QuicDatagramSendingResult
18+
{
19+
public System.Threading.Tasks.Task Completion { get { throw null; } }
20+
public System.Threading.Tasks.Task LostSuspect { get { throw null; } }
21+
}
1722
public sealed partial class QuicConnection : System.IDisposable
1823
{
1924
public QuicConnection(System.Net.EndPoint remoteEndPoint, System.Net.Security.SslClientAuthenticationOptions? sslClientAuthenticationOptions, System.Net.IPEndPoint? localEndPoint = null) { }
@@ -39,18 +44,14 @@ public void Dispose() { }
3944
public bool DatagramSendEnabled { get { throw null; } set { } }
4045
public int DatagramMaxSendLength { get { throw null; } }
4146
public event QuicDatagramReceivedEventHandler? DatagramReceived { add { } remove { } }
42-
public System.Threading.Tasks.ValueTask<bool> SendDatagramAsync(ReadOnlyMemory<byte> buffer, bool priority = false) { throw null; }
47+
public System.Threading.Tasks.Task<QuicDatagramSendingResult> SendDatagramAsync(ReadOnlyMemory<byte> buffer, bool priority = false) { throw null; }
48+
public System.Threading.Tasks.Task<QuicDatagramSendingResult> SendDatagramAsync(System.Buffers.ReadOnlySequence<byte> buffers, bool priority = false) { throw null; }
4349
}
4450
public partial class QuicConnectionAbortedException : System.Net.Quic.QuicException
4551
{
4652
public QuicConnectionAbortedException(string message, long errorCode) : base (default(string)) { }
4753
public long ErrorCode { get { throw null; } }
4854
}
49-
public partial class QuicDatagramException : System.Net.Quic.QuicException
50-
{
51-
public QuicDatagramException(string message, int errorCode) : base(default(string)) { }
52-
public int ErrorCode { get { throw null; } }
53-
}
5455
public partial class QuicException : System.Exception
5556
{
5657
public QuicException(string? message) { }

0 commit comments

Comments
 (0)