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);
}
}