Last active
January 18, 2019 06:39
-
-
Save abbeycode/b97801aa424495215f93 to your computer and use it in GitHub Desktop.
A class to facilitate publishing a Bonjour service in Swift
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
// | |
// BonjourPublisher.swift | |
// BonjourTest | |
// | |
// Created by Dov Frankel on 10/12/15. | |
// Copyright © 2015 Dov Frankel. All rights reserved. | |
// | |
import Foundation | |
public enum BonjourServerError: ErrorType { | |
case SettingIPv6Options | |
case BindingIPv4 | |
case BindingIPv6 | |
case GettingSocketIPv4 | |
case GettingSocketIPv6 | |
case ListeningIPv4 | |
case ListeningIPv6 | |
case CreatingNativeSocket | |
case CreatingSocketRunLoopSource | |
case PublishingService([String : NSNumber]) | |
} | |
@objc | |
public class BonjourPublisher: NSObject, NSNetServiceDelegate { | |
public let serviceType: String | |
public let serviceName: String | |
private var service: NSNetService! | |
private var serviceSocket: CFSocket! | |
private var serviceRunLoopSource: CFRunLoopSource! | |
private var publishCompletedCallback: ((success: Bool, error: BonjourServerError?) -> Void)! | |
init(serviceType: String, serviceName: String) { | |
self.serviceType = serviceType | |
self.serviceName = serviceName | |
} | |
convenience init(serviceType: String) { | |
self.init(serviceType: serviceType, serviceName: "") | |
} | |
public func configure(completionBlock: (success: Bool, error: BonjourServerError?) -> Void) { | |
dispatch_async(dispatch_get_main_queue()) { | |
let fd4, fd6: Int32 | |
let port: in_port_t | |
do { | |
(fd4, port) = try self.registerIPv4Socket() | |
fd6 = try self.registerIPv6Socket(port) | |
} catch let socketError { | |
NSLog("Failed to register a socket: BonjourServerError.\(socketError)") | |
completionBlock(success: false, error: socketError as? BonjourServerError) | |
return | |
} | |
do { | |
try self.hookUpSocket(fd4) | |
} catch let socketError { | |
NSLog("Failed to hoook up an IPv4 socket: BonjourServerError.\(socketError)") | |
completionBlock(success: false, error: socketError as? BonjourServerError) | |
return | |
} | |
do { | |
try self.hookUpSocket(fd6) | |
} catch let socketError { | |
NSLog("Failed to hoook up an IPv6 socket: BonjourServerError.\(socketError)") | |
completionBlock(success: false, error: socketError as? BonjourServerError) | |
return | |
} | |
self.publishCompletedCallback = completionBlock | |
self.service = self.publishService(port) | |
} | |
} | |
private func registerIPv4Socket() throws -> (Int32, in_port_t) { | |
let fd4 = socket(AF_INET, SOCK_STREAM, 0) | |
var sin = sockaddr_in() | |
sin.sin_family = sa_family_t(AF_INET) | |
sin.sin_len = UInt8(sizeofValue(sin)) | |
sin.sin_port = 0 | |
let bindError = withUnsafePointer(&sin) { | |
Foundation.bind(fd4, UnsafePointer($0), UInt32(sin.sin_len)) | |
} | |
guard bindError == 0 else { | |
throw BonjourServerError.BindingIPv4 | |
} | |
var addrLen = socklen_t(sizeofValue(sin)) | |
let socketError = withUnsafeMutablePointers(&sin, &addrLen) { (sinPtr, addrPtr) -> Int32 in | |
getsockname(fd4, UnsafeMutablePointer(sinPtr), UnsafeMutablePointer(addrPtr)) | |
} | |
guard socketError == 0 else { | |
throw BonjourServerError.GettingSocketIPv4 | |
} | |
let listenError = listen(fd4, 5) | |
guard listenError == 0 else { | |
throw BonjourServerError.ListeningIPv4 | |
} | |
return (fd4, sin.sin_port) | |
} | |
private func registerIPv6Socket(port: in_port_t) throws -> Int32 { | |
let fd6 = socket(AF_INET6, SOCK_STREAM, 0) | |
var one: Int32 = 1 | |
let sockOptError = withUnsafePointer(&one) { | |
setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, UnsafePointer($0), socklen_t(sizeofValue(one))) | |
} | |
guard sockOptError == 0 else { | |
throw BonjourServerError.SettingIPv6Options | |
} | |
var sin6 = sockaddr_in6() | |
sin6.sin6_family = sa_family_t(AF_INET6) | |
sin6.sin6_len = UInt8(sizeofValue(sin6)) | |
sin6.sin6_port = port.bigEndian | |
let bindError = withUnsafePointer(&sin6) { | |
Foundation.bind(fd6, UnsafePointer($0), UInt32(sin6.sin6_len)) | |
} | |
guard bindError == 0 else { | |
throw BonjourServerError.BindingIPv6 | |
} | |
var addrLen = socklen_t(sizeofValue(sin6)) | |
let socketError = withUnsafeMutablePointers(&sin6, &addrLen) { (sinPtr, addrPtr) -> Int32 in | |
getsockname(fd6, UnsafeMutablePointer(sinPtr), UnsafeMutablePointer(addrPtr)) | |
} | |
guard socketError == 0 else { | |
throw BonjourServerError.GettingSocketIPv6 | |
} | |
let listenError = listen(fd6, 5) | |
guard listenError == 0 else { | |
throw BonjourServerError.ListeningIPv6 | |
} | |
return fd6 | |
} | |
private func hookUpSocket(fd: CFSocketNativeHandle) throws { | |
var context = CFSocketContext() | |
var selfPtr = self | |
withUnsafeMutablePointer(&selfPtr) { | |
context.info = UnsafeMutablePointer<Void>($0) | |
} | |
serviceSocket = withUnsafePointer(&context) { | |
CFSocketCreateWithNative(nil, fd, CFSocketCallBackType.AcceptCallBack.rawValue, CallbackListen, UnsafePointer<CFSocketContext>($0)) | |
} | |
guard serviceSocket != nil && CFSocketIsValid(serviceSocket) else { | |
throw BonjourServerError.CreatingNativeSocket | |
} | |
serviceRunLoopSource = CFSocketCreateRunLoopSource(nil, serviceSocket, 0) | |
guard serviceRunLoopSource != nil && CFRunLoopSourceIsValid(serviceRunLoopSource) else { | |
throw BonjourServerError.CreatingSocketRunLoopSource | |
} | |
CFRunLoopAddSource(CFRunLoopGetCurrent(), serviceRunLoopSource, kCFRunLoopCommonModes) | |
} | |
private func publishService(port: in_port_t) -> NSNetService { | |
let serviceTypeAndTransport = "_\(self.serviceType)._tcp." | |
let service = NSNetService(domain: "", type: serviceTypeAndTransport, name: serviceName, port: Int32(port.bigEndian)) | |
service.delegate = self | |
service.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes) | |
service.publish() | |
return service | |
} | |
// MARK: - Callbacks | |
@objc public func netServiceDidPublish(sender: NSNetService) { | |
NSLog("Advertising service with name '\(sender.name)' on port \(sender.port)") | |
guard publishCompletedCallback != nil else { | |
NSLog("Publish succeeded, but there was no callback reference") | |
return | |
} | |
publishCompletedCallback(success: true, error: nil) | |
self.publishCompletedCallback = nil | |
} | |
@objc public func netService(sender: NSNetService, didNotPublish errorDict: [String : NSNumber]) { | |
NSLog("Error publishing: \(errorDict)") | |
guard publishCompletedCallback != nil else { | |
NSLog("Publish failed, but there was no callback reference") | |
return | |
} | |
publishCompletedCallback(success: false, error: BonjourServerError.PublishingService(errorDict)) | |
self.publishCompletedCallback = nil | |
} | |
} | |
func CallbackListen(s: CFSocket!, callbackType: CFSocketCallBackType, address: CFData!, data: UnsafePointer<Void>, info: UnsafeMutablePointer<Void>) { | |
NSLog("Callback called") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
how can I make this work for peer to peer connections? seems this doesn't like ipv6.