Last active
October 3, 2025 15:09
-
-
Save ruurdadema/735a4db58a25308a2b39f79a8402d4da to your computer and use it in GitHub Desktop.
Easy BonjourBrowser around NetService
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
| // | |
| // 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