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
package com.battery.device.bts;
 
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
/**
 * 新威 BTS 对外接口的**只读命令白名单**(ADR-0068 §1)。
 *
 * <p>启用厂商对外接口等于同时打开了控制通道:厂商协议里 {@code start}/{@code stop}/{@code reset}/
 * {@code broadcaststop} 与只读命令走同一条管道、同一套报文格式,服务端不做区分。
 * 因此"只观测不控制"这条不变量在新威这条路上**不是服务端保证,而是代码纪律**,
 * 落点就是这里:每条出站请求在发出前按 {@code <cmd>} 取值比对白名单,不在白名单内的一律拒绝。
 *
 * <p>为什么拦在传输层而不是各调用处:调用处会随需求增加,白名单只有一处才可能被审计。
 * 独立成类而不写在传输类里,是为了让这条边界能被单独测到(不需要真的有一条管道)。
 */
final class NewareBtsReadOnlyCommands
{
    /** 允许发送的命令,全部是只读命令;取值来自《BTS API技术协议》与现场实测(ADR-0068)。 */
    private static final Set<String> ALLOWED = Set.of(
            "getdevinfo", "getchlstatus", "inquire", "inquiredf", "download",
            "downloadsteplayer", "downloadlog", "getparallel", "getpause");
 
    private static final Pattern COMMAND = Pattern.compile("<cmd>\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*</cmd>");
 
    private NewareBtsReadOnlyCommands()
    {
    }
 
    /**
     * 校验请求只含白名单内的命令,返回解析出的命令名(小写)。
     *
     * <p>没有 {@code <cmd>} 的报文不是本协议的请求,同样拒绝——放行一个解析不出命令的报文,
     * 等于把"这道门"的判定权交给厂商服务端。
     *
     * @param request 完整请求报文
     * @return 请求里的命令名(小写)
     * @throws BtsTransportException 缺少命令,或命令不在只读白名单内
     */
    static String requireAllowed(String request)
    {
        Matcher matcher = request == null ? null : COMMAND.matcher(request);
        if (matcher == null || !matcher.find())
            throw new BtsTransportException(BtsTransportException.Reason.READ_ONLY_REJECTED,
                    "BTS 请求缺少 <cmd>,拒绝发出", null);
        String command = matcher.group(1).toLowerCase(Locale.ROOT);
        if (!ALLOWED.contains(command))
            throw new BtsTransportException(BtsTransportException.Reason.READ_ONLY_REJECTED,
                    "BTS 只读白名单外的命令被拒绝:" + command, null);
        return command;
    }
}