@@ -340,6 +340,32 @@ def test_aaaa_query():
340
340
zc .close ()
341
341
342
342
343
+ @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
+
343
369
@unittest .skipIf (not has_working_ipv6 (), 'Requires IPv6' )
344
370
@unittest .skipIf (os .environ .get ('SKIP_IPV6' ), 'IPv6 tests disabled' )
345
371
def test_a_and_aaaa_record_fate_sharing ():
@@ -481,6 +507,48 @@ async def test_probe_answered_immediately():
481
507
zc .close ()
482
508
483
509
510
+ @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
+
484
552
def test_qu_response ():
485
553
"""Handle multicast incoming with the QU bit set."""
486
554
# instantiate a zeroconf instance
@@ -842,6 +910,45 @@ def test_known_answer_supression_service_type_enumeration_query():
842
910
zc .close ()
843
911
844
912
913
+ 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
+ )
D216
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
+
845
952
# This test uses asyncio because it needs to access the cache directly
846
953
# which is not threadsafe
847
954
@pytest .mark .asyncio
0 commit comments