Skip to content

Instantly share code, notes, and snippets.

@username0x0a
Last active November 22, 2020 18:13
Show Gist options
  • Save username0x0a/beac1069b5b00ea66d92bdf54567cdb9 to your computer and use it in GitHub Desktop.
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.
//: 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