[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