[PATCH 12/18] tests: hwsim: Verify single NDC establishment and reuse across NDLs

Andrei Otcheretianski andrei.otcheretianski at intel.com
Mon Oct 12 08:46:46 PDT 2026


Add a hwsim test that verifies the single-NDC-reuse logic.

Signed-off-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 tests/hwsim/test_nan.py | 201 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 201 insertions(+)

diff --git a/tests/hwsim/test_nan.py b/tests/hwsim/test_nan.py
index 7944dfaece..920d5144a4 100644
--- a/tests/hwsim/test_nan.py
+++ b/tests/hwsim/test_nan.py
@@ -3192,3 +3192,204 @@ def test_nan_dp_non_overlapping_schedule(dev, apdev, params):
         ev = pub.wpas.wait_event(["NAN-NDP-DISCONNECTED"], timeout=5)
         if ev is None or "failure=1" not in ev:
             raise Exception("NDP not rejected on publisher: %s" % ev)
+
+def _nan_get_ndc_id(dev):
+    """Return the ID of the established NDC, or None if there is none."""
+    status = dev.wpas.request("NAN_STATUS")
+    m = re.search(r'^nan_ndc_id=([0-9a-f:]+)$', status, re.MULTILINE)
+    return m.group(1) if m else None
+
+def _nan_get_ndc_freq(dev):
+    """Return the frequency of the established NDC, or None if there is none."""
+    status = dev.wpas.request("NAN_STATUS")
+    m = re.search(r'^nan_ndc_freq=(\d+)$', status, re.MULTILINE)
+    return int(m.group(1)) if m else None
+
+def _nan_get_advertised_ndc(dev, addr):
+    """Query the NDC ID and time bitmap advertised in a peer's SDFs, if any
+    (NAN_PEER_INFO schedule)."""
+    info = dev.wpas.request(f"NAN_PEER_INFO {addr} schedule")
+    m = re.search(r'^\tndc_advertised\[\d+\] id=([0-9a-f:]+): .*bitmap=([0-9a-f]+)$',
+                  info, re.MULTILINE)
+    return (m.group(1), m.group(2)) if m else (None, None)
+
+def test_nan_dp_single_ndc_reuse(dev, apdev, params):
+    """NAN DP - single NDC established and reused across NDLs with different peers"""
+    set_country("US")
+    try:
+        with hwsim_nan_radios(count=3) as (wpas1, wpas2, wpas3), \
+            NanDevice(wpas1, "nan0", "ndi0") as dev_a, \
+            NanDevice(wpas2, "nan1", "ndi1") as dev_b, \
+            NanDevice(wpas3, "nan2", "ndi2") as dev_c:
+
+            pssi = "aabbccdd001122334455667788"
+            sssi = "ddbbccaa001122334455667788"
+
+            if _nan_get_ndc_id(dev_a) is not None:
+                raise Exception("NDC unexpectedly valid on A before any NDP")
+            if _nan_get_ndc_id(dev_b) is not None:
+                raise Exception("NDC unexpectedly valid on B before any NDP")
+
+            # NDP1: A (publisher) <-> B (subscriber), both without an
+            # established NDC yet, so B's Request is not expected to propose
+            # one; A negotiates a new NDC for this first NDL.
+            pid1, sid1, addr_a, addr_b = _nan_discover_service(
+                dev_a, dev_b, "svc1", pssi, sssi, data_path=True)
+
+            ndp_id1, init_ndi1 = _nan_ndp_request_and_accept(
+                dev_a, dev_b, pid1, sid1, addr_a, addr_b,
+                req_ssi="aabbcc", resp_ssi="ddeeff")
+
+            id_a = _nan_get_ndc_id(dev_a)
+            id_b = _nan_get_ndc_id(dev_b)
+            if id_a is None:
+                raise Exception("NDC not established on A (publisher) after NDP1")
+            if id_b is None:
+                raise Exception("NDC not established on B (subscriber) after NDP1")
+            if id_a != id_b:
+                raise Exception(f"NDC ID mismatch after NDP1: A={id_a} B={id_b}")
+
+            logger.info(f"Single NDC established after NDP1: id={id_a}")
+
+            # A keeps publishing; verify its ongoing Publish SDFs actually
+            # carry the established NDC, as observed independently by C.
+            _, _, addr_a2, _ = _nan_discover_service(
+                dev_a, dev_c, "svc3", pssi, sssi)
+
+            ndc_id, ndc_bitmap = _nan_get_advertised_ndc(dev_c, addr_a2)
+            if not ndc_bitmap:
+                raise Exception("A's Publish SDF did not advertise the "
+                                "established NDC time bitmap")
+            if ndc_id != id_a:
+                raise Exception(f"A advertised NDC id={ndc_id}, expected {id_a}")
+
+            logger.info(f"Confirmed NDC advertised in A's Publish SDF: "
+                        f"id={ndc_id} bitmap={ndc_bitmap}")
+
+            # NDP2: C (publisher) <-> B (subscriber). B already has an
+            # established NDC, so its Request to a brand-new peer (C) must
+            # explicitly propose it instead of leaving it to C to decide.
+            pid2, sid2, addr_c, addr_b2 = _nan_discover_service(
+                dev_c, dev_b, "svc2", pssi, sssi, ttl=10, data_path=True)
+
+            ndp_id2, init_ndi2 = _nan_ndp_request_and_accept(
+                dev_c, dev_b, pid2, sid2, addr_c, addr_b2,
+                req_ssi="112233", resp_ssi="445566")
+
+            id_b2 = _nan_get_ndc_id(dev_b)
+            id_c = _nan_get_ndc_id(dev_c)
+            if id_b2 != id_a:
+                raise Exception("B's established NDC changed unexpectedly "
+                                f"after NDP2: id={id_b2} (was {id_a})")
+            if id_c is None:
+                raise Exception("NDC not established on C after NDP2")
+            if id_c != id_a:
+                raise Exception("C did not converge on the already "
+                                f"established NDC: id={id_c} (expected {id_a})")
+
+            logger.info(f"Existing NDC correctly reused for NDP2: id={id_c}")
+
+            _nan_test_connectivity(dev_a, dev_b)
+            _nan_ndp_terminate(dev_a, dev_b, addr_a, init_ndi1, ndp_id1)
+
+            # A is left without any NDP, while B still has NDP2 with C
+            if _nan_get_ndc_id(dev_a) is not None:
+                raise Exception("NDC not released on A after its last NDP")
+            if _nan_get_ndc_id(dev_b) != id_a:
+                raise Exception("B released the NDC while NDP2 is still up")
+
+            _nan_test_connectivity(dev_c, dev_b)
+            _nan_ndp_terminate(dev_c, dev_b, addr_c, init_ndi2, ndp_id2)
+
+            for nan_dev, name in ((dev_b, "B"), (dev_c, "C")):
+                if _nan_get_ndc_id(nan_dev) is not None:
+                    raise Exception(f"NDC not released on {name} after its "
+                                    "last NDP")
+    finally:
+        set_country("00")
+
+def test_nan_dp_ndc_not_reusable(dev, apdev, params):
+    """NAN DP - NDP rejected when the peer cannot adopt the established NDC"""
+    set_country("US")
+    try:
+        with hwsim_nan_radios(count=3) as (wpas1, wpas2, wpas3), \
+            NanDevice(wpas1, "nan0", "ndi0") as dev_a, \
+            NanDevice(wpas2, "nan1", "ndi1") as dev_b, \
+            NanDevice(wpas3, "nan2", "ndi2") as dev_c:
+
+            pssi = "aabbccdd001122334455667788"
+            sssi = "ddbbccaa001122334455667788"
+
+            chans = ((2437, "0e000000"), (5180, "f0ffffff"))
+
+            pid1, sid1, addr_a, addr_b = _nan_discover_service(
+                dev_a, dev_b, "svc1", pssi, sssi, data_path=True)
+
+            _nan_schedule_config(dev_a, *chans)
+            _nan_schedule_config(dev_b, *chans)
+
+            ndp_id1, init_ndi1 = _nan_ndp_request_and_accept(
+                dev_a, dev_b, pid1, sid1, addr_a, addr_b,
+                req_ssi="aabbcc", resp_ssi="ddeeff",
+                configure_schedule=False)
+
+            ndc_id = _nan_get_ndc_id(dev_b)
+            if ndc_id is None:
+                raise Exception("NDC not established after NDP1")
+
+            ndc_freq = _nan_get_ndc_freq(dev_b)
+            if ndc_freq is None:
+                raise Exception("NDC frequency not reported after NDP1")
+
+            id_a = _nan_get_ndc_id(dev_a)
+            freq_a = _nan_get_ndc_freq(dev_a)
+            if id_a != ndc_id:
+                raise Exception(f"A did not converge on B's NDC: id={id_a} "
+                                f"(expected {ndc_id})")
+            if freq_a != ndc_freq:
+                raise Exception(f"A's NDC is on {freq_a} MHz, expected "
+                                f"{ndc_freq} MHz")
+
+            logger.info(f"NDC established on {ndc_freq} MHz: id={ndc_id}")
+
+            # Leave C on every channel but the one carrying the NDC, so that it
+            # still shares slots with B but cannot adopt B's established NDC.
+            other = [chan for chan in chans if chan[0] != ndc_freq]
+            if not other:
+                raise Exception(f"NDC on unexpected freq {ndc_freq}")
+            _nan_schedule_config(dev_c, *other)
+
+            pid2, sid2, addr_c, addr_b2 = _nan_discover_service(
+                dev_c, dev_b, "svc2", pssi, sssi, data_path=True)
+
+            # B has to propose its established NDC to C
+            ndp_id2, init_ndi2 = _nan_dp_expect_counter(dev_c, dev_b, pid2,
+                                                        sid2, addr_c, addr_b2)
+
+            logger.info("C countered the NDC proposed by B")
+
+            # B is pinned to the NDC by NDP1, so it cannot adopt the counter
+            if "OK" not in dev_b.ndp_response("accept", addr_c,
+                                              ndi=dev_b.ndi_name,
+                                              ndp_id=ndp_id2,
+                                              init_ndi=init_ndi2,
+                                              handle=sid2, ssi="778899"):
+                raise Exception("NDP response (confirm) failed")
+
+            for nan_dev, name in ((dev_b, "B"), (dev_c, "C")):
+                ev = nan_dev.wpas.wait_event(["NAN-NDP-DISCONNECTED",
+                                              "NAN-NDP-CONNECTED"], timeout=5)
+                if ev is None or "NAN-NDP-DISCONNECTED" not in ev or \
+                   "failure=1" not in ev:
+                    raise Exception(f"NDP2 not rejected on {name}: {ev}")
+
+            # The failed NDP must not have disturbed the NDC or NDP1
+            if _nan_get_ndc_id(dev_b) != ndc_id:
+                raise Exception("B's NDC changed after the rejected NDP")
+            if _nan_get_ndc_id(dev_c) is not None:
+                raise Exception("C established an NDC for the rejected NDP")
+
+            _nan_test_connectivity(dev_a, dev_b)
+            _nan_ndp_terminate(dev_a, dev_b, addr_a, init_ndi1, ndp_id1)
+    finally:
+        set_country("00")
-- 
2.53.0




More information about the Hostap mailing list