Files
terminalX/packages/TXTransport/Sources/TXTransport/MoshSession.swift
kid 32520f09e0 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>
2026-07-24 16:16:15 +08:00

163 lines
6.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Foundation
import CMosh
/// MoshSession vendored mosh MoshCore.xcframework `mosh_main`
///
/// `mosh_main` `FILE*`
/// - Swift `send(_:)` inPipe[1] mosh `fileno(f_in)=inPipe[0]`
/// - mosh `f_out=outPipe[1]` 线 outPipe[0] `onBytes`
/// - resize `winsize` `pthread_kill(SIGWINCH)`mosh select
///
/// ip:port mosh-over-tsnet `127.0.0.1:<relay >`
/// TsnetBridge MoshRelaymosh tailnet
public final class MoshSession: @unchecked Sendable {
/// mosh libghostty
public var onBytes: (@Sendable ([UInt8]) -> Void)?
/// mosh_main 0=退
public var onClosed: (@Sendable (Int32) -> Void)?
private let ip: String
private let port: String
private let key: String
private let predictMode: String
private let winPtr = UnsafeMutablePointer<winsize>.allocate(capacity: 1)
private let lock = NSLock()
private var inWriteFD: Int32 = -1 // Swift
private var outReadFD: Int32 = -1 // Swift
private var moshThread: pthread_t?
private var started = false
private var closed = false
/// - Parameters:
/// - ip/port: mosh-server tsnet loopback relay
/// - key: `mosh-server new` MOSH_KEYbase64
/// - predictMode: always | never | adaptive | experimental adaptive
public init(ip: String, port: Int, key: String, cols: Int, rows: Int, predictMode: String = "adaptive") {
self.ip = ip
self.port = String(port)
self.key = key
self.predictMode = predictMode
winPtr.pointee = winsize(ws_row: UInt16(rows), ws_col: UInt16(cols), ws_xpixel: 0, ws_ypixel: 0)
}
deinit {
winPtr.deallocate()
}
/// pipe线 mosh 线
public func start() {
lock.lock()
if started { lock.unlock(); return }
started = true
lock.unlock()
// pipe SIGPIPE EPIPE
signal(SIGPIPE, SIG_IGN)
// mosh UTF-8 native locale is_utf8_locale() exit(1)
setenv("LANG", "en_US.UTF-8", 1)
setenv("LC_ALL", "en_US.UTF-8", 1)
var inPipe: [Int32] = [-1, -1]
var outPipe: [Int32] = [-1, -1]
_ = inPipe.withUnsafeMutableBufferPointer { pipe($0.baseAddress) }
_ = outPipe.withUnsafeMutableBufferPointer { pipe($0.baseAddress) }
lock.lock()
inWriteFD = inPipe[1]
outReadFD = outPipe[0]
lock.unlock()
startReader(fd: outPipe[0])
startMosh(inReadFD: inPipe[0], outWriteFD: outPipe[1])
}
///
public func send(_ bytes: [UInt8]) {
lock.lock(); let fd = inWriteFD; lock.unlock()
guard fd >= 0, !bytes.isEmpty else { return }
var off = 0
bytes.withUnsafeBytes { buf in
while off < buf.count {
let n = write(fd, buf.baseAddress!.advanced(by: off), buf.count - off)
if n <= 0 { break }
off += n
}
}
}
/// moshSIGWINCH process_resize
public func resize(cols: Int, rows: Int) {
winPtr.pointee.ws_col = UInt16(cols)
winPtr.pointee.ws_row = UInt16(rows)
lock.lock(); let tid = moshThread; lock.unlock()
if let tid { pthread_kill(tid, SIGWINCH) }
}
/// pipemosh select EOF/EPIPE线
public func close() {
lock.lock()
if closed { lock.unlock(); return }
closed = true
let w = inWriteFD, r = outReadFD
inWriteFD = -1; outReadFD = -1
lock.unlock()
if w >= 0 { Foundation.close(w) }
if r >= 0 { Foundation.close(r) }
}
// MARK: -
private func setMoshThread(_ tid: pthread_t) { lock.lock(); moshThread = tid; lock.unlock() }
private func handleExit(_ rc: Int32) {
lock.lock(); moshThread = nil; lock.unlock()
onClosed?(rc)
}
/// mosh 线fdopen FILE* mosh_main Foundation.Thread
/// pthread_create + Unmanaged Swift6 SendNonSendable pass
/// 线 pthread_self() tid SIGWINCH
private func startMosh(inReadFD: Int32, outWriteFD: Int32) {
let ip = self.ip, port = self.port, key = self.key, predict = self.predictMode
let winPtr = self.winPtr
let t = Thread { [weak self] in
self?.setMoshThread(pthread_self())
guard let fin = fdopen(inReadFD, "r"), let fout = fdopen(outWriteFD, "w") else {
self?.handleExit(-1); return
}
setvbuf(fout, nil, _IONBF, 0) // mosh fwrite out_fd
let rc = ip.withCString { ipC in
port.withCString { portC in
key.withCString { keyC in
predict.withCString { prC in
mosh_main(fin, fout, winPtr, ipC, portC, keyC, prC)
}
}
}
}
fflush(fout)
self?.handleExit(rc)
}
t.name = "mosh-main"
t.stackSize = 2 * 1024 * 1024
t.start()
}
/// 线 mosh onBytes
private func startReader(fd: Int32) {
let t = Thread { [weak self] in
var buf = [UInt8](repeating: 0, count: 16384)
while true {
let n = buf.withUnsafeMutableBytes { read(fd, $0.baseAddress, $0.count) }
if n <= 0 { break }
let chunk = Array(buf[0..<n])
self?.onBytes?(chunk)
}
}
t.name = "mosh-reader"
t.stackSize = 512 * 1024
t.start()
}
}