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:
50
packages/TXCore/Sources/TXCore/Mosh/MoshConnect.swift
Normal file
50
packages/TXCore/Sources/TXCore/Mosh/MoshConnect.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
37
packages/TXCore/Tests/TXCoreTests/MoshConnectTests.swift
Normal file
37
packages/TXCore/Tests/TXCoreTests/MoshConnectTests.swift
Normal 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"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user