using System.Text.Json;
using Fleet.Hosting;
using Fleet.Core;
using Fleet.Rpc;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace MoShiro.Agent;
///
/// 期望任务集(task_set)的调和器:每个任务先经过 (参数校验 + 本地硬上限)。
/// 通过的线路任务为每个拨号方会话各登记一组流(流编号和监听方积木都属于会话,互不干扰),并汇总成 line_task_set 下发给这个拨号方;
/// 通过的目标任务在本地运行,与拨号方无关。被拒绝的任务带着原因(rejected[])回报给 Backend,其余任务照常运行。
/// 每次调用都应用完整的快照,所以是幂等的。预算(码率、包速率)按单个拨号方会话计算,会话总数由 PeerListenerOptions.MaxSessions 限制。
///
public sealed class TaskManager
{
// 线路任务 = 有监听方积木、并且不是只用于按需测试的 Kind(quic_stream、tcp_transfer 会占满链路);目标任务 = 可以由 Agent 自己运行的 Kind
private static readonly HashSet s_lineKinds = [.. BlockCatalog.All.Where(p => p.Listener is not null && !p.OnDemandOnly).Select(p => p.Kind)];
private static readonly HashSet s_targetKinds = [.. BlockCatalog.All.Where(p => p.RunsAsTarget).Select(p => p.Kind)];
private readonly object _gate = new();
private readonly Func _guardFactory;
private readonly StreamRegistry _streams;
private readonly BlockEnv _env;
private readonly TargetRunners _targets;
private readonly Func> _quicPorts;
private readonly Func> _tcpPorts;
private readonly TrustAnchor _trust;
private readonly bool _acceptLineTasks;
private readonly Dictionary> _sessionLines = [];
private volatile Dictionary _peers = new(StringComparer.Ordinal);
private Dictionary _lineSpecs = [];
private long _version;
private BudgetGuard? _guard;
public TaskManager(Func guardFactory, StreamRegistry streams, BlockEnv env, TargetRunners targets, Func> quicPorts, Func> tcpPorts, TrustAnchor? trust = null, bool acceptLineTasks = true)
{
_trust = trust ?? TrustAnchor.None;
_acceptLineTasks = acceptLineTasks;
_guardFactory = guardFactory;
_streams = streams;
_env = env;
_targets = targets;
_quicPorts = quicPorts;
_tcpPorts = tcpPorts;
}
public BudgetGuard Guard => _guard ??= _guardFactory();
/// 某个拨号方的线路任务集变化(版本递增):由 推送给这个拨号方。
public Action? OnLineTasksChanged { get; set; }
/// 允许连接的拨号方变了(期望状态里的 peers):已经不在名单里的会话由 关闭。
public Action>? OnPeersChanged { get; set; }
/// 点对点握手时查找拨号方的公钥(SPKI)。
public byte[]? LookupPeerKey(string peerId) => _peers.TryGetValue(peerId, out byte[]? key) ? key : null;
/// 名册里全部拨号方的公钥:敲门关卡用它们派生配对密钥。
public IReadOnlyList PeerKeys() => [.. _peers.Values];
/// 一个新的拨号方会话开始:为它登记全部线路任务的流并启动监听方积木,返回要下发给它的线路任务集。
public LineTaskSet OpenSession(DialerSession dialer)
{
lock (_gate)
{
var entries = new Dictionary();
foreach ((string id, (TaskSpec task, TaskDecision decision)) in _lineSpecs)
{
LineEntry entry = BuildLineEntry(task, decision, dialer);
Start(entry);
entries[id] = entry;
}
_sessionLines[dialer] = entries;
return SetFor(entries);
}
}
/// 拨号方 会话结束:释放它的所有流和积木(记录随之丢弃,下一个会话从零开始)。
public async Task CloseSessionAsync(DialerSession dialer)
{
List entries;
lock (_gate)
{
if (!_sessionLines.Remove(dialer, out Dictionary? lines))
{
return;
}
entries = [.. lines.Values];
}
foreach (LineEntry entry in entries)
{
await Retire(entry);
}
}
public async ValueTask ApplyAsync(long version, TaskSet set, CancellationToken ct)
{
_ = ct;
var result = new StateSyncResult { AppliedVersion = version };
// 固定了操作员创世的节点:面板带来的新版本操作员集合,从固定的创世验过才采用(名册本身由面板决定)
_trust.Update(set.TrustChain);
var peers = new Dictionary(StringComparer.Ordinal);
foreach (PeerSpec peer in set.Peers)
{
try
{
peers[peer.NodeId.Value] = Convert.FromBase64String(peer.PublicKey);
}
catch (FormatException)
{
result.Rejected.Add(new RejectedItem { Id = peer.NodeId.Value, Reason = RejectReasons.InvalidParams });
}
}
_peers = peers;
BudgetGuard guard = Guard;
double usedMbps = 0;
long usedPps = 0;
var line = new Dictionary();
var targets = new List<(TaskSpec Spec, TaskDecision Decision)>();
foreach (TaskSpec task in set.Tasks)
{
if (!task.Enabled)
{
continue;
}
bool shapeOk = task.Scope == "target" ? s_targetKinds.Contains(task.Kind) : _acceptLineTasks && s_lineKinds.Contains(task.Kind);
TaskDecision decision = shapeOk ? guard.Check(task, ref usedMbps, ref usedPps) : TaskDecision.Reject(RejectReasons.Unsupported);
if (!decision.Accepted)
{
result.Rejected.Add(new RejectedItem { Id = task.Id.Value, Reason = decision.Reason! });
continue;
}
if (task.Scope == "target")
{
targets.Add((task, decision));
}
else
{
line[task.Id.Value] = (task, decision);
}
}
_targets.Reconcile(targets, guard.AddressAllowed);
// 参数没有变化的任务沿用原来的流和积木,数据面上的会话(重放窗口、记录)不被打断
var retired = new List();
var changes = new List<(DialerSession Dialer, LineTaskSet Set)>();
lock (_gate)
{
_lineSpecs = line;
_version = version;
foreach ((DialerSession dialer, Dictionary current) in _sessionLines)
{
var next = new Dictionary();
bool lineTasksChanged = current.Count != line.Count;
foreach ((string id, (TaskSpec task, TaskDecision decision)) in line)
{
LineEntry candidate = BuildLineEntry(task, decision, dialer);
if (current.TryGetValue(id, out LineEntry? existing) && existing.Signature == candidate.Signature)
{
next[id] = existing;
}
else
{
lineTasksChanged = true;
Start(candidate);
next[id] = candidate;
}
}
foreach ((string id, LineEntry entry) in current)
{
if (!next.TryGetValue(id, out LineEntry? kept) || !ReferenceEquals(kept, entry))
{
retired.Add(entry);
}
}
current.Clear();
foreach ((string id, LineEntry entry) in next)
{
current[id] = entry;
}
// The Agent receives a full task snapshot for peer/trust changes and target-only tasks too.
// Those changes do not affect a 拨号方's line blocks, so avoid retransmitting the same full
// line_task_set to every 拨号方. New sessions still get their initial snapshot in OpenSession.
if (lineTasksChanged)
{
changes.Add((dialer, SetFor(current)));
}
}
}
foreach (LineEntry entry in retired)
{
await Retire(entry);
}
OnPeersChanged?.Invoke(new HashSet(peers.Keys, StringComparer.Ordinal));
foreach ((DialerSession dialer, LineTaskSet next) in changes)
{
OnLineTasksChanged?.Invoke(dialer, next);
}
return result;
}
/// 停止时释放所有拨号方会话的流和所有目标任务(Backend 失联的时候任务继续运行:节点自治)。
public async Task StopAsync()
{
_targets.Reconcile([]);
List entries;
lock (_gate)
{
entries = [.. _sessionLines.Values.SelectMany(l => l.Values)];
_sessionLines.Clear();
}
foreach (LineEntry entry in entries)
{
await Retire(entry);
}
}
private LineTaskSet SetFor(Dictionary entries) =>
new()
{
Version = _version,
Tasks = [.. entries.Values.Select(e => e.Task)],
QuicPorts = [.. _quicPorts()],
TcpPorts = [.. _tcpPorts()],
};
private LineEntry BuildLineEntry(TaskSpec task, TaskDecision decision, DialerSession dialer)
{
var info = new StreamInfo(_streams.Allocate(), task.Kind, decision.Resolved!) { TaskId = task.Id, Owner = dialer };
var lineTask = new LineTask
{
Id = task.Id,
Kind = task.Kind,
Params = new Dictionary(decision.Resolved!.Values),
StreamId = info.Id,
Port = info.Port,
};
var spec = new BlockSpec(decision.Kind!, decision.Resolved, info.Id, info.Port, new FragmentScope(task.Id, null, null));
return new LineEntry(lineTask, info, spec, _env.WithSession(dialer));
}
/// 在数据面登记这个流并启动它的监听方积木。
private void Start(LineEntry entry)
{
IBlock block = BlockCatalog.Find(entry.Stream.Kind)!.Listener!(entry.Env, entry.Spec);
entry.Stream.Running = RunningBlock.Start(block, entry.Signature);
_streams.Add(entry.Stream);
}
private async Task Retire(LineEntry entry)
{
_streams.Remove(entry.Stream.Id);
if (entry.Stream.Running is { } running)
{
await running.StopAsync();
}
entry.Stream.Owner?.ForgetStream(entry.Stream.Id);
}
private sealed record LineEntry(LineTask Task, StreamInfo Stream, BlockSpec Spec, BlockEnv Env)
{
public string Signature => Spec.ConfigSignature;
}
}