From b544d5cccd76735e034c8055918a50927b437474 Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期二, 10 十一月 2026 08:29:04 +0800
Subject: [PATCH] fix: 维护模式重复进入失败与按钮灰死 + Broker 并发验证用例

---
 tests/Aging.MvpTests/SimulatorFramingTests.cs |  130 +++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 130 insertions(+), 0 deletions(-)

diff --git a/tests/Aging.MvpTests/SimulatorFramingTests.cs b/tests/Aging.MvpTests/SimulatorFramingTests.cs
new file mode 100644
index 0000000..ea68216
--- /dev/null
+++ b/tests/Aging.MvpTests/SimulatorFramingTests.cs
@@ -0,0 +1,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 绾垮崗璁垎甯х紪瑙g爜鍣ㄦ祴璇曘��
+    /// 绾挎牸寮忥細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()
+        {
+            // 瀵圭姝e父鍏抽棴锛氳涓嶅埌浠讳綍瀛楄妭鏃惰繑鍥� 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();
+        }
+    }
+}

--
Gitblit v1.9.1