Files
MoShiro/backend/shared/MoShiro.Measurement/Net/QuicSettings.cs
T

111 lines
4.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}