wenxiangchao
3 天以前 b544d5cccd76735e034c8055918a50927b437474
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CanSimulator;
using Xunit;
 
namespace Aging.MvpTests
{
    /// <summary>
    /// CAN 模拟器 TCP 线协议分帧编解码器测试。
    /// 线格式:4 字节大端长度前缀 + UTF8 文本(与 BrokerIpc 同惯用法)。
    /// </summary>
    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<InvalidDataException>(() =>
                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<InvalidDataException>(() =>
                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);
        }
 
        /// <summary>每次 Read 只从内部缓冲区取固定片段,模拟网络分片到达</summary>
        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();
        }
    }
}