Skip to content

Instantly share code, notes, and snippets.

@RockfordWei
Created December 28, 2018 18:22
Show Gist options
  • Save RockfordWei/0c7939838f8a22349c7c867397cc79bd to your computer and use it in GitHub Desktop.
Save RockfordWei/0c7939838f8a22349c7c867397cc79bd to your computer and use it in GitHub Desktop.
How to setup a secure socket on macOS/iOS/tvOS
//
// ClientConnection.swift
//
// Created by Rocky Wei on 2018-12-27.
// Copyright © 2018 Rocky Wei. All rights reserved.
//
import Foundation
public class ClientConnection: NSObject, StreamDelegate {
public enum Exception: Error {
case streamInitFailure
case unreadyToWrite
case unableToSetupReadTLS
case unableToSetupWriteTLS
}
public let bufferSize = 4096
public let pReader: Unmanaged<CFReadStream>
public let pWriter: Unmanaged<CFWriteStream>
public let reader: InputStream
public let writer: OutputStream
public typealias ClientEvent = () -> Void
public var onOpenRead: ClientEvent = { print("onOpenRead()") }
public var onOpenWrite: ClientEvent = { print("onOpenWrite()") }
public var onCloseRead: ClientEvent = { print("onCloseRead()") }
public var onCloseWrite: ClientEvent = { print("onCloseWrite()") }
public var onArrival: ClientEvent = { print("onArrival()") }
public var onReady: ClientEvent = { print("onReady()") }
public var onFaultRead: ClientEvent = { print("onFaultRead()") }
public var onFaultWrite: ClientEvent = { print("onFaultWrite()") }
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
let readable = aStream is InputStream
let writable = aStream is OutputStream
guard readable || writable else {
print("event ", eventCode, " on neither input nor output stream ", type(of: aStream))
return
}
switch eventCode {
case Stream.Event.openCompleted:
if readable {
self.onOpenRead()
} else {
self.onOpenWrite()
}
case Stream.Event.endEncountered:
aStream.close()
aStream.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
if readable {
self.onCloseRead()
} else {
self.onCloseWrite()
}
case Stream.Event.hasBytesAvailable:
if readable {
self.onArrival()
} else {
print("unresolved event: write stream has bytes to read?")
}
case Stream.Event.hasSpaceAvailable:
if readable {
print("unresolved event: write stream has room to write?")
} else {
self.onReady()
}
case Stream.Event.endEncountered:
if readable {
self.onFaultRead()
} else {
self.onFaultWrite()
}
default:
print("unresolved event for ", readable ? "read" : "write", eventCode)
}
}
public init(host: String, port: UInt32, tls: Bool = false) throws {
var rd: Unmanaged<CFReadStream>? = nil
var wt: Unmanaged<CFWriteStream>? = nil
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host as CFString, port, &rd, &wt)
guard let r = rd, let w = wt else {
throw Exception.streamInitFailure
}
pReader = r
pWriter = w
reader = r.takeUnretainedValue()
writer = w.takeUnretainedValue()
reader.schedule(in: RunLoop.current, forMode: RunLoop.Mode.default)
writer.schedule(in: RunLoop.current, forMode: RunLoop.Mode.default)
if tls {
let setting = [kCFStreamPropertySocketSecurityLevel: kCFStreamSocketSecurityLevelNegotiatedSSL] as CFDictionary
guard CFReadStreamSetProperty(reader, CFStreamPropertyKey(kCFStreamPropertySSLSettings), setting) else {
throw Exception.unableToSetupReadTLS
}
guard CFWriteStreamSetProperty(writer, CFStreamPropertyKey(kCFStreamPropertySSLSettings), setting) else {
throw Exception.unableToSetupWriteTLS
}
}
}
public func connect() {
reader.delegate = self
writer.delegate = self
reader.open()
writer.open()
}
public func disconnect() {
reader.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
writer.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
reader.close()
writer.close()
}
public func recv() -> [UInt8] {
var buffer = Array<UInt8>(repeating: 0, count: Int(bufferSize))
var ret: [UInt8] = []
while(self.reader.hasBytesAvailable) {
let rec = self.reader.read(&buffer, maxLength: buffer.count)
if rec > 0 {
ret.append(contentsOf: buffer[0..<rec])
} else {
break
}
}
return ret
}
public func send(_ bytes: [UInt8]) throws -> Int {
guard self.writer.hasSpaceAvailable else {
throw Exception.unreadyToWrite
}
return self.writer.write(UnsafePointer(bytes), maxLength: bytes.count)
}
public func read() -> String? {
let bytes = self.recv()
if bytes.isEmpty {
return nil
}
return String.init(cString: bytes)
}
public func write(_ string: String = "") throws -> Int {
let bytes:[UInt8] = string.utf8.map { $0 }
return try self.send(bytes)
}
deinit {
disconnect()
_ = pWriter.autorelease()
_ = pReader.autorelease()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment