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;
}
}