181 lines
6.1 KiB
C#
181 lines
6.1 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace Fleet.Rpc;
|
|
|
|
/// <summary>框架保留的方法名。<c>fleet.*</c> 只属于框架,产品使用自己的前缀。</summary>
|
|
public static class FleetMethods
|
|
{
|
|
public const string Challenge = "fleet.challenge";
|
|
public const string Auth = "fleet.auth";
|
|
public const string Hello = "fleet.hello";
|
|
public const string Heartbeat = "fleet.heartbeat";
|
|
public const string Stopping = "fleet.stopping";
|
|
public const string StateSync = "fleet.state.sync";
|
|
public const string OutboxAck = "fleet.outbox.ack";
|
|
public const string Revoked = "fleet.revoked";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 框架自己的方法声明,两个平面各一份。认证和握手阶段由连接代码直接处理的(<c>Inline</c>)不经过路由器。
|
|
/// 同一个方法名在两个平面里可以有不同的发起方:<c>fleet.state.sync</c> 在节点面是服务端发给节点,在对等面是 Agent 发给 Probe。
|
|
/// </summary>
|
|
public static class FleetMethodTable
|
|
{
|
|
public static readonly MethodTable Table = new(
|
|
[
|
|
// 节点面
|
|
MethodSpec.Notification(FleetMethods.Challenge, RpcSide.Server).AsInline(),
|
|
MethodSpec.Request(FleetMethods.Auth, RpcSide.Node).AsInline(),
|
|
MethodSpec.Request(FleetMethods.Hello, RpcSide.Node).AsInline(),
|
|
MethodSpec.Notification(FleetMethods.Heartbeat, RpcSide.Node).AsInline(),
|
|
MethodSpec.Notification(FleetMethods.Stopping, RpcSide.Node).AsInline(),
|
|
MethodSpec.Request(FleetMethods.StateSync, RpcSide.Server),
|
|
MethodSpec.Notification(FleetMethods.OutboxAck, RpcSide.Server),
|
|
MethodSpec.Notification(FleetMethods.Revoked, RpcSide.Server),
|
|
|
|
// 对等面
|
|
MethodSpec.Notification(PeerMethods.Challenge, RpcSide.Listener).AsInline(),
|
|
MethodSpec.Request(PeerMethods.Auth, RpcSide.Dialer).AsInline(),
|
|
MethodSpec.Request(FleetMethods.StateSync, RpcSide.Listener),
|
|
MethodSpec.Notification(FleetMethods.OutboxAck, RpcSide.Listener),
|
|
]);
|
|
}
|
|
|
|
public sealed class ChallengeParams
|
|
{
|
|
[JsonPropertyName("nonce")]
|
|
public string Nonce { get; set; } = "";
|
|
|
|
[JsonPropertyName("expires_at_ms")]
|
|
public long ExpiresAtMs { get; set; }
|
|
|
|
/// <summary>服务器这次连接的临时 ECDH 公钥(SPKI,base64);服务器有身份密钥时才有。</summary>
|
|
[JsonPropertyName("server_eph")]
|
|
public string? ServerEphemeral { get; set; }
|
|
|
|
/// <summary>服务器密钥对「随机数加临时公钥」的签名(base64):节点据此确认对面是操作员授权的服务器。</summary>
|
|
[JsonPropertyName("server_sig")]
|
|
public string? ServerSignature { get; set; }
|
|
}
|
|
|
|
public sealed class AuthParams
|
|
{
|
|
[JsonPropertyName("node_id")]
|
|
public string NodeId { get; set; } = "";
|
|
|
|
/// <summary>对 <c>fleet|v1|auth|{node_id}|{nonce}</c> 的签名(base64)。</summary>
|
|
[JsonPropertyName("signature")]
|
|
public string Signature { get; set; } = "";
|
|
|
|
/// <summary>节点的临时 ECDH 公钥(SPKI,base64):有它就是安全链路,签名覆盖双方的临时公钥,认证结果起所有帧都是加密的。</summary>
|
|
[JsonPropertyName("node_eph")]
|
|
public string? NodeEphemeral { get; set; }
|
|
}
|
|
|
|
public sealed class AuthResult
|
|
{
|
|
/// <summary>pending | active</summary>
|
|
[JsonPropertyName("status")]
|
|
public string Status { get; set; } = "";
|
|
}
|
|
|
|
public sealed class HelloParams
|
|
{
|
|
[JsonPropertyName("node_version")]
|
|
public string NodeVersion { get; set; } = "";
|
|
|
|
[JsonPropertyName("capabilities")]
|
|
public List<string> Capabilities { get; set; } = [];
|
|
|
|
[JsonPropertyName("now_utc_ms")]
|
|
public long NowUtcMs { get; set; }
|
|
|
|
/// <summary>产品自定义的元数据(例如 MoShiro 的 tls_spki、probe_ports、local_limits 摘要)。</summary>
|
|
[JsonPropertyName("meta")]
|
|
public JsonElement? Meta { get; set; }
|
|
}
|
|
|
|
public sealed class HelloResult
|
|
{
|
|
[JsonPropertyName("server_time_ms")]
|
|
public long ServerTimeMs { get; set; }
|
|
|
|
/// <summary>节点时钟 − 服务端时钟(毫秒,未扣除网络往返)。</summary>
|
|
[JsonPropertyName("clock_offset_ms")]
|
|
public long ClockOffsetMs { get; set; }
|
|
}
|
|
|
|
public sealed class HeartbeatParams
|
|
{
|
|
[JsonPropertyName("extra")]
|
|
public JsonElement? Extra { get; set; }
|
|
}
|
|
|
|
public sealed class StateSyncParams
|
|
{
|
|
[JsonPropertyName("type")]
|
|
public string Type { get; set; } = "";
|
|
|
|
[JsonPropertyName("version")]
|
|
public long Version { get; set; }
|
|
|
|
[JsonPropertyName("doc")]
|
|
public JsonElement Doc { get; set; }
|
|
}
|
|
|
|
public sealed class StateSyncResult
|
|
{
|
|
[JsonPropertyName("applied_version")]
|
|
public long AppliedVersion { get; set; }
|
|
|
|
[JsonPropertyName("rejected")]
|
|
public List<RejectedItem> Rejected { get; set; } = [];
|
|
}
|
|
|
|
public sealed class RejectedItem
|
|
{
|
|
[JsonPropertyName("id")]
|
|
public string Id { get; set; } = "";
|
|
|
|
/// <summary>unsupported | budget_exceeded | policy_denied | …</summary>
|
|
[JsonPropertyName("reason")]
|
|
public string Reason { get; set; } = "";
|
|
}
|
|
|
|
public sealed class OutboxAckParams
|
|
{
|
|
[JsonPropertyName("ids")]
|
|
public List<string> Ids { get; set; } = [];
|
|
|
|
[JsonPropertyName("duplicates")]
|
|
public List<string> Duplicates { get; set; } = [];
|
|
}
|
|
|
|
public sealed class RevokedParams
|
|
{
|
|
[JsonPropertyName("reason")]
|
|
public string Reason { get; set; } = "";
|
|
}
|
|
|
|
public sealed class StoppingParams
|
|
{
|
|
[JsonPropertyName("reason")]
|
|
public string Reason { get; set; } = "stopping";
|
|
}
|
|
|
|
[JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
|
|
[JsonSerializable(typeof(RpcMessage))]
|
|
[JsonSerializable(typeof(ChallengeParams))]
|
|
[JsonSerializable(typeof(AuthParams))]
|
|
[JsonSerializable(typeof(AuthResult))]
|
|
[JsonSerializable(typeof(HelloParams))]
|
|
[JsonSerializable(typeof(HelloResult))]
|
|
[JsonSerializable(typeof(HeartbeatParams))]
|
|
[JsonSerializable(typeof(StoppingParams))]
|
|
[JsonSerializable(typeof(StateSyncParams))]
|
|
[JsonSerializable(typeof(StateSyncResult))]
|
|
[JsonSerializable(typeof(OutboxAckParams))]
|
|
[JsonSerializable(typeof(RevokedParams))]
|
|
public sealed partial class FleetRpcContext : JsonSerializerContext;
|