Signal-iOS/SignalUI/Environment/WebSocketFactoryHybrid.swift
Evan Hahn 370ff654e7
Change license to AGPL
Change license to AGPL

This commit:

- Updates the `LICENSE` file

- Start every file with something like:

      // Copyright YEAR_FIRST_PUBLISHED Signal Messenger, LLC
      // SPDX-License-Identifier: AGPL-3.0-only

---

First, I removed existing license headers with this Ruby 3.1.2 script:

    require 'set'

    EXTENSIONS_TO_CHECK = Set['.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift']

    same = 0
    different = 0

    all_files = `git ls-files`.lines.map { |line| line.strip }
    all_files.each do |relative_path|
      if relative_path == 'Pods'
        next
      end

      unless EXTENSIONS_TO_CHECK.include? File.extname(relative_path)
        next
      end

      path = File.expand_path(relative_path)

      contents = File.read(path)
      new_contents = contents.sub(/\/\/\n\/\/  Copyright .*\n\/\/\n\n/, '')

      if contents == new_contents
        same += 1
      else
        different += 1
      end

      File.write(path, new_contents)
    end

    puts "updated #{different} file(s), left #{same} untouched"

I'm sure this script could be improved, but it worked well enough.

Then, I created `Scripts/lint/lint-license-headers` and ran it to auto-
fix a lot of files. This changed the mode of some files, but I think
that's actually desirable. For example,
`SignalServiceKit/src/Util/AppContext.m` previously had a mode of
`0755/-rwxr-xr-x`, and it's now `0644/-rw-r--r--`.

Then I fixed some stragglers and updated the precommit script.

See [a similar change in the Desktop app][0].

[0]: 8bfaf598af
2022-10-13 08:25:37 -05:00

206 lines
5.6 KiB
Swift

//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalServiceKit
import SignalMessaging
import Starscream
@objc
public class WebSocketFactoryHybrid: NSObject, WebSocketFactory {
public var canBuildWebSocket: Bool { true }
public func buildSocket(request: URLRequest,
callbackQueue: DispatchQueue) -> SSKWebSocket? {
if FeatureFlags.canUseNativeWebsocket,
#available(iOS 13, *) {
return SSKWebSocketNative(request: request, callbackQueue: callbackQueue)
} else {
return SSKWebSocketStarScream(request: request, callbackQueue: callbackQueue)
}
}
public func statusCode(forError error: Error) -> Int {
switch error {
case let error as StarscreamError:
return error.code
case SSKWebSocketNativeError.failedToConnect(let statusCode):
return statusCode ?? 0
case SSKWebSocketNativeError.remoteClosed(let statusCode, _):
return statusCode
default:
return error.httpStatusCode ?? 0
}
}
}
// MARK: -
class SSKWebSocketStarScream: SSKWebSocket {
private static let idCounter = AtomicUInt()
public let id = SSKWebSocketStarScream.idCounter.increment()
private let socket: Starscream.WebSocket
public var callbackQueue: DispatchQueue { socket.callbackQueue }
init(request: URLRequest,
callbackQueue: DispatchQueue? = nil) {
let socket = WebSocket(request: request)
if let callbackQueue = callbackQueue {
socket.callbackQueue = callbackQueue
}
socket.disableSSLCertValidation = true
socket.socketSecurityLevel = StreamSocketSecurityLevel.tlSv1_2
let security = SSLSecurity(certs: [TextSecureCertificate(), SignalMessengerCertificate()], usePublicKeys: false)
security.validateEntireChain = false
socket.security = security
// TODO cipher suite selection
// socket.enabledSSLCipherSuites = [TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
self.socket = socket
socket.delegate = self
}
// MARK: - SSKWebSocket
weak var delegate: SSKWebSocketDelegate?
private let hasEverConnected = AtomicBool(false)
// This method is thread-safe.
var state: SSKWebSocketState {
if socket.isConnected {
return .open
}
if hasEverConnected.get() {
return .disconnected
}
return .connecting
}
func connect() {
socket.connect()
}
func disconnect() {
socket.disconnect()
}
func write(data: Data) {
socket.write(data: data)
}
func writePing() {
socket.write(ping: Data())
}
}
// MARK: -
extension SSKWebSocketStarScream: WebSocketDelegate {
func websocketDidConnect(socket: WebSocketClient) {
assertOnQueue(callbackQueue)
hasEverConnected.set(true)
delegate?.websocketDidConnect(socket: self)
}
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
assertOnQueue(callbackQueue)
let websocketError: Error?
switch error {
case let wsError as WSError:
websocketError = StarscreamError(underlyingError: wsError)
case let nsError as NSError:
// Assert that error is either a Starscream.WSError or an OS level networking error
assert(nsError.domain == NSPOSIXErrorDomain as String
|| nsError.domain == kCFErrorDomainCFNetwork as String
|| nsError.domain == NSOSStatusErrorDomain as String)
websocketError = error
default:
assert(error == nil, "unexpected error type: \(String(describing: error))")
websocketError = error
}
delegate?.websocketDidDisconnectOrFail(socket: self, error: websocketError)
}
func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
assertOnQueue(callbackQueue)
owsFailDebug("We only expect binary frames.")
}
func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
assertOnQueue(callbackQueue)
delegate?.websocket(self, didReceiveData: data)
}
}
// MARK: -
private func TextSecureCertificate() -> SSLCert {
let data = SSKTextSecureServiceCertificateData()
return SSLCert(data: data)
}
private func SignalMessengerCertificate() -> SSLCert {
let data = SSKSignalMessengerCertificateData()
return SSLCert(data: data)
}
// MARK: -
private extension StreamSocketSecurityLevel {
static var tlSv1_2: StreamSocketSecurityLevel {
return StreamSocketSecurityLevel(rawValue: "kCFStreamSocketSecurityLevelTLSv1_2")
}
}
// MARK: -
// TODO: Replace with OWSHTTPError.
@objc
public class StarscreamError: NSObject, CustomNSError {
let underlyingError: Starscream.WSError
public var code: Int { underlyingError.code }
init(underlyingError: Starscream.WSError) {
self.underlyingError = underlyingError
}
// MARK: - CustomNSError
@objc
public static let errorDomain = "SignalServiceKit.StarscreamError"
public var errorUserInfo: [String: Any] {
return [
type(of: self).kStatusCodeKey: code,
NSUnderlyingErrorKey: (underlyingError as NSError)
]
}
// MARK: -
// TODO: Eliminate.
@objc
public static var kStatusCodeKey: String { "StarscreamErrorStatusCode" }
public override var description: String {
return "StarscreamError - underlyingError: \(underlyingError)"
}
}