FoundationTransport.swift 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. //////////////////////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // FoundationTransport.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. import Foundation
  23. public enum FoundationTransportError: Error {
  24. case invalidRequest
  25. case invalidOutputStream
  26. case timeout
  27. }
  28. public class FoundationTransport: NSObject, Transport, StreamDelegate {
  29. private weak var delegate: TransportEventClient?
  30. private let workQueue = DispatchQueue(label: "com.vluxe.starscream.websocket", attributes: [])
  31. private var inputStream: InputStream?
  32. private var outputStream: OutputStream?
  33. private var isOpen = false
  34. private var onConnect: ((InputStream, OutputStream) -> Void)?
  35. private var isTLS = false
  36. private var certPinner: CertificatePinning?
  37. public var usingTLS: Bool {
  38. return self.isTLS
  39. }
  40. public init(streamConfiguration: ((InputStream, OutputStream) -> Void)? = nil) {
  41. super.init()
  42. onConnect = streamConfiguration
  43. }
  44. deinit {
  45. inputStream?.delegate = nil
  46. outputStream?.delegate = nil
  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(FoundationTransportError.invalidRequest))
  51. return
  52. }
  53. self.certPinner = certificatePinning
  54. self.isTLS = parts.isTLS
  55. var readStream: Unmanaged<CFReadStream>?
  56. var writeStream: Unmanaged<CFWriteStream>?
  57. let h = parts.host as NSString
  58. CFStreamCreatePairWithSocketToHost(nil, h, UInt32(parts.port), &readStream, &writeStream)
  59. inputStream = readStream!.takeRetainedValue()
  60. outputStream = writeStream!.takeRetainedValue()
  61. guard let inStream = inputStream, let outStream = outputStream else {
  62. return
  63. }
  64. inStream.delegate = self
  65. outStream.delegate = self
  66. if isTLS {
  67. let key = CFStreamPropertyKey(rawValue: kCFStreamPropertySocketSecurityLevel)
  68. CFReadStreamSetProperty(inStream, key, kCFStreamSocketSecurityLevelNegotiatedSSL)
  69. CFWriteStreamSetProperty(outStream, key, kCFStreamSocketSecurityLevelNegotiatedSSL)
  70. }
  71. onConnect?(inStream, outStream)
  72. isOpen = false
  73. CFReadStreamSetDispatchQueue(inStream, workQueue)
  74. CFWriteStreamSetDispatchQueue(outStream, workQueue)
  75. inStream.open()
  76. outStream.open()
  77. workQueue.asyncAfter(deadline: .now() + timeout, execute: { [weak self] in
  78. guard let s = self else { return }
  79. if !s.isOpen {
  80. s.delegate?.connectionChanged(state: .failed(FoundationTransportError.timeout))
  81. }
  82. })
  83. }
  84. public func disconnect() {
  85. if let stream = inputStream {
  86. stream.delegate = nil
  87. CFReadStreamSetDispatchQueue(stream, nil)
  88. stream.close()
  89. }
  90. if let stream = outputStream {
  91. stream.delegate = nil
  92. CFWriteStreamSetDispatchQueue(stream, nil)
  93. stream.close()
  94. }
  95. isOpen = false
  96. outputStream = nil
  97. inputStream = nil
  98. }
  99. public func register(delegate: TransportEventClient) {
  100. self.delegate = delegate
  101. }
  102. public func write(data: Data, completion: @escaping ((Error?) -> ())) {
  103. guard let outStream = outputStream else {
  104. completion(FoundationTransportError.invalidOutputStream)
  105. return
  106. }
  107. var total = 0
  108. let buffer = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self)
  109. //NOTE: this might need to be dispatched to the work queue instead of being written inline. TBD.
  110. while total < data.count {
  111. let written = outStream.write(buffer, maxLength: data.count)
  112. if written < 0 {
  113. completion(FoundationTransportError.invalidOutputStream)
  114. return
  115. }
  116. total += written
  117. }
  118. completion(nil)
  119. }
  120. private func getSecurityData() -> (SecTrust?, String?) {
  121. #if os(watchOS)
  122. return (nil, nil)
  123. #else
  124. guard let outputStream = outputStream else {
  125. return (nil, nil)
  126. }
  127. let trust = outputStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust?
  128. var domain = outputStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as! String?
  129. if domain == nil,
  130. let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? {
  131. var peerNameLen: Int = 0
  132. SSLGetPeerDomainNameLength(sslContextOut, &peerNameLen)
  133. var peerName = Data(count: peerNameLen)
  134. let _ = peerName.withUnsafeMutableBytes { (peerNamePtr: UnsafeMutablePointer<Int8>) in
  135. SSLGetPeerDomainName(sslContextOut, peerNamePtr, &peerNameLen)
  136. }
  137. if let peerDomain = String(bytes: peerName, encoding: .utf8), peerDomain.count > 0 {
  138. domain = peerDomain
  139. }
  140. }
  141. return (trust, domain)
  142. #endif
  143. }
  144. private func read() {
  145. guard let stream = inputStream else {
  146. return
  147. }
  148. let maxBuffer = 4096
  149. let buf = NSMutableData(capacity: maxBuffer)
  150. let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self)
  151. let length = stream.read(buffer, maxLength: maxBuffer)
  152. if length < 1 {
  153. return
  154. }
  155. let data = Data(bytes: buffer, count: length)
  156. delegate?.connectionChanged(state: .receive(data))
  157. }
  158. // MARK: - StreamDelegate
  159. open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
  160. switch eventCode {
  161. case .hasBytesAvailable:
  162. if aStream == inputStream {
  163. read()
  164. }
  165. case .errorOccurred:
  166. delegate?.connectionChanged(state: .failed(aStream.streamError))
  167. case .endEncountered:
  168. if aStream == inputStream {
  169. delegate?.connectionChanged(state: .cancelled)
  170. }
  171. case .openCompleted:
  172. if aStream == inputStream {
  173. let (trust, domain) = getSecurityData()
  174. if let pinner = certPinner, let trust = trust {
  175. pinner.evaluateTrust(trust: trust, domain: domain, completion: { [weak self] (state) in
  176. switch state {
  177. case .success:
  178. self?.isOpen = true
  179. self?.delegate?.connectionChanged(state: .connected)
  180. case .failed(let error):
  181. self?.delegate?.connectionChanged(state: .failed(error))
  182. }
  183. })
  184. } else {
  185. isOpen = true
  186. delegate?.connectionChanged(state: .connected)
  187. }
  188. }
  189. case .endEncountered:
  190. if aStream == inputStream {
  191. delegate?.connectionChanged(state: .cancelled)
  192. }
  193. default:
  194. break
  195. }
  196. }
  197. }