using System;
|
using System.Buffers.Binary;
|
using System.Text;
|
using System.Text.Json;
|
|
namespace Aging.ResourceContracts
|
{
|
/// <summary>
|
/// Broker IPC 帧协议:4 字节大端长度前缀 + UTF8 JSON 封套。
|
/// 协议主版本不匹配时 Broker 直接拒绝连接(V3.0 §13)。
|
/// DTO 线格式独立于 LeaseGrant 等只读构造器类型,规避 System.Text.Json 反序列化限制。
|
/// </summary>
|
public static class BrokerIpc
|
{
|
public const int ProtocolVersion = 1;
|
|
/// <summary>单帧最大字节数(1MB),超限视为协议错误</summary>
|
public const int MaxFrameBytes = 1024 * 1024;
|
|
private static readonly JsonSerializerOptions JsonOpts = new JsonSerializerOptions
|
{
|
PropertyNameCaseInsensitive = true,
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
};
|
|
// ==================== 帧编解码 ====================
|
|
/// <summary>编码为一帧:[4 字节大端长度][UTF8 JSON]</summary>
|
public static byte[] EncodeFrame(string json)
|
{
|
var payload = Encoding.UTF8.GetBytes(json);
|
if (payload.Length > MaxFrameBytes)
|
throw new InvalidOperationException($"帧超限: {payload.Length} > {MaxFrameBytes}");
|
var frame = new byte[4 + payload.Length];
|
BinaryPrimitives.WriteInt32BigEndian(frame.AsSpan(), payload.Length);
|
payload.CopyTo(frame, 4);
|
return frame;
|
}
|
|
/// <summary>从 payload JSON 解码封套。非法 JSON 抛 FormatException。</summary>
|
public static IpcEnvelope DecodeEnvelope(string payloadJson)
|
{
|
var env = JsonSerializer.Deserialize<IpcEnvelope>(payloadJson, JsonOpts);
|
if (env == null || string.IsNullOrEmpty(env.MessageType))
|
throw new FormatException("无效 IPC 封套");
|
return env;
|
}
|
|
public static string EncodeEnvelope(IpcEnvelope env)
|
=> JsonSerializer.Serialize(env, JsonOpts);
|
|
// ==================== 线格式 DTO ====================
|
|
/// <summary>SubmitTask Body:一次有界设备动作(OneShot 同步模型)</summary>
|
public sealed class SubmitBody
|
{
|
public string DeviceId { get; set; } = "";
|
/// <summary>白名单操作码:CONNECT/DISCONNECT/SEND/QUERY/STATUS</summary>
|
public string OperationCode { get; set; } = "";
|
/// <summary>操作载荷(命令或查询字符串)</summary>
|
public string Payload { get; set; } = "";
|
/// <summary>仲裁资源键(broker 侧 fence 再校验用,如 CAN-003:Generic)</summary>
|
public string ResourceKey { get; set; } = "";
|
/// <summary>客户端进程实例身份 boot+pid,与 arb.db owner 对齐</summary>
|
public string ClientInstanceId { get; set; } = "";
|
}
|
|
/// <summary>SubmitTask 响应 Body</summary>
|
public sealed class ResultBody
|
{
|
public string State { get; set; } = ""; // Succeeded/Failed/RejectedStaleLease/RejectedConfigMismatch/...
|
public string ErrorCode { get; set; } = "";
|
public string Readback { get; set; } = "";
|
public long ServerEpoch { get; set; }
|
}
|
|
public static string SerializeBody(object body)
|
=> JsonSerializer.Serialize(body, JsonOpts);
|
|
public static T? DeserializeBody<T>(string json) where T : class
|
{
|
if (string.IsNullOrWhiteSpace(json)) return default;
|
try { return JsonSerializer.Deserialize<T>(json, JsonOpts); }
|
catch (JsonException) { return default; }
|
}
|
|
/// <summary>deadline 计算:UTC 毫秒</summary>
|
public static long NowUtcMs()
|
=> DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
}
|
}
|