package com.battery.device.bts;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import org.junit.jupiter.api.Test;
/**
* 远程命名管道传输的行为:分帧按实测的 {@code \n\n}、只读白名单在发出前生效。
* 分帧用普通文件驱动 {@link NewareBtsPipeTransport#readFrame} —— 生产方法读写的就是 {@code RandomAccessFile},
* 换掉的是对端是不是管道,帧扫描这一段完全一致。
*/
class NewareBtsPipeTransportTest
{
private static final int MAX = 1024;
@Test
void returnsPayloadUpToThePlainDoubleNewlineTerminator() throws Exception
{
assertEquals("getdevinfo_resp", frame("getdevinfo_resp\n\n"));
}
@Test
void ignoresBytesAfterTheTerminator() throws Exception
{
assertEquals("first", frame("first\n\nsecond\n\n"));
}
@Test
void keepsTerminatorPrefixThatBelongsToThePayload() throws Exception
{
// 正文里出现结束符的前缀时要原样补回,不能把它吃掉。
assertEquals("a\n#b", frame("a\n#b\n\n"));
}
@Test
void doesNotTreatDocumentedHashTerminatorAsAnEnd() throws Exception
{
// 协议文档给的 #\r\n 现场实测无效;照它写就永远读不到帧尾,这里以"读到尾也没结束"表示这一点。
assertThrows(EOFException.class, () -> frame("#\r\n"));
}
@Test
void rejectsPayloadBeyondTheLimit() throws Exception
{
assertThrows(IOException.class, () -> frameRaw("123456789\n\n", 8));
}
@Test
void allowsPayloadExactlyAtTheLimit() throws Exception
{
assertEquals("12345678", frameRaw("12345678\n\n", 8));
}
@Test
void rejectsEveryCommandOutsideTheReadOnlyWhitelistBeforeAnyTransportWork()
{
try (NewareBtsPipeTransport transport = new NewareBtsPipeTransport("203.0.113.1", "Administrator", "secret",
Duration.ofSeconds(1), Duration.ofSeconds(1)))
{
// 控制命令一律不得发出:这里没有任何会话与管道,若白名单没生效就会变成连接失败而不是被拒绝。
for (String command : new String[] {"start", "stop", "reset", "broadcaststop", "setpause",
"cancelpause", "goto", "chl_ctrl", "parallel", "light", "continue", "clearflag"})
{
BtsTransportException exception = assertThrows(BtsTransportException.class,
() -> transport.exchange("" + command + ""));
assertEquals(BtsTransportException.Reason.READ_ONLY_REJECTED, exception.reason(), command);
}
}
}
@Test
void acceptsTheReadOnlyWhitelistAndRejectsRequestsWithoutACommand()
{
try (NewareBtsPipeTransport transport = new NewareBtsPipeTransport("203.0.113.1", "Administrator", "secret",
Duration.ofSeconds(1), Duration.ofSeconds(1)))
{
for (String command : new String[] {"getdevinfo", "getchlstatus", "inquire", "inquiredf", "download",
"downloadStepLayer", "downloadlog", "getparallel", "getpause"})
assertEquals(command.toLowerCase(java.util.Locale.ROOT),
NewareBtsReadOnlyCommands.requireAllowed("" + command + ""));
BtsTransportException exception = assertThrows(BtsTransportException.class,
() -> NewareBtsReadOnlyCommands.requireAllowed("没有命令"));
assertEquals(BtsTransportException.Reason.READ_ONLY_REJECTED, exception.reason());
}
}
private static String frame(String content) throws Exception
{
return frameRaw(content, MAX);
}
private static String frameRaw(String content, int maxPayloadBytes) throws Exception
{
Path file = Files.createTempFile("neware-bts-frame", ".txt");
try
{
Files.write(file, content.getBytes(StandardCharsets.UTF_8));
try (RandomAccessFile pipe = new RandomAccessFile(file.toFile(), "r"))
{
return new String(NewareBtsPipeTransport.readFrame(pipe, maxPayloadBytes), StandardCharsets.UTF_8);
}
}
finally
{
Files.deleteIfExists(file);
}
}
}