123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- //
- // SocketConnection.swift
- // Broadcast Extension
- //
- // Created by Alex-Dan Bumbu on 22/03/2021.
- // Copyright © 2021 Atlassian Inc. All rights reserved.
- //
- // From https://github.com/jitsi/jitsi-meet-sdk-samples (Apache 2.0 license)
- // SPDX-FileCopyrightText: 2021 Alex-Dan Bumbu, Atlassian Inc. All rights reserved.
- // SPDX-License-Identifier: Apache-2.0
- import Foundation
- class SocketConnection: NSObject {
- var didOpen: (() -> Void)?
- var didClose: ((Error?) -> Void)?
- var streamHasSpaceAvailable: (() -> Void)?
- private let filePath: String
- private var socketHandle: Int32 = -1
- private var address: sockaddr_un?
- private var inputStream: InputStream?
- private var outputStream: OutputStream?
- private var networkQueue: DispatchQueue?
- private var shouldKeepRunning = false
- init?(filePath path: String) {
- filePath = path
- socketHandle = Darwin.socket(AF_UNIX, SOCK_STREAM, 0)
- guard socketHandle != -1 else {
- print("failure: create socket")
- return nil
- }
- }
- func open() -> Bool {
- print("open socket connection")
- guard FileManager.default.fileExists(atPath: filePath) else {
- print("failure: socket file missing")
- return false
- }
- guard setupAddress() == true else {
- return false
- }
- guard connectSocket() == true else {
- return false
- }
- setupStreams()
- inputStream?.open()
- outputStream?.open()
- return true
- }
- func close() {
- unscheduleStreams()
- inputStream?.delegate = nil
- outputStream?.delegate = nil
- inputStream?.close()
- outputStream?.close()
- inputStream = nil
- outputStream = nil
- }
- func writeToStream(buffer: UnsafePointer<UInt8>, maxLength length: Int) -> Int {
- outputStream?.write(buffer, maxLength: length) ?? 0
- }
- }
- extension SocketConnection: StreamDelegate {
- func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
- switch eventCode {
- case .openCompleted:
- print("client stream open completed")
- if aStream == outputStream {
- didOpen?()
- }
- case .hasBytesAvailable:
- if aStream == inputStream {
- var buffer: UInt8 = 0
- let numberOfBytesRead = inputStream?.read(&buffer, maxLength: 1)
- if numberOfBytesRead == 0 && aStream.streamStatus == .atEnd {
- print("server socket closed")
- close()
- notifyDidClose(error: nil)
- }
- }
- case .hasSpaceAvailable:
- if aStream == outputStream {
- streamHasSpaceAvailable?()
- }
- case .errorOccurred:
- print("client stream error occured: \(String(describing: aStream.streamError))")
- close()
- notifyDidClose(error: aStream.streamError)
- default:
- break
- }
- }
- }
- private extension SocketConnection {
- func setupAddress() -> Bool {
- var addr = sockaddr_un()
- guard filePath.count < MemoryLayout.size(ofValue: addr.sun_path) else {
- print("failure: fd path is too long")
- return false
- }
- _ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in
- filePath.withCString {
- strncpy(ptr, $0, filePath.count)
- }
- }
- address = addr
- return true
- }
- func connectSocket() -> Bool {
- guard var addr = address else {
- return false
- }
- let status = withUnsafePointer(to: &addr) { ptr in
- ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) {
- Darwin.connect(socketHandle, $0, socklen_t(MemoryLayout<sockaddr_un>.size))
- }
- }
- guard status == noErr else {
- print("failure: \(status)")
- return false
- }
- return true
- }
- func setupStreams() {
- var readStream: Unmanaged<CFReadStream>?
- var writeStream: Unmanaged<CFWriteStream>?
- CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketHandle, &readStream, &writeStream)
- inputStream = readStream?.takeRetainedValue()
- inputStream?.delegate = self
- inputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
- outputStream = writeStream?.takeRetainedValue()
- outputStream?.delegate = self
- outputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String))
- scheduleStreams()
- }
- func scheduleStreams() {
- shouldKeepRunning = true
- networkQueue = DispatchQueue.global(qos: .userInitiated)
- networkQueue?.async { [weak self] in
- self?.inputStream?.schedule(in: .current, forMode: .common)
- self?.outputStream?.schedule(in: .current, forMode: .common)
- RunLoop.current.run()
- var isRunning = false
- repeat {
- isRunning = self?.shouldKeepRunning ?? false && RunLoop.current.run(mode: .default, before: .distantFuture)
- } while (isRunning)
- }
- }
- func unscheduleStreams() {
- networkQueue?.sync { [weak self] in
- self?.inputStream?.remove(from: .current, forMode: .common)
- self?.outputStream?.remove(from: .current, forMode: .common)
- }
- shouldKeepRunning = false
- }
- func notifyDidClose(error: Error?) {
- if didClose != nil {
- didClose?(error)
- }
- }
- }
|