using System; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Globalization; using System.IO; using System.IO.Pipes; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Linq; using Aging.Infrastructure.Broker; using Aging.Infrastructure.Contracts; using Aging.Infrastructure.Core; using Aging.Infrastructure.Drivers; using Aging.ResourceContracts; using log4net; namespace DeviceBroker { /// /// DeviceBroker 宿主:唯一持有 Broker 域设备物理会话的进程(V3.0 混合架构)。 /// 出队时双重校验:arb.db 栅栏(epoch/generation/相位,INV-01/03)+ configHash 对账(INV-04), /// SessionGate 把“选择通道 + 写入/查询 + 读取响应”视为不可拆分原子片段串行执行(§04)。 /// MVP 为 OneShot 命令同步模型:周期任务/订阅/Outbox 推迟(P2+)。 /// public sealed class BrokerServer { private static readonly ILog Log = LogManager.GetLogger(typeof(BrokerServer)); private static readonly string[] AllowedOps = { "CONNECT", "DISCONNECT", "SEND", "QUERY", "STATUS" }; private readonly string _brokerId; private readonly string _pipeName; private readonly string _configDbPath; private readonly DbLeaseStore _lease; private sealed class ManagedDevice { public string DeviceId = ""; public string Transport = ""; public string Endpoint = ""; public string SessionKey = ""; public IDeviceDriver? Driver; public bool Connected; } private readonly Dictionary _devices = new(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary _gates = new(); private readonly SemaphoreSlim _driverCreateLock = new(1, 1); private long _hashStampMs; private volatile string _cachedHash = ""; public BrokerServer(string configDbPath, string arbDbPath, string brokerId) { _configDbPath = configDbPath; _brokerId = brokerId; _pipeName = BrokerPipeName.For(brokerId); _lease = new DbLeaseStore(arbDbPath, brokerId); // 宿主重启对账:回收上次进程残留的待回收租约(INV-03 自动化形式,证据落库) _lease.ReconcileAfterBoot($"broker:{brokerId}/{System.Diagnostics.Process.GetCurrentProcess().Id}"); } public long Epoch => _lease.CurrentEpoch; /// 加载本 Broker 域设备清单(ExecutionMode=Broker 且 BrokerId 匹配)。 public void LoadDevices() { _devices.Clear(); using var conn = new System.Data.SQLite.SQLiteConnection( $"Data Source={_configDbPath};Version=3;Read Only=True;"); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = @"SELECT DeviceId, Transport, Endpoint, PhysicalSessionKey, BrokerId, AuthorityId FROM DeviceDefinition WHERE ExecutionMode = 'Broker';"; using var rd = cmd.ExecuteReader(); var dt = new DataTable(); dt.Load(rd); foreach (DataRow row in dt.Rows) { var brokerId = row["BrokerId"].ToString() ?? ""; if (!string.Equals(brokerId, _brokerId, StringComparison.OrdinalIgnoreCase)) continue; // 属其他 Broker 域,本宿主拒绝代收 _devices[row["DeviceId"].ToString()!] = new ManagedDevice { DeviceId = row["DeviceId"].ToString() ?? "", Transport = row["Transport"].ToString() ?? "", Endpoint = row["Endpoint"].ToString() ?? "", SessionKey = string.IsNullOrWhiteSpace(row["PhysicalSessionKey"].ToString()) ? row["DeviceId"].ToString()! // 无会话键时退化为按设备串行 : row["PhysicalSessionKey"].ToString()!, }; } Log.Info($"[Broker] {_brokerId} 载入 {_devices.Count} 台 Broker 设备, epoch={_lease.CurrentEpoch}"); } /// 接受循环:每个连接一个处理任务;管道保持多实例可用。 public async Task RunAsync(CancellationToken ct) { Log.Info($"[Broker] 监听管道 [{_pipeName}]"); while (!ct.IsCancellationRequested) { NamedPipeServerStream? stream = null; try { stream = new NamedPipeServerStream(_pipeName, PipeDirection.InOut, 8, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); await stream.WaitForConnectionAsync(ct).ConfigureAwait(false); var s = stream; stream = null; _ = Task.Run(() => HandleConnectionAsync(s, ct), CancellationToken.None); } catch (OperationCanceledException) { break; } catch (Exception ex) { Log.Error($"[Broker] 接受连接异常: {ex.Message}", ex); stream?.Dispose(); try { await Task.Delay(500, ct).ConfigureAwait(false); } catch (OperationCanceledException) { break; } } } } private async Task HandleConnectionAsync(NamedPipeServerStream stream, CancellationToken ct) { var lenBuf = new byte[4]; try { using (stream) { while (!ct.IsCancellationRequested && stream.IsConnected) { if (!await ReadExactAsync(stream, lenBuf, 4, ct).ConfigureAwait(false)) break; int len = BinaryPrimitives.ReadInt32BigEndian(lenBuf); if (len <= 0 || len > BrokerIpc.MaxFrameBytes) break; var payload = new byte[len]; if (!await ReadExactAsync(stream, payload, len, ct).ConfigureAwait(false)) break; IpcEnvelope request; try { request = BrokerIpc.DecodeEnvelope(Encoding.UTF8.GetString(payload)); } catch (Exception) { break; // 协议失步,断开该连接 } IpcEnvelope response = await DispatchAsync(request, ct).ConfigureAwait(false); var frame = BrokerIpc.EncodeFrame(BrokerIpc.EncodeEnvelope(response)); await stream.WriteAsync(frame, 0, frame.Length, ct).ConfigureAwait(false); await stream.FlushAsync(ct).ConfigureAwait(false); } } } catch (OperationCanceledException) { } catch (IOException) { } catch (Exception ex) { Log.Warn($"[Broker] 连接处理结束: {ex.Message}"); } } private async Task DispatchAsync(IpcEnvelope request, CancellationToken ct) { if (request.ProtocolVersion != BrokerIpc.ProtocolVersion) return Reply(request, BrokerIpc.SerializeBody(new BrokerIpc.ResultBody { State = "Failed", ErrorCode = "PROTOCOL_MISMATCH", Readback = $"Broker 协议 v{BrokerIpc.ProtocolVersion},拒绝 v{request.ProtocolVersion}" })); switch (request.MessageType) { case IpcMessageTypes.Hello: return Reply(request, BrokerIpc.SerializeBody(new HelloResult { ServerEpoch = _lease.CurrentEpoch, ConfigHash = CurrentHash(), ProtocolVersion = BrokerIpc.ProtocolVersion, BrokerId = _brokerId })); case IpcMessageTypes.SubmitTask: var body = BrokerIpc.DeserializeBody(request.BodyJson); if (body == null) return Reply(request, Result("Failed", "BAD_BODY", "SubmitBody 解析失败")); return Reply(request, await ExecuteOneShotAsync(body, request, ct).ConfigureAwait(false)); case IpcMessageTypes.ProbeReadback: var pb = BrokerIpc.DeserializeBody(request.BodyJson); return Reply(request, await ProbeReadbackAsync(pb?.DeviceId ?? "").ConfigureAwait(false)); case IpcMessageTypes.CancelTask: // MVP 不支持中断在途原子片段(INV-06:不得假装已取消) return Reply(request, Result("Failed", "NOT_SUPPORTED", "MVP 不支持取消;片段将在 deadline 内完成")); default: return Reply(request, Result("Failed", "UNKNOWN_MESSAGE", request.MessageType)); } } // ==================== OneShot 执行核心 ==================== private async Task ExecuteOneShotAsync( BrokerIpc.SubmitBody body, IpcEnvelope request, CancellationToken ct) { // 0) OpCode 白名单 if (!AllowedOps.Contains(body.OperationCode) || !_devices.TryGetValue(body.DeviceId, out var device)) { return Result("Failed", "DEVICE_NOT_BROKERED", $"设备 {body.DeviceId} 不属于本 Broker 域或操作 {body.OperationCode} 不在白名单"); } // 1) INV-04:configHash 对账(空哈希视为旧客户端,放行并记录) var hash = CurrentHash(); if (!string.IsNullOrWhiteSpace(request.ConfigHash) && !string.Equals(request.ConfigHash, hash, StringComparison.OrdinalIgnoreCase)) { return Result("RejectedConfigMismatch", "CONFIG_MISMATCH", $"客户端快照 {Trim(request.ConfigHash)} ≠ Broker 生效 {Trim(hash)},先 Apply 再 I/O"); } // 2) 栅栏再校验:CONNECT/DISCONNECT 仅登记或释放宿主物理会话、无总线输出,豁免租约; // 纯读回收邮箱(RECV/RECV_ALL/LOG/BUS_STATUS)为被动监控无写副作用,也豁免(支持调试页只读监听); // SEND 及其它 QUERY(可能触发设备行为,如 AUTO_RESPONSE/FILTER)必须有活跃租约(INV-01) string leaseKey = string.IsNullOrWhiteSpace(body.ResourceKey) ? body.DeviceId : body.ResourceKey; bool readOnlyDrain = body.OperationCode == "QUERY" && (body.Payload.StartsWith("RECV", StringComparison.OrdinalIgnoreCase) || body.Payload.StartsWith("LOG", StringComparison.OrdinalIgnoreCase) || body.Payload.StartsWith("BUS_STATUS", StringComparison.OrdinalIgnoreCase) || body.Payload.StartsWith("STATUS", StringComparison.OrdinalIgnoreCase)); if (body.OperationCode != "DISCONNECT" && body.OperationCode != "CONNECT" && !readOnlyDrain) { var phase = _lease.PhaseOf(leaseKey); if (phase == LeasePhase.RecoveryRequired) { return Result("RejectedStaleLease", "RECOVERY_REQUIRED", $"资源 {leaseKey} 过期待回收(INV-03),禁止 I/O"); } var holder = _lease.ProbeConflict(leaseKey); if (phase != LeasePhase.Active || holder == null) { return Result("RejectedStaleLease", "NO_ACTIVE_LEASE", $"资源 {leaseKey} 无活跃租约,客户端须先 Acquire(禁止无租约直接 I/O)"); } // MVP:同机 arb.db 弱身份校验——只做存在性/相位检查; // owner↔clientInstanceId 精确对齐待 epoch 签名凭据(V3.0 §13 P2)。 } // 3) SessionGate:同一物理会话原子的“寻道+执行”片段串行 var gate = _gates.GetOrAdd(device.SessionKey, _ => new SemaphoreSlim(1, 1)); bool entered; try { entered = await gate.WaitAsync(5000, CancellationToken.None).ConfigureAwait(false); } catch (ObjectDisposedException) { entered = false; } if (!entered) { // 未进入片段 → 确定未执行,可安全判忙 return Result("Failed", "RESOURCE_BUSY", $"会话 {device.SessionKey} 片段队列等待超时,未执行可安全重试"); } try { return await RunFragmentAsync(device, body, ct).ConfigureAwait(false); } finally { gate.Release(); } } private async Task RunFragmentAsync( ManagedDevice device, BrokerIpc.SubmitBody body, CancellationToken ct) { try { if (body.OperationCode == "CONNECT" || (device.Driver == null && body.OperationCode != "DISCONNECT")) { await EnsureConnectedAsync(device, ct).ConfigureAwait(false); } switch (body.OperationCode) { case "CONNECT": return Result("Succeeded", "", $"OK:CONNECTED:{device.DeviceId}", _lease.CurrentEpoch); case "DISCONNECT": if (device.Connected) { await device.Driver!.DisconnectAsync().ConfigureAwait(false); device.Connected = false; } return Result("Succeeded", "", $"OK:DISCONNECTED:{device.DeviceId}", _lease.CurrentEpoch); case "SEND": { EnsureConnectedOrThrow(device); var resp = await device.Driver!.SendCommandAsync(body.Payload, ct).ConfigureAwait(false); return Result("Succeeded", "", AppendPushes(device, resp), _lease.CurrentEpoch); } case "QUERY": { EnsureConnectedOrThrow(device); var resp = await device.Driver!.QueryAsync(body.Payload, ct).ConfigureAwait(false); return Result("Succeeded", "", AppendPushes(device, resp), _lease.CurrentEpoch); } case "STATUS": { var st = device.Driver?.GetStatus(); return Result("Succeeded", "", st == null ? "STATUS:NOT_CREATED" : $"STATUS:{st.IsConnected}|{st.LastError}", _lease.CurrentEpoch); } default: return Result("Failed", "BAD_OP", body.OperationCode); } } catch (OperationCanceledException) { // 片段中途取消:物理侧可能已执行 → INV-06 return Result("Unknown", "DEADLINE", $"{body.OperationCode} 超 deadline,结果未知"); } catch (Exception ex) { bool ioMayHappened = body.OperationCode is "SEND" or "QUERY"; return Result(ioMayHappened ? "Unknown" : "Failed", "DRIVER_ERROR", $"{body.OperationCode} 执行异常: {ex.Message}", _lease.CurrentEpoch); } } /// /// 捎带推送(§5.4):将宿主物理驱动自上次响应以来收到的 EV:RECV 帧, /// 以 "|PUSHED:n|EV:RECV…" 后缀拼到响应 readback,客户端 BrokerRpcTransport 解析上屏。 /// private static string AppendPushes(ManagedDevice device, string resp) { if (device.Driver is CanDeviceDriver can) { var lines = can.DrainPendingPushLines(); if (lines.Count > 0) return resp + "|PUSHED:" + lines.Count + "|" + string.Join("|", lines); } return resp; } private void EnsureConnectedOrThrow(ManagedDevice device) { if (!device.Connected || device.Driver == null) throw new InvalidOperationException($"设备 {device.DeviceId} 未连接,请先 CONNECT"); } private async Task EnsureConnectedAsync(ManagedDevice device, CancellationToken ct) { if (device.Connected && device.Driver != null) return; await _driverCreateLock.WaitAsync(ct).ConfigureAwait(false); try { if (device.Connected && device.Driver != null) return; device.Driver ??= DriverFactory.CreateDriver(device.DeviceId, device.Transport, device.Endpoint); await device.Driver.ConnectAsync(ct).ConfigureAwait(false); device.Connected = true; Log.Info($"[Broker] 物理会话已建立: {device.DeviceId} ({device.Transport} {device.Endpoint})"); } finally { _driverCreateLock.Release(); } } /// 恢复回收前读回探测:宿主读回设备状态作为合格证据(INV-03 人工回收链的 Broker 侧)。 private async Task ProbeReadbackAsync(string deviceId) { if (!_devices.TryGetValue(deviceId, out var device) || device.Driver == null) return Result("Failed", "NO_DRIVER", $"设备 {deviceId} 无活动驱动"); try { var readback = await device.Driver.QueryAsync("STATUS", CancellationToken.None).ConfigureAwait(false); return Result("Succeeded", "", readback, _lease.CurrentEpoch); } catch (Exception ex) { return Result("Failed", "READBACK_ERROR", ex.Message); } } // ==================== 杂项 ==================== private string CurrentHash() { long now = BrokerIpc.NowUtcMs(); if (now - Interlocked.Read(ref _hashStampMs) > 2000) { try { _cachedHash = DeviceConfigHash.Compute(_configDbPath); } catch (Exception ex) { Log.Warn($"[Broker] configHash 重算失败: {ex.Message}"); } Interlocked.Exchange(ref _hashStampMs, now); } return _cachedHash; } private static BrokerIpc.ResultBody Result(string state, string code, string readback, long epoch = 0) => new BrokerIpc.ResultBody { State = state, ErrorCode = code, Readback = readback, ServerEpoch = epoch }; private static IpcEnvelope Reply(IpcEnvelope request, string bodyJson) => new IpcEnvelope { ProtocolVersion = BrokerIpc.ProtocolVersion, MessageType = "Result", RequestId = Guid.NewGuid().ToString("N"), CorrelationId = request.RequestId, ClientInstanceId = request.ClientInstanceId, AuthorityId = request.AuthorityId, BodyJson = bodyJson }; private static IpcEnvelope Reply(IpcEnvelope request, BrokerIpc.ResultBody body) => Reply(request, BrokerIpc.SerializeBody(body)); private static async Task ReadExactAsync(Stream s, byte[] buf, int count, CancellationToken ct) { int off = 0; while (off < count) { int n; try { n = await s.ReadAsync(buf, off, count - off, ct).ConfigureAwait(false); } catch (IOException) { return false; } if (n <= 0) return false; off += n; } return true; } private static string Trim(string h) => string.IsNullOrEmpty(h) ? "" : (h.Length > 8 ? h.Substring(0, 8) : h); } }