using System.Runtime.InteropServices; namespace MoShiro.Measurement; /// Small, versioned view of the MsQuic v2 C API table used by this transport. 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(SetContextIndex); private static readonly SetCallbackHandlerDelegate s_setCallbackHandler = MsQuicRuntime.Function(SetCallbackHandlerIndex); private static readonly SetParamDelegate s_setParam = MsQuicRuntime.Function(SetParamIndex); private static readonly GetParamDelegate s_getParam = MsQuicRuntime.Function(GetParamIndex); private static readonly RegistrationOpenDelegate s_registrationOpen = MsQuicRuntime.Function(RegistrationOpenIndex); private static readonly HandleCloseDelegate s_registrationClose = MsQuicRuntime.Function(RegistrationCloseIndex); private static readonly ConfigurationOpenDelegate s_configurationOpen = MsQuicRuntime.Function(ConfigurationOpenIndex); private static readonly HandleCloseDelegate s_configurationClose = MsQuicRuntime.Function(ConfigurationCloseIndex); private static readonly ConfigurationLoadCredentialDelegate s_loadCredential = MsQuicRuntime.Function(ConfigurationLoadCredentialIndex); private static readonly ListenerOpenDelegate s_listenerOpen = MsQuicRuntime.Function(ListenerOpenIndex); private static readonly HandleCloseDelegate s_listenerClose = MsQuicRuntime.Function(ListenerCloseIndex); private static readonly ListenerStartDelegate s_listenerStart = MsQuicRuntime.Function(ListenerStartIndex); private static readonly ListenerStopDelegate s_listenerStop = MsQuicRuntime.Function(ListenerStopIndex); private static readonly ConnectionOpenDelegate s_connectionOpen = MsQuicRuntime.Function(ConnectionOpenIndex); private static readonly HandleCloseDelegate s_connectionClose = MsQuicRuntime.Function(ConnectionCloseIndex); private static readonly ConnectionShutdownDelegate s_connectionShutdown = MsQuicRuntime.Function(ConnectionShutdownIndex); private static readonly ConnectionStartDelegate s_connectionStart = MsQuicRuntime.Function(ConnectionStartIndex); private static readonly ConnectionSetConfigurationDelegate s_connectionSetConfiguration = MsQuicRuntime.Function(ConnectionSetConfigurationIndex); private static readonly DatagramSendDelegate s_datagramSend = MsQuicRuntime.Function(DatagramSendIndex); public static void SetContext(nint handle, nint context) => s_setContext(handle, context); public static void SetCallbackHandler(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(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(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); }