344 lines
15 KiB
C#
344 lines
15 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace MoShiro.Measurement;
|
|
|
|
/// <summary>Small, versioned view of the MsQuic v2 C API table used by this transport.</summary>
|
|
internal static unsafe class MsQuicApi
|
|
{
|
|
private const int SetContextIndex = 0;
|
|
private const int SetCallbackHandlerIndex = 2;
|
|
private const int SetParamIndex = 3;
|
|
private const int GetParamIndex = 4;
|
|
private const int RegistrationOpenIndex = 5;
|
|
private const int RegistrationCloseIndex = 6;
|
|
private const int ConfigurationOpenIndex = 8;
|
|
private const int ConfigurationCloseIndex = 9;
|
|
private const int ConfigurationLoadCredentialIndex = 10;
|
|
private const int ListenerOpenIndex = 11;
|
|
private const int ListenerCloseIndex = 12;
|
|
private const int ListenerStartIndex = 13;
|
|
private const int ListenerStopIndex = 14;
|
|
private const int ConnectionOpenIndex = 15;
|
|
private const int ConnectionCloseIndex = 16;
|
|
private const int ConnectionShutdownIndex = 17;
|
|
private const int ConnectionStartIndex = 18;
|
|
private const int ConnectionSetConfigurationIndex = 19;
|
|
private const int DatagramSendIndex = 28;
|
|
private const uint ConnectionStatisticsV2Parameter = 0x05000016;
|
|
|
|
private static readonly SetContextDelegate s_setContext = MsQuicRuntime.Function<SetContextDelegate>(SetContextIndex);
|
|
private static readonly SetCallbackHandlerDelegate s_setCallbackHandler = MsQuicRuntime.Function<SetCallbackHandlerDelegate>(SetCallbackHandlerIndex);
|
|
private static readonly SetParamDelegate s_setParam = MsQuicRuntime.Function<SetParamDelegate>(SetParamIndex);
|
|
private static readonly GetParamDelegate s_getParam = MsQuicRuntime.Function<GetParamDelegate>(GetParamIndex);
|
|
private static readonly RegistrationOpenDelegate s_registrationOpen = MsQuicRuntime.Function<RegistrationOpenDelegate>(RegistrationOpenIndex);
|
|
private static readonly HandleCloseDelegate s_registrationClose = MsQuicRuntime.Function<HandleCloseDelegate>(RegistrationCloseIndex);
|
|
private static readonly ConfigurationOpenDelegate s_configurationOpen = MsQuicRuntime.Function<ConfigurationOpenDelegate>(ConfigurationOpenIndex);
|
|
private static readonly HandleCloseDelegate s_configurationClose = MsQuicRuntime.Function<HandleCloseDelegate>(ConfigurationCloseIndex);
|
|
private static readonly ConfigurationLoadCredentialDelegate s_loadCredential = MsQuicRuntime.Function<ConfigurationLoadCredentialDelegate>(ConfigurationLoadCredentialIndex);
|
|
private static readonly ListenerOpenDelegate s_listenerOpen = MsQuicRuntime.Function<ListenerOpenDelegate>(ListenerOpenIndex);
|
|
private static readonly HandleCloseDelegate s_listenerClose = MsQuicRuntime.Function<HandleCloseDelegate>(ListenerCloseIndex);
|
|
private static readonly ListenerStartDelegate s_listenerStart = MsQuicRuntime.Function<ListenerStartDelegate>(ListenerStartIndex);
|
|
private static readonly ListenerStopDelegate s_listenerStop = MsQuicRuntime.Function<ListenerStopDelegate>(ListenerStopIndex);
|
|
private static readonly ConnectionOpenDelegate s_connectionOpen = MsQuicRuntime.Function<ConnectionOpenDelegate>(ConnectionOpenIndex);
|
|
private static readonly HandleCloseDelegate s_connectionClose = MsQuicRuntime.Function<HandleCloseDelegate>(ConnectionCloseIndex);
|
|
private static readonly ConnectionShutdownDelegate s_connectionShutdown = MsQuicRuntime.Function<ConnectionShutdownDelegate>(ConnectionShutdownIndex);
|
|
private static readonly ConnectionStartDelegate s_connectionStart = MsQuicRuntime.Function<ConnectionStartDelegate>(ConnectionStartIndex);
|
|
private static readonly ConnectionSetConfigurationDelegate s_connectionSetConfiguration = MsQuicRuntime.Function<ConnectionSetConfigurationDelegate>(ConnectionSetConfigurationIndex);
|
|
private static readonly DatagramSendDelegate s_datagramSend = MsQuicRuntime.Function<DatagramSendDelegate>(DatagramSendIndex);
|
|
|
|
public static void SetContext(nint handle, nint context) => s_setContext(handle, context);
|
|
|
|
public static void SetCallbackHandler<T>(nint handle, T callback, nint context)
|
|
where T : Delegate => s_setCallbackHandler(handle, Marshal.GetFunctionPointerForDelegate(callback), context);
|
|
|
|
public static void SetParam(nint handle, uint parameter, byte value)
|
|
{
|
|
unsafe
|
|
{
|
|
uint status;
|
|
status = s_setParam(handle, parameter, sizeof(byte), &value);
|
|
Check(status, "SetParam");
|
|
}
|
|
}
|
|
|
|
public static byte[] GetAddress(nint handle, uint parameter)
|
|
{
|
|
byte[] address = new byte[128];
|
|
unsafe
|
|
{
|
|
fixed (byte* buffer = address)
|
|
{
|
|
uint length = (uint)address.Length;
|
|
uint status = s_getParam(handle, parameter, &length, buffer);
|
|
Check(status, "GetParam(address)");
|
|
Array.Resize(ref address, checked((int)length));
|
|
}
|
|
}
|
|
|
|
return address;
|
|
}
|
|
|
|
public static QuicStatisticsV2 GetStatisticsV2(nint connection)
|
|
{
|
|
QuicStatisticsV2 statistics = default;
|
|
unsafe
|
|
{
|
|
uint length = (uint)sizeof(QuicStatisticsV2);
|
|
uint status = s_getParam(connection, ConnectionStatisticsV2Parameter, &length, &statistics);
|
|
Check(status, "GetParam(connection statistics v2)");
|
|
}
|
|
|
|
return statistics;
|
|
}
|
|
|
|
public static nint RegistrationOpen(string appName)
|
|
{
|
|
nint utf8 = Marshal.StringToCoTaskMemUTF8(appName);
|
|
try
|
|
{
|
|
var config = new RegistrationConfig { AppName = utf8, ExecutionProfile = 0 };
|
|
uint status = s_registrationOpen(ref config, out nint registration);
|
|
Check(status, "RegistrationOpen");
|
|
return registration;
|
|
}
|
|
finally
|
|
{
|
|
Marshal.FreeCoTaskMem(utf8);
|
|
}
|
|
}
|
|
|
|
public static void RegistrationClose(nint handle) => s_registrationClose(handle);
|
|
|
|
public static nint ConfigurationOpen(nint registration, byte[] alpn, QuicSettings? settings = null)
|
|
{
|
|
byte[] native = settings is { IsDefault: false } ? settings.ToNative() : [];
|
|
unsafe
|
|
{
|
|
fixed (byte* alpnBytes = alpn)
|
|
fixed (byte* settingsBytes = native)
|
|
{
|
|
var buffer = new QuicBuffer { Length = (uint)alpn.Length, Buffer = (nint)alpnBytes };
|
|
uint status = s_configurationOpen(registration, ref buffer, 1, native.Length == 0 ? 0 : (nint)settingsBytes, (uint)native.Length, 0, out nint configuration);
|
|
Check(status, "ConfigurationOpen");
|
|
return configuration;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void ConfigurationClose(nint handle) => s_configurationClose(handle);
|
|
|
|
public static void ConfigurationLoadCredential(nint configuration, ref CredentialConfig credential)
|
|
{
|
|
uint status = s_loadCredential(configuration, ref credential);
|
|
Check(status, "ConfigurationLoadCredential");
|
|
}
|
|
|
|
public static nint ListenerOpen<T>(nint registration, T callback, nint context)
|
|
where T : Delegate
|
|
{
|
|
uint status = s_listenerOpen(registration, Marshal.GetFunctionPointerForDelegate(callback), context, out nint listener);
|
|
Check(status, "ListenerOpen");
|
|
return listener;
|
|
}
|
|
|
|
public static void ListenerClose(nint handle) => s_listenerClose(handle);
|
|
|
|
public static void ListenerStart(nint listener, byte[] alpn, byte[] address)
|
|
{
|
|
unsafe
|
|
{
|
|
fixed (byte* alpnBytes = alpn)
|
|
fixed (byte* addressBytes = address)
|
|
{
|
|
var buffer = new QuicBuffer { Length = (uint)alpn.Length, Buffer = (nint)alpnBytes };
|
|
uint status = s_listenerStart(listener, ref buffer, 1, (nint)addressBytes);
|
|
Check(status, "ListenerStart");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void ListenerStop(nint handle) => s_listenerStop(handle);
|
|
|
|
public static nint ConnectionOpen<T>(nint registration, T callback, nint context)
|
|
where T : Delegate
|
|
{
|
|
uint status = s_connectionOpen(registration, Marshal.GetFunctionPointerForDelegate(callback), context, out nint connection);
|
|
Check(status, "ConnectionOpen");
|
|
return connection;
|
|
}
|
|
|
|
public static void ConnectionClose(nint handle) => s_connectionClose(handle);
|
|
|
|
public static void ConnectionShutdown(nint handle, ulong errorCode = 0) => s_connectionShutdown(handle, 0x00000001, errorCode);
|
|
|
|
public static void ConnectionStart(nint handle, nint configuration, ushort addressFamily, nint serverName, ushort port)
|
|
{
|
|
uint status = s_connectionStart(handle, configuration, addressFamily, serverName, port);
|
|
Check(status, "ConnectionStart");
|
|
}
|
|
|
|
public static void ConnectionBind(nint handle, byte[] address)
|
|
{
|
|
unsafe
|
|
{
|
|
fixed (byte* addressBytes = address)
|
|
{
|
|
uint status = s_setParam(handle, 0x05000001, (uint)address.Length, addressBytes);
|
|
Check(status, "SetParam(local address)");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void ConnectionSetConfiguration(nint handle, nint configuration)
|
|
{
|
|
uint status = s_connectionSetConfiguration(handle, configuration);
|
|
Check(status, "ConnectionSetConfiguration");
|
|
}
|
|
|
|
public static uint DatagramSend(nint connection, nint buffers, nint clientContext) => s_datagramSend(connection, buffers, 1, 0, clientContext);
|
|
|
|
|
|
public static void Check(uint status, string operation)
|
|
{
|
|
if (!Succeeded(status))
|
|
{
|
|
throw new InvalidOperationException($"MsQuic {operation} failed with status 0x{status:X8}.");
|
|
}
|
|
}
|
|
|
|
// QUIC_STATUS_PENDING (0xFFFFFFFE) is a successful asynchronous result. Other
|
|
// negative HRESULT/NTSTATUS values are failures on both Windows and POSIX.
|
|
public static bool Succeeded(uint status) => status == 0 || status == 0xFFFFFFFE;
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct QuicBuffer
|
|
{
|
|
public uint Length;
|
|
public nint Buffer;
|
|
}
|
|
|
|
// MsQuic QUIC_STATISTICS_V2 through v2.5. All fields are fixed-width on
|
|
// the supported 64-bit RIDs; keeping the field order aligned with msquic.h.
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct QuicStatisticsV2
|
|
{
|
|
public ulong CorrelationId;
|
|
public uint Flags;
|
|
public uint RttUs;
|
|
public uint MinRttUs;
|
|
public uint MaxRttUs;
|
|
public ulong TimingStart;
|
|
public ulong TimingInitialFlightEnd;
|
|
public ulong TimingHandshakeFlightEnd;
|
|
public uint HandshakeClientFlight1Bytes;
|
|
public uint HandshakeServerFlight1Bytes;
|
|
public uint HandshakeClientFlight2Bytes;
|
|
public ushort SendPathMtu;
|
|
public ulong SendTotalPackets;
|
|
public ulong SendRetransmittablePackets;
|
|
public ulong SendSuspectedLostPackets;
|
|
public ulong SendSpuriousLostPackets;
|
|
public ulong SendTotalBytes;
|
|
public ulong SendTotalStreamBytes;
|
|
public uint SendCongestionCount;
|
|
public uint SendPersistentCongestionCount;
|
|
public ulong RecvTotalPackets;
|
|
public ulong RecvReorderedPackets;
|
|
public ulong RecvDroppedPackets;
|
|
public ulong RecvDuplicatePackets;
|
|
public ulong RecvTotalBytes;
|
|
public ulong RecvTotalStreamBytes;
|
|
public ulong RecvDecryptionFailures;
|
|
public ulong RecvValidAckFrames;
|
|
public uint KeyUpdateCount;
|
|
public uint SendCongestionWindow;
|
|
public uint DestCidUpdateCount;
|
|
public uint SendEcnCongestionCount;
|
|
public byte HandshakeHopLimitTtl;
|
|
public uint RttVarianceUs;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct RegistrationConfig
|
|
{
|
|
public nint AppName;
|
|
public int ExecutionProfile;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct CredentialConfig
|
|
{
|
|
public uint Type;
|
|
public uint Flags;
|
|
public nint Certificate;
|
|
public nint Principal;
|
|
public nint Reserved;
|
|
public nint AsyncHandler;
|
|
public uint AllowedCipherSuites;
|
|
public nint CaCertificateFile;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal struct Pkcs12Certificate
|
|
{
|
|
public nint Asn1Blob;
|
|
public uint Asn1BlobLength;
|
|
public nint PrivateKeyPassword;
|
|
}
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
internal delegate uint ListenerCallback(nint listener, nint context, nint @event);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
internal delegate uint ConnectionCallback(nint connection, nint context, nint @event);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void SetContextDelegate(nint handle, nint context);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void SetCallbackHandlerDelegate(nint handle, nint handler, nint context);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint SetParamDelegate(nint handle, uint parameter, uint bufferLength, void* buffer);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint GetParamDelegate(nint handle, uint parameter, uint* bufferLength, void* buffer);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint RegistrationOpenDelegate(ref RegistrationConfig config, out nint registration);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void HandleCloseDelegate(nint handle);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint ConfigurationOpenDelegate(nint registration, ref QuicBuffer alpn, uint alpnCount, nint settings, uint settingsLength, nint context, out nint configuration);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint ConfigurationLoadCredentialDelegate(nint configuration, ref CredentialConfig credential);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint ListenerOpenDelegate(nint registration, nint callback, nint context, out nint listener);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint ListenerStartDelegate(nint listener, ref QuicBuffer alpn, uint alpnCount, nint address);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void ListenerStopDelegate(nint listener);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint ConnectionOpenDelegate(nint registration, nint callback, nint context, out nint connection);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void ConnectionShutdownDelegate(nint connection, uint flags, ulong errorCode);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint ConnectionStartDelegate(nint connection, nint configuration, ushort family, nint serverName, ushort serverPort);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint ConnectionSetConfigurationDelegate(nint connection, nint configuration);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate uint DatagramSendDelegate(nint connection, nint buffers, uint bufferCount, uint flags, nint clientContext);
|
|
|
|
}
|