using System; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using CanSimulator; using Xunit; namespace Aging.MvpTests { /// /// CAN 模拟器 TCP 线协议分帧编解码器测试。 /// 线格式:4 字节大端长度前缀 + UTF8 文本(与 BrokerIpc 同惯用法)。 /// public class SimulatorFramingTests { [Fact] public void Encode_RoundTrip_DecodesOriginalText() { string msg = "HELLO:CAN_BUS_1:0:HOST/PID"; byte[] frame = SimulatorFraming.Encode(msg); Assert.Equal(4 + Encoding.UTF8.GetByteCount(msg), frame.Length); var ms = new MemoryStream(frame); byte[] all = ms.ToArray(); var got = SimulatorFraming.Decode(all, SimulatorFraming.HeaderSize, all.Length - SimulatorFraming.HeaderSize); Assert.Equal(msg, got); } [Fact] public void ReadFrameAsync_TwoFramesInOneBuffer_DecodesBothInOrder() { // 粘包:两个完整帧连续写入同一流 var ms = new MemoryStream(); byte[] f1 = SimulatorFraming.Encode("OK:TX:0x100:8"); ms.Write(f1, 0, f1.Length); byte[] f2 = SimulatorFraming.Encode("EV:RECV:0x123:AA BB:12:00:00.111:2"); ms.Write(f2, 0, f2.Length); ms.Position = 0; string? r1 = SimulatorFraming.ReadFrameAsync(ms, CancellationToken.None).GetAwaiter().GetResult(); string? r2 = SimulatorFraming.ReadFrameAsync(ms, CancellationToken.None).GetAwaiter().GetResult(); Assert.Equal("OK:TX:0x100:8", r1); Assert.Equal("EV:RECV:0x123:AA BB:12:00:00.111:2", r2); } [Fact] public void ReadFrameAsync_FrameSplitAcrossTwoReads_ReassemblesFullFrame() { // 半包:帧体被拆成两次到达(用分段流模拟) byte[] frame = SimulatorFraming.Encode("SEND:0x777:DE AD BE EF"); var chunked = new ChunkedStream(frame, chunk: 6); // 头部 6 字节后切断 string? got = SimulatorFraming.ReadFrameAsync(chunked, CancellationToken.None).GetAwaiter().GetResult(); Assert.Equal("SEND:0x777:DE AD BE EF", got); } [Fact] public void ReadFrameAsync_LengthExceedsMax_Throws() { // 非法长度前缀:超过 MaxFrameBytes byte[] bad = new byte[4]; bad[0] = 0x7F; bad[1] = 0xFF; bad[2] = 0xFF; bad[3] = 0xFF; // ~2GB var ms = new MemoryStream(bad); Assert.Throws(() => SimulatorFraming.ReadFrameAsync(ms, CancellationToken.None).GetAwaiter().GetResult()); } [Fact] public void ReadFrameAsync_ZeroLengthPrefix_Throws() { byte[] bad = new byte[4]; // 长度 0 var ms = new MemoryStream(bad); Assert.Throws(() => SimulatorFraming.ReadFrameAsync(ms, CancellationToken.None).GetAwaiter().GetResult()); } [Fact] public void ReadFrameAsync_EmptyStream_ReturnsNull() { // 对端正常关闭:读不到任何字节时返回 null(断线感知契约) var ms = new MemoryStream(new byte[0]); string? got = SimulatorFraming.ReadFrameAsync(ms, CancellationToken.None).GetAwaiter().GetResult(); Assert.Null(got); } [Fact] public void ReadFrameAsync_TruncatedBody_ReturnsNull() { // 头完整但体中途 EOF:返回 null 触发重连 byte[] frame = SimulatorFraming.Encode("QUERY:RECV_ALL"); var truncated = new byte[frame.Length - 3]; Array.Copy(frame, truncated, truncated.Length); var ms = new MemoryStream(truncated); string? got = SimulatorFraming.ReadFrameAsync(ms, CancellationToken.None).GetAwaiter().GetResult(); Assert.Null(got); } /// 每次 Read 只从内部缓冲区取固定片段,模拟网络分片到达 private sealed class ChunkedStream : Stream { private readonly byte[] _data; private readonly int _chunk; private int _pos; public ChunkedStream(byte[] data, int chunk) { _data = data; _chunk = chunk; } public override bool CanRead => true; public override bool CanSeek => false; public override bool CanWrite => false; public override long Length => _data.Length; public override long Position { get => _pos; set => throw new NotSupportedException(); } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { if (_pos >= _data.Length) return 0; int n = Math.Min(Math.Min(count, _chunk), _data.Length - _pos); Array.Copy(_data, _pos, buffer, offset, n); _pos += n; return n; } public override long Seek(long o, SeekOrigin s) => throw new NotSupportedException(); public override void SetLength(long v) => throw new NotSupportedException(); public override void Write(byte[] b, int o, int c) => throw new NotSupportedException(); } } }