using Fleet.Core; using Fleet.Link; using MoShiro.Measurement; using MoShiro.Protocol; namespace MoShiro.Agent; /// /// 拨号方 的列块到达之后:找到对应的流 → 与 Agent 在数据面记录的列合并 → 盖章(块头、时钟锚点)→ 编码 → 放进发件箱(通道 16)。 /// 找不到对应流的列块(任务已经被撤销、测试已经结束)被丢弃并计数;这只会发生在配置变更的瞬间。 /// public sealed class ChunkAssembler { private readonly StreamRegistry _streams; private readonly MonoClock _clock; private readonly AgentCounters _counters; private readonly Outbox _outbox; private readonly NodeId _agent; private readonly TimeProvider _time; private readonly INodeLog _log; private readonly TaskTrafficReporter _traffic; public ChunkAssembler(StreamRegistry streams, MonoClock clock, AgentCounters counters, Outbox outbox, NodeId agent, TimeProvider time, INodeLog log) { _streams = streams; _clock = clock; _counters = counters; _outbox = outbox; _agent = agent; _time = time; _log = log; _traffic = new TaskTrafficReporter(log, time); } /// /// 处理某个拨号方交来的一个列块(已经解码)。返回是否被接受。 /// 块头里的 dialer_id 以认证过的会话为准(拨号方 自报的值不可信),别的拨号方的流也不会被命中。 /// public bool Accept(RawChunk fragment, DialerSession dialer) { RawChunkHeader header = fragment.Header; StreamInfo? stream = header switch { { TestId: { } test, Phase: { } phase } => _streams.FindByTest(test, phase) is { } found && ReferenceEquals(found.Owner, dialer) ? found : null, { TaskId: { } task } => _streams.FindByTask(task, dialer), _ => null, }; header.DialerId = new NodeId(dialer.DialerId); header.IpVersion = dialer.IpVersion; // 地址族以认证过的控制会话为准,不接受列块自报值 KindSpec? spec = stream is null ? null : Kinds.Find(stream.Kind); if (stream is null || spec is null || spec.Id != header.Kind) { _counters.FragmentDiscarded(); return false; } Result> merged = FragmentMerger.Merge(spec, fragment, stream.Id, _streams); if (!merged.TryGet(out IReadOnlyList? streams, out FleetError? error)) { _log.Warn("fragment discarded", ("kind", spec.Id), ("err", error.Code)); _counters.FragmentDiscarded(); stream.FragmentDone(); return false; } RawChunk chunk = ChunkFinalizer.Finalize(spec, fragment, streams, _agent, _clock.Anchor(), stream.Params.Values, stream.Port, ChunkId.New(_time)); Emit(chunk); _counters.FragmentAccepted(); stream.FragmentDone(); return true; } public Task RunTrafficReportingAsync(CancellationToken ct) => _traffic.RunAsync(ct); public void FlushTraffic() => _traffic.Flush(); /// Agent 自己采集的块(目标任务)直接发出。 public void Emit(RawChunk chunk) { byte[] payload = RawChunkCodec.Encode(chunk, out int uncompressedBytes); _outbox.Enqueue(MoShiroChannels.RawChunk, chunk.Header.ChunkId.Value, payload); _traffic.Record( chunk.Header.TaskId?.Value ?? chunk.Header.TestId?.Value ?? "unknown", chunk.Header.Kind, chunk.Streams.Sum(stream => (long)stream.Count), uncompressedBytes, payload.Length); } }