using System.Buffers.Binary; namespace MoShiro.Measurement; /// /// 数据通道 QUIC 连接的传输参数(agent.json 的 quic 段);null 的项用 MsQuic 默认值。 /// 一条 QUIC 连接由这个 Agent 与对端之间的所有任务共用,所以这些参数属于节点,不属于某个任务。 /// 监听方用它接受连接,拨号方用它发起连接,两边各自生效。 /// public sealed record QuicSettings { /// MsQuic 默认的最小、最大 MTU 是 1248 和 1500(IP 包大小,含 IP 和 UDP 头)。 public const int MinAllowedMtu = 1200; public const int MaxAllowedMtu = 65535; /// 路径 MTU 探测的起点;链路的真实 MTU 更小时探测会往下收缩,更大时往上探测到 。 public int? MinMtu { get; init; } /// 路径 MTU 探测的上限。设成链路的真实 MTU 可以省掉探测,设小可以避开会丢大包的路径。 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; /// 参数是否合法;不合法返回原因。 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; } }