111 lines
4.0 KiB
C#
111 lines
4.0 KiB
C#
using System.Buffers.Binary;
|
||
|
||
namespace MoShiro.Measurement;
|
||
|
||
/// <summary>
|
||
/// 数据通道 QUIC 连接的传输参数(<c>agent.json</c> 的 <c>quic</c> 段);null 的项用 MsQuic 默认值。
|
||
/// 一条 QUIC 连接由这个 Agent 与对端之间的所有任务共用,所以这些参数属于节点,不属于某个任务。
|
||
/// 监听方用它接受连接,拨号方用它发起连接,两边各自生效。
|
||
/// </summary>
|
||
public sealed record QuicSettings
|
||
{
|
||
/// <summary>MsQuic 默认的最小、最大 MTU 是 1248 和 1500(IP 包大小,含 IP 和 UDP 头)。</summary>
|
||
public const int MinAllowedMtu = 1200;
|
||
|
||
public const int MaxAllowedMtu = 65535;
|
||
|
||
/// <summary>路径 MTU 探测的起点;链路的真实 MTU 更小时探测会往下收缩,更大时往上探测到 <see cref="MaxMtu"/>。</summary>
|
||
public int? MinMtu { get; init; }
|
||
|
||
/// <summary>路径 MTU 探测的上限。设成链路的真实 MTU 可以省掉探测,设小可以避开会丢大包的路径。</summary>
|
||
public int? MaxMtu { get; init; }
|
||
|
||
public bool? Pacing { get; init; }
|
||
|
||
public int? IdleTimeoutMs { get; init; }
|
||
|
||
public int? KeepAliveMs { get; init; }
|
||
|
||
public bool IsDefault => MinMtu is null && MaxMtu is null && Pacing is null && IdleTimeoutMs is null && KeepAliveMs is null;
|
||
|
||
/// <summary>参数是否合法;不合法返回原因。</summary>
|
||
public string? Validate()
|
||
{
|
||
if (MinMtu is { } min && min is < MinAllowedMtu or > MaxAllowedMtu)
|
||
{
|
||
return $"min_mtu must be {MinAllowedMtu}..{MaxAllowedMtu}";
|
||
}
|
||
|
||
if (MaxMtu is { } max && max is < MinAllowedMtu or > MaxAllowedMtu)
|
||
{
|
||
return $"max_mtu must be {MinAllowedMtu}..{MaxAllowedMtu}";
|
||
}
|
||
|
||
if (MinMtu is { } lo && MaxMtu is { } hi && lo > hi)
|
||
{
|
||
return "min_mtu must not exceed max_mtu";
|
||
}
|
||
|
||
if (IdleTimeoutMs is < 0)
|
||
{
|
||
return "idle_timeout_ms must not be negative";
|
||
}
|
||
|
||
return KeepAliveMs is < 0 ? "keep_alive_ms must not be negative" : null;
|
||
}
|
||
|
||
// QUIC_SETTINGS(msquic.h)的布局:IsSetFlags(u64) 后面依次是 u64、u32、u16 各一组字段,再是位域字节。
|
||
// 这里只写用到的几项,其余字段保持 0 且不置位。长度只算到 MtuDiscoveryMissingProbeCount,更靠后的字段 MsQuic 不会读。
|
||
private const int Size = 112;
|
||
private const int IdleTimeoutOffset = 24;
|
||
private const int KeepAliveOffset = 88;
|
||
private const int MinMtuOffset = 102;
|
||
private const int MaxMtuOffset = 104;
|
||
private const int BitFieldOffset = 106;
|
||
private const int PacingBit = 1 << 1;
|
||
|
||
private const int IdleTimeoutFlag = 2;
|
||
private const int KeepAliveFlag = 16;
|
||
private const int MinMtuFlag = 22;
|
||
private const int MaxMtuFlag = 23;
|
||
private const int PacingFlag = 25;
|
||
|
||
internal byte[] ToNative()
|
||
{
|
||
byte[] native = new byte[Size];
|
||
ulong isSet = 0;
|
||
if (IdleTimeoutMs is { } idle)
|
||
{
|
||
BinaryPrimitives.WriteUInt64LittleEndian(native.AsSpan(IdleTimeoutOffset), (ulong)idle);
|
||
isSet |= 1UL << IdleTimeoutFlag;
|
||
}
|
||
|
||
if (KeepAliveMs is { } keepAlive)
|
||
{
|
||
BinaryPrimitives.WriteUInt32LittleEndian(native.AsSpan(KeepAliveOffset), (uint)keepAlive);
|
||
isSet |= 1UL << KeepAliveFlag;
|
||
}
|
||
|
||
if (MinMtu is { } min)
|
||
{
|
||
BinaryPrimitives.WriteUInt16LittleEndian(native.AsSpan(MinMtuOffset), (ushort)min);
|
||
isSet |= 1UL << MinMtuFlag;
|
||
}
|
||
|
||
if (MaxMtu is { } max)
|
||
{
|
||
BinaryPrimitives.WriteUInt16LittleEndian(native.AsSpan(MaxMtuOffset), (ushort)max);
|
||
isSet |= 1UL << MaxMtuFlag;
|
||
}
|
||
|
||
if (Pacing is { } pacing)
|
||
{
|
||
native[BitFieldOffset] = (byte)(pacing ? PacingBit : 0);
|
||
isSet |= 1UL << PacingFlag;
|
||
}
|
||
|
||
BinaryPrimitives.WriteUInt64LittleEndian(native, isSet);
|
||
return native;
|
||
}
|
||
}
|