mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-14 02:04:13 +00:00
单测不再以 app 可执行为宿主:新增 ShadowsocksX-NG2Core 静态库 target 同源 编译 App/(除 @main)与 Domain/,模块名沿用 ShadowsocksX_NG2 使全部测试的 @testable import 零改动;app target 模块改名 ShadowsocksX_NG2App 避免共享 Products 目录 swiftmodule 撞名。两个测试 target 改链 Core 后 xcodegen 不再 生成 TEST_HOST,xcodebuild test 只构建不启动 app——与安装版运行实例零接触 (pgrep 全程验证无 app 进程)。同源双编译沿用 Agent/Helper 共享 Domain 文件 的既定模式,Core 只被测试 target 依赖,发布链不编译它。 随之删除 MainApp.swift 的整套测试宿主脚手架(isUnitTesting/makeTesting/ Noop 服务/Ephemeral 存储,-142 行),app 在测试下不再启动,守卫失去存在 理由。宿主移除暴露三处 Bundle.main 依赖,改为显式缝:控制器新增 appBundle 注入(内置规则快照 BuiltinRuleCatalog 与 LaunchDaemon 清单指纹 launchDaemonPlistData(in:) 均按 bundle 读取,生产默认 .main 不变);XPC listener delegate 期望客户端身份改为可注入(测试按本进程 SecCode 自查)。 测试经 AppArtifact.bundle(沿测试包落盘位置向上查找 app 产物,兼容 TEST_HOST 内嵌与平级两种布局)统一取产物。 665 单测 + 11 冒烟全绿,task build 生产构建不受影响。
76 lines
3.2 KiB
Swift
76 lines
3.2 KiB
Swift
import Foundation
|
||
|
||
/// GUI 与特权系统代理 helper 的共享 XPC 契约(issue #71)。类型同时编入 GUI、
|
||
/// helper 与测试 target;payload 以 JSON 编码保持 closed typed 语义。
|
||
@objc protocol SystemProxyHelperControlling {
|
||
/// 应用 closed typed 配置到活动网络位置的每个服务。
|
||
func apply(_ payload: Data, withReply reply: @escaping (Data) -> Void)
|
||
/// 无条件清除全部网络位置全部服务的完整 Proxies 字典。
|
||
func clear(withReply reply: @escaping (Data) -> Void)
|
||
}
|
||
|
||
/// helper 的固定身份:MachService 名、LaunchDaemon plist 名与客户端校验基准。
|
||
/// 客户端只按 2.0 的 bundle identifier(代码签名 identifier)接受,不限
|
||
/// Team ID 或账号(issue #71)。
|
||
enum SystemProxyHelperIdentity {
|
||
static let machServiceName = "com.qiuyuzhou.ShadowsocksX-NG2.systemproxy"
|
||
static let plistName = "com.qiuyuzhou.ShadowsocksX-NG2.systemproxy.plist"
|
||
static let launchdLabel = "com.qiuyuzhou.ShadowsocksX-NG2.systemproxy"
|
||
static let clientCodeSigningIdentifier = "com.qiuyuzhou.ShadowsocksX-NG2"
|
||
|
||
/// App bundle 内 LaunchDaemon 清单内容(注册漂移指纹的基准)。helper 进程
|
||
/// 内没有 bundle,返回 nil,调用方按「无基准不动作」处理。Contents/Library
|
||
/// 不是资源目录,Bundle 资源 API 搜不到,按固定路径直读。bundle 参数生产
|
||
/// 默认 Bundle.main;去宿主化(ADR 0021)后单测经控制器的 appBundle 缝注入
|
||
/// 构建产物 bundle。
|
||
static func launchDaemonPlistData(in bundle: Bundle = .main) -> Data? {
|
||
let url = bundle.bundleURL
|
||
.appendingPathComponent("Contents/Library/LaunchDaemons")
|
||
.appendingPathComponent(plistName)
|
||
return try? Data(contentsOf: url)
|
||
}
|
||
}
|
||
|
||
/// helper XPC 应答:成功携带 typed 结果,失败携带 typed 错误族。
|
||
enum SystemProxyHelperResponse: Equatable, Sendable, Codable {
|
||
case applied(SystemProxyWriteOutcome)
|
||
case cleared
|
||
case failure(SystemProxyError)
|
||
}
|
||
|
||
enum SystemProxyHelperWireError: Error, Equatable, Sendable {
|
||
case undecodableResponse(String)
|
||
}
|
||
|
||
enum SystemProxyHelperWire {
|
||
/// SystemProxyHelperResponse 全部由简单可编码类型组成,JSON 编码不可失败;
|
||
/// 若未来加入不可编码载荷,显式失败好过发出无法解码的应答。
|
||
static func encode(_ response: SystemProxyHelperResponse) -> Data {
|
||
do {
|
||
return try JSONEncoder().encode(response)
|
||
} catch {
|
||
preconditionFailure("system proxy helper response encode failed: \(error)")
|
||
}
|
||
}
|
||
|
||
static func decode(_ payload: Data) throws -> SystemProxyHelperResponse {
|
||
do {
|
||
return try JSONDecoder().decode(SystemProxyHelperResponse.self, from: payload)
|
||
} catch {
|
||
throw SystemProxyHelperWireError.undecodableResponse(String(describing: error))
|
||
}
|
||
}
|
||
|
||
static func encodeConfiguration(_ configuration: SystemProxyConfiguration) throws -> Data {
|
||
try JSONEncoder().encode(configuration)
|
||
}
|
||
|
||
static func decodeConfiguration(_ payload: Data) throws -> SystemProxyConfiguration {
|
||
do {
|
||
return try JSONDecoder().decode(SystemProxyConfiguration.self, from: payload)
|
||
} catch {
|
||
throw SystemProxyError.invalidRequest(String(describing: error))
|
||
}
|
||
}
|
||
}
|