Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ ResolverListener()

### Implementation Details

The callback functions are based on a simple-minded implementation: they will be called only after each ScanTime interval has expired for each distinct
protocol/mDNS service.
The callback functions are based on a somewhat simple-minded implementation: if multiple protocols/mDNS services are requested,
callbacks will be called only after the ScanTime interval for the previous protocol has expired.

The more protocols/mDNS services you resolve, the longer it takes the library to return: minimumTotalDelayTime = (nServices * ScanTime).

Expand Down
88 changes: 48 additions & 40 deletions Zeroconf/BonjourBrowser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if __IOS__
#if __IOS__
using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand All @@ -18,17 +18,19 @@ 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>();
readonly Dictionary<string, NSNetService> discoveredServiceDict = new Dictionary<string, NSNetService>();
readonly Dictionary<string, ZeroconfHost> zeroconfHostDict = new Dictionary<string, ZeroconfHost>();
readonly HashSet<string> domainHash = new HashSet<string>();

double netServiceResolveTimeout;
private Action<IZeroconfHost> zeroConfHostCallback;

/// <summary>
/// Implements iOS mDNS browse and resolve
/// </summary>
/// <param name="resolveTimeout">Time limit for NSNetService.Resolve() operation</param>
public BonjourBrowser(TimeSpan resolveTimeout = default(TimeSpan))
/// <param name="callback">Returns host information as it becomes available.</param>
public BonjourBrowser(TimeSpan resolveTimeout = default(TimeSpan), Action<IZeroconfHost> callback = null)
{
netServiceBrowser.FoundDomain += Browser_FoundDomain;
netServiceBrowser.DomainRemoved += Browser_DomainRemoved;
Expand All @@ -41,6 +43,7 @@ class BonjourBrowser
netServiceBrowser.SearchStopped += Browser_SearchStopped;

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

private void Browser_FoundDomain(object sender, NSNetDomainEventArgs e)
Expand Down Expand Up @@ -123,6 +126,7 @@ private void NetService_AddressResolved(object sender, EventArgs e)
lock (discoveredServiceDict)
{
discoveredServiceDict[serviceKey] = netService;
CreateZeroconfHostRecord(netService);
}
}
}
Expand Down Expand Up @@ -353,11 +357,6 @@ public IReadOnlyList<IZeroconfHost> ReturnZeroconfHostResults()
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}");

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

RefreshZeroconfHostDict();

List<IZeroconfHost> hostList = new List<IZeroconfHost>();
Expand Down Expand Up @@ -387,54 +386,63 @@ void RefreshZeroconfHostDict()

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

private void CreateZeroconfHostRecord(NSNetService nsNetService)
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: 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.ServiceName = GetNsNetServiceFullName(nsNetService);
// svc.Ttl = is not available
Service svc = new Service();
svc.Name = GetNsNetServiceName(nsNetService);
svc.Port = (int)nsNetService.Port;
svc.ServiceName = GetNsNetServiceFullName(nsNetService);
// svc.Ttl = is not available

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

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

foreach (var key in txtDict.Keys)
{
propertyDict[key.ToString()] = txtDict[key].ToString();
}
svc.AddPropertySet(propertyDict);
}
else
foreach (var key in txtDict.Keys)
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: Service.DictionaryFromTxtRecord has 0 entries");
propertyDict[key.ToString()] = txtDict[key].ToString();
}
svc.AddPropertySet(propertyDict);
}
else
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: Service.DictionaryFromTxtRecord returned null");
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: Service.DictionaryFromTxtRecord has 0 entries");
}
}

else
{
Debug.WriteLine($"{nameof(ReturnZeroconfHostResults)}: Service.DictionaryFromTxtRecord returned null");
}
}
if (!host.Services.TryGetValue(svc.ServiceName, out var previousService)
|| !svc.Equals(previousService))
{
host.AddService(svc);
zeroConfHostCallback?.Invoke(host);
}
}

Expand Down
9 changes: 2 additions & 7 deletions Zeroconf/Zeroconf.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="MSBuild.Sdk.Extras">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net8.0;netstandard2.0;net48;xamarinios10;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks>net6.0;net8.0;netstandard2.0;net48;net10.0-ios;net10.0-maccatalyst;net10.0-android</TargetFrameworks>
<Copyright>© Claire Novotny 2016-2024</Copyright>
<PackageTags>zeroconf;bonjour;mdns;service;discovery;maui;xamarin;netstandard;universal</PackageTags>
<Authors>Claire Novotny</Authors>
Expand Down Expand Up @@ -41,9 +41,4 @@
</ItemGroup>
</Target>

<ItemGroup Condition=" '$(UseMaui)' == 'true' " >
<PackageReference Update="Microsoft.Maui.Controls.Compatibility" Version="8.0.100" />
<PackageReference Update="Microsoft.Maui.Controls" Version="8.0.100" />
</ItemGroup>

</Project>
19 changes: 10 additions & 9 deletions Zeroconf/ZeroconfNetServiceBrowser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ static internal async Task<IReadOnlyList<IZeroconfHost>> ResolveAsync(ResolveOpt

// 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);
BonjourBrowser bonjourBrowser = new BonjourBrowser(options.ScanTime,
callback != null ? (host) =>
{
if (host.Services.Keys.Any(serviceKey => options.Protocols.Any(protocol => serviceKey.Contains(protocol))))
{
callback.Invoke(host);
}
}
: null);

foreach (var protocol in options.Protocols)
{
Expand All @@ -32,13 +40,6 @@ static internal async Task<IReadOnlyList<IZeroconfHost>> ResolveAsync(ResolveOpt
await Task.Delay(options.ScanTime, cancellationToken).ConfigureAwait(false);

bonjourBrowser.StopServiceSearch();

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

return bonjourBrowser.ReturnZeroconfHostResults();
Expand Down Expand Up @@ -107,4 +108,4 @@ class IntermediateResult
}
}
}
#endif
#endif
36 changes: 35 additions & 1 deletion Zeroconf/ZeroconfRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ internal void AddService(IService service)
}
}

internal class Service : IService
internal class Service : IService, IEquatable<Service>, IEquatable<IService>
{
private readonly List<IReadOnlyDictionary<string, string>> properties = new List<IReadOnlyDictionary<string, string>>();

Expand All @@ -211,6 +211,40 @@ internal class Service : IService

public IReadOnlyList<IReadOnlyDictionary<string, string>> Properties => properties;

public bool Equals(IService other)
{
return Equals(other as Service);
}
public bool Equals(Service other)
{
if (ReferenceEquals(null, other))
{
return false;
}

if (ReferenceEquals(this, other))
{
return true;
}

if (!string.Equals(Name, other.Name) || !string.Equals(ServiceName, other.ServiceName) || Port != other.Port || Ttl != other.Ttl)
{
return false;
}
if (Properties.Count != other.Properties.Count)
{
return false;
}
for(int i=0; i<Properties.Count; i++)
{
if (!Properties[i].SequenceEqual(other.Properties[i]))
{
return false;
}
}
return true;
}

public override string ToString()
{
var sb = new StringBuilder();
Expand Down
Loading