wenxiangchao
3 天以前 b544d5cccd76735e034c8055918a50927b437474
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Aging.Infrastructure.Broker;
using Aging.ResourceContracts;
using log4net;
 
namespace Aging.Infrastructure.Drivers
{
    /// <summary>
    /// Broker 传输后端:命令经命名管道 RPC 委托 DeviceBroker 宿主执行(物理会话由宿主唯一持有)。
    /// 三重关卡(白名单/configHash/租约栅栏)与 SessionGate 全在宿主侧,本类只做提交与态映射。
    /// 接收帧由宿主随响应以 "|PUSHED:n|EV:RECV…" 后缀捎带返回,解析后经 FramePushed 上抛(§5.4 捎带模型)。
    /// </summary>
    internal sealed class BrokerRpcTransport : ICanTransport
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(BrokerRpcTransport));
 
        private readonly BrokerClientChannel _channel;
        private readonly string _deviceId;
        private readonly string _configHash;
        private bool _disposed;
 
        public bool IsConnected => _channel.IsConnected;
        public event Action<string> FramePushed;
        public event Action TransportBroken;
 
        public BrokerRpcTransport(string deviceId, string pipeName, string configHash)
        {
            _deviceId = deviceId ?? throw new ArgumentNullException(nameof(deviceId));
            _configHash = configHash ?? "";
            string instanceId = $"{Environment.MachineName}/{Process.GetCurrentProcess().Id}";
            _channel = new BrokerClientChannel(pipeName, instanceId);
        }
 
        // busName/nodeIndex 对 Broker 无意义(物理会话在宿主按设备端点建立),CONNECT 即注册
        public Task ConnectAsync(string busName, int nodeIndex, CancellationToken ct)
            => SubmitExpectOkAsync("CONNECT", "", ct);
 
        public Task DisconnectAsync()
            => SubmitExpectOkAsync("DISCONNECT", "", CancellationToken.None);
 
        public async Task<string> TransceiveAsync(string line, CancellationToken ct)
        {
            if (_disposed) throw new ObjectDisposedException(nameof(BrokerRpcTransport));
 
            // line 形如 "SEND:…" 或 "QUERY:…",映射回宿主白名单 opCode + payload:
            //   SEND  → 宿主的 device.Driver.SendCommandAsync 需完整 "SEND:frameId:data",payload 保留前缀
            //   QUERY → 宿主的 device.Driver.QueryAsync 需去 QUERY: 前缀的裸查询
            string opCode;
            string payload;
            if (line.StartsWith("SEND:", StringComparison.Ordinal))
            {
                opCode = "SEND";
                payload = line;
            }
            else if (line.StartsWith("QUERY:", StringComparison.Ordinal))
            {
                opCode = "QUERY";
                payload = line.Substring("QUERY:".Length);
            }
            else
            {
                opCode = "QUERY";
                payload = line;
            }
 
            var result = await SubmitAsync(opCode, payload, ct).ConfigureAwait(false);
            DrainPiggyback(result.Readback, out string clean);
            return clean;
        }
 
        private async Task SubmitExpectOkAsync(string opCode, string payload, CancellationToken ct)
        {
            var r = await SubmitAsync(opCode, payload, ct).ConfigureAwait(false);
            // CONNECT/DISCONNECT 无有效读回帧,忽略捎带
            _ = r.Readback;
        }
 
        private async Task<BrokerIpc.ResultBody> SubmitAsync(string opCode, string payload, CancellationToken ct)
        {
            try
            {
                await _channel.EnsureConnectedAsync(ct).ConfigureAwait(false);
                var body = new BrokerIpc.SubmitBody
                {
                    DeviceId = _deviceId,
                    OperationCode = opCode,
                    Payload = payload ?? "",
                    ResourceKey = _deviceId,
                    ClientInstanceId = $"{Environment.MachineName}/{Process.GetCurrentProcess().Id}"
                };
                var r = await _channel.SubmitAsync(body, _configHash, ct).ConfigureAwait(false);
                ThrowForState(opCode, r);
                return r;
            }
            catch (Exception ex) when (!(ex is ObjectDisposedException))
            {
                Log.Error($"[BrokerRpcCAN] {_deviceId} {opCode} 失败: {ex.Message}", ex);
                throw;
            }
        }
 
        /// <summary>从 readback 尾部剥离 |PUSHED:n|EV:RECV… 段并触发 FramePushed。</summary>
        private void DrainPiggyback(string readback, out string clean)
        {
            clean = readback ?? "";
            if (string.IsNullOrEmpty(clean)) return;
 
            int idx = clean.IndexOf("|PUSHED:", StringComparison.Ordinal);
            if (idx < 0) return;
 
            string pushSection = clean.Substring(idx + "|PUSHED:".Length);
            clean = clean.Substring(0, idx);
 
            // pushSection 形如 "n|EV:RECV:…|EV:RECV:…"
            var segs = pushSection.Split('|');
            for (int i = 1; i < segs.Length; i++)
            {
                if (segs[i].StartsWith("EV:", StringComparison.Ordinal))
                    FramePushed?.Invoke(segs[i]);
            }
        }
 
        private void ThrowForState(string opCode, BrokerIpc.ResultBody r)
        {
            switch (r.State)
            {
                case "Succeeded":
                    return;
                case "RejectedStaleLease":
                    throw new InvalidOperationException($"LEASE_STALE: {_deviceId} 租约栅栏校验失败: {r.ErrorCode} {r.Readback}");
                case "RejectedConfigMismatch":
                    throw new InvalidOperationException($"CONFIG_MISMATCH: Broker 生效配置与本地快照不一致(INV-04): {r.Readback}");
                case "RejectedQueueFull":
                    throw new InvalidOperationException($"QUEUE_FULL: {r.Readback}");
                case "Unknown":
                    throw new TimeoutException($"UNKNOWN: {_deviceId} 动作 {opCode} 结果未知,禁止重试假定未执行(INV-06): {r.Readback}");
                default:
                    throw new InvalidOperationException($"Broker 执行失败[{r.ErrorCode}]: {r.Readback}");
            }
        }
 
        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
            _channel.Dispose();
        }
    }
}