release: 1.4.4 — channel «Облако» (WebSocket via KeenDNS cloud to own Keenetic): wsgw gateway, Android 1.4.4, iOS, docs/cloud.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Бобырев Андрей
2026-10-31 05:16:31 +03:00
parent d795bc6f70
commit dde6403cea
38 changed files with 855 additions and 58 deletions

59
ios/Tunnel/WsLink.swift Normal file
View File

@@ -0,0 +1,59 @@
import Foundation
/// Channel «Облако»: one binary WebSocket message = one obfuscated packet, both ways.
/// pcarelay (C) calls send() for every packet from the obfuscator; received messages go back via pcarelay_ws_in().
final class WsLink {
static let shared = WsLink()
private let q = DispatchQueue(label: "pca.ws")
private let session = URLSession(configuration: .ephemeral)
private var url: URL?
private var task: URLSessionWebSocketTask?
func start(_ url: URL) {
q.async { self.url = url; self.connect() }
}
func send(_ p: UnsafePointer<UInt8>?, _ n: Int32) {
guard let p, n > 0 else { return }
let data = Data(bytes: p, count: Int(n))
q.async {
if self.task == nil { self.connect() }
guard let t = self.task else { return }
t.send(.data(data)) { [weak self] err in
if let err { self?.q.async { if self?.task === t { self?.drop("send: \(err.localizedDescription)") } } }
}
}
}
private func connect() {
guard let url, task == nil else { return }
let t = session.webSocketTask(with: url)
t.maximumMessageSize = 1 << 20
task = t
t.resume()
NSLog("PCAPhobos ws: connect %@", url.absoluteString)
receive(t)
}
private func receive(_ t: URLSessionWebSocketTask) {
t.receive { [weak self] result in
switch result {
case .success(.data(let d)):
d.withUnsafeBytes { pcarelay_ws_in($0.bindMemory(to: UInt8.self).baseAddress, Int32(d.count)) }
self?.receive(t)
case .success:
self?.receive(t)
case .failure(let err):
self?.q.async { if self?.task === t { self?.drop("recv: \(err.localizedDescription)") } }
}
}
}
/// Next packet from the obfuscator reconnects (WireGuard keepalives make that quick).
private func drop(_ why: String) {
NSLog("PCAPhobos ws: %@", why)
task?.cancel(with: .goingAway, reason: nil)
task = nil
}
}