Created
December 28, 2018 18:22
-
-
Save RockfordWei/0c7939838f8a22349c7c867397cc79bd to your computer and use it in GitHub Desktop.
How to setup a secure socket on macOS/iOS/tvOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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