using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CanSimulator
{
///
/// CAN 模拟器 TCP 线协议分帧编解码器。
/// 线格式:4 字节大端长度前缀 + UTF8 文本行(与 BrokerIpc 同惯用法)。
/// 读侧负责粘包/半包重组:循环读满长度前缀与帧体后返回完整文本。
///
public static class SimulatorFraming
{
/// 长度前缀字节数
public const int HeaderSize = 4;
/// 单帧最大字节数(防畸形长度前缀导致内存爆掉;命令串与 LOG 响应远小于此值)
public const int MaxFrameBytes = 1024 * 1024;
/// 把一条文本消息编码为完整帧(含长度前缀)
public static byte[] Encode(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
byte[] body = Encoding.UTF8.GetBytes(text);
if (body.Length > MaxFrameBytes)
throw new InvalidOperationException($"帧体超限: {body.Length} > {MaxFrameBytes}");
var frame = new byte[HeaderSize + body.Length];
WriteBigEndian(frame, 0, body.Length);
Buffer.BlockCopy(body, 0, frame, HeaderSize, body.Length);
return frame;
}
/// 解码已去掉长度前缀的帧体
public static string Decode(byte[] buffer, int offset, int count)
=> Encoding.UTF8.GetString(buffer, offset, count);
///
/// 从流中读取一个完整帧。
/// 返回 null 表示对端已关闭或帧中途 EOF(连接失效,调用方应触发重连);
/// 长度前缀非法(≤0 或超 MaxFrameBytes)抛 InvalidDataException(协议失步)。
///
public static async Task ReadFrameAsync(Stream s, CancellationToken ct)
{
var header = new byte[HeaderSize];
if (!await ReadExactAsync(s, header, HeaderSize, ct).ConfigureAwait(false))
return null;
int len = ReadBigEndian(header, 0);
if (len <= 0 || len > MaxFrameBytes)
throw new InvalidDataException($"非法帧长度前缀: {len}");
var body = new byte[len];
if (!await ReadExactAsync(s, body, len, ct).ConfigureAwait(false))
return null; // 帧体中途 EOF
return Decode(body, 0, len);
}
/// 循环读满 count 字节;期间 EOF(读到 0 字节)或 IO 异常时返回 false,由调用方按连接失效处理
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;
}
// net472 无 System.Buffers.Binary,手写大端读写
private static void WriteBigEndian(byte[] buf, int offset, int value)
{
buf[offset] = (byte)(value >> 24);
buf[offset + 1] = (byte)(value >> 16);
buf[offset + 2] = (byte)(value >> 8);
buf[offset + 3] = (byte)value;
}
private static int ReadBigEndian(byte[] buf, int offset)
=> (buf[offset] << 24) | (buf[offset + 1] << 16) | (buf[offset + 2] << 8) | buf[offset + 3];
}
}