Last active
November 22, 2020 18:13
-
-
Save username0x0a/beac1069b5b00ea66d92bdf54567cdb9 to your computer and use it in GitHub Desktop.
A set of classes useful for discovering devices in a local network via Bonjour and sending short messages.
This file contains 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
//: Playground - noun: a place where people can play | |
import Cocoa | |
import PlaygroundSupport | |
PlaygroundPage.current.needsIndefiniteExecution = true | |
let BUFFER_SIZE = 1024 | |
let SERVICE_TYPE = "_myservice._tcp" | |
let SERVICE_DOMAIN = "local" | |
let TEST_MESSAGE = "This is a testing request!" | |
func toByteArray<T>(_ value: T) -> [UInt8] { | |
var value = value | |
return withUnsafeBytes(of: &value) { Array($0) } | |
} | |
func fromByteArray<T>(_ value: [UInt8], _: T.Type) -> T { | |
return value.withUnsafeBytes { | |
$0.baseAddress!.load(as: T.self) | |
} | |
} | |
class StreamController: NSObject, StreamDelegate | |
{ | |
var inStream: InputStream? | |
var outStream: OutputStream? | |
var readBuffer: NSMutableData? | |
var readLength: Int64 = -1 | |
var sendBuffer: NSMutableData? | |
var sendIndex: NSInteger = 0 | |
deinit { | |
closeStreams() | |
} | |
func assignStreams(_ inp: InputStream, _ out: OutputStream) { | |
inStream = inp | |
outStream = out | |
} | |
func openStreams() { | |
inStream?.schedule(in: RunLoop.current, forMode: .defaultRunLoopMode) | |
outStream?.schedule(in: RunLoop.current, forMode: .defaultRunLoopMode) | |
inStream?.delegate = self | |
outStream?.delegate = self | |
inStream?.open() | |
outStream?.open() | |
} | |
func closeStreams() { | |
inStream?.remove(from: RunLoop.current, forMode: .defaultRunLoopMode) | |
outStream?.remove(from: RunLoop.current, forMode: .defaultRunLoopMode) | |
inStream?.close() | |
outStream?.close() | |
inStream = nil | |
outStream = nil | |
} | |
func sendData(data: Data) { | |
sendBuffer = NSMutableData(capacity: 8+data.count) | |
let length = Int64(data.count) | |
let bytes = toByteArray(length) | |
sendBuffer?.append(bytes, length: MemoryLayout<UInt64>.stride) | |
sendBuffer?.append(data) | |
sendIndex = 0 | |
if outStream?.hasSpaceAvailable ?? false { | |
self.flushSendData() | |
} | |
} | |
func flushSendData() { | |
if sendBuffer?.length ?? 0 <= 0 { return } | |
let bytesToWrite = min(BUFFER_SIZE, sendBuffer?.length ?? 0 - sendIndex) | |
let data = sendBuffer?.copy() as? Data | |
let range = Range<Data.Index>(NSRange(location: sendIndex, length: bytesToWrite))! | |
let subdata = data?.subdata(in: range) | |
let arr = [UInt8](subdata!) | |
let bytesWritten = outStream?.write(arr, maxLength: bytesToWrite) ?? 0 | |
if bytesWritten < 0 { print("Writing to stream failed") } | |
else { | |
print("Sent \(bytesWritten) bytes of data") | |
sendIndex += bytesWritten | |
if sendIndex == sendBuffer?.length { | |
print("Sending buffer finished") | |
sendBuffer = nil | |
sendIndex = 0 | |
} | |
} | |
} | |
func handleReceivedData(data: Data) { | |
print("Received data: \(String(data: data, encoding: .utf8) ?? "(nil)")") | |
} | |
func handleStreamEnd(_ stream: Stream) { | |
self.closeStreams() | |
} | |
func stream(_ aStream: Stream, handle eventCode: Stream.Event) { | |
switch eventCode { | |
case Stream.Event.openCompleted: | |
break | |
case Stream.Event.hasSpaceAvailable: | |
self.flushSendData() | |
break | |
case Stream.Event.hasBytesAvailable: | |
if readBuffer == nil { | |
readBuffer = NSMutableData() | |
readLength = -1 | |
} | |
print("Bytes to read, wohoo!") | |
while inStream?.hasBytesAvailable ?? false { | |
if readLength < 0 { | |
let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: 8) | |
let bytesRead = inStream?.read(buf, maxLength: 8) | |
if bytesRead == 8 { | |
readLength = buf.withMemoryRebound(to: Int64.self, capacity: 8, { (pointer) -> Int64 in | |
return pointer.pointee | |
}) | |
} | |
} | |
else { | |
let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: BUFFER_SIZE) | |
let bytesRead = inStream?.read(buf, maxLength: BUFFER_SIZE) ?? 0 | |
if bytesRead > 0 { | |
print("Received \(bytesRead) bytes") | |
readBuffer?.append(buf, length: bytesRead) | |
if readBuffer?.length ?? 0 >= readLength { | |
// if !(inStream?.hasBytesAvailable ?? false) { | |
print("All bytes received") | |
self.handleReceivedData(data: (readBuffer?.copy() as? Data)!) | |
readBuffer = nil | |
readLength = -1 | |
} | |
} | |
} | |
} | |
break | |
case Stream.Event.errorOccurred: | |
self.handleStreamEnd(aStream) | |
case Stream.Event.endEncountered: | |
self.handleStreamEnd(aStream) | |
default: | |
self.handleStreamEnd(aStream) | |
} | |
} | |
} | |
class Server : StreamController, NetServiceDelegate | |
{ | |
var server = NetService(domain: SERVICE_DOMAIN, type: SERVICE_TYPE, name: "Server") | |
func startAdvertising() { | |
server.includesPeerToPeer = true | |
server.delegate = self | |
server.publish(options: .listenForConnections) | |
} | |
func stop() { | |
server.stop() | |
server.delegate = nil | |
} | |
override func handleStreamEnd(_ stream: Stream) { | |
self.startAdvertising() | |
} | |
func netService(_ sender: NetService, didAcceptConnectionWith inputStream: InputStream, outputStream: OutputStream) { | |
OperationQueue.main.addOperation { | |
if self.inStream != nil { | |
inputStream.open(); inputStream.close() | |
outputStream.open(); outputStream.close() | |
} | |
else { | |
print("[S] Connection accepted!") | |
self.assignStreams(inputStream, outputStream) | |
self.openStreams() | |
} | |
} | |
} | |
func netService(_ sender: NetService, didNotPublish errorDict: [String : NSNumber]) { | |
if let e = errorDict[NetService.errorCode]?.intValue { | |
if e == NetService.ErrorCode.unknownError.rawValue { | |
self.startAdvertising() | |
} | |
} | |
} | |
} | |
class Client : StreamController, NetServiceDelegate, NetServiceBrowserDelegate | |
{ | |
var browser = NetServiceBrowser() | |
var connectedService: NetService? | |
func startBrowsing() { | |
browser.includesPeerToPeer = true | |
browser.delegate = self | |
browser.searchForServices(ofType: SERVICE_TYPE, inDomain: SERVICE_DOMAIN) | |
} | |
func stopBrowsing() { | |
browser.stop() | |
browser.delegate = nil | |
} | |
func stop() { | |
self.disconnectFromService() | |
self.stopBrowsing() | |
} | |
func connectToService(_ service: NetService) { | |
if connectedService != nil { return } | |
print("Connecting to service \(service.name)") | |
connectedService = service | |
// service.delegate = self | |
var ins: InputStream? | |
var out: OutputStream? | |
let success = service.getInputStream(&ins, outputStream: &out) | |
if success, let ins = ins, let out = out { | |
self.assignStreams(ins, out) | |
self.openStreams() | |
self.sendExampleData() | |
} | |
} | |
func sendExampleData() { | |
let str = TEST_MESSAGE | |
self.sendData(data: str.data(using: .utf8)!) | |
} | |
override func handleReceivedData(data: Data) { | |
super.handleReceivedData(data: data) | |
let str = String(data: data, encoding: .utf8) ?? "(nil)" | |
print("Response: \(str)") | |
self.stop() | |
} | |
func disconnectFromService() { | |
self.closeStreams() | |
connectedService?.delegate = nil | |
connectedService = nil | |
} | |
func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) { | |
print("Found service: \(service)") | |
self.connectToService(service) | |
} | |
func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) { | |
if connectedService?.name == service.name { | |
self.disconnectFromService() | |
} | |
} | |
} | |
let c = Client() | |
c.startBrowsing() | |
let s = Server() | |
s.startAdvertising() | |
OperationQueue().addOperation { | |
sleep(5) | |
OperationQueue.main.addOperation { | |
c.stopBrowsing() | |
s.stop() | |
PlaygroundPage.current.finishExecution() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment