feat: 完成 M2 mosh(LAN + tsnet 端到端验证)

- mosh(blinksh/ios C++)+protobuf-lite 交叉编译为 MoshCore.xcframework
  (CommonCrypto 后端、含 arm64 模拟器 slice;绕 autotools 用 CMake 直编 + 手写 config.h)
- tsnet 桥新增 UDP relay(StartMoshRelay:loopback↔tsnet.Dial 逐包搬运)
- MoshSession(pipe↔FILE*/pthread/SIGWINCH)+ MoshConnectScanner(TXCore,+4 单测=39)
- SSHTerminalModel 编排:SSH 引导 mosh-server→解析 MOSH CONNECT→relay→mosh 接管
- LAN 直连 + mosh-over-tsnet 均端到端验证通过,R1(tsnet UDP 数据报语义) 证伪

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
kid
2026-07-24 16:14:40 +08:00
parent aa92d0e676
commit 32520f09e0
1232 changed files with 728138 additions and 37 deletions

View File

@@ -0,0 +1,50 @@
import Foundation
/// 解析 `mosh-server new` 打印的握手行 `MOSH CONNECT <port> <key>`。
///
/// 引导流程:经 SSH 跑 `mosh-server new …` → 服务端 detach 并在 stdout 打印这一行 →
/// 客户端据此用 UDP + key 连到 host:port。因 SSH 流里混有提示符/回显,采用流式扫描:
/// 逐段 `feed` 追加缓冲并尝试匹配,命中即返回(幂等,多次命中以首次为准)。
public struct MoshConnectInfo: Equatable, Sendable {
public let port: Int
public let key: String
public init(port: Int, key: String) { self.port = port; self.key = key }
}
public final class MoshConnectScanner {
private var buffer = ""
private var found: MoshConnectInfo?
public init() {}
/// 追加一段输出并尝试匹配;返回本次(或此前)解析到的握手信息(若有)。
@discardableResult
public func feed(_ text: String) -> MoshConnectInfo? {
if let found { return found }
buffer += text
// 只保留尾部,避免无限增长(握手行很短,8KB 足够跨 feed 边界)。
if buffer.count > 8192 { buffer = String(buffer.suffix(8192)) }
if let info = Self.parse(buffer) { found = info; return info }
return nil
}
public var result: MoshConnectInfo? { found }
/// 从任意文本中提取第一处 `MOSH CONNECT <port> <key>`。key 为 base64(AES-128 会话密钥)。
public static func parse(_ text: String) -> MoshConnectInfo? {
guard let range = text.range(of: "MOSH CONNECT ") else { return nil }
let rest = text[range.upperBound...]
// 取该 token 起至行尾,按空白切分:port key。
// 注意:Swift 把 "\r\n" 视为单个 Character(扩展字形簇),不能用 $0 != "\r" 判行尾,
// 必须用 isNewline(对 \n / \r / \r\n 均为真)。
let line = rest.split(whereSeparator: { $0.isNewline }).first ?? ""
let parts = line.split(whereSeparator: { $0 == " " || $0 == "\t" })
guard parts.count >= 2, let port = Int(parts[0]) else { return nil }
let key = String(parts[1])
// key 须为纯 base64 字符集且非空,避免把提示符碎片误当 key。
guard !key.isEmpty, key.allSatisfy({ $0.isLetter || $0.isNumber || $0 == "+" || $0 == "/" || $0 == "=" }) else {
return nil
}
return MoshConnectInfo(port: port, key: key)
}
}

View File

@@ -0,0 +1,37 @@
import XCTest
@testable import TXCore
final class MoshConnectTests: XCTestCase {
func testParseBasic() {
let info = MoshConnectScanner.parse("MOSH CONNECT 60001 gPFm3s8bkfMGrun5QI0Xdw")
XCTAssertEqual(info, MoshConnectInfo(port: 60001, key: "gPFm3s8bkfMGrun5QI0Xdw"))
}
func testParseAmidNoise() {
let out = """
user@host:~$ mosh-server new -s -c 256\r
\r
MOSH CONNECT 60022 AbCd1234+/xyzKEY0000==\r
\r
mosh-server (mosh 1.4.0)\r
"""
let info = MoshConnectScanner.parse(out)
XCTAssertEqual(info?.port, 60022)
XCTAssertEqual(info?.key, "AbCd1234+/xyzKEY0000==")
}
func testNoMatch() {
XCTAssertNil(MoshConnectScanner.parse("bash: mosh-server: command not found"))
XCTAssertNil(MoshConnectScanner.parse("MOSH CONNECT notaport keyhere"))
}
func testStreamingAcrossFeeds() {
let s = MoshConnectScanner()
XCTAssertNil(s.feed("prompt$ mosh-server new\r\nMOSH CON"))
XCTAssertNil(s.result)
let info = s.feed("NECT 60007 KeyAcrossBoundary123\r\n")
XCTAssertEqual(info, MoshConnectInfo(port: 60007, key: "KeyAcrossBoundary123"))
// 幂等:后续 feed 不改变结果。
XCTAssertEqual(s.feed("MOSH CONNECT 61000 other"), MoshConnectInfo(port: 60007, key: "KeyAcrossBoundary123"))
}
}