wenxiangchao
4 天以前 559890c482f5973f06aafdfec9550fee5ba3c7f2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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();
    }
}