-- protocol_support_test.lua — hysteria2 / tuic / wireguard 导入、JSON 配置导入放开、Surge 家族输出单元测试 -- 用法:lua5.1 tests/protocol_support_test.lua package.path = "./root/usr/share/?.lua;" .. package.path local parser = require("substore.parser") local parser_json_config = require("substore.parser_json_config") local output = require("substore.output") local output_uri = require("substore.output_uri") local util = require("substore.util") local passed, failed = 0, 0 local function check(name, cond) if cond then passed = passed + 1 print("PASS " .. name) else failed = failed + 1 print("FAIL " .. name) end end -- ---------- hysteria2:// 分享链接导入 ---------- local hy2 = parser.parse_uri("hysteria2://my-secret@1.2.3.4:443/?sni=sni.example.com&insecure=1#HY2节点") check("hy2 parses", hy2 ~= nil) check("hy2 proto", hy2 and hy2.proto == "hysteria2") check("hy2 server", hy2 and hy2.server == "1.2.3.4") check("hy2 port", hy2 and hy2.port == 443) check("hy2 password", hy2 and hy2.password == "my-secret") check("hy2 sni", hy2 and hy2.sni == "sni.example.com") check("hy2 insecure", hy2 and hy2.insecure == "1") check("hy2 name", hy2 and hy2.name == "HY2节点") -- ---------- tuic:// 分享链接导入 ---------- local tuic = parser.parse_uri("tuic://uuid-1234:tuic-pass@5.6.7.8:8443/?congestion_control=bbr&alpn=h3&sni=t.example.com#TUIC节点") check("tuic parses", tuic ~= nil) check("tuic proto", tuic and tuic.proto == "tuic") check("tuic server", tuic and tuic.server == "5.6.7.8") check("tuic port", tuic and tuic.port == 8443) check("tuic uuid", tuic and tuic.uuid == "uuid-1234") check("tuic password", tuic and tuic.password == "tuic-pass") check("tuic congestion_control", tuic and tuic.congestion_control == "bbr") check("tuic alpn", tuic and tuic.alpn == "h3") check("tuic sni", tuic and tuic.sni == "t.example.com") check("tuic name", tuic and tuic.name == "TUIC节点") -- ---------- wireguard:// 导入(固定 base64 外部样本,非本实现生成) ---------- -- base64 内容:{"server":"10.0.0.9","port":"51820","private-key":"privkey123","peer-public-key":"pubkey456","preshared-key":"psk789","mtu":"1420"} local wg_uri = "wireguard://eyJzZXJ2ZXIiOiIxMC4wLjAuOSIsInBvcnQiOiI1MTgyMCIsInByaXZhdGUta2V5IjoicHJpdmtleTEyMyIsInBlZXItcHVibGljLWtleSI6InB1YmtleTQ1NiIsInByZXNoYXJlZC1rZXkiOiJwc2s3ODkiLCJtdHUiOiIxNDIwIn0=#WG节点" check("detect wg = uri", parser.detect(wg_uri) == "uri") local wg = parser.parse_uri(wg_uri) check("wg parses", wg ~= nil) check("wg proto", wg and wg.proto == "wireguard") check("wg server", wg and wg.server == "10.0.0.9") check("wg port", wg and wg.port == 51820) check("wg private-key", wg and wg["private-key"] == "privkey123") check("wg peer-public-key", wg and wg["peer-public-key"] == "pubkey456") check("wg preshared-key", wg and wg["preshared-key"] == "psk789") check("wg mtu", wg and wg.mtu == 1420) check("wg name", wg and wg.name == "WG节点") -- ---------- 订阅(URI 列表 / base64)含 hy2 / tuic / wg ---------- local sub = "hysteria2://p@1.1.1.1:8443#A\ntuic://u:p@2.2.2.2:8443#B\nvless://uuid1@3.3.3.3:443#C" local res = parser.parse(sub) check("sub format uri", res and res.format == "uri") check("sub 3 nodes", res and #res.nodes == 3) local b64sub = util.base64_encode("hysteria2://p@1.1.1.1:8443#A\ntuic://u:p@2.2.2.2:8443#B") local res2 = parser.parse(b64sub) check("base64 sub 2 nodes", res2 and #res2.nodes == 2) check("base64 sub proto hy2", res2 and res2.nodes[1] and res2.nodes[1].proto == "hysteria2") -- ---------- URI 输出(Shadowrocket / V2Ray URI)含 hy2 / tuic / wg ---------- local hy2node = { proto = "hysteria2", name = "HY", server = "1.1.1.1", port = 443, password = "p", sni = "s.example.com" } local tuicnode = { proto = "tuic", name = "TC", server = "2.2.2.2", port = 8443, uuid = "u", password = "p" } local wgnode = { proto = "wireguard", name = "WG", server = "10.0.0.9", port = 51820, ["private-key"] = "pk", ["peer-public-key"] = "ppk", ["preshared-key"] = "psk" } local uri_out = output.generate({ hy2node, tuicnode, wgnode }, "v2rayuri") check("uri has hysteria2", uri_out and uri_out:find("hysteria2://") ~= nil) check("uri has tuic", uri_out and uri_out:find("tuic://") ~= nil) check("uri has wireguard", uri_out and uri_out:find("wireguard://") ~= nil) local rocket = output.generate({ hy2node, tuicnode, wgnode }, "shadowrocket") local decoded = util.base64_decode(rocket) check("shadowrocket has hysteria2", decoded:find("hysteria2://") ~= nil) check("shadowrocket has wireguard", decoded:find("wireguard://") ~= nil) -- ---------- wireguard 回环(输出 -> 解析) ---------- local wg_round = parser.parse_uri(uri_out:match("wireguard://[^\n]*")) check("wg roundtrip proto", wg_round and wg_round.proto == "wireguard") check("wg roundtrip server", wg_round and wg_round.server == "10.0.0.9") check("wg roundtrip private", wg_round and wg_round["private-key"] == "pk") check("wg roundtrip peer", wg_round and wg_round["peer-public-key"] == "ppk") -- ---------- JSON 配置导入放开:sing-box JSON ---------- local singbox = [[ { "outbounds": [ { "type": "hysteria2", "tag": "sb-hy2", "server": "1.1.1.1", "server_port": 443, "password": "hy2pw", "tls": { "server_name": "h.sni.com" } }, { "type": "tuic", "tag": "sb-tuic", "server": "2.2.2.2", "server_port": 8443, "uuid": "sb-uuid", "password": "sb-tuicpw", "congestion_control": "bbr" }, { "type": "wireguard", "tag": "sb-wg", "server": "3.3.3.3", "server_port": 51820, "private_key": "sb-priv", "peer_public_key": "sb-pub", "preshared_key": "sb-psk", "mtu": 1400 }, { "type": "socks", "tag": "sb-socks", "server": "4.4.4.4", "server_port": 1080, "username": "suser", "password": "spass" } ] } ]] local sbnodes = parser_json_config.parse_singbox_json(singbox) check("singbox 4 nodes", sbnodes and #sbnodes == 4) check("singbox hy2", sbnodes and sbnodes[1] and sbnodes[1].proto == "hysteria2" and sbnodes[1].password == "hy2pw" and sbnodes[1].sni == "h.sni.com") check("singbox tuic", sbnodes and sbnodes[2] and sbnodes[2].proto == "tuic" and sbnodes[2].uuid == "sb-uuid" and sbnodes[2].password == "sb-tuicpw") check("singbox wg kebab", sbnodes and sbnodes[3] and sbnodes[3].proto == "wireguard" and sbnodes[3]["private-key"] == "sb-priv" and sbnodes[3]["peer-public-key"] == "sb-pub" and sbnodes[3]["preshared-key"] == "sb-psk") check("singbox socks", sbnodes and sbnodes[4] and sbnodes[4].proto == "socks" and sbnodes[4].username == "suser" and sbnodes[4].password == "spass") -- ---------- JSON 配置导入放开:Clash JSON(含 ssr) ---------- local clashjson = [[ { "proxies": [ { "name": "c-ssr", "type": "ssr", "server": "1.1.1.1", "port": 8388, "cipher": "aes-128-cfb", "password": "ssrpw", "protocol": "auth_sha1_v4", "obfs": "http_simple", "obfs-param": "obfshost.com" }, { "name": "c-hy2", "type": "hysteria2", "server": "2.2.2.2", "port": 443, "password": "hy2pw", "sni": "h2.example.com" }, { "name": "c-tuic", "type": "tuic", "server": "3.3.3.3", "port": 8443, "uuid": "c-uuid", "password": "c-tuicpw" }, { "name": "c-wg", "type": "wireguard", "server": "4.4.4.4", "port": 51820, "private-key": "c-priv", "peer-public-key": "c-pub", "preshared-key": "c-psk" }, { "name": "c-socks", "type": "socks5", "server": "5.5.5.5", "port": 1080, "username": "cu", "password": "cp" } ] } ]] local cnodes = parser_json_config.parse_clash_json(clashjson) check("clash json 5 nodes", cnodes and #cnodes == 5) check("clash json ssr", cnodes and cnodes[1] and cnodes[1].proto == "ssr" and cnodes[1].method == "aes-128-cfb" and cnodes[1].protocol == "auth_sha1_v4" and cnodes[1].obfs == "http_simple" and cnodes[1].obfs_param == "obfshost.com") check("clash json hy2", cnodes and cnodes[2] and cnodes[2].proto == "hysteria2" and cnodes[2].password == "hy2pw") check("clash json tuic", cnodes and cnodes[3] and cnodes[3].proto == "tuic" and cnodes[3].uuid == "c-uuid") check("clash json wg", cnodes and cnodes[4] and cnodes[4].proto == "wireguard" and cnodes[4]["private-key"] == "c-priv" and cnodes[4]["peer-public-key"] == "c-pub") check("clash json socks5->socks", cnodes and cnodes[5] and cnodes[5].proto == "socks" and cnodes[5].username == "cu" and cnodes[5].password == "cp") -- ---------- Surge 家族:tuic 输出 / wireguard 丢弃 ---------- local surge = output.generate({ tuicnode, wgnode }, "surge") check("surge has tuic line", surge and surge:find("tuic, 2%.2%.2%.2, 8443") ~= nil) check("surge tuic username", surge and surge:find("username=u") ~= nil) check("surge drops wireguard", surge and surge:find("10%.0%.0%.9") == nil and surge:find("wireguard") == nil) local loon = output.generate({ wgnode }, "loon") check("loon drops wireguard too", loon and loon:find("wireguard") == nil) -- ---------- sing-box 输出 wireguard 使用 kebab 字段(导入后可正确输出) ---------- local sb_out = output.generate({ wgnode }, "singbox") local sb_decoded = util.json_decode(sb_out) check("singbox wg private_key", sb_decoded and sb_decoded.outbounds[1] and sb_decoded.outbounds[1].private_key == "pk") check("singbox wg peer_public_key", sb_decoded and sb_decoded.outbounds[1] and sb_decoded.outbounds[1].peer_public_key == "ppk") -- ---------- 结果 ---------- print(string.format("\n%d passed, %d failed", passed, failed)) os.exit(failed == 0 and 0 or 1)