TCPTransport.swift 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. //////////////////////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // HTTPTransport.swift
  4. // Starscream
  5. //
  6. // Created by Dalton Cherry on 1/23/19.
  7. // Copyright © 2019 Vluxe. All rights reserved.
  8. //
  9. // Licensed under the Apache License, Version 2.0 (the "License");
  10. // you may not use this file except in compliance with the License.
  11. // You may obtain a copy of the License at
  12. //
  13. // http://www.apache.org/licenses/LICENSE-2.0
  14. //
  15. // Unless required by applicable law or agreed to in writing, software
  16. // distributed under the License is distributed on an "AS IS" BASIS,
  17. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. // See the License for the specific language governing permissions and
  19. // limitations under the License.
  20. //
  21. //////////////////////////////////////////////////////////////////////////////////////////////////
  22. #if canImport(Network)
  23. import Foundation
  24. import Network
  25. public enum TCPTransportError: Error {
  26. case invalidRequest
  27. }
  28. @available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
  29. public class TCPTransport: Transport {
  30. private var connection: NWConnection?
  31. private let queue = DispatchQueue(label: "com.vluxe.starscream.networkstream", attributes: [])
  32. private weak var delegate: TransportEventClient?
  33. private var isRunning = false
  34. private var isTLS = false
  35. deinit {
  36. disconnect()
  37. }
  38. public var usingTLS: Bool {
  39. return self.isTLS
  40. }
  41. public init(connection: NWConnection) {
  42. self.connection = connection
  43. start()
  44. }
  45. public init() {
  46. //normal connection, will use the "connect" method below
  47. }
  48. public func connect(url: URL, timeout: Double = 10, certificatePinning: CertificatePinning? = nil) {
  49. guard let parts = url.getParts() else {
  50. delegate?.connectionChanged(state: .failed(TCPTransportError.invalidRequest))
  51. return
  52. }
  53. self.isTLS = parts.isTLS
  54. let options = NWProtocolTCP.Options()
  55. options.connectionTimeout = Int(timeout.rounded(.up))
  56. let tlsOptions = isTLS ? NWProtocolTLS.Options() : nil
  57. if let tlsOpts = tlsOptions {
  58. sec_protocol_options_set_verify_block(tlsOpts.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
  59. let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
  60. guard let pinner = certificatePinning else {
  61. sec_protocol_verify_complete(true)
  62. return
  63. }
  64. pinner.evaluateTrust(trust: trust, domain: parts.host, completion: { (state) in
  65. switch state {
  66. case .success:
  67. sec_protocol_verify_complete(true)
  68. case .failed(_):
  69. sec_protocol_verify_complete(false)
  70. }
  71. })
  72. }, queue)
  73. }
  74. let parameters = NWParameters(tls: tlsOptions, tcp: options)
  75. let conn = NWConnection(host: NWEndpoint.Host.name(parts.host, nil), port: NWEndpoint.Port(rawValue: UInt16(parts.port))!, using: parameters)
  76. connection = conn
  77. start()
  78. }
  79. public func disconnect() {
  80. isRunning = false
  81. connection?.cancel()
  82. connection = nil
  83. }
  84. public func register(delegate: TransportEventClient) {
  85. self.delegate = delegate
  86. }
  87. public func write(data: Data, completion: @escaping ((Error?) -> ())) {
  88. connection?.send(content: data, completion: .contentProcessed { (error) in
  89. completion(error)
  90. })
  91. }
  92. private func start() {
  93. guard let conn = connection else {
  94. return
  95. }
  96. conn.stateUpdateHandler = { [weak self] (newState) in
  97. switch newState {
  98. case .ready:
  99. self?.delegate?.connectionChanged(state: .connected)
  100. case .waiting:
  101. self?.delegate?.connectionChanged(state: .waiting)
  102. case .cancelled:
  103. self?.delegate?.connectionChanged(state: .cancelled)
  104. case .failed(let error):
  105. self?.delegate?.connectionChanged(state: .failed(error))
  106. case .setup, .preparing:
  107. break
  108. @unknown default:
  109. break
  110. }
  111. }
  112. conn.viabilityUpdateHandler = { [weak self] (isViable) in
  113. self?.delegate?.connectionChanged(state: .viability(isViable))
  114. }
  115. conn.betterPathUpdateHandler = { [weak self] (isBetter) in
  116. self?.delegate?.connectionChanged(state: .shouldReconnect(isBetter))
  117. }
  118. conn.start(queue: queue)
  119. isRunning = true
  120. readLoop()
  121. }
  122. //readLoop keeps reading from the connection to get the latest content
  123. private func readLoop() {
  124. if !isRunning {
  125. return
  126. }
  127. connection?.receive(minimumIncompleteLength: 2, maximumLength: 4096, completion: {[weak self] (data, context, isComplete, error) in
  128. guard let s = self else {return}
  129. if let data = data {
  130. s.delegate?.connectionChanged(state: .receive(data))
  131. }
  132. // Refer to https://developer.apple.com/documentation/network/implementing_netcat_with_network_framework
  133. if let context = context, context.isFinal, isComplete {
  134. if let delegate = s.delegate {
  135. // Let the owner of this TCPTransport decide what to do next: disconnect or reconnect?
  136. delegate.connectionChanged(state: .peerClosed)
  137. } else {
  138. // No use to keep connection alive
  139. s.disconnect()
  140. }
  141. return
  142. }
  143. if error == nil {
  144. s.readLoop()
  145. }
  146. })
  147. }
  148. }
  149. #else
  150. typealias TCPTransport = FoundationTransport
  151. #endif