Skip to content

Instantly share code, notes, and snippets.

@ruurdadema
Last active October 3, 2025 15:09
Show Gist options
  • Select an option

  • Save ruurdadema/735a4db58a25308a2b39f79a8402d4da to your computer and use it in GitHub Desktop.

Select an option

Save ruurdadema/735a4db58a25308a2b39f79a8402d4da to your computer and use it in GitHub Desktop.
Easy BonjourBrowser around NetService
//
// BonjourBrowser.swift
// Listen
//
// Created by Ruurd Adema on 13/12/2019.
// Copyright © 2019 Ruurd Adema. All rights reserved.
//
import Foundation
struct NetworkAddress
{
enum AddressType
{
case hostname
case ipv4
case ipv6
}
let address: String
let type: AddressType
let port: UInt16
var debugString: String
{
return "\(address):\(port) (\(type))"
}
}
class BonjourServiceRecord: Identifiable
{
private static var nextID: Int = 0
let id: Int
let name: String
let type: String
let domain: String
var addresses = [NetworkAddress]()
var txtRecords = [String:String]()
let netService: NetService
init(name: String, type: String, domain: String, netService: NetService)
{
self.id = BonjourServiceRecord.nextID
BonjourServiceRecord.nextID += 1
self.name = name
self.type = type
self.domain = domain
self.netService = netService
}
var debugString: String
{
get {
var output = "BonjourBrowserRecord: name=\"\(self.name)\" domain=\(self.domain) type=\(self.type)\n"
for (key, value) in txtRecords
{
output += "\(key) : \(value)"
}
output += "\n"
for addr in addresses
{
output += addr.debugString + "\n"
}
return output
}
}
}
protocol BonjourBrowserDelegate: AnyObject {
func serviceDiscovered(record: BonjourServiceRecord)
func serviceResolved(record: BonjourServiceRecord)
func serviceDisappeared(record: BonjourServiceRecord)
}
class BonjourBrowser: NSObject, NetServiceBrowserDelegate, NetServiceDelegate
{
weak var delegate: BonjourBrowserDelegate?
private var browser: NetServiceBrowser = NetServiceBrowser()
private var services: [Int: BonjourServiceRecord] = [:]
override init()
{
super.init()
self.browser.delegate = self
}
func searchForServices(ofType: String, inDomain: String)
{
self.browser.searchForServices(ofType: ofType, inDomain: inDomain)
}
private func findRecordForNetService(netService: NetService) -> BonjourServiceRecord?
{
for record in self.services
{
if (record.value.netService == netService) { return record.value }
}
return nil
}
func getService(id: Int) -> BonjourServiceRecord?
{
return services[id]
}
// MARK: NetServiceBrowserDelegate
func netServiceBrowser(_: NetServiceBrowser, didFindDomain: String, moreComing: Bool) {}
func netServiceBrowser(_: NetServiceBrowser, didRemoveDomain: String, moreComing: Bool) {}
func netServiceBrowser(_: NetServiceBrowser, didFind service: NetService, moreComing: Bool)
{
let record = BonjourServiceRecord(
name: service.name,
type: service.type,
domain: service.domain,
netService: service)
service.delegate = self
service.resolve(withTimeout: 5)
services[record.id] = record
self.delegate?.serviceDiscovered(record: record)
}
func netServiceBrowser(_: NetServiceBrowser, didRemove sender: NetService, moreComing: Bool)
{
if let record = findRecordForNetService(netService: sender)
{
if let record = self.services.removeValue(forKey: record.id)
{
self.delegate?.serviceDisappeared(record: record)
}
else
{
print("Key did not exist. Not deleted.")
}
}
else
{
print("Record not found for given service. Cannot remove.")
}
sender.delegate = nil
}
func netServiceBrowserWillSearch(_: NetServiceBrowser) {}
func netServiceBrowser(_: NetServiceBrowser, didNotSearch: [String : NSNumber]) {}
func netServiceBrowserDidStopSearch(_: NetServiceBrowser) {}
// MARK: NetServiceDelegate
func netServiceWillPublish(_: NetService) {}
func netService(_: NetService, didNotPublish: [String : NSNumber]) {}
func netServiceDidPublish(_: NetService) {}
func netServiceWillResolve(_: NetService) {}
func netService(_: NetService, didNotResolve: [String : NSNumber]) {}
func netServiceDidResolveAddress(_ sender: NetService)
{
if let record = findRecordForNetService(netService: sender)
{
// Get txtRecord
let txtRecordDict = NetService.dictionary(fromTXTRecord: sender.txtRecordData() ?? Data())
for (key, value) in txtRecordDict
{
record.txtRecords[key] = String(decoding: value, as: UTF8.self)
}
// Add hostname as first address (if available)
if let hostname = sender.hostName {
record.addresses.append(NetworkAddress(address: String("\(hostname)"),
type: NetworkAddress.AddressType.hostname,
port: UInt16(sender.port)))
}
if let addresses = sender.addresses, addresses.count > 0
{
for address in addresses
{
let data = address as NSData
var inetAddress = sockaddr_in()
data.getBytes(&inetAddress, length:MemoryLayout<sockaddr_in>.size)
if inetAddress.sin_family == __uint8_t(AF_INET)
{
if let ip = String(cString: inet_ntoa(inetAddress.sin_addr), encoding: .ascii)
{
// IPv4
record.addresses.append(NetworkAddress(address: ip,
type: NetworkAddress.AddressType.ipv4,
port: UInt16(sender.port)))
}
}
else if inetAddress.sin_family == __uint8_t(AF_INET6)
{
var inetAddress6 = sockaddr_in6()
data.getBytes(&inetAddress6, length:MemoryLayout<sockaddr_in6>.size)
let ipStringBuffer = UnsafeMutablePointer<Int8>.allocate(capacity: Int(INET6_ADDRSTRLEN))
var addr = inetAddress6.sin6_addr
if let ipString = inet_ntop(Int32(inetAddress6.sin6_family), &addr, ipStringBuffer, __uint32_t(INET6_ADDRSTRLEN))
{
if let ip = String(cString: ipString, encoding: .ascii)
{
// IPv6
record.addresses.append(NetworkAddress(address: ip,
type: NetworkAddress.AddressType.ipv6,
port: UInt16(sender.port)))
}
}
ipStringBuffer.deallocate()
}
}
}
self.delegate?.serviceResolved(record: record)
}
else
{
print("Could not find BonjourBrowserRecord for given NetService")
}
sender.delegate = nil
}
func netService(_: NetService, didUpdateTXTRecord: Data) {}
func netServiceDidStop(_: NetService) {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment