Skip to content

Commit ff76634

Browse files
authored
Avoid linear type searches in ServiceBrowsers (#1044)
1 parent 27e50ff commit ff76634

4 files changed

Lines changed: 43 additions & 11 deletions

File tree

tests/test_services.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def test_integration_with_listener_class(self):
4444
sub_service_updated = Event()
4545
duplicate_service_added = Event()
4646

47-
subtype_name = "My special Subtype"
47+
subtype_name = "_printer"
4848
type_ = "_http._tcp.local."
4949
subtype = subtype_name + "._sub." + type_
5050
name = "UPPERxxxyyyæøå"

tests/utils/test_name.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,25 @@ def test_service_type_name_overlong_full_name():
2323
nameutils.service_type_name(f"{long_name}._tivo-videostream._tcp.local.")
2424
with pytest.raises(BadTypeInNameException):
2525
nameutils.service_type_name(f"{long_name}._tivo-videostream._tcp.local.", strict=False)
26+
27+
28+
def test_possible_types():
29+
"""Test possible types from name."""
30+
assert nameutils.possible_types('.') == set()
31+
assert nameutils.possible_types('local.') == set()
32+
assert nameutils.possible_types('_tcp.local.') == set()
33+
assert nameutils.possible_types('_test-srvc-type._tcp.local.') == {'_test-srvc-type._tcp.local.'}
34+
assert nameutils.possible_types('_any._tcp.local.') == {'_any._tcp.local.'}
35+
assert nameutils.possible_types('.._x._tcp.local.') == {'_x._tcp.local.'}
36+
assert nameutils.possible_types('x.y._http._tcp.local.') == {'_http._tcp.local.'}
37+
assert nameutils.possible_types('1.2.3._mqtt._tcp.local.') == {'_mqtt._tcp.local.'}
38+
assert nameutils.possible_types('x.sub._http._tcp.local.') == {'_http._tcp.local.'}
39+
assert nameutils.possible_types('6d86f882b90facee9170ad3439d72a4d6ee9f511._zget._http._tcp.local.') == {
40+
'_http._tcp.local.',
41+
'_zget._http._tcp.local.',
42+
}
43+
assert nameutils.possible_types('my._printer._sub._http._tcp.local.') == {
44+
'_http._tcp.local.',
45+
'_sub._http._tcp.local.',
46+
'_printer._sub._http._tcp.local.',
47+
}

zeroconf/_services/browser.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
SignalRegistrationInterface,
3939
)
4040
from .._updates import RecordUpdate, RecordUpdateListener
41-
from .._utils.name import service_type_name
41+
from .._utils.name import possible_types, service_type_name
4242
from .._utils.time import current_time_millis, millis_to_seconds
4343
from ..const import (
4444
_BROWSER_BACKOFF_LIMIT,
@@ -326,7 +326,7 @@ def service_state_changed(self) -> SignalRegistrationInterface:
326326

327327
def _names_matching_types(self, names: Iterable[str]) -> List[Tuple[str, str]]:
328328
"""Return the type and name for records matching the types we are browsing."""
329-
return [(type_, name) for type_ in self.types for name in names if name.endswith(f".{type_}")]
329+
return [(type_, name) for name in names for type_ in self.types.intersection(possible_types(name))]
330330

331331
def _enqueue_callback(
332332
self,
@@ -352,16 +352,11 @@ def _async_process_record_update(
352352
) -> None:
353353
"""Process a single record update from a batch of updates."""
354354
if isinstance(record, DNSPointer):
355-
name = record.name
356-
alias = record.alias
357-
matches = self._names_matching_types((alias,))
358-
if name in self.types:
359-
matches.append((name, alias))
360-
for type_, name in matches:
355+
for type_ in self.types.intersection(possible_types(record.name)):
361356
if old_record is None:
362-
self._enqueue_callback(ServiceStateChange.Added, type_, name)
357+
self._enqueue_callback(ServiceStateChange.Added, type_, record.alias)
363358
elif record.is_expired(now):
364-
self._enqueue_callback(ServiceStateChange.Removed, type_, name)
359+
self._enqueue_callback(ServiceStateChange.Removed, type_, record.alias)
365360
else:
366361
self.reschedule_type(type_, now, record.get_expiration_time(_EXPIRE_REFRESH_TIME_PERCENT))
367362
return

zeroconf/_utils/name.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
USA
2121
"""
2222

23+
from typing import Set
24+
2325
from .._exceptions import BadTypeInNameException
2426
from ..const import (
2527
_HAS_ASCII_CONTROL_CHARS,
@@ -155,3 +157,16 @@ def service_type_name(type_: str, *, strict: bool = True) -> str: # pylint: dis
155157
)
156158

157159
return service_name + trailer
160+
161+
162+
def possible_types(name: str) -> Set[str]:
163+
"""Build a set of all possible types from a fully qualified name."""
164+
labels = name.split('.')
165+
label_count = len(labels)
166+
types = set()
167+
for count in range(label_count):
168+
parts = labels[label_count - count - 4 :]
169+
if not parts[0].startswith('_'):
170+
break
171+
types.add('.'.join(parts))
172+
return types

0 commit comments

Comments
 (0)