TM-SGNL-iOS/SignalServiceKit/Network/SignalProxy/SignalProxy+ProxyClient.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

130 lines
4.4 KiB
Swift

//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import Network
extension SignalProxy {
/// Establishes a connection to a Signal TLS Proxy and relays transmitted data via the provided `RelayClient`
class ProxyClient {
@Atomic private(set) var isStarted = false
let id: UUID
var didStopCallback: ((Error?) -> Void)?
private weak var relayClient: RelayClient?
private var connection: NWConnection?
private lazy var queue = DispatchQueue(label: "org.signal.proxy.client", attributes: .concurrent)
init(relayClient: RelayClient) {
self.id = relayClient.id
self.relayClient = relayClient
}
static func parseHost(_ host: String) -> (String, UInt16)? {
let proxyHostComponents = host.components(separatedBy: ":")
guard let proxyHost = proxyHostComponents[safe: 0] else {
return nil
}
let proxyPort: UInt16
if let portString = proxyHostComponents[safe: 1], let port = UInt16(portString) {
proxyPort = port
} else {
proxyPort = 443
}
return (proxyHost, proxyPort)
}
func start() {
guard !isStarted else { return }
isStarted = true
guard let (proxyHost, proxyPort) = SignalProxy.host.flatMap({ Self.parseHost($0) }) else {
return stop(error: OWSAssertionError("Unexpectedly missing proxy host!"))
}
// Special case for testing: UNENCRYPTED_FOR_TESTING@foo.bar connects over TCP instead of TLS.
// (This is only the "outer" layer of the proxy; the inner connection to the Signal servers
// still uses TLS with a pinned root cert.)
let actualHost: String
let networkParams: NWParameters
if let atSign = proxyHost.firstIndex(of: "@"), proxyHost[..<atSign] == "UNENCRYPTED_FOR_TESTING" {
actualHost = String(proxyHost[atSign...].dropFirst())
networkParams = .tcp
} else {
actualHost = proxyHost
networkParams = .tls
}
connection = NWConnection(
to: NWEndpoint.hostPort(
host: NWEndpoint.Host(actualHost),
port: NWEndpoint.Port(integerLiteral: proxyPort)
),
using: networkParams
)
connection?.stateUpdateHandler = stateDidChange
receive()
connection?.start(queue: queue)
}
func stop(error: Error? = nil) {
guard isStarted else { return }
isStarted = false
if let error = error {
owsFailDebug("Proxy client \(id) did fail with error \(error)")
}
connection?.stateUpdateHandler = nil
connection?.cancel()
if let didStopCallback = didStopCallback {
self.didStopCallback = nil
didStopCallback(error)
}
}
func send(_ data: Data) {
connection?.send(content: data, completion: .contentProcessed({ [weak self] error in
guard let self = self else { return }
if let error = error {
self.stop(error: error)
return
}
}))
}
private func stateDidChange(to state: NWConnection.State) {
switch state {
case .ready:
relayClient?.send("HTTP/1.1 200\r\n\r\n".data(using: .utf8)!)
case .failed(let error), .waiting(let error):
relayClient?.send("HTTP/1.1 503\r\n\r\n".data(using: .utf8)!)
stop(error: error)
default:
break
}
}
private func receive() {
connection?.receive(minimumIncompleteLength: 1, maximumLength: 65535) { [weak self] content, _, isComplete, error in
guard let self = self else { return }
content.map { self.relayClient?.send($0) }
if isComplete {
self.stop()
} else if let error = error {
self.stop(error: error)
} else {
self.receive()
}
}
}
}
}