Files
MoShiro/backend/shared/MoShiro.Protocol/Raw/RawChunk.cs
T
justme ad5efc62ff agent: unify listening and dialing with directed mutual measurements
Replace Probe with Agent across node runtime, Fleet contracts, backend state, API and storage. Report incoming sessions and outgoing link health through each Agent uplink, and retain directed link controls and retry.

Update protocol fixtures and end-to-end coverage for the unified model; accept requested cancellation during the Fleet timeout test cleanup.
2026-10-21 11:55:36 +08:00

215 lines
5.8 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.Text.Json;
using System.Text.Json.Serialization;
using Fleet.Core;
namespace MoShiro.Protocol;
/// <summary>监听方单调时钟与 UTC 的锚点,以及拨号方时钟相对监听方时钟的偏移估计。</summary>
public sealed class ChunkClock
{
[JsonPropertyName("listener_mono_us")]
public long ListenerMonoUs { get; set; }
[JsonPropertyName("utc_ms")]
public long UtcMs { get; set; }
/// <summary>监听方时钟 − 拨号方时钟(微秒)。</summary>
[JsonPropertyName("dialer_offset_us")]
public long DialerOffsetUs { get; set; }
/// <summary>拨号方相对监听方的时钟漂移(ppb,十亿分之一)。</summary>
[JsonPropertyName("dialer_drift_ppb")]
public long DialerDriftPpb { get; set; }
}
public sealed class ColumnHeader
{
[JsonPropertyName("name")]
public string Name { get; set; } = "";
/// <summary>u8 | u16 | u32 | u64 | i32 | i64</summary>
[JsonPropertyName("type")]
public string Type { get; set; } = "";
/// <summary>plain | delta</summary>
[JsonPropertyName("enc")]
public string Enc { get; set; } = "plain";
[JsonPropertyName("nullable")]
public bool Nullable { get; set; }
/// <summary>ts_dialer | ts_listener | ts_local,其他为空。</summary>
[JsonPropertyName("role")]
public string? Role { get; set; }
}
public sealed class StreamHeader
{
[JsonPropertyName("name")]
public string Name { get; set; } = "";
[JsonPropertyName("count")]
public int Count { get; set; }
[JsonPropertyName("columns")]
public List<ColumnHeader> Columns { get; set; } = [];
}
/// <summary>块头(JSON,自描述):列定义写在块里,新增列或新增 Kind 不需要升级 container_version。</summary>
public sealed class RawChunkHeader
{
[JsonPropertyName("ip_version")]
[JsonPropertyOrder(1)]
public string IpVersion { get; set; } = IpVersions.V4;
[JsonPropertyName("chunk_id")]
public ChunkId ChunkId { get; set; }
[JsonPropertyName("container_version")]
public int ContainerVersion { get; set; } = ProtocolVersion.Container;
[JsonPropertyName("kind")]
public string Kind { get; set; } = "";
[JsonPropertyName("task_id")]
public TaskId? TaskId { get; set; }
[JsonPropertyName("test_id")]
public TestId? TestId { get; set; }
[JsonPropertyName("phase")]
public int? Phase { get; set; }
[JsonPropertyName("agent_id")]
public NodeId AgentId { get; set; }
[JsonPropertyName("dialer_id")]
public NodeId? DialerId { get; set; }
[JsonPropertyName("target")]
public string? Target { get; set; }
/// <summary>参数的完整快照(包含默认值)。</summary>
[JsonPropertyName("params")]
public Dictionary<string, JsonElement> Params { get; set; } = [];
[JsonPropertyName("port")]
public int Port { get; set; }
[JsonPropertyName("direction")]
public string? Direction { get; set; }
[JsonPropertyName("start_utc_ms")]
public long StartUtcMs { get; set; }
[JsonPropertyName("duration_ms")]
public int DurationMs { get; set; }
/// <summary>拨号方一侧的列没有在宽限期内送达:只有监听方一侧的数据。</summary>
[JsonPropertyName("partial")]
public bool Partial { get; set; }
[JsonPropertyName("clock")]
public ChunkClock Clock { get; set; } = new();
[JsonPropertyName("streams")]
public List<StreamHeader> Streams { get; set; } = [];
/// <summary>
/// "line:&lt;dialer_id&gt;" 或 "target:&lt;task_id&gt;":存储和分析用的序列标识。
/// 同一个监听节点上每个拨号方的每种地址族各是一个序列,IPv6 追加 :ipv6,互不混合。
/// </summary>
[JsonIgnore]
public string Series => TaskId is { } id && Target is not null ? "target:" + id.Value : IpVersions.LineSeries(DialerId?.Value ?? "", IpVersion);
public const string LinePrefix = "line:";
}
public sealed class RawColumn
{
public RawColumn(string name, ColumnType type, ColumnEnc enc, bool nullable, ColumnRole role, long[] values, bool[]? valid)
{
Name = name;
Type = type;
Enc = enc;
Nullable = nullable;
Role = role;
Values = values;
Valid = valid;
}
public string Name { get; }
public ColumnType Type { get; }
public ColumnEnc Enc { get; }
public bool Nullable { get; }
public ColumnRole Role { get; }
/// <summary>所有列统一用 long 存放(u64 按位保存)。缺失值对应的位置内容无意义,请先看 <see cref="IsValid"/>。</summary>
public long[] Values { get; }
public bool[]? Valid { get; }
public int Count => Values.Length;
public bool IsValid(int row) => Valid is null || Valid[row];
}
public sealed class RawStream
{
public RawStream(string name, int count, IReadOnlyList<RawColumn> columns)
{
Name = name;
Count = count;
Columns = columns;
}
public string Name { get; }
public int Count { get; }
public IReadOnlyList<RawColumn> Columns { get; }
public RawColumn? Find(string name)
{
foreach (RawColumn column in Columns)
{
if (column.Name == name)
{
return column;
}
}
return null;
}
}
public sealed class RawChunk
{
public RawChunk(RawChunkHeader header, IReadOnlyList<RawStream> streams)
{
Header = header;
Streams = streams;
}
public RawChunkHeader Header { get; }
public IReadOnlyList<RawStream> Streams { get; }
public RawStream? Find(string name)
{
foreach (RawStream stream in Streams)
{
if (stream.Name == name)
{
return stream;
}
}
return null;
}
}