119 lines
3.8 KiB
C#
119 lines
3.8 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace MoShiro.Measurement;
|
|
|
|
/// <summary>Loads the MsQuic v2 ABI used by the QUIC datagram transport.</summary>
|
|
internal static class MsQuicRuntime
|
|
{
|
|
private const uint ApiVersion = 2;
|
|
private static readonly object s_gate = new();
|
|
private static nint s_library;
|
|
private static nint s_api;
|
|
private static OpenVersionDelegate? s_open;
|
|
private static CloseDelegate? s_close;
|
|
private static readonly List<nint> s_supportLibraries = [];
|
|
|
|
public static nint Api
|
|
{
|
|
get
|
|
{
|
|
lock (s_gate)
|
|
{
|
|
if (s_api != 0)
|
|
{
|
|
return s_api;
|
|
}
|
|
|
|
string[] candidates = OperatingSystem.IsWindows()
|
|
? ["msquic.dll"]
|
|
: OperatingSystem.IsMacOS()
|
|
? ["libmsquic.dylib"]
|
|
: ["libmsquic.so.2", "libmsquic.so"];
|
|
LoadBundledSupportLibraries();
|
|
string appDirectory = AppContext.BaseDirectory;
|
|
foreach (string candidate in candidates)
|
|
{
|
|
if (NativeLibrary.TryLoad(Path.Combine(appDirectory, candidate), out s_library)
|
|
|| NativeLibrary.TryLoad(candidate, out s_library))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (s_library == 0)
|
|
{
|
|
throw new DllNotFoundException($"MsQuic runtime was not found. Expected one of: {string.Join(", ", candidates)}.");
|
|
}
|
|
|
|
s_open = Marshal.GetDelegateForFunctionPointer<OpenVersionDelegate>(NativeLibrary.GetExport(s_library, "MsQuicOpenVersion"));
|
|
s_close = Marshal.GetDelegateForFunctionPointer<CloseDelegate>(NativeLibrary.GetExport(s_library, "MsQuicClose"));
|
|
int status = s_open(ApiVersion, out s_api);
|
|
if (status != 0 || s_api == 0)
|
|
{
|
|
NativeLibrary.Free(s_library);
|
|
s_library = 0;
|
|
s_api = 0;
|
|
throw new InvalidOperationException($"MsQuic API v{ApiVersion} could not be opened (status {status}).");
|
|
}
|
|
|
|
return s_api;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void Close()
|
|
{
|
|
lock (s_gate)
|
|
{
|
|
if (s_api != 0)
|
|
{
|
|
s_close!(s_api);
|
|
s_api = 0;
|
|
}
|
|
|
|
if (s_library != 0)
|
|
{
|
|
NativeLibrary.Free(s_library);
|
|
s_library = 0;
|
|
}
|
|
|
|
for (int i = s_supportLibraries.Count - 1; i >= 0; i--)
|
|
{
|
|
NativeLibrary.Free(s_supportLibraries[i]);
|
|
}
|
|
|
|
s_supportLibraries.Clear();
|
|
}
|
|
}
|
|
|
|
private static void LoadBundledSupportLibraries()
|
|
{
|
|
if (OperatingSystem.IsLinux())
|
|
{
|
|
TryLoadSupport("libnuma.so.1");
|
|
}
|
|
else if (OperatingSystem.IsMacOS())
|
|
{
|
|
TryLoadSupport("libcrypto.3.dylib");
|
|
TryLoadSupport("libssl.3.dylib");
|
|
}
|
|
}
|
|
|
|
private static void TryLoadSupport(string library)
|
|
{
|
|
if (NativeLibrary.TryLoad(Path.Combine(AppContext.BaseDirectory, library), out nint handle))
|
|
{
|
|
s_supportLibraries.Add(handle);
|
|
}
|
|
}
|
|
|
|
public static T Function<T>(int index)
|
|
where T : Delegate => Marshal.GetDelegateForFunctionPointer<T>(Marshal.ReadIntPtr(Api, checked(index * IntPtr.Size)));
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate int OpenVersionDelegate(uint version, out nint api);
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
private delegate void CloseDelegate(nint api);
|
|
}
|