feat: speed up the service registry (#1174) · python-zeroconf/python-zeroconf@360ceb2
@@ -340,6 +340,32 @@ def test_aaaa_query():
340340zc.close()
341341342342343+@unittest.skipIf(not has_working_ipv6(), 'Requires IPv6')
344+@unittest.skipIf(os.environ.get('SKIP_IPV6'), 'IPv6 tests disabled')
345+def test_aaaa_query_upper_case():
346+"""Test that queries for AAAA records work and should respond right away with an upper case name."""
347+zc = Zeroconf(interfaces=['127.0.0.1'])
348+type_ = "_knownaaaservice._tcp.local."
349+name = "knownname"
350+registration_name = f"{name}.{type_}"
351+desc = {'path': '/~paulsm/'}
352+server_name = "ash-2.local."
353+ipv6_address = socket.inet_pton(socket.AF_INET6, "2001:db8::1")
354+info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, server_name, addresses=[ipv6_address])
355+zc.registry.async_add(info)
356+357+generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
358+question = r.DNSQuestion(server_name.upper(), const._TYPE_AAAA, const._CLASS_IN)
359+generated.add_question(question)
360+packets = generated.packets()
361+question_answers = zc.query_handler.async_response([r.DNSIncoming(packet) for packet in packets], False)
362+mcast_answers = list(question_answers.mcast_now)
363+assert mcast_answers[0].address == ipv6_address # type: ignore[attr-defined]
364+# unregister
365+zc.registry.async_remove(info)
366+zc.close()
367+368+343369@unittest.skipIf(not has_working_ipv6(), 'Requires IPv6')
344370@unittest.skipIf(os.environ.get('SKIP_IPV6'), 'IPv6 tests disabled')
345371def test_a_and_aaaa_record_fate_sharing():
@@ -481,6 +507,48 @@ async def test_probe_answered_immediately():
481507zc.close()
482508483509510+@pytest.mark.asyncio
511+async def test_probe_answered_immediately_with_uppercase_name():
512+"""Verify probes are responded to immediately with an uppercase name."""
513+# instantiate a zeroconf instance
514+zc = Zeroconf(interfaces=['127.0.0.1'])
515+516+# service definition
517+type_ = "_test-srvc-type._tcp.local."
518+name = "xxxyyy"
519+registration_name = f"{name}.{type_}"
520+desc = {'path': '/~paulsm/'}
521+info = ServiceInfo(
522+type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[socket.inet_aton("10.0.1.2")]
523+ )
524+zc.registry.async_add(info)
525+query = r.DNSOutgoing(const._FLAGS_QR_QUERY)
526+question = r.DNSQuestion(info.type.upper(), const._TYPE_PTR, const._CLASS_IN)
527+query.add_question(question)
528+query.add_authorative_answer(info.dns_pointer())
529+question_answers = zc.query_handler.async_response(
530+ [r.DNSIncoming(packet) for packet in query.packets()], False
531+ )
532+assert not question_answers.ucast
533+assert not question_answers.mcast_aggregate
534+assert not question_answers.mcast_aggregate_last_second
535+assert question_answers.mcast_now
536+537+query = r.DNSOutgoing(const._FLAGS_QR_QUERY)
538+question = r.DNSQuestion(info.type, const._TYPE_PTR, const._CLASS_IN)
539+question.unicast = True
540+query.add_question(question)
541+query.add_authorative_answer(info.dns_pointer())
542+question_answers = zc.query_handler.async_response(
543+ [r.DNSIncoming(packet) for packet in query.packets()], False
544+ )
545+assert question_answers.ucast
546+assert question_answers.mcast_now
547+assert not question_answers.mcast_aggregate
548+assert not question_answers.mcast_aggregate_last_second
549+zc.close()
550+551+484552def test_qu_response():
485553"""Handle multicast incoming with the QU bit set."""
486554# instantiate a zeroconf instance
@@ -842,6 +910,45 @@ def test_known_answer_supression_service_type_enumeration_query():
842910zc.close()
843911844912913+def test_upper_case_enumeration_query():
914+zc = Zeroconf(interfaces=['127.0.0.1'])
915+type_ = "_otherknown._tcp.local."
916+name = "knownname"
917+registration_name = f"{name}.{type_}"
918+desc = {'path': '/~paulsm/'}
919+server_name = "ash-2.local."
920+info = ServiceInfo(
921+type_, registration_name, 80, 0, 0, desc, server_name, addresses=[socket.inet_aton("10.0.1.2")]
922+ )
923+zc.registry.async_add(info)
924+925+type_2 = "_otherknown2._tcp.local."
926+name = "knownname"
927+registration_name2 = f"{name}.{type_2}"
928+desc = {'path': '/~paulsm/'}
929+server_name2 = "ash-3.local."
930+info2 = ServiceInfo(
931+type_2, registration_name2, 80, 0, 0, desc, server_name2, addresses=[socket.inet_aton("10.0.1.2")]
932+ )
933+zc.registry.async_add(info2)
934+_clear_cache(zc)
935+936+# Test PTR supression
937+generated = r.DNSOutgoing(const._FLAGS_QR_QUERY)
938+question = r.DNSQuestion(const._SERVICE_TYPE_ENUMERATION_NAME.upper(), const._TYPE_PTR, const._CLASS_IN)
939+generated.add_question(question)
940+packets = generated.packets()
941+question_answers = zc.query_handler.async_response([r.DNSIncoming(packet) for packet in packets], False)
942+assert not question_answers.ucast
943+assert not question_answers.mcast_now
944+assert question_answers.mcast_aggregate
945+assert not question_answers.mcast_aggregate_last_second
946+# unregister
947+zc.registry.async_remove(info)
948+zc.registry.async_remove(info2)
949+zc.close()
950+951+845952# This test uses asyncio because it needs to access the cache directly
846953# which is not threadsafe
847954@pytest.mark.asyncio