You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SocketConnection.swift 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /*
  2. * Copyright @ 2021-present 8x8, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import Foundation
  17. class SocketConnection: NSObject {
  18. var didOpen: (() -> Void)?
  19. var didClose: ((Error?) -> Void)?
  20. var streamHasSpaceAvailable: (() -> Void)?
  21. private let filePath: String
  22. private var socketHandle: Int32 = -1
  23. private var address: sockaddr_un?
  24. private var inputStream: InputStream?
  25. private var outputStream: OutputStream?
  26. private var networkQueue: DispatchQueue?
  27. private var shouldKeepRunning = false
  28. init?(filePath path: String) {
  29. filePath = path
  30. socketHandle = Darwin.socket(AF_UNIX, SOCK_STREAM, 0)
  31. guard socketHandle != -1 else {
  32. print("failure: create socket")
  33. return nil
  34. }
  35. }
  36. func open() -> Bool {
  37. print("open socket connection")
  38. guard FileManager.default.fileExists(atPath: filePath) else {
  39. print("failure: socket file missing")
  40. return false
  41. }
  42. guard setupAddress() == true else {
  43. return false
  44. }
  45. guard connectSocket() == true else {
  46. return false
  47. }
  48. setupStreams()
  49. inputStream?.open()
  50. outputStream?.open()
  51. return true
  52. }
  53. func close() {
  54. unscheduleStreams()
  55. inputStream?.delegate = nil
  56. outputStream?.delegate = nil
  57. inputStream?.close()
  58. outputStream?.close()
  59. inputStream = nil
  60. outputStream = nil
  61. }
  62. func writeToStream(buffer: UnsafePointer<UInt8>, maxLength length: Int) -> Int {
  63. return outputStream?.write(buffer, maxLength: length) ?? 0
  64. }
  65. }
  66. extension SocketConnection: StreamDelegate {
  67. func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
  68. switch eventCode {
  69. case .openCompleted:
  70. print("client stream open completed")
  71. if aStream == outputStream {
  72. didOpen?()
  73. }
  74. case .hasBytesAvailable:
  75. if aStream == inputStream {
  76. var buffer: UInt8 = 0
  77. let numberOfBytesRead = inputStream?.read(&buffer, maxLength: 1)
  78. if numberOfBytesRead == 0 && aStream.streamStatus == .atEnd {
  79. print("server socket closed")
  80. close()
  81. notifyDidClose(error: nil)
  82. }
  83. }
  84. case .hasSpaceAvailable:
  85. if aStream == outputStream {
  86. streamHasSpaceAvailable?()
  87. }
  88. case .errorOccurred:
  89. print("client stream error occured: \(String(describing: aStream.streamError))")
  90. close()
  91. notifyDidClose(error: aStream.streamError)
  92. default:
  93. break
  94. }
  95. }
  96. }
  97. private extension SocketConnection {
  98. func setupAddress() -> Bool {
  99. var addr = sockaddr_un()
  100. guard filePath.count < MemoryLayout.size(ofValue: addr.sun_path) else {
  101. print("failure: fd path is too long")
  102. return false
  103. }
  104. _ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in
  105. filePath.withCString {
  106. strncpy(ptr, $0, filePath.count)
  107. }
  108. }
  109. address = addr
  110. return true
  111. }
  112. func connectSocket() -> Bool {
  113. guard var addr = address else {
  114. return false
  115. }
  116. let status = withUnsafePointer(to: &addr) { ptr in
  117. ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) {
  118. Darwin.connect(socketHandle, $0, socklen_t(MemoryLayout<sockaddr_un>.size))
  119. }
  120. }
  121. guard status == noErr else {
  122. print("failure: \(status)")
  123. return false
  124. }
  125. return true
  126. }
  127. func setupStreams() {
  128. var readStream: Unmanaged<CFReadStream>?
  129. var writeStream: Unmanaged<CFWriteStream>?
  130. CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketHandle, &readStream, &writeStream)
  131. inputStream = readStream?.takeRetainedValue()
  132. inputStream?.delegate = self
  133. inputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
  134. outputStream = writeStream?.takeRetainedValue()
  135. outputStream?.delegate = self
  136. outputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
  137. scheduleStreams()
  138. }
  139. func scheduleStreams() {
  140. shouldKeepRunning = true
  141. networkQueue = DispatchQueue.global(qos: .userInitiated)
  142. networkQueue?.async { [weak self] in
  143. self?.inputStream?.schedule(in: .current, forMode: .common)
  144. self?.outputStream?.schedule(in: .current, forMode: .common)
  145. var isRunning = false
  146. repeat {
  147. isRunning = self?.shouldKeepRunning ?? false && RunLoop.current.run(mode: .default, before: .distantFuture)
  148. } while (isRunning)
  149. }
  150. }
  151. func unscheduleStreams() {
  152. networkQueue?.sync { [weak self] in
  153. self?.inputStream?.remove(from: .current, forMode: .common)
  154. self?.outputStream?.remove(from: .current, forMode: .common)
  155. }
  156. shouldKeepRunning = false
  157. }
  158. func notifyDidClose(error: Error?) {
  159. if didClose != nil {
  160. didClose?(error)
  161. }
  162. }
  163. }