Skip to content

Commit

Permalink
Improvements to ZeroConf in order to make it run on iOS16
Browse files Browse the repository at this point in the history
  • Loading branch information
MarLoe committed Mar 18, 2023
1 parent 238df25 commit 741ac5d
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 187 deletions.
229 changes: 53 additions & 176 deletions Zeroconf/BonjourBrowser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Foundation;
using CoreFoundation;
using Foundation;
using Network;


namespace Zeroconf
{
class BonjourBrowser
{
NSNetServiceBrowser netServiceBrowser = new NSNetServiceBrowser();

Dictionary<string, NSNetService> discoveredServiceDict = new Dictionary<string, NSNetService>();
Dictionary<string, ZeroconfHost> zeroconfHostDict = new Dictionary<string, ZeroconfHost>();
HashSet<string> domainHash = new HashSet<string>();

Expand All @@ -40,7 +40,7 @@ class BonjourBrowser
netServiceBrowser.NotSearched += Browser_NotSearched;
netServiceBrowser.SearchStopped += Browser_SearchStopped;

netServiceResolveTimeout = (resolveTimeout != default(TimeSpan)) ? resolveTimeout.TotalSeconds : 5D;
netServiceResolveTimeout = (resolveTimeout != default(TimeSpan)) ? resolveTimeout.TotalSeconds : 5d;
}

private void Browser_FoundDomain(object sender, NSNetDomainEventArgs e)
Expand All @@ -63,67 +63,28 @@ private void Browser_DomainRemoved(object sender, NSNetDomainEventArgs e)

private void Browser_FoundService(object sender, NSNetServiceEventArgs e)
{
NSNetService netService = e.Service;

netService.AddressResolved += NetService_AddressResolved;
netService.Stopped += NetService_Stopped;
netService.ResolveFailure += NetService_ResolveFailure;
if (e.Service is NSNetService netService)
{
netService.AddressResolved += NetService_AddressResolved;
netService.Stopped += NetService_Stopped;
netService.ResolveFailure += NetService_ResolveFailure;

Debug.WriteLine($"{nameof(Browser_FoundService)}: Name {netService?.Name} Type {netService?.Type} Domain {netService?.Domain} " +
$"HostName {netService?.HostName} Port {netService?.Port} MoreComing {e.MoreComing.ToString()}");
Debug.WriteLine($"{nameof(Browser_FoundService)}: Name {netService?.Name} Type {netService?.Type} Domain {netService?.Domain} " +
$"HostName {netService?.HostName} Port {netService?.Port} MoreComing {e.MoreComing.ToString()}");

if (netService != null)
{
Debug.WriteLine($"{nameof(Browser_FoundService)}: {nameof(netService)}.Resolve({netServiceResolveTimeout.ToString()})");
netService.Resolve(netServiceResolveTimeout);
}
else
{
Debug.WriteLine($"{nameof(Browser_FoundService)}: service is null");
}
}

private void NetService_AddressResolved(object sender, EventArgs e)
{
if (sender is NSNetService netService)
{
netService = (NSNetService)sender;

Debug.WriteLine($"{nameof(NetService_AddressResolved)}: Name {netService?.Name} Type {netService?.Type} Domain {netService?.Domain} " +
$"HostName {netService?.HostName} Port {netService?.Port} Addresses {GetZeroconfHostKey(netService)}");

if (netService.TxtRecordData != null)
{
NSDictionary dict = NSNetService.DictionaryFromTxtRecord(netService.TxtRecordData);
if (dict != null)
{
if (dict.Count > 0)
{
foreach (var key in dict.Keys)
{
Debug.WriteLine($"{nameof(Browser_FoundService)}: Key {key} Value {dict[key].ToString()}");
}
}
else
{
Debug.WriteLine($"{nameof(Browser_FoundService)}: Service.DictionaryFromTxtRecord has 0 entries");
}
}
else
{
Debug.WriteLine($"{nameof(Browser_FoundService)}: Service.DictionaryFromTxtRecord returned null");
}
}
else
{
Debug.WriteLine($"{nameof(Browser_FoundService)}: TxtRecordData is null");
}

string serviceKey = GetNsNetServiceKey(netService);
lock (discoveredServiceDict)
{
discoveredServiceDict[serviceKey] = netService;
}
RefreshZeroconfHostDict(netService);
}
}

Expand Down Expand Up @@ -176,13 +137,17 @@ private void Browser_ServiceRemoved(object sender, NSNetServiceEventArgs e)
{
NSNetService service = e.Service;

Debug.WriteLine($"{nameof(Browser_ServiceRemoved)}: Name {service.Name} Type {service.Type} Domain {service.Domain} " +
Debug.WriteLine($"{nameof(Browser_ServiceRemoved)}: Name {service.Name} Type {service.Type} Domain {service.Domain} " +
$"HostName {service.HostName} Port {service.Port} MoreComing {e.MoreComing.ToString()}");

string serviceKey = GetNsNetServiceKey(service);
lock (discoveredServiceDict)
string hostKey = GetZeroconfHostKey(service);
string serviceKey = GetNsNetServiceName(service);
lock (zeroconfHostDict)
{
discoveredServiceDict.Remove(serviceKey);
if (zeroconfHostDict.TryGetValue(hostKey, out var zeroconfHost))
{
zeroconfHost.RemoveService(serviceKey);
}
}
}

Expand Down Expand Up @@ -264,16 +229,6 @@ public void StartServiceSearch(string protocol)

// All previous service discovery results are discarded

lock (discoveredServiceDict)
{
discoveredServiceDict.Clear();
}

lock (zeroconfHostDict)
{
zeroconfHostDict.Clear();
}

string serviceType = string.Empty;
string domain = string.Empty;

Expand Down Expand Up @@ -315,6 +270,7 @@ public void StartServiceSearch(string protocol)
netServiceBrowser.SearchForServices(serviceType, domain);
}


public void StopServiceSearch()
{
Debug.WriteLine($"{nameof(StopServiceSearch)}: {nameof(netServiceBrowser)}.Stop()");
Expand Down Expand Up @@ -351,123 +307,65 @@ public static string GetServiceType(string protocol, bool includeTcpUdpDelimiter

public IReadOnlyList<IZeroconfHost> ReturnZeroconfHostResults()
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}");

lock (zeroconfHostDict)
{
zeroconfHostDict.Clear();
return zeroconfHostDict.Values.OfType<IZeroconfHost>().ToList();
}

RefreshZeroconfHostDict();

List<IZeroconfHost> hostList = new List<IZeroconfHost>();

lock (zeroconfHostDict)
{
hostList.AddRange(zeroconfHostDict.Values);
}

Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: Returning hostList.Count {hostList.Count}");

return hostList;
}

void RefreshZeroconfHostDict()
void RefreshZeroconfHostDict(NSNetService nsNetService)
{
// Do not walk discoveredServiceDict[] directly
// If a NSNetService is in discoveredServiceDict[], it was resolved successfully before it was added

List<NSNetService> nsNetServiceList = new List<NSNetService>();
lock (discoveredServiceDict)
{
nsNetServiceList.AddRange(discoveredServiceDict.Values);
}

// For each NSNetService, create a ZeroconfHost record

foreach (var nsNetService in nsNetServiceList)
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: Name {nsNetService.Name} Type {nsNetService.Type} Domain {nsNetService.Domain} " +
Debug.WriteLine($"{nameof(RefreshZeroconfHostDict)}: Name {nsNetService.Name} Type {nsNetService.Type} Domain {nsNetService.Domain} " +
$"HostName {nsNetService.HostName} Port {nsNetService.Port}");

// Obtain or create ZeroconfHost
// Obtain or create ZeroconfHost

ZeroconfHost host = GetOrCreateZeroconfHost(nsNetService);
ZeroconfHost host = GetOrCreateZeroconfHost(nsNetService);

// Add service to ZeroconfHost record
// Add service to ZeroconfHost record

Service svc = new Service();
svc.Name = GetNsNetServiceName(nsNetService);
svc.Port = (int)nsNetService.Port;
// svc.Ttl = is not available
Service svc = new Service();
svc.Name = GetNsNetServiceName(nsNetService);
svc.Port = (int)nsNetService.Port;
// svc.Ttl = is not available

NSData txtRecordData = nsNetService.GetTxtRecordData();
if (txtRecordData != null)
NSData txtRecordData = nsNetService.GetTxtRecordData();
if (txtRecordData is not null)
{
NSDictionary txtDict = NSNetService.DictionaryFromTxtRecord(txtRecordData);
if (txtDict?.Any() is true)
{
NSDictionary txtDict = NSNetService.DictionaryFromTxtRecord(txtRecordData);
if (txtDict != null)
{
if (txtDict.Count > 0)
{
foreach (var key in txtDict.Keys)
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: Key {key} Value {txtDict[key].ToString()}");
}

Dictionary<string, string> propertyDict = new Dictionary<string, string>();

foreach (var key in txtDict.Keys)
{
propertyDict[key.ToString()] = txtDict[key].ToString();
}
svc.AddPropertySet(propertyDict);
}
else
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: Service.DictionaryFromTxtRecord has 0 entries");
}
}
else
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: Service.DictionaryFromTxtRecord returned null");
}
Debug.WriteLine($"{nameof(RefreshZeroconfHostDict)}: {string.Join(Environment.NewLine, txtDict.Select(r => $"Key {r.Key} Value {r.Value}"))}");

Dictionary<string, string> propertyDict = txtDict.ToDictionary(r => r.Key.ToString(), r => r.Value.ToString());
svc.AddPropertySet(propertyDict);
}
}

lock (zeroconfHostDict)
{
host.AddService(svc);
}
}

ZeroconfHost GetOrCreateZeroconfHost(NSNetService service)
{
ZeroconfHost host;
string hostKey = GetZeroconfHostKey(service);
var hostKey = GetZeroconfHostKey(service);

lock (zeroconfHostDict)
{
if (!zeroconfHostDict.TryGetValue(hostKey, out host))
if (!zeroconfHostDict.TryGetValue(hostKey, out var host))
{
host = new ZeroconfHost();
host.DisplayName = service.Name;

List<string> ipAddrList = new List<string>();
foreach (NSData address in service.Addresses)
host = new()
{
Sockaddr saddr = Sockaddr.CreateSockaddr(address.Bytes);
IPAddress ipAddr = Sockaddr.CreateIPAddress(saddr);
if (ipAddr != null)
{
ipAddrList.Add(ipAddr.ToString());
}
}
host.IPAddresses = ipAddrList;

DisplayName = service.Name,
IPAddresses = service.Addresses?.Select(a => $@"{Sockaddr.CreateIPAddress(Sockaddr.CreateSockaddr(a.Bytes))}").ToList() ?? new(),
};
host.Id = host.IPAddress;

zeroconfHostDict[hostKey] = host;
}
return host;
}

return host;
}

//
Expand All @@ -487,33 +385,12 @@ string GetNsNetServiceName(NSNetService service)

string GetZeroconfHostKey(NSNetService service)
{
StringBuilder sb = new StringBuilder();

if (service.Addresses != null)
{
foreach (NSData address in service.Addresses)
{
if (address != null)
{
Sockaddr saddr = Sockaddr.CreateSockaddr(address.Bytes);
IPAddress ipAddr = Sockaddr.CreateIPAddress(saddr);
if (ipAddr != null)
{
sb.Append((sb.Length == 0 ? ipAddr.ToString() : $";{ipAddr.ToString()}"));
}
}
else
{
Console.WriteLine($"{nameof(GetZeroconfHostKey)}: Got null entry in NSNetService.Addresses, Service {service?.Name}");
}
}
}
else
if (service.Addresses is null)
{
Console.WriteLine($"{nameof(GetZeroconfHostKey)}: NSNetService.Addresses is null, Service {service?.Name}");
return string.Empty;
}

return sb.ToString();
return string.Join(';', service.Addresses.Select(a => Sockaddr.CreateIPAddress(Sockaddr.CreateSockaddr(a.Bytes))));
}

public static List<string> GetNSBonjourServices(string domain = null)
Expand Down
13 changes: 3 additions & 10 deletions Zeroconf/ZeroconfNetServiceBrowser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ static internal async Task<IReadOnlyList<IZeroconfHost>> ResolveAsync(ResolveOpt
throw new NotImplementedException($"iOS NSNetServiceBrowser/NSNetService does not support per-network interface requests");
}

List<IZeroconfHost> combinedResultList = new List<IZeroconfHost>();

// Seems you must reuse the one BonjourBrowser (which is really an NSNetServiceBrowser)... multiple instances do not play well together

BonjourBrowser bonjourBrowser = new BonjourBrowser(options.ScanTime);
Expand All @@ -37,18 +35,13 @@ static internal async Task<IReadOnlyList<IZeroconfHost>> ResolveAsync(ResolveOpt

// Simpleminded callback implementation
var results = bonjourBrowser.ReturnZeroconfHostResults();
foreach (var result in results)
foreach (var result in results.Where(r => r.Services.ContainsKey(protocol)))
{
if (callback != null)
{
callback(result);
}
callback?.Invoke(result);
}

combinedResultList.AddRange(results);
}

return combinedResultList;
return bonjourBrowser.ReturnZeroconfHostResults();
}

static internal async Task<ILookup<string, string>> BrowseDomainsAsync(BrowseDomainsOptions options,
Expand Down
7 changes: 6 additions & 1 deletion Zeroconf/ZeroconfRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,12 @@ public override string ToString()

internal void AddService(IService service)
{
services[service.ServiceName] = service ?? throw new ArgumentNullException(nameof(service));
services[service.ServiceName ?? service.Name] = service ?? throw new ArgumentNullException(nameof(service));
}

internal void RemoveService(string serviceName)
{
services.Remove(serviceName);
}
}

Expand Down

0 comments on commit 741ac5d

Please sign in to comment.