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