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.
This commit is contained in:
1 parent
d5ebf29e57
commit
ad5efc62ff
206 files changed
+4202
-4964
No files matched your search
@@ -18,7 +18,7 @@ namespace MoShiro.TestSupport;
|
||||
public sealed class AgentSimulator : IAsyncDisposable
|
||||
{
|
||||
private readonly NodeIdentity _identity;
|
||||
private readonly NodeIdentity _tlsKey = NodeIdentity.Generate();
|
||||
private readonly NodeIdentity _tlsKey;
|
||||
private readonly ClientWebSocket _socket = new();
|
||||
private readonly ConcurrentDictionary<Guid, TaskCompletionSource<bool>> _acks = new();
|
||||
private readonly ConcurrentDictionary<long, TaskCompletionSource<RpcMessage>> _responses = new();
|
||||
@@ -28,9 +28,11 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
private int _disposed;
|
||||
private Task? _loop;
|
||||
|
||||
private AgentSimulator(NodeIdentity identity, NodeId nodeId, string status)
|
||||
private AgentSimulator(NodeIdentity identity, NodeIdentity tlsKey, NodeId nodeId, string status, bool listens)
|
||||
{
|
||||
_identity = identity;
|
||||
_tlsKey = tlsKey;
|
||||
Listens = listens;
|
||||
ExportedKey = identity.ExportPkcs8();
|
||||
NodeId = nodeId;
|
||||
EnrollmentStatus = status;
|
||||
@@ -38,6 +40,9 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
|
||||
public NodeId NodeId { get; }
|
||||
|
||||
/// <summary>是否像监听的节点那样上报端口(false:只拨出的节点)。</summary>
|
||||
public bool Listens { get; set; }
|
||||
|
||||
/// <summary>身份私钥(PKCS#8):释放之后仍可用它以同一个身份重新连接。</summary>
|
||||
public byte[] ExportedKey { get; }
|
||||
|
||||
@@ -72,19 +77,19 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
|
||||
public byte[] PublicKey => _identity.PublicKeySpki;
|
||||
|
||||
/// <summary>hello 里上报的 TLS 证书公钥(Probe 用它固定证书、派生敲门密钥)。</summary>
|
||||
/// <summary>注册时交给面板的 TLS 证书公钥(拨号方用它固定证书、派生敲门密钥)。</summary>
|
||||
public byte[] TlsPublicKey => _tlsKey.PublicKeySpki;
|
||||
|
||||
/// <summary>用这个节点的私钥为认证随机数签名(可以冒充别的节点 ID:用来测试服务端的校验)。</summary>
|
||||
public byte[] SignAuth(NodeId claimedNode, string nonce) => _identity.Sign(SignaturePurpose.Auth, claimedNode.Value, nonce);
|
||||
|
||||
public static async Task<AgentSimulator> EnrollAsync(HttpClient http, string joinToken, string name, string[]? kinds = null, string publicHost = "203.0.113.10")
|
||||
public static async Task<AgentSimulator> EnrollAsync(HttpClient http, string joinToken, string name, string[]? kinds = null, string publicHost = "203.0.113.10", bool listens = true)
|
||||
{
|
||||
NodeIdentity identity = NodeIdentity.Generate();
|
||||
NodeIdentity tls = NodeIdentity.Generate();
|
||||
string pubkey = Convert.ToBase64String(identity.PublicKeySpki);
|
||||
byte[] tokenHash = SHA256.HashData(Encoding.UTF8.GetBytes(joinToken));
|
||||
string proof = Convert.ToBase64String(identity.Sign(SignaturePurpose.Enroll, Convert.ToHexStringLower(tokenHash), pubkey));
|
||||
using NodeIdentity tls = NodeIdentity.Generate();
|
||||
var body = new JsonObject
|
||||
{
|
||||
["join_token"] = joinToken,
|
||||
@@ -96,10 +101,9 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
["node_version"] = "0.0.1-sim",
|
||||
["meta"] = new JsonObject
|
||||
{
|
||||
["probe_tcp_port"] = 7443,
|
||||
["probe_quic_ports"] = new JsonArray(7443),
|
||||
["probe_udp_ports"] = new JsonArray(7443),
|
||||
["public_host"] = publicHost,
|
||||
["tcp_port"] = listens ? 7443 : 0,
|
||||
["quic_ports"] = listens ? new JsonArray(7443) : new JsonArray(),
|
||||
["public_host"] = listens ? publicHost : "",
|
||||
["kinds"] = new JsonArray((kinds ?? ["udp_echo", "tcp_echo", "udp_train", "connect", "hold", "tcp_bulk", "icmp_echo"]).Select(k => (JsonNode?)JsonValue.Create(k)).ToArray()),
|
||||
},
|
||||
};
|
||||
@@ -110,15 +114,16 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
identity.Dispose();
|
||||
tls.Dispose();
|
||||
throw new InvalidOperationException($"enroll failed: {(int)response.StatusCode} {text}");
|
||||
}
|
||||
|
||||
JsonObject result = JsonNode.Parse(text)!.AsObject();
|
||||
return new AgentSimulator(identity, new NodeId(result["node_id"]!.GetValue<string>()), result["status"]!.GetValue<string>());
|
||||
return new AgentSimulator(identity, tls, new NodeId(result["node_id"]!.GetValue<string>()), result["status"]!.GetValue<string>(), listens);
|
||||
}
|
||||
|
||||
/// <summary>用已有的身份恢复一个模拟器(例如后端重启之后同一个节点重新连接)。</summary>
|
||||
public static AgentSimulator Restore(byte[] pkcs8, NodeId nodeId) => new(NodeIdentity.FromPkcs8(pkcs8), nodeId, "active");
|
||||
public static AgentSimulator Restore(byte[] pkcs8, NodeId nodeId, bool listens = true) => new(NodeIdentity.FromPkcs8(pkcs8), NodeIdentity.Generate(), nodeId, "active", listens);
|
||||
|
||||
/// <summary>连接并完成认证与 hello。认证失败时抛出异常。</summary>
|
||||
public async Task ConnectAsync(Uri baseAddress, bool sendHello = true, string[]? kinds = null)
|
||||
@@ -150,16 +155,12 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
public async Task HelloAsync(string[]? kinds = null)
|
||||
{
|
||||
var meta = JsonSerializer.SerializeToElement(
|
||||
new AgentHelloMeta
|
||||
new NodeHelloMeta
|
||||
{
|
||||
TlsPublicKey = Convert.ToBase64String(_tlsKey.PublicKeySpki),
|
||||
Addresses = Addresses is null ? null : [.. Addresses],
|
||||
ProbeTcpPort = 7443,
|
||||
ProbeQuicPorts = [7443],
|
||||
LegacyProbeUdpPorts = [7443],
|
||||
Listen = Listens ? new ListenInfo { TcpPort = 7443, QuicPorts = [7443], Addresses = Addresses is null ? null : [.. Addresses] } : null,
|
||||
LocalLimits = new LocalLimits { MaxTotalMbps = 200, MaxTotalPps = 20_000, MaxConcurrentTests = 1 },
|
||||
},
|
||||
MoShiroJsonContext.Default.AgentHelloMeta);
|
||||
MoShiroJsonContext.Default.NodeHelloMeta);
|
||||
var hello = new HelloParams
|
||||
{
|
||||
NodeVersion = "0.0.1-sim",
|
||||
@@ -171,14 +172,14 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
}
|
||||
|
||||
/// <summary>上传一个原始块并等待服务端 ACK(持久化之后才会确认)。</summary>
|
||||
/// <summary>设置之后,这个假 Agent 发出的每个块都归属于这个 Probe(夹具的默认 Probe)。</summary>
|
||||
public ProbeId? ProbeForChunks { get; set; }
|
||||
/// <summary>设置之后,这个假 Agent 发出的每个块都归属于这个拨号方(夹具的默认拨号方)。</summary>
|
||||
public NodeId? DialerForChunks { get; set; }
|
||||
|
||||
public async Task<bool> SendChunkAsync(RawChunk chunk, TimeSpan? timeout = null)
|
||||
{
|
||||
if (ProbeForChunks is { } probe)
|
||||
if (DialerForChunks is { } dialer)
|
||||
{
|
||||
chunk.Header.ProbeId = probe;
|
||||
chunk.Header.DialerId = dialer;
|
||||
}
|
||||
|
||||
byte[] bytes = RawChunkCodec.Encode(chunk);
|
||||
@@ -214,7 +215,7 @@ public sealed class AgentSimulator : IAsyncDisposable
|
||||
public Task SendUpdateStatusAsync(UpdateStatus status) =>
|
||||
SendAsync(RpcMessage.Notification(
|
||||
FleetMethods.Heartbeat,
|
||||
new HeartbeatParams { Extra = JsonSerializer.SerializeToElement(new AgentHeartbeatExtra { Update = status }, MoShiroJsonContext.Default.AgentHeartbeatExtra) },
|
||||
new HeartbeatParams { Extra = JsonSerializer.SerializeToElement(new NodeHeartbeatExtra { Update = status }, MoShiroJsonContext.Default.NodeHeartbeatExtra) },
|
||||
FleetRpcContext.Default.HeartbeatParams));
|
||||
|
||||
public async Task<RpcMessage> RequestRawAsync(string method, JsonElement? parameters = null) => await RequestAsync(method, parameters);
|
||||
|
||||
@@ -7,14 +7,14 @@ namespace MoShiro.TestSupport;
|
||||
/// <summary>每一个回显包的设定:上行或下行丢失,以及两个方向的单向时延(微秒)。</summary>
|
||||
public readonly record struct EchoPacket(bool LostUp, bool LostDown, long OwdUpUs, long OwdDownUs, long ProcessUs = 50);
|
||||
|
||||
/// <summary>合成原始块:用于测试、模拟器和离线工具。块头里的时钟锚点保证 Probe 与 Agent 时钟相差 <see cref="ProbeOffsetUs"/>。</summary>
|
||||
/// <summary>合成原始块:用于测试、模拟器和离线工具。块头里的时钟锚点保证 拨号方与监听方时钟相差 <see cref="DialerOffsetUs"/>。</summary>
|
||||
public static class ChunkFactory
|
||||
{
|
||||
/// <summary>合成块默认属于哪个 Probe(测试夹具在后端创建一个真实的 Probe 之后把它的 id 写在这里)。</summary>
|
||||
public static string DefaultProbe { get; set; } = "prb_test";
|
||||
/// <summary>合成块默认属于哪个拨号侧 Agent(测试夹具在后端创建一个真实的拨号侧 Agent 之后把它的 id 写在这里)。</summary>
|
||||
public static string DefaultDialer { get; set; } = "agt_dialer_test";
|
||||
|
||||
public const long AgentMonoAnchorUs = 1_000_000_000;
|
||||
public const long ProbeOffsetUs = 5_000_000;
|
||||
public const long DialerOffsetUs = 5_000_000;
|
||||
|
||||
public static RawChunkHeader Header(NodeId agent, string kind, long startUtcMs, int durationMs = 10_000, IReadOnlyDictionary<string, JsonElement>? parameters = null)
|
||||
{
|
||||
@@ -25,17 +25,17 @@ public static class ChunkFactory
|
||||
ChunkId = new ChunkId(Guid.CreateVersion7()),
|
||||
Kind = kind,
|
||||
AgentId = agent,
|
||||
ProbeId = new ProbeId(DefaultProbe),
|
||||
DialerId = new NodeId(DefaultDialer),
|
||||
Params = new Dictionary<string, JsonElement>(resolved.Values!.Values),
|
||||
Port = 7443,
|
||||
StartUtcMs = startUtcMs,
|
||||
DurationMs = durationMs,
|
||||
Clock = new ChunkClock { AgentMonoUs = AgentMonoAnchorUs, UtcMs = startUtcMs, ProbeOffsetUs = ProbeOffsetUs },
|
||||
Clock = new ChunkClock { ListenerMonoUs = AgentMonoAnchorUs, UtcMs = startUtcMs, DialerOffsetUs = DialerOffsetUs },
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Probe 时钟下,距离块起点 <paramref name="offsetMs"/> 毫秒的时刻(微秒)。</summary>
|
||||
public static long ProbeUs(long offsetMs) => (offsetMs * 1000) + AgentMonoAnchorUs - ProbeOffsetUs;
|
||||
/// <summary>拨号方时钟下,距离块起点 <paramref name="offsetMs"/> 毫秒的时刻(微秒)。</summary>
|
||||
public static long DialerUs(long offsetMs) => (offsetMs * 1000) + AgentMonoAnchorUs - DialerOffsetUs;
|
||||
|
||||
public static long AgentUs(long offsetMs) => (offsetMs * 1000) + AgentMonoAnchorUs;
|
||||
|
||||
@@ -54,10 +54,10 @@ public static class ChunkFactory
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
EchoPacket p = packet(i);
|
||||
long t1 = ProbeUs((long)i * intervalMs);
|
||||
long t2 = t1 + ProbeOffsetUs + p.OwdUpUs;
|
||||
long t1 = DialerUs((long)i * intervalMs);
|
||||
long t2 = t1 + DialerOffsetUs + p.OwdUpUs;
|
||||
long t3 = t2 + p.ProcessUs;
|
||||
long t4 = t3 - ProbeOffsetUs + p.OwdDownUs;
|
||||
long t4 = t3 - DialerOffsetUs + p.OwdDownUs;
|
||||
msg.Add(i, 64, t1, p.LostUp ? null : t2, p.LostUp ? null : t3, p.LostUp || p.LostDown ? null : t4);
|
||||
}
|
||||
|
||||
@@ -91,14 +91,14 @@ public static class ChunkFactory
|
||||
int i = 0;
|
||||
foreach ((long tcp, long? tls, ConnectOutcomeCode outcome) in attempts)
|
||||
{
|
||||
builder.Add(i, ProbeUs(i * 5000L), outcome == ConnectOutcomeCode.Timeout ? null : tcp, tls, (long)outcome);
|
||||
builder.Add(i, DialerUs(i * 5000L), outcome == ConnectOutcomeCode.Timeout ? null : tcp, tls, (long)outcome);
|
||||
i++;
|
||||
}
|
||||
|
||||
return new RawChunk(Header(agent, "connect", startUtcMs), [builder.Build()]);
|
||||
}
|
||||
|
||||
/// <summary>udp_train:Agent 以固定间隔发包,Probe 记录收到的时刻;<paramref name="lossEvery"/> 为 N 时每 N 个包丢一个。</summary>
|
||||
/// <summary>udp_train:Agent 以固定间隔发包,Dialer 记录收到的时刻;<paramref name="lossEvery"/> 为 N 时每 N 个包丢一个。</summary>
|
||||
public static RawChunk Train(NodeId agent, long startUtcMs, double mbps, int phase, int seconds = 10, int lossEvery = 0, long owdUs = 20_000)
|
||||
{
|
||||
const int size = 1250;
|
||||
@@ -109,8 +109,8 @@ public static class ChunkFactory
|
||||
{
|
||||
long sendAgent = AgentMonoAnchorUs + (long)(i * intervalUs);
|
||||
bool lost = lossEvery > 0 && i % lossEvery == 0;
|
||||
long recvProbe = sendAgent + owdUs - ProbeOffsetUs;
|
||||
pkt.Add(i, size, sendAgent, lost ? null : recvProbe);
|
||||
long recvDialer = sendAgent + owdUs - DialerOffsetUs;
|
||||
pkt.Add(i, size, sendAgent, lost ? null : recvDialer);
|
||||
}
|
||||
|
||||
var parameters = new Dictionary<string, JsonElement>
|
||||
|
||||
+34
-37
@@ -14,15 +14,15 @@ using MoShiro.Protocol;
|
||||
namespace MoShiro.TestSupport;
|
||||
|
||||
/// <summary>
|
||||
/// 假 Probe:通过真实的点对点握手(TLS 固定证书 + 签名 + ECDH)连接真实的 Agent,接收 <c>line_task_set</c>,
|
||||
/// 通过 QUIC Datagram 发回显包、接收单向数据流、估计时钟偏移,并把 Probe 一侧的列块经发件箱通道 17 交给 Agent。
|
||||
/// 它实现 Probe 的协议职责,是将来真实 Probe 的参照。
|
||||
/// 拨号侧模拟器:通过真实的点对点握手(TLS 固定证书 + 签名 + ECDH)连接真实的 Agent,接收 <c>line_task_set</c>,
|
||||
/// 通过 QUIC Datagram 发回显包、接收单向数据流、估计时钟偏移,并把 拨号方一侧的列块经发件箱通道 17 交给 Agent。
|
||||
/// 它实现 Agent 拨号侧的协议职责,用于验证监听侧和数据合并行为。
|
||||
/// </summary>
|
||||
public sealed class ProbeSimulator : IAsyncDisposable
|
||||
public sealed class DialerSimulator : IAsyncDisposable
|
||||
{
|
||||
private readonly NodeIdentity _identity;
|
||||
private byte[]? _agentKey;
|
||||
private readonly RpcRouter<ProbeSimulator> _router;
|
||||
private readonly RpcRouter<DialerSimulator> _router;
|
||||
private readonly ConcurrentDictionary<Guid, TaskCompletionSource> _acks = new();
|
||||
private readonly ConcurrentDictionary<uint, EchoCollector> _echoes = new();
|
||||
private readonly ConcurrentDictionary<uint, TrainCollector> _trains = new();
|
||||
@@ -30,27 +30,26 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
private readonly long _origin = TimeProvider.System.GetTimestamp();
|
||||
private readonly CancellationTokenSource _stop = new();
|
||||
private NodeId _agentId;
|
||||
private RpcSession<ProbeSimulator>? _rpc;
|
||||
private RpcSession<DialerSimulator>? _rpc;
|
||||
private QuicDatagramTransport? _quic;
|
||||
private DataSession? _dataSession;
|
||||
private IPEndPoint? _agentQuic;
|
||||
private PeerSessionInfo? _info;
|
||||
private Task? _receiver;
|
||||
private Task? _run;
|
||||
|
||||
public ProbeSimulator(string probeId, NodeIdentity identity)
|
||||
public DialerSimulator(string dialerId, NodeIdentity identity)
|
||||
{
|
||||
ProbeId = probeId;
|
||||
DialerId = dialerId;
|
||||
_identity = identity;
|
||||
_router = BuildRouter();
|
||||
}
|
||||
|
||||
public string ProbeId { get; }
|
||||
public string DialerId { get; }
|
||||
|
||||
/// <summary>同一个身份的另一个假 Probe(模拟 Probe 重启:新的进程、新的会话,序号从 0 重新开始)。</summary>
|
||||
public ProbeSimulator Restarted() => new(ProbeId, _identity);
|
||||
/// <summary>同一个身份的另一个拨号侧模拟器(模拟 Dialer 重启:新的进程、新的会话,序号从 0 重新开始)。</summary>
|
||||
public DialerSimulator Restarted() => new(DialerId, _identity);
|
||||
|
||||
/// <summary>true 时拒绝按需测试的阶段(用来测试 Agent 对 Probe 拒绝的处理)。</summary>
|
||||
/// <summary>true 时拒绝按需测试的阶段(用来测试 Agent 对 Dialer 拒绝的处理)。</summary>
|
||||
public bool RefusePhases { get; set; }
|
||||
|
||||
/// <summary>收到 phase_end 之后不交出列块,只报告 0 个(用来测试 Agent 的等待逻辑)。</summary>
|
||||
@@ -73,7 +72,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
timeout.CancelAfter(TimeSpan.FromSeconds(10));
|
||||
_agentId = agentId;
|
||||
(PeerSessionInfo info, TlsFrameTransport transport) = await PeerConnector.ConnectAsync(host, controlPort, agentPublicKey, _identity, ProbeId, "0.0.0-sim", 8 << 20, time ?? TimeProvider.System, timeout.Token);
|
||||
(PeerSessionInfo info, TlsFrameTransport transport) = await PeerConnector.ConnectAsync(host, controlPort, agentPublicKey, _identity, DialerId, "0.0.0-sim", 8 << 20, time ?? TimeProvider.System, timeout.Token);
|
||||
_info = info;
|
||||
_agentKey = agentPublicKey;
|
||||
_dataSession = new DataSession(info.Credentials.SessionIdValue, info.Credentials.Key);
|
||||
@@ -84,12 +83,10 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
new DataPlaneCounters(),
|
||||
new TestNodeLog(),
|
||||
_agentQuic,
|
||||
agentPublicKey,
|
||||
0);
|
||||
_rpc = new RpcSession<ProbeSimulator>(transport, _router, this, time ?? TimeProvider.System);
|
||||
agentPublicKey);
|
||||
_rpc = new RpcSession<DialerSimulator>(transport, _router, this, time ?? TimeProvider.System);
|
||||
_rpc.OnBinary = (_, _) => ValueTask.CompletedTask;
|
||||
_run = _rpc.RunAsync(_stop.Token);
|
||||
_receiver = _quic.RunAsync(_stop.Token);
|
||||
await SyncClockAsync(ct: timeout.Token);
|
||||
await _quic.ConnectAsync(timeout.Token);
|
||||
}
|
||||
@@ -112,7 +109,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
throw new TimeoutException("the agent did not send the expected line task set");
|
||||
}
|
||||
|
||||
/// <summary>NTP 式的时钟同步:偏移 = Agent 时钟 − (往返中点的) Probe 时钟。取最小往返的一次。</summary>
|
||||
/// <summary>NTP 式的时钟同步:偏移 = Agent 时钟 − (往返中点的) 拨号方时钟。取最小往返的一次。</summary>
|
||||
public async Task SyncClockAsync(int rounds = 3, CancellationToken ct = default)
|
||||
{
|
||||
long bestRoundTrip = long.MaxValue;
|
||||
@@ -121,7 +118,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
long t0 = NowUs();
|
||||
Result<ClockResult> reply = await _rpc!.RequestAsync(
|
||||
MoShiroMethods.Clock,
|
||||
new ClockParams { ProbeMonoUs = t0 },
|
||||
new ClockParams { DialerMonoUs = t0 },
|
||||
MoShiroJsonContext.Default.ClockParams,
|
||||
MoShiroJsonContext.Default.ClockResult,
|
||||
ct,
|
||||
@@ -136,7 +133,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
if (roundTrip < bestRoundTrip)
|
||||
{
|
||||
bestRoundTrip = roundTrip;
|
||||
ClockOffsetUs = reply.Value.AgentMonoUs - ((t0 + t1) / 2);
|
||||
ClockOffsetUs = reply.Value.ListenerMonoUs - ((t0 + t1) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +160,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
_echoes.TryRemove(task.StreamId, out _);
|
||||
_quic.Unregister(task.StreamId);
|
||||
|
||||
var msg = new StreamBuilder(new StreamSpec("msg", [Col.Seq("seq"), Col.U16("size"), Col.TsProbe("t1"), Col.TsProbe("t4", nullable: true)]));
|
||||
var msg = new StreamBuilder(new StreamSpec("msg", [Col.Seq("seq"), Col.U16("size"), Col.TsDialer("t1"), Col.TsDialer("t4", nullable: true)]));
|
||||
foreach ((uint seq, long t1) in collector.Sent.OrderBy(p => p.Key))
|
||||
{
|
||||
msg.Add(seq, size, t1, collector.Received.TryGetValue(seq, out long t4) ? t4 : null);
|
||||
@@ -198,7 +195,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
await stream.WriteAsync(auth, ct);
|
||||
|
||||
long startUtc = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds();
|
||||
var msg = new StreamBuilder(new StreamSpec("msg", [Col.Seq("seq"), Col.U16("size"), Col.TsProbe("t1"), Col.TsProbe("t4", nullable: true)]));
|
||||
var msg = new StreamBuilder(new StreamSpec("msg", [Col.Seq("seq"), Col.U16("size"), Col.TsDialer("t1"), Col.TsDialer("t4", nullable: true)]));
|
||||
int received = 0;
|
||||
for (uint seq = 0; seq < count; seq++)
|
||||
{
|
||||
@@ -278,7 +275,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Task? task in new[] { _run, _receiver })
|
||||
foreach (Task? task in new[] { _run })
|
||||
{
|
||||
if (task is not null)
|
||||
{
|
||||
@@ -306,10 +303,10 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
TaskId = task,
|
||||
TestId = test,
|
||||
Phase = phase,
|
||||
ProbeId = new ProbeId(ProbeId),
|
||||
DialerId = new NodeId(DialerId),
|
||||
StartUtcMs = startUtcMs,
|
||||
DurationMs = durationMs,
|
||||
Clock = new ChunkClock { ProbeOffsetUs = ClockOffsetUs },
|
||||
Clock = new ChunkClock { DialerOffsetUs = ClockOffsetUs },
|
||||
};
|
||||
if (extra is not null)
|
||||
{
|
||||
@@ -337,17 +334,17 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private RpcRouter<ProbeSimulator> BuildRouter() =>
|
||||
new RpcRouter<ProbeSimulator>(RpcSide.Dialer, MoShiroMethodTable.Table)
|
||||
private RpcRouter<DialerSimulator> BuildRouter() =>
|
||||
new RpcRouter<DialerSimulator>(RpcSide.Dialer, MoShiroMethodTable.Table)
|
||||
.On(
|
||||
FleetMethods.StateSync,
|
||||
FleetRpcContext.Default.StateSyncParams,
|
||||
FleetRpcContext.Default.StateSyncResult,
|
||||
(probe, sync, _) =>
|
||||
(dialer, sync, _) =>
|
||||
{
|
||||
if (sync.Type == LineTaskSet.StateType)
|
||||
{
|
||||
probe._taskSets.Enqueue(sync.Doc.Deserialize(MoShiroJsonContext.Default.LineTaskSet)!);
|
||||
dialer._taskSets.Enqueue(sync.Doc.Deserialize(MoShiroJsonContext.Default.LineTaskSet)!);
|
||||
}
|
||||
|
||||
return ValueTask.FromResult(Result<StateSyncResult>.Ok(new StateSyncResult { AppliedVersion = sync.Version }));
|
||||
@@ -356,28 +353,28 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
MoShiroMethods.TestPhase,
|
||||
MoShiroJsonContext.Default.TestPhaseStartParams,
|
||||
MoShiroJsonContext.Default.TestPhaseStartResult,
|
||||
async (probe, start, ct) => Result<TestPhaseStartResult>.Ok(new TestPhaseStartResult { Accepted = await probe.StartPhaseAsync(start, ct) }))
|
||||
async (dialer, start, ct) => Result<TestPhaseStartResult>.Ok(new TestPhaseStartResult { Accepted = await dialer.StartPhaseAsync(start, ct) }))
|
||||
.On(
|
||||
MoShiroMethods.TestPhaseEnd,
|
||||
MoShiroJsonContext.Default.TestPhaseEndParams,
|
||||
MoShiroJsonContext.Default.TestPhaseEndResult,
|
||||
async (probe, end, ct) => Result<TestPhaseEndResult>.Ok(new TestPhaseEndResult { Fragments = await probe.EndPhaseAsync(end, ct) }))
|
||||
async (dialer, end, ct) => Result<TestPhaseEndResult>.Ok(new TestPhaseEndResult { Fragments = await dialer.EndPhaseAsync(end, ct) }))
|
||||
.OnNotify(
|
||||
MoShiroMethods.TestAbort,
|
||||
MoShiroJsonContext.Default.TestAbortParams,
|
||||
(probe, abort, _) =>
|
||||
(dialer, abort, _) =>
|
||||
{
|
||||
probe.Aborts.Enqueue(abort.TestId);
|
||||
dialer.Aborts.Enqueue(abort.TestId);
|
||||
return ValueTask.CompletedTask;
|
||||
})
|
||||
.OnNotify(
|
||||
FleetMethods.OutboxAck,
|
||||
FleetRpcContext.Default.OutboxAckParams,
|
||||
(probe, ack, _) =>
|
||||
(dialer, ack, _) =>
|
||||
{
|
||||
foreach (string id in ack.Ids.Concat(ack.Duplicates))
|
||||
{
|
||||
if (probe._acks.TryRemove(Guid.Parse(id), out TaskCompletionSource? waiter))
|
||||
if (dialer._acks.TryRemove(Guid.Parse(id), out TaskCompletionSource? waiter))
|
||||
{
|
||||
waiter.TrySetResult();
|
||||
}
|
||||
@@ -437,7 +434,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
|
||||
for (uint from = 0; from < total || fragments == 0; from += PerFragment)
|
||||
{
|
||||
uint to = (uint)Math.Min((long)from + PerFragment, total);
|
||||
var pkt = new StreamBuilder(new StreamSpec("pkt", [Col.Seq("seq"), Col.TsProbe("t_recv", nullable: true)]));
|
||||
var pkt = new StreamBuilder(new StreamSpec("pkt", [Col.Seq("seq"), Col.TsDialer("t_recv", nullable: true)]));
|
||||
foreach ((uint seq, long recv) in train.Received.Where(p => p.Key >= from && p.Key < to).OrderBy(p => p.Key))
|
||||
{
|
||||
pkt.Add(seq, recv);
|
||||
Reference in new issue
Block a user