yeg
2026-08-26 2e3a206767d5042880bb6aab0964cfddd2085e39
fix(device): 统一 BTS 帧边界解析
1个文件已添加
3个文件已修改
130 ■■■■■ 已修改文件
ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsFrameReader.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsTcpTransport.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-device/src/main/java/com/ruoyi/device/bts/ScriptedBtsTcpSimulator.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-device/src/test/java/com/ruoyi/device/contract/SimulatorPublicContractTest.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-device/src/main/java/com/ruoyi/device/bts/BtsFrameReader.java
New file
@@ -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;
    }
}
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
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
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));
        }
    }
}