From 2e3a206767d5042880bb6aab0964cfddd2085e39 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 26 八月 2026 13:35:15 +0800
Subject: [PATCH] fix(device): 统一 BTS 帧边界解析

---
 ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java          |   21 ------
 ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsFrameReader.java                   |   56 ++++++++++++++++++
 ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java                  |   39 ------------
 ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java |   14 ++++
 4 files changed, 73 insertions(+), 57 deletions(-)

diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsFrameReader.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsFrameReader.java
new file mode 100644
index 0000000..72e9878
--- /dev/null
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsFrameReader.java
@@ -0,0 +1,56 @@
+package com.ruoyi.device.bts;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+final class BtsFrameReader
+{
+    private static final byte[] FRAME_END = "#\r\n".getBytes(StandardCharsets.UTF_8);
+
+    private BtsFrameReader()
+    {
+    }
+
+    static byte[] read(InputStream input, int maxPayloadBytes) throws IOException
+    {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        int matched = 0;
+        while (true)
+        {
+            int value = input.read();
+            if (value < 0) throw new EOFException("BTS connection closed before the frame ended");
+            if ((byte) value == FRAME_END[matched])
+            {
+                matched++;
+                if (matched == FRAME_END.length) return buffer.toByteArray();
+                continue;
+            }
+            if (matched > 0)
+            {
+                for (int i = 0; i < matched; i++) append(buffer, FRAME_END[i], maxPayloadBytes);
+                matched = 0;
+                if ((byte) value == FRAME_END[0])
+                {
+                    matched = 1;
+                    continue;
+                }
+            }
+            append(buffer, value, maxPayloadBytes);
+        }
+    }
+
+    private static void append(ByteArrayOutputStream buffer, int value, int maxPayloadBytes)
+            throws FrameTooLargeException
+    {
+        if (buffer.size() >= maxPayloadBytes) throw new FrameTooLargeException();
+        buffer.write(value);
+    }
+
+    static final class FrameTooLargeException extends IOException
+    {
+        private static final long serialVersionUID = 1L;
+    }
+}
diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java
index fa9eb5e..f3d3449 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java
@@ -1,6 +1,5 @@
 package com.ruoyi.device.bts;
 
-import java.io.ByteArrayOutputStream;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -83,7 +82,7 @@
             throw new BtsTransportException(BtsTransportException.Reason.DISCONNECTED,
                     "BTS disconnected before completing a response for " + host + ":" + port, ex);
         }
-        catch (ResponseTooLargeException ex)
+        catch (BtsFrameReader.FrameTooLargeException ex)
         {
             closeSocket();
             throw new BtsTransportException(BtsTransportException.Reason.RESPONSE_TOO_LARGE,
@@ -118,36 +117,7 @@
 
     private String readFrame(InputStream input) throws IOException
     {
-        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        int matched = 0;
-        while (true)
-        {
-            int value = input.read();
-            if (value < 0) throw new EOFException("BTS closed the connection before the response frame ended");
-            if ((byte) value == FRAME_END[matched])
-            {
-                matched++;
-                if (matched == FRAME_END.length) return decodeUtf8(buffer.toByteArray());
-                continue;
-            }
-            if (matched > 0)
-            {
-                for (int i = 0; i < matched; i++) appendPayload(buffer, FRAME_END[i]);
-                matched = 0;
-                if ((byte) value == FRAME_END[0])
-                {
-                    matched = 1;
-                    continue;
-                }
-            }
-            appendPayload(buffer, value);
-        }
-    }
-
-    private void appendPayload(ByteArrayOutputStream buffer, int value) throws ResponseTooLargeException
-    {
-        if (buffer.size() >= maxResponseBytes) throw new ResponseTooLargeException();
-        buffer.write(value);
+        return decodeUtf8(BtsFrameReader.read(input, maxResponseBytes));
     }
 
     private static String decodeUtf8(byte[] bytes) throws InvalidUtf8Exception
@@ -194,11 +164,6 @@
         {
             socket = null;
         }
-    }
-
-    private static final class ResponseTooLargeException extends IOException
-    {
-        private static final long serialVersionUID = 1L;
     }
 
     private static final class InvalidUtf8Exception extends IOException
diff --git a/ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java b/ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java
index f4fb784..1ef262c 100644
--- a/ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java
+++ b/ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java
@@ -1,6 +1,5 @@
 package com.ruoyi.device.bts;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.ServerSocket;
@@ -116,25 +115,7 @@
 
     private String readFrame(InputStream input) throws IOException
     {
-        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        int matched = 0;
-        while (true)
-        {
-            int value = input.read();
-            if (value < 0) throw new IOException("Client disconnected");
-            if ((byte) value == FRAME_END[matched])
-            {
-                matched++;
-                if (matched == FRAME_END.length) return buffer.toString(StandardCharsets.UTF_8);
-                continue;
-            }
-            if (matched > 0)
-            {
-                buffer.write(FRAME_END, 0, matched);
-                matched = 0;
-            }
-            buffer.write(value);
-        }
+        return new String(BtsFrameReader.read(input, Integer.MAX_VALUE), StandardCharsets.UTF_8);
     }
 
     @Override
diff --git a/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java b/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java
index 383fd6d..7fca87f 100644
--- a/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java
+++ b/ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java
@@ -22,4 +22,18 @@
             assertEquals(1, simulator.requests().size());
         }
     }
+
+    @Test
+    void simulatorPreservesRequestEndingWithDelimiterPrefix() throws Exception
+    {
+        try (ScriptedBtsTcpSimulator simulator = new ScriptedBtsTcpSimulator();
+             BtsTcpTransport transport = new BtsTcpTransport("127.0.0.1", simulator.port(),
+                     Duration.ofSeconds(1), Duration.ofSeconds(1)))
+        {
+            simulator.enqueue("ok");
+
+            assertEquals("ok", transport.exchange("payload#"));
+            assertEquals("payload#", simulator.requests().get(0));
+        }
+    }
 }

--
Gitblit v1.7.1