Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2229806/?format=api
{ "id": 2229806, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2229806/?format=api", "web_url": "http://patchwork.ozlabs.org/project/hostap/patch/20260428200639.40243-3-andrei.otcheretianski@intel.com/", "project": { "id": 22, "url": "http://patchwork.ozlabs.org/api/1.1/projects/22/?format=api", "name": "HostAP Development", "link_name": "hostap", "list_id": "hostap.lists.infradead.org", "list_email": "hostap@lists.infradead.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20260428200639.40243-3-andrei.otcheretianski@intel.com>", "date": "2026-04-28T20:05:03", "name": "[RFC,02/97] tests: Add NAN data path, bootstrapping and pairing tests", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "fa03536979ce7ba0a74d5366e111a598359a1d4d", "submitter": { "id": 62065, "url": "http://patchwork.ozlabs.org/api/1.1/people/62065/?format=api", "name": "Andrei Otcheretianski", "email": "andrei.otcheretianski@intel.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/hostap/patch/20260428200639.40243-3-andrei.otcheretianski@intel.com/mbox/", "series": [ { "id": 501927, "url": "http://patchwork.ozlabs.org/api/1.1/series/501927/?format=api", "web_url": "http://patchwork.ozlabs.org/project/hostap/list/?series=501927", "date": "2026-04-28T20:05:05", "name": "NAN: Group keys support, schedule update and more", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/501927/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2229806/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2229806/checks/", "tags": {}, "headers": { "Return-Path": "\n <hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n secure) header.d=lists.infradead.org header.i=@lists.infradead.org\n header.a=rsa-sha256 header.s=bombadil.20210309 header.b=HfaUMIhv;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256\n header.s=Intel header.b=ny0u666y;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=none (no SPF record) smtp.mailfrom=lists.infradead.org\n (client-ip=2607:7c80:54:3::133; helo=bombadil.infradead.org;\n envelope-from=hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org;\n receiver=patchwork.ozlabs.org)" ], "Received": [ "from bombadil.infradead.org (bombadil.infradead.org\n [IPv6:2607:7c80:54:3::133])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g4s1X3Pgqz1yJT\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 29 Apr 2026 06:07:58 +1000 (AEST)", "from localhost ([::1] helo=bombadil.infradead.org)\n\tby bombadil.infradead.org with esmtp (Exim 4.98.2 #2 (Red Hat Linux))\n\tid 1wHoiH-00000002CEA-0V6a;\n\tTue, 28 Apr 2026 20:07:21 +0000", "from mgamail.intel.com ([198.175.65.16])\n\tby bombadil.infradead.org with esmtps (Exim 4.98.2 #2 (Red Hat Linux))\n\tid 1wHoiD-00000002CBW-48UD\n\tfor hostap@lists.infradead.org;\n\tTue, 28 Apr 2026 20:07:19 +0000", "from fmviesa001.fm.intel.com ([10.60.135.141])\n by orvoesa108.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384;\n 28 Apr 2026 13:07:18 -0700", "from iapp347.iil.intel.com (HELO 87c02287900a.iil.intel.com)\n ([10.167.28.6])\n by smtpauth.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384;\n 28 Apr 2026 13:07:16 -0700" ], "DKIM-Signature": [ "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed;\n\td=lists.infradead.org; s=bombadil.20210309; h=Sender:\n\tContent-Transfer-Encoding:Content-Type:List-Subscribe:List-Help:List-Post:\n\tList-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To:\n\tMessage-ID:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description:\n\tResent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:\n\tList-Owner; bh=agmHoRtjFuoiU/Q3xnI5b5sxj+qW8JjEuHBmqFj9CKk=; b=HfaUMIhv9sVBUM\n\tmSoaFKWVkYJphYKiJGqclYeSe9OasEWRla76wymPVn0JxFk89hQQpPX3ix1VGQHUKksorw1o6VR+j\n\tu+oU5o9KXsdzgWIdpDqHdcFrjvbN0BywvDY4ZUkLnWTgiQzLiQljDYPEAHinRyugQ9eUgL7gl12+n\n\tCGjsETiR1yei6H8ZpR2Bhivoln7u1xmU+cCIUktJzz584I+BN027GPyRDYEfDZSVNm9r+2SII+HMU\n\tkzDy/s4WtQ+MW4MFKStDjwubSCeObYKgbILSlr/kkpzaU0xxz7CGX+seuRQ0vzc3hUg/ypPbnpOyv\n\t28Ujms0djD/SEAVA3cqQ==;", "v=1; a=rsa-sha256; c=relaxed/simple;\n d=intel.com; i=@intel.com; q=dns/txt; s=Intel;\n t=1777406839; x=1808942839;\n h=from:to:cc:subject:date:message-id:in-reply-to:\n references:mime-version:content-transfer-encoding;\n bh=l7trUYqPIS+XoGLOn7vgy7eYs9/jQ/yVyKUZsooI5cU=;\n b=ny0u666yJXh6VmNGHGGv95gQGmGY/X/vIGaC+ny+T4+PmEJGduhzIRpN\n oyoIlwrfqBQH8A6ejZSZAwGq/gV7OWW8TAYHnCnGq8C0G9Dkxn7sI8xqu\n 1nhoFKFiqohkwx/luQxop8fLGcM6jff782d0wBR8WT5QRe9tyF4NHxzoj\n cGNZWBp1jwVqXNG/R2fvlQ3IEg3DVYSTEXG/s07zpfaa3uke2T9E0s9cm\n nobWZ2OdpR486nwZe24Zy2gH0JKBxXRvh1wmMHuerIyph9OamKbqSSmdE\n 4aJ/UCA60QDNWs2oXCxybYHfs7i5qosldX59U/ckeI6epmezzq2ZLtxnB\n g==;" ], "X-CSE-ConnectionGUID": [ "epvMcZcTQt2Q23raFTNkBw==", "yamM0pqUR++i/LBX2klw7Q==" ], "X-CSE-MsgGUID": [ "s5ue03LzSNa1kYt4iRSMUw==", "UsXBHBqZQjuaDR4YkV7qdA==" ], "X-IronPort-AV": [ "E=McAfee;i=\"6800,10657,11770\"; a=\"78519245\"", "E=Sophos;i=\"6.23,204,1770624000\";\n d=\"scan'208\";a=\"78519245\"", "E=Sophos;i=\"6.23,204,1770624000\";\n d=\"scan'208\";a=\"257609904\"" ], "X-ExtLoop1": "1", "From": "Andrei Otcheretianski <andrei.otcheretianski@intel.com>", "To": "hostap@lists.infradead.org", "Cc": "vamsin@qti.qualcomm.com,\n\tmaheshkkv@google.com,\n\tAndrei Otcheretianski <andrei.otcheretianski@intel.com>,\n\tAvraham Stern <avraham.stern@intel.com>", "Subject": "[RFC 02/97] tests: Add NAN data path, bootstrapping and pairing tests", "Date": "Tue, 28 Apr 2026 23:05:03 +0300", "Message-ID": "<20260428200639.40243-3-andrei.otcheretianski@intel.com>", "X-Mailer": "git-send-email 2.53.0", "In-Reply-To": "<20260428200639.40243-1-andrei.otcheretianski@intel.com>", "References": "<20260428200639.40243-1-andrei.otcheretianski@intel.com>", "MIME-Version": "1.0", "X-CRM114-Version": "20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 ", "X-CRM114-CacheID": "sfid-20260428_130718_136692_BDA4B2FF ", "X-CRM114-Status": "GOOD ( 14.31 )", "X-Spam-Score": "-4.5 (----)", "X-Spam-Report": "Spam detection software,\n running on the system \"bombadil.infradead.org\",\n has NOT identified this incoming email as spam. The original\n message has been attached to this so you can view it or label\n similar future email. If you have any questions, see\n the administrator of that system for details.\n Content preview: Add NDP establishment, bootstrapping and pairing NAN tests.\n Signed-off-by: Avraham Stern <avraham.stern@intel.com> Signed-off-by:\n Andrei\n Otcheretianski <andrei.otcheretianski@intel.com> ---\n tests/hwsim/test_nan.py\n | 793 +++++++++++++++++++++++++++++++++++++++- [...]\n Content analysis details: (-4.5 points, 5.0 required)\n pts rule name description\n ---- ----------------------\n --------------------------------------------------\n -2.3 RCVD_IN_DNSWL_MED RBL: Sender listed at https://www.dnswl.org/,\n medium trust\n [198.175.65.16 listed in list.dnswl.org]\n 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record\n -0.0 SPF_PASS SPF: sender matches SPF record\n -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from\n envelope-from domain\n 0.1 DKIM_SIGNED Message has a DKIM or DK signature,\n not necessarily valid\n -0.1 DKIM_VALID Message has at least one valid DKIM or DK\n signature\n -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from\n author's\n domain\n -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1%\n [score: 0.0000]\n -0.1 DKIMWL_WL_HIGH DKIMwl.org - High trust sender", "X-BeenThere": "hostap@lists.infradead.org", "X-Mailman-Version": "2.1.34", "Precedence": "list", "List-Id": "<hostap.lists.infradead.org>", "List-Unsubscribe": "<http://lists.infradead.org/mailman/options/hostap>,\n <mailto:hostap-request@lists.infradead.org?subject=unsubscribe>", "List-Archive": "<http://lists.infradead.org/pipermail/hostap/>", "List-Post": "<mailto:hostap@lists.infradead.org>", "List-Help": "<mailto:hostap-request@lists.infradead.org?subject=help>", "List-Subscribe": "<http://lists.infradead.org/mailman/listinfo/hostap>,\n <mailto:hostap-request@lists.infradead.org?subject=subscribe>", "Content-Type": "text/plain; charset=\"us-ascii\"", "Content-Transfer-Encoding": "7bit", "Sender": "\"Hostap\" <hostap-bounces@lists.infradead.org>", "Errors-To": "hostap-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org" }, "content": "Add NDP establishment, bootstrapping and pairing NAN tests.\n\nSigned-off-by: Avraham Stern <avraham.stern@intel.com>\nSigned-off-by: Andrei Otcheretianski <andrei.otcheretianski@intel.com>\n---\n tests/hwsim/test_nan.py | 793 +++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 789 insertions(+), 4 deletions(-)", "diff": "diff --git a/tests/hwsim/test_nan.py b/tests/hwsim/test_nan.py\nindex f7a212f7f8..ce7a65fb84 100644\n--- a/tests/hwsim/test_nan.py\n+++ b/tests/hwsim/test_nan.py\n@@ -9,8 +9,10 @@ import logging\n logger = logging.getLogger()\n from utils import *\n import string\n+from hwsim_utils import test_connectivity\n from hwsim import HWSimRadio\n from contextlib import contextmanager, ExitStack\n+from test_p2p_channel import set_country\n \n @contextmanager\n def hwsim_nan_radios(count=2, n_channels=3):\n@@ -41,10 +43,11 @@ def check_nan_capab(dev):\n raise HwsimSkip(f\"NAN not supported: {capa}\")\n \n class NanDevice:\n- def __init__(self, dev, ifname):\n+ def __init__(self, dev, ifname, ndi_name=None):\n self.dev = dev\n self.ifname = ifname\n self.wpas = None\n+ self.ndi_name = ndi_name\n \n def __enter__(self):\n self.start()\n@@ -71,12 +74,19 @@ class NanDevice:\n \n logger.info(f\"NAN device started on {self.ifname}\")\n \n+ # Add NDI\n+ if self.ndi_name is not None:\n+ self.dev.interface_add(self.ndi_name, if_type=\"nan_data\", create=True)\n+\n def stop(self):\n logger.info(f\"NAN device stopping on {self.ifname}\")\n \n if \"OK\" not in self.wpas.request(\"NAN_STOP\"):\n raise Exception(f\"Failed to stop NAN functionality on {self.ifname}\")\n \n+ if self.ndi_name is not None:\n+ self.dev.interface_remove(self.ndi_name)\n+\n self.dev.global_request(f\"INTERFACE_REMOVE {self.ifname}\")\n self.wpas.remove_ifname()\n \n@@ -84,7 +94,7 @@ class NanDevice:\n \n def publish(self, service_name, ssi=None, unsolicited=1, solicited=1,\n sync=1, match_filter_rx=None, match_filter_tx=None,\n- close_proximity=0):\n+ close_proximity=0, pbm=0, nd_pmk=None, cipher_suites=None):\n \n cmd = f\"NAN_PUBLISH service_name={service_name} sync={sync} srv_proto_type=2 fsd=0\"\n \n@@ -103,6 +113,77 @@ class NanDevice:\n if match_filter_tx:\n cmd += f\" match_filter_tx={match_filter_tx}\"\n \n+ if pbm:\n+ cmd += f\" pbm={pbm}\"\n+\n+ if cipher_suites is not None:\n+ cmd += f\" cipher_suites={cipher_suites}\"\n+\n+ if nd_pmk is not None:\n+ cmd += f\" nd_pmk={nd_pmk}\"\n+\n+ return self.wpas.request(cmd)\n+\n+ def schedule_config(self, *chans, map_id=1):\n+ cmd = f\"NAN_SCHED_CONFIG_MAP map_id={map_id} \"\n+ cmd += \" \".join([f\"{freq}:{bitmap}\" for freq, bitmap in chans])\n+ return self.wpas.request(cmd)\n+\n+ def remove_schedule(self, map_id=1):\n+ cmd = f\"NAN_SCHED_CONFIG_MAP map_id={map_id}\"\n+ return self.wpas.request(cmd)\n+\n+ def ndp_request(self, ndi, handle, peer_nmi, peer_id, ssi=None,\n+ qos_slots=0, qos_latency=0xffff, csid=None, password=None,\n+ pmk=None):\n+ cmd = f\"NAN_NDP_REQUEST handle={handle} ndi={ndi} peer_nmi={peer_nmi} peer_id={peer_id}\"\n+\n+ params = [\n+ (\"ssi\", ssi),\n+ (\"csid\", csid),\n+ (\"password\", password),\n+ (\"pmk\", pmk),\n+ ]\n+\n+ cmd += \"\".join(f\" {name}={value}\" for name, value in params if value is not None)\n+\n+ if qos_slots > 0 or qos_latency != 0xffff:\n+ cmd += f\" qos={qos_slots}:{qos_latency}\"\n+\n+ return self.wpas.request(cmd)\n+\n+ def ndp_response(self, action, peer_nmi, ndi=None, peer_ndi=None,\n+ ndp_id=None, init_ndi=None, reason_code=None, ssi=None,\n+ qos_slots=0, qos_latency=0xffff, handle=None, csid=None,\n+ password=None, pmk=None):\n+ if action not in [\"accept\", \"reject\"]:\n+ raise Exception(f\"Invalid action: {action}. Must be 'accept' or 'reject'\")\n+\n+ cmd = f\"NAN_NDP_RESPONSE {action} peer_nmi={peer_nmi}\"\n+\n+ params = [\n+ (\"reason_code\", reason_code),\n+ (\"ndi\", ndi),\n+ (\"peer_ndi\", peer_ndi),\n+ (\"ndp_id\", ndp_id),\n+ (\"init_ndi\", init_ndi),\n+ (\"handle\", handle),\n+ (\"ssi\", ssi),\n+ (\"csid\", csid),\n+ (\"password\", password),\n+ (\"pmk\", pmk),\n+ ]\n+\n+ cmd += \"\".join(f\" {name}={value}\" for name, value in params if value is not None)\n+\n+ if qos_slots > 0 or qos_latency != 0xffff:\n+ cmd += f\" qos={qos_slots}:{qos_latency}\"\n+\n+ return self.wpas.request(cmd)\n+\n+\n+ def ndp_terminate(self, peer_nmi, init_ndi, ndp_id):\n+ cmd = f\"NAN_NDP_TERMINATE peer_nmi={peer_nmi} init_ndi={init_ndi} ndp_id={ndp_id}\"\n return self.wpas.request(cmd)\n \n def subscribe(self, service_name, ssi=None, active=1,\n@@ -138,6 +219,20 @@ class NanDevice:\n \n return self.wpas.request(cmd)\n \n+ def bootstrap(self, peer, handle, peer_instance_id, pbm, auth=False):\n+ logger.info(f\"Bootstrapping NAN with peer {peer} pbm={pbm} auth={auth} on {self.ifname}\")\n+ auth_param = \" auth\" if auth else \"\"\n+\n+ if \"OK\" not in self.wpas.request(f\"NAN_BOOTSTRAP {peer} handle={handle} \"\n+ f\"req_instance_id={peer_instance_id} method={pbm}\" + auth_param):\n+ raise Exception(f\"{self.ifname}: failed to bootstrap with {peer}\")\n+\n+ def bootstrap_reset(self, peer):\n+ logger.info(f\"Reset Bootstrapping NAN with peer {peer}\")\n+\n+ if \"OK\" not in self.wpas.request(f\"NAN_BOOTSTRAP_RESET {peer}\"):\n+ raise Exception(f\"{self.ifname}: failed to reset bootstrap with {peer}\")\n+\n def cancel_publish(self, publish_id):\n logger.info(f\"Cancelling publish with ID {publish_id} on {self.ifname}\")\n if \"OK\" not in self.wpas.request(f\"NAN_CANCEL_PUBLISH publish_id={publish_id}\"):\n@@ -172,11 +267,34 @@ class NanDevice:\n if \"OK\" not in self.wpas.request(cmd):\n raise Exception(f\"{self.ifname}: failed to transmit NAN followup\")\n \n+ def pairing_request(self, peer, handle, peer_instance_id, mode, responder=False, password=None):\n+ if mode == \"SAE\":\n+ mode = 1\n+ elif mode == \"PASN\":\n+ mode = 0\n+ elif mode == \"PMK\":\n+ mode = 2\n+\n+ peer_nmi = peer.wpas.own_addr()\n+ cmd = f\"NAN_PAIR {peer_nmi} auth={mode} cipher=GCMP-256 handle={handle} peer_instance_id={peer_instance_id}\"\n+ if password is not None:\n+ cmd += f\" password={password}\"\n+ if responder:\n+ cmd += \" responder\"\n+\n+ if \"OK\" not in self.wpas.request(cmd):\n+ raise Exception(\"NAN_PAIR Failed on requesting device\")\n+\n+ def pair_abort(self, peer_nmi):\n+ cmd = f\"NAN_PAIR_ABORT {peer_nmi}\"\n+ return self.wpas.request(cmd)\n+\n def split_nan_event(ev):\n vals = dict()\n for p in ev.split(' ')[1:]:\n- name, val = p.split('=')\n- vals[name] = val\n+ if '=' in p:\n+ name, val = p.split('=', 1)\n+ vals[name] = val\n return vals\n \n def nan_sync_verify_event(ev, addr, pid, sid, ssi):\n@@ -197,6 +315,40 @@ def nan_sync_verify_event(ev, addr, pid, sid, ssi):\n if data['address'] != addr:\n raise Exception(\"Unexpected peer_addr: \" + ev)\n \n+def nan_ndp_verify_event(ev, peer_nmi, publish_inst_id=None, init_ndi=None,\n+ ssi=None, csid=None):\n+ \"\"\"Verify NAN-NDP-REQUEST event format and content\"\"\"\n+ data = split_nan_event(ev)\n+\n+ if 'peer_nmi' not in data:\n+ raise Exception(f\"Missing peer_nmi in NDP event: {ev}\")\n+\n+ if 'init_ndi' not in data:\n+ raise Exception(f\"Missing init_ndi in NDP event: {ev}\")\n+\n+ if 'ndp_id' not in data:\n+ raise Exception(f\"Missing ndp_id in NDP event: {ev}\")\n+\n+ if publish_inst_id is not None and 'publish_inst_id' not in data:\n+ raise Exception(f\"Missing publish_inst_id in NDP event: {ev}\")\n+\n+ if data['peer_nmi'] != peer_nmi:\n+ raise Exception(f\"Unexpected peer_nmi: got {data['peer_nmi']}, expected {peer_nmi} in event: {ev}\")\n+\n+ if init_ndi is not None and data['init_ndi'] != init_ndi:\n+ raise Exception(f\"Unexpected init_ndi: got {data['init_ndi']}, expected {init_ndi} in event: {ev}\")\n+\n+ if (publish_inst_id is not None and data['publish_inst_id'] != publish_inst_id):\n+ raise Exception(f\"Unexpected publish_inst_id: got {data['publish_inst_id']}, expected {publish_inst_id} in event: {ev}\")\n+\n+ if ssi is not None and 'ssi' in data:\n+ if data['ssi'] != ssi:\n+ raise Exception(f\"Unexpected ssi: got {data['ssi']}, expected {ssi} in event: {ev}\")\n+\n+ if csid is not None and 'csid' in data:\n+ if data['csid'] != str(csid):\n+ raise Exception(f\"Unexpected csid: got {data['csid']}, expected {str(csid)} in event: {ev}\")\n+\n def nan_sync_discovery(pub, sub, service_name, pssi, sssi,\n unsolicited=1, solicited=1, active=1,\n expect_discovery=True,\n@@ -834,3 +986,636 @@ def test_nan_config(dev, apdev, params):\n # and finally update the configuration\n logger.info(\"Updating NAN configuration\")\n nan.update_config()\n+\n+def test_nan_sched(dev, apdev, params):\n+ \"\"\"NAN configure schedule\"\"\"\n+ set_country(\"US\")\n+ try:\n+ with hwsim_nan_radios() as (wpas1, wpas2), NanDevice(wpas1, \"nan0\") as pub:\n+ if \"OK\" not in pub.schedule_config((2437, \"03000000\"), (5180, \"0000ff00\"), (5825, \"000000ff\")):\n+ raise Exception(\"Failed to configure schedule\")\n+ # Remove\n+ if \"OK\" not in pub.remove_schedule():\n+ raise Exception(\"Failed to remove schedule\")\n+ # Overlapping maps\n+ if \"FAIL\" not in pub.schedule_config((2437, \"03000000\"), (5180, \"0000ff00\"), (5825, \"050000ff\")):\n+ raise Exception(\"A schedule with overlapping time bitmaps was unexpectedly accepted\")\n+ # Same channel\n+ if \"FAIL\" not in pub.schedule_config((2437, \"03000000\"), (2437, \"0000ff00\")):\n+ raise Exception(\"A schedule with duplicate channel entries was unexpectedly accepted\")\n+ # Bad length\n+ if \"FAIL\" not in pub.schedule_config((2437, \"0300\")):\n+ raise Exception(\"Too short schedule bitmap accepted\")\n+ finally:\n+ set_country(\"00\")\n+\n+def _nan_discover_service(pub, sub, service_name, pssi, sssi):\n+ paddr = pub.wpas.own_addr()\n+ saddr = sub.wpas.own_addr()\n+\n+ pid = pub.publish(service_name, ssi=pssi)\n+ sid = sub.subscribe(service_name, ssi=sssi, active=0)\n+\n+ logger.info(f\"Publish ID: {pid}, Subscribe ID: {sid}\")\n+\n+ ev = sub.wpas.wait_event([\"NAN-DISCOVERY-RESULT\"], timeout=2)\n+ if ev is None:\n+ raise Exception(f\"NAN-DISCOVERY-RESULT event not seen for {service_name}\")\n+\n+ nan_sync_verify_event(ev, paddr, pid, sid, pssi)\n+\n+ return pid, sid, paddr, saddr\n+\n+def _nan_ndp_request_and_accept(pub, sub, pid, sid, paddr, saddr, req_ssi, resp_ssi, csid=None,\n+ password=None, pmk=None, counter=False, wrong_pwd=False, configure_schedule=True):\n+ \"\"\"\n+ Request NDP from subscriber and accept on publisher.\n+\n+ Returns: (ndp_id, init_ndi) or None if wrong_pwd test completed\n+ \"\"\"\n+ # Configure schedule on subscriber if needed\n+ if configure_schedule:\n+ if \"OK\" not in sub.schedule_config((2437, \"0e000000\"), (5180, \"f0ffffff\")):\n+ raise Exception(\"Failed to configure schedule (sub)\")\n+\n+ # NDP request\n+ if \"OK\" not in sub.ndp_request(sub.ndi_name, sid, paddr, pid, req_ssi,\n+ csid=csid, password=password, pmk=pmk):\n+ raise Exception(\"NDP request failed\")\n+\n+ ev = pub.wpas.wait_event([\"NAN-NDP-REQUEST\"], timeout=5)\n+ if ev is None:\n+ raise Exception(\"NAN-NDP-REQUEST event not seen\")\n+\n+ ndi_sub = sub.dev.get_iface_addr(sub.ndi_name)\n+ nan_ndp_verify_event(ev, saddr, pid, ndi_sub, req_ssi)\n+\n+ data = split_nan_event(ev)\n+ ndp_id = data['ndp_id']\n+ init_ndi = data['init_ndi']\n+\n+ # Configure schedule on publisher\n+ if configure_schedule:\n+ if counter:\n+ if \"OK\" not in pub.schedule_config((5745, \"feffffff\")):\n+ raise Exception(\"Failed to configure schedule (pub)\")\n+ else:\n+ if \"OK\" not in pub.schedule_config((2437, \"0e000000\"), (5180, \"f0ffffff\")):\n+ raise Exception(\"Failed to configure schedule (pub)\")\n+\n+ # Accept NDP request\n+ accept_pwd = \"WRONG_PWD\" if wrong_pwd else password\n+ if \"OK\" not in pub.ndp_response(\"accept\", saddr, ndi=pub.ndi_name, ndp_id=ndp_id, init_ndi=init_ndi,\n+ handle=pid, ssi=resp_ssi, csid=csid, password=accept_pwd, pmk=pmk):\n+ raise Exception(\"NDP response (accept) failed\")\n+\n+ # Verify disconnection on wrong password\n+ if wrong_pwd:\n+ ev = sub.wpas.wait_event([\"NAN-NDP-DISCONNECTED\"], timeout=5)\n+ if ev is None or \"reason=3\" not in ev:\n+ raise Exception(\"NAN-NDP-DISCONNECTED event not seen on subscriber\")\n+ ev = pub.wpas.wait_event([\"NAN-NDP-DISCONNECTED\"], timeout=5)\n+ if ev is None or \"reason=3\" not in ev:\n+ raise Exception(\"NAN-NDP-DISCONNECTED event not seen on publisher\")\n+ return None\n+\n+ # Handle counter proposal\n+ if counter:\n+ ev = sub.wpas.wait_event([\"NAN-NDP-COUNTER-REQUEST\"], timeout=5)\n+ if ev is None:\n+ raise Exception(\"NAN-NDP-COUNTER-REQUEST event not seen\")\n+\n+ nan_ndp_verify_event(ev, paddr, init_ndi=ndi_sub, ssi=resp_ssi)\n+\n+ data = split_nan_event(ev)\n+ ndp_id = data['ndp_id']\n+ init_ndi = data['init_ndi']\n+\n+ if \"OK\" not in sub.schedule_config((5745, \"feffffff\")):\n+ raise Exception(\"Failed to configure schedule (sub)\")\n+\n+ if \"OK\" not in sub.ndp_response(\"accept\", paddr, ndi=sub.ndi_name, ndp_id=ndp_id, handle=sid,\n+ init_ndi=init_ndi, ssi=\"11223344\", csid=csid, password=password, pmk=pmk):\n+ raise Exception(\"NDP response (confirm) failed\")\n+\n+ # Wait for NDP connected events\n+ ev = pub.wpas.wait_event([\"NAN-NDP-CONNECTED\"], timeout=5)\n+ if ev is None:\n+ raise Exception(\"NAN-NDP-CONNECTED event not seen on publisher\")\n+\n+ ev = sub.wpas.wait_event([\"NAN-NDP-CONNECTED\"], timeout=5)\n+ if ev is None:\n+ raise Exception(\"NAN-NDP-CONNECTED event not seen on subscriber\")\n+\n+ logger.info(\"NDP connection established successfully\")\n+\n+ return ndp_id, init_ndi\n+\n+def _nan_ndp_terminate(pub, sub, paddr, init_ndi, ndp_id):\n+ \"\"\"Terminate an NDP and wait for disconnect events on both sides.\"\"\"\n+\n+ sub.ndp_terminate(paddr, init_ndi, ndp_id)\n+\n+ ev = pub.wpas.wait_event([\"NAN-NDP-DISCONNECTED\"], timeout=5)\n+ if ev is None:\n+ raise Exception(f\"NAN-NDP-DISCONNECTED event not seen on publisher\")\n+\n+ ev = sub.wpas.wait_event([\"NAN-NDP-DISCONNECTED\"], timeout=5)\n+ if ev is None:\n+ raise Exception(f\"NAN-NDP-DISCONNECTED event not seen on subscriber\")\n+\n+def _nan_test_connectivity(pub, sub):\n+ \"\"\"Test IP connectivity between publisher and subscriber NDI interfaces.\"\"\"\n+ wpas_ndi_pub = WpaSupplicant(ifname=pub.ndi_name)\n+ wpas_ndi_sub = WpaSupplicant(ifname=sub.ndi_name)\n+ test_connectivity(wpas_ndi_pub, wpas_ndi_sub, tos=0, ifname1=pub.ndi_name, ifname2=sub.ndi_name,\n+ max_tries=3, timeout=5, broadcast=False)\n+\n+def _run_nan_dp(counter=False, csid=None, wrong_pwd=False, use_pmk=False):\n+ if use_pmk:\n+ pmk = \"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff\"\n+ pwd = None\n+ else:\n+ pwd = \"NAN\" if csid is not None else None\n+ pmk = None\n+\n+ with hwsim_nan_radios() as (wpas1, wpas2), \\\n+ NanDevice(wpas1, \"nan0\", \"ndi0\") as pub, NanDevice(wpas2, \"nan1\", \"ndi1\") as sub:\n+\n+ pssi = \"aabbccdd001122334455667788\"\n+ sssi = \"ddbbccaa001122334455667788\"\n+\n+ pid, sid, paddr, saddr= _nan_discover_service(pub, sub, \"test_service\", pssi, sssi)\n+\n+ # Log peer info (specific to this test)\n+ peer_schedule = pub.wpas.request(\"NAN_PEER_INFO \" + saddr + \" schedule\")\n+ logger.info(\"\\n\" + peer_schedule)\n+ potential = pub.wpas.request(\"NAN_PEER_INFO \" + saddr + \" potential\")\n+ logger.info(\"\\n\" + potential)\n+ capa = pub.wpas.request(\"NAN_PEER_INFO \" + saddr + \" capa\")\n+ logger.info(\"\\n\" + capa)\n+\n+ result = _nan_ndp_request_and_accept(pub, sub, pid, sid, paddr, saddr, req_ssi=\"aabbcc\",\n+ resp_ssi=\"ddeeff\", csid=csid, password=pwd, pmk=pmk,\n+ counter=counter, wrong_pwd=wrong_pwd)\n+\n+ if result is None:\n+ # wrong_pwd test completed\n+ return\n+\n+ ndp_id, init_ndi = result\n+\n+ _nan_test_connectivity(pub, sub)\n+ _nan_ndp_terminate(pub, sub, paddr, init_ndi, ndp_id)\n+\n+def run_nan_dp(country=\"US\", counter=False, csid=None, wrong_pwd=False, use_pmk=False):\n+ set_country(country)\n+ try:\n+ _run_nan_dp(counter=counter, csid=csid, wrong_pwd=wrong_pwd, use_pmk=use_pmk)\n+ finally:\n+ set_country(\"00\")\n+\n+def test_nan_dp_open(dev, apdev, params):\n+ \"\"\"NAN DP open\"\"\"\n+ run_nan_dp()\n+\n+def test_nan_dp_open_counter(dev, apdev, params):\n+ \"\"\"NAN DP open with counter proposal\"\"\"\n+ run_nan_dp(counter=True)\n+\n+def test_nan_dp_sk_ccmp128(dev, apdev, params):\n+ \"\"\"NAN DP - 2way NDL + SK CCMP security\"\"\"\n+ run_nan_dp(csid=1)\n+\n+def test_nan_dp_sk_gcmp256(dev, apdev, params):\n+ \"\"\"NAN DP - 3way NDL + SK GCMP-256 security\"\"\"\n+ run_nan_dp(counter=True, csid=2)\n+\n+def test_nan_dp_wrong_pwd(dev, apdev, params):\n+ \"\"\"NAN DP - Wrong password\"\"\"\n+ run_nan_dp(csid=1, wrong_pwd=True)\n+\n+def test_nan_dp_pmk(dev, apdev, params):\n+ \"\"\"NAN DP - 3way NDL + SK CCMP security with PMK\"\"\"\n+ run_nan_dp(counter=True, csid=1, use_pmk=True)\n+\n+def nan_pre_bootstrap(pub, sub, pmb=0x1):\n+ paddr = pub.wpas.own_addr()\n+ saddr = sub.wpas.own_addr()\n+\n+ pssi = \"aabbccdd\"\n+ sssi = \"ddbbccaa\"\n+\n+ pid = pub.publish(\"test_service\", ssi=pssi, unsolicited=0, pbm=pmb)\n+ sid = sub.subscribe(\"test_service\", ssi=sssi)\n+\n+ logger.info(f\"Publish ID: {pid}, Subscribe ID: {sid}\")\n+\n+ ev = sub.wpas.wait_event([\"NAN-DISCOVERY-RESULT\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-DISCOVERY-RESULT event not seen\")\n+\n+ nan_sync_verify_event(ev, paddr, pid, sid, pssi)\n+\n+ ev = pub.wpas.wait_event([\"NAN-REPLIED\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-REPLIED event not seen\")\n+\n+ nan_sync_verify_event(ev, saddr, pid, sid, sssi)\n+\n+ return pid, sid, paddr, saddr\n+\n+def test_nan_bootstrap_opportunistic(dev, apdev, params):\n+ \"\"\"NAN opportunistic bootstrap with auto accept\"\"\"\n+ with hwsim_nan_radios(count=2) as [wpas1, wpas2], \\\n+ NanDevice(wpas1, \"nan0\") as pub, NanDevice(wpas2, \"nan1\") as sub:\n+ pid, sid, paddr, saddr = nan_pre_bootstrap(pub, sub)\n+\n+ sub.bootstrap(paddr, sid, pid, 0x1)\n+\n+ ev = sub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen\")\n+\n+ ev = pub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen\")\n+\n+def test_nan_bootstrap_password(dev, apdev, params):\n+ \"\"\"NAN bootstrap with password\"\"\"\n+ with hwsim_nan_radios(count=2) as [wpas1, wpas2], \\\n+ NanDevice(wpas1, \"nan0\") as pub, NanDevice(wpas2, \"nan1\") as sub:\n+ pid, sid, paddr, saddr = nan_pre_bootstrap(pub, sub, pmb=0x4)\n+\n+ # request bootstrap with passphrase using passpharse keypad method (BIT 6)\n+ sub.bootstrap(paddr, sid, pid, 0x40)\n+\n+ ev = pub.wpas.wait_event([\"NAN-BOOTSTRAP-REQUEST\"], timeout=2)\n+ if ev is None or \"peer_nmi=\" + saddr not in ev or \"pbm=0x0004\" not in ev:\n+ raise Exception(\"NAN-BOOTSTRAP-REQUEST event not seen\")\n+\n+ pub.bootstrap(saddr, pid, sid, 0x4, auth=True)\n+ ev = sub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None or \"pbm=0x0040\" not in ev:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen (subscriber)\")\n+\n+ ev = pub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None or \"pbm=0x0004\" not in ev:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen (publisher)\")\n+\n+def test_nan_bootstrap_password_with_delays(dev, apdev, params):\n+ \"\"\"NAN bootstrap with password with delay and wrong method\"\"\"\n+ with hwsim_nan_radios(count=2) as [wpas1, wpas2], \\\n+ NanDevice(wpas1, \"nan0\") as pub, NanDevice(wpas2, \"nan1\") as sub:\n+ pid, sid, paddr, saddr = nan_pre_bootstrap(pub, sub, pmb=0x4)\n+\n+ # request bootstrap with passphrase using passpharse keypad method (BIT 6)\n+ sub.bootstrap(paddr, sid, pid, 0x40)\n+\n+ ev = pub.wpas.wait_event([\"NAN-BOOTSTRAP-REQUEST\"], timeout=2)\n+ if ev is None or \"peer_nmi=\" + saddr not in ev or \"pbm=0x0004\" not in ev:\n+ raise Exception(\"NAN-BOOTSTRAP-REQUEST event not seen\")\n+\n+ # To not authenticate the peer for 10 seconds and verify that no success event is sent\n+ ev = sub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=10)\n+ if ev is not None:\n+ raise Exception(\"Got unexpected NAN-BOOTSTRAP-SUCCESS event seen (subscriber)\")\n+\n+ # now try with wrong method (QR code display, BIT 3)\n+ pub.bootstrap(saddr, pid, sid, 0x8, auth=True)\n+ ev = sub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=5)\n+ if ev is not None:\n+ raise Exception(\"Got unexpected NAN-BOOTSTRAP-SUCCESS event seen (subscriber)\")\n+\n+ # now authenticate properly\n+ pub.bootstrap(saddr, pid, sid, 0x4, auth=True)\n+ ev = sub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None or \"pbm=0x0040\" not in ev:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen (subscriber)\")\n+\n+ ev = pub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None or \"pbm=0x0004\" not in ev:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen (publisher)\")\n+\n+ pub.bootstrap_reset(saddr)\n+ sub.bootstrap_reset(paddr)\n+\n+def test_nan_bootstrap_password_no_response(dev, apdev, params):\n+ \"\"\"NAN bootstrap with password with no response from publisher\"\"\"\n+ with hwsim_nan_radios(count=2) as [wpas1, wpas2], \\\n+ NanDevice(wpas1, \"nan0\") as pub, NanDevice(wpas2, \"nan1\") as sub:\n+ pid, sid, paddr, saddr = nan_pre_bootstrap(pub, sub, pmb=0x4)\n+\n+ # cancel the publish to simulate no response\n+ pub.cancel_publish(pid)\n+\n+ # request bootstrap with passphrase using passpharse keypad method (BIT 6)\n+ sub.bootstrap(paddr, sid, pid, 0x40)\n+\n+ ev = sub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=10)\n+ if ev is not None:\n+ raise Exception(\"Got unexpected NAN-BOOTSTRAP-SUCCESS event seen (subscriber)\")\n+\n+ sub.bootstrap_reset(paddr)\n+ sub.cancel_subscribe(sid)\n+\n+def test_nan_pair_abort(dev, apdev, params):\n+ \"\"\"NAN pair abort\"\"\"\n+ with hwsim_nan_radios() as (wpas1, wpas2), \\\n+ NanDevice(wpas1, \"nan0\") as pub, NanDevice(wpas2, \"nan1\") as sub:\n+ pid, sid, paddr, saddr = nan_pre_bootstrap(pub, sub, pmb=0x4)\n+\n+ # Complete bootstrap\n+ sub.bootstrap(paddr, sid, pid, 0x40)\n+ ev = pub.wpas.wait_event([\"NAN-BOOTSTRAP-REQUEST\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-BOOTSTRAP-REQUEST event not seen\")\n+ pub.bootstrap(saddr, pid, sid, 0x4, auth=True)\n+ ev = sub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen (subscriber)\")\n+ ev = pub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen (publisher)\")\n+\n+ # Start PASN pairing without responder ready\n+ sub.pairing_request(pub, sid, pid, \"SAE\", responder=False, password=\"password123\")\n+\n+ ev_sub = sub.wpas.wait_event([\"NAN-PAIRING-STATUS\"], timeout=2)\n+ if ev_sub is not None:\n+ raise Exception(\"Unexpected PASN result seen on subscriber\")\n+\n+ ev_pub = pub.wpas.wait_event([\"NAN-PAIRING-REQUEST\"], timeout=2)\n+ if ev_pub is None:\n+ raise Exception(\"PASN pairing request not seen on publisher\")\n+\n+ if \"OK\" not in sub.pair_abort(paddr):\n+ raise Exception(\"NAN_PAIR_ABORT failed on subscriber\")\n+ if \"OK\" not in pub.pair_abort(saddr):\n+ raise Exception(\"NAN_PAIR_ABORT failed on publisher\")\n+\n+ # After abort, we should be able to restart pairing successfully\n+ sub.pairing_request(pub, sid, pid, \"SAE\", responder=False, password=\"password123\")\n+ ev_pub = pub.wpas.wait_event([\"NAN-PAIRING-REQUEST\"], timeout=2)\n+ if ev_pub is None:\n+ raise Exception(\"PASN pairing request not seen on publisher after abort\")\n+\n+ pub.pairing_request(sub, sid, pid, \"SAE\", responder=True, password=\"password123\")\n+ ev_sub = sub.wpas.wait_event([\"NAN-PAIRING-STATUS\"], timeout=5)\n+ if ev_sub is None:\n+ raise Exception(\"PASN result not seen on subscriber\")\n+ if \"status=success\" not in ev_sub:\n+ raise Exception(\"NAN PASN pairing failed on subscriber after abort\")\n+ ev_pub = pub.wpas.wait_event([\"NAN-PAIRING-STATUS\"], timeout=5)\n+ if ev_pub is None:\n+ raise Exception(\"PASN result not seen on publisher\")\n+ if \"status=success\" not in ev_pub:\n+ raise Exception(\"NAN PASN pairing failed on publisher after abort\")\n+\n+ # Test abort with invalid peer address\n+ if \"FAIL\" not in sub.pair_abort(\"02:00:00:00:00:00\"):\n+ raise Exception(\"NAN_PAIR_ABORT with invalid peer address succeeded unexpectedly\")\n+\n+def run_nan_pairing(sub, pub, pid, sid, pairing_type, password=None):\n+ if pairing_type == \"SAE\":\n+ if password is not None:\n+ pass\n+ else:\n+ raise Exception(\"Password must be provided for SAE pairing\")\n+\n+ pub.pairing_request(sub, sid, pid, pairing_type, responder=True, password=password)\n+ sub.pairing_request(pub, sid, pid, pairing_type, responder=False, password=password)\n+\n+ ev_sub = sub.wpas.wait_event([\"NAN-PAIRING-STATUS\"], timeout=5)\n+ if ev_sub is None:\n+ raise Exception(\"PASN result not seen on requesting device\")\n+ if \"status=success\" not in ev_sub:\n+ raise Exception(\"NAN PASN pairing failed on requesting device\")\n+\n+ ev_pub = pub.wpas.wait_event([\"NAN-PAIRING-STATUS\"], timeout=5)\n+ if ev_pub is None:\n+ raise Exception(\"PASN result not seen on publisher\")\n+ if \"status=success\" not in ev_pub:\n+ raise Exception(\"NAN PASN pairing failed on publisher\")\n+\n+ # Extract nd_pmk from both events\n+ data_sub = split_nan_event(ev_sub)\n+ data_pub = split_nan_event(ev_pub)\n+\n+ nd_pmk_sub = data_sub.get('nd_pmk')\n+ nd_pmk_pub = data_pub.get('nd_pmk')\n+\n+ if nd_pmk_sub is None:\n+ raise Exception(\"nd_pmk not found in subscriber pairing status event\")\n+ if nd_pmk_pub is None:\n+ raise Exception(\"nd_pmk not found in publisher pairing status event\")\n+\n+ if nd_pmk_sub != nd_pmk_pub:\n+ raise Exception(f\"nd_pmk mismatch: sub={nd_pmk_sub}, pub={nd_pmk_pub}\")\n+\n+ logger.info(f\"NAN pairing successful, nd_pmk={nd_pmk_sub}\")\n+\n+ ev = pub.wpas.wait_event([\"NAN-NIK-RECEIVED\"], timeout=1)\n+ ev = sub.wpas.wait_event([\"NAN-NIK-RECEIVED\"], timeout=1)\n+\n+ return nd_pmk_sub\n+\n+def run_nan_pairing_bootstrap(pairing_type, password=None):\n+ with hwsim_nan_radios(count=2) as [wpas1, wpas2], \\\n+ NanDevice(wpas1, \"nan0\") as pub, NanDevice(wpas2, \"nan1\") as sub:\n+ if \"OK\" not in sub.schedule_config((2437, \"0e000000\")):\n+ raise Exception(\"Failed to configure schedule (sub)\")\n+\n+ if \"OK\" not in pub.schedule_config((2437, \"0e000000\")):\n+ raise Exception(\"Failed to configure schedule (pub)\")\n+\n+ pid, sid, paddr, saddr = nan_pre_bootstrap(pub, sub)\n+ sub.bootstrap(paddr, sid, pid, 0x1)\n+\n+ ev = sub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen\")\n+\n+ ev = pub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen\")\n+\n+ run_nan_pairing(sub, pub, pid, sid, pairing_type, password)\n+\n+ pub.cancel_publish(pid)\n+ sub.cancel_subscribe(sid)\n+\n+def test_nan_sae_pairing_bootstrap(dev, apdev, params):\n+ \"\"\"NAN Pairing setup using opportunistic bootstrapping\"\"\"\n+ run_nan_pairing_bootstrap(\"SAE\", password=\"password123\")\n+\n+def run_nan_pairing_verification(pairing_type, password=None):\n+ with hwsim_nan_radios(count=2) as [wpas1, wpas2], \\\n+ NanDevice(wpas1, \"nan0\") as pub, NanDevice(wpas2, \"nan1\") as sub:\n+ paddr = pub.wpas.own_addr()\n+ saddr = sub.wpas.own_addr()\n+\n+ if \"OK\" not in sub.schedule_config((2437, \"0e000000\")):\n+ raise Exception(\"Failed to configure schedule (sub)\")\n+\n+ if \"OK\" not in pub.schedule_config((2437, \"0e000000\")):\n+ raise Exception(\"Failed to configure schedule (pub)\")\n+\n+ pid, sid, paddr, saddr = nan_sync_discovery(pub, sub, \"test_service\",\n+ pssi=\"aabbccdd\",\n+ sssi=\"ddbbccaa\",\n+ unsolicited=0)\n+\n+ if pairing_type == \"SAE\":\n+ if password is not None:\n+ pass\n+ else:\n+ raise Exception(\"Password must be provided for SAE pairing\")\n+\n+ run_nan_pairing(sub, pub, pid, sid, pairing_type, password)\n+\n+ pub.cancel_publish(pid)\n+ sub.cancel_subscribe(sid)\n+\n+ import time\n+ time.sleep(1)\n+ pub.wpas.dump_monitor()\n+ sub.wpas.dump_monitor()\n+\n+ pid, sid, paddr, saddr = nan_sync_discovery(pub, sub, \"test_service\",\n+ pssi=\"aabbccee\",\n+ sssi=\"ddbbccee\",\n+ unsolicited=0,\n+ timeout=5)\n+\n+ run_nan_pairing(sub, pub, pid, sid, \"PMK\")\n+\n+def test_nan_opportunistic_pairing(dev, apdev, params):\n+ \"\"\"NAN Pairing setup using opportunistic bootstrapping\"\"\"\n+ run_nan_pairing_verification(\"PASN\")\n+\n+def test_nan_sae_pairing(dev, apdev, params):\n+ \"\"\"NAN Pairing setup using a password (SAE)\"\"\"\n+ run_nan_pairing_verification(\"SAE\", \"nanpassword\")\n+\n+def test_nan_publish_with_pmk(dev, apdev, params):\n+ \"\"\"NAN publish with PMK and cipher suites\"\"\"\n+ with hwsim_nan_radios(count=2) as [wpas1, wpas2], \\\n+ NanDevice(wpas1, \"nan0\") as pub, NanDevice(wpas2, \"nan1\") as sub:\n+ paddr = pub.wpas.own_addr()\n+\n+ # Test PMK - 32 bytes (64 hex characters)\n+ nd_pmk = \"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"\n+ cipher_suites = \"1,2\" # NAN_CS_SK_CCM_128, NAN_CS_SK_GCM_256\n+\n+ # Publish with PMK and cipher suites\n+ pid = pub.publish(\"secure_test\", nd_pmk=nd_pmk, cipher_suites=cipher_suites)\n+ if \"FAIL\" in pid:\n+ raise Exception(f\"Failed to publish with PMK: {pid}\")\n+\n+ logger.info(f\"Published with PMK, ID: {pid}\")\n+\n+ # Subscribe to the service\n+ sid = sub.subscribe(\"secure_test\", ssi=None)\n+\n+ # Wait for discovery with PMKIDs advertised\n+ ev = sub.wpas.wait_event([\"NAN-DISCOVERY-RESULT\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-DISCOVERY-RESULT event not seen\")\n+\n+ if f\"address={paddr}\" not in ev:\n+ raise Exception(f\"Unexpected publisher address in discovery: {ev}\")\n+\n+ if f\"publish_id={pid}\" not in ev:\n+ raise Exception(f\"Unexpected publish ID in discovery: {ev}\")\n+\n+ logger.info(f\"Discovery event: {ev}\")\n+\n+ pub.cancel_publish(pid)\n+ sub.cancel_subscribe(sid)\n+\n+def _run_nan_pairing_bootstrap_ndp(pairing_type, password=None, csid=1):\n+ \"\"\"Run NAN pairing bootstrap followed by NDP setup with security\"\"\"\n+ with hwsim_nan_radios() as (wpas1, wpas2), \\\n+ NanDevice(wpas1, \"nan0\", \"ndi0\") as pub, NanDevice(wpas2, \"nan1\", \"ndi1\") as sub:\n+ paddr = pub.wpas.own_addr()\n+ saddr = sub.wpas.own_addr()\n+\n+ # Configure schedules for both devices\n+ if \"OK\" not in sub.schedule_config((2437, \"0e000000\")):\n+ raise Exception(\"Failed to configure schedule (sub)\")\n+\n+ if \"OK\" not in pub.schedule_config((2437, \"0e000000\")):\n+ raise Exception(\"Failed to configure schedule (pub)\")\n+\n+ # Step 1: Pre-bootstrap discovery\n+ pssi = \"aabbccdd\"\n+ sssi = \"ddbbccaa\"\n+\n+ pid = pub.publish(\"test_service\", ssi=pssi, unsolicited=0, pbm=0x1)\n+ sid = sub.subscribe(\"test_service\", ssi=sssi)\n+\n+ logger.info(f\"Publish ID: {pid}, Subscribe ID: {sid}\")\n+\n+ ev = sub.wpas.wait_event([\"NAN-DISCOVERY-RESULT\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-DISCOVERY-RESULT event not seen\")\n+\n+ nan_sync_verify_event(ev, paddr, pid, sid, pssi)\n+\n+ ev = pub.wpas.wait_event([\"NAN-REPLIED\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-REPLIED event not seen\")\n+\n+ nan_sync_verify_event(ev, saddr, pid, sid, sssi)\n+\n+ # Step 2: Bootstrapping\n+ sub.bootstrap(paddr, sid, pid, 0x1)\n+\n+ ev = sub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen on subscriber\")\n+\n+ ev = pub.wpas.wait_event([\"NAN-BOOTSTRAP-SUCCESS\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-BOOTSTRAP-SUCCESS event not seen on publisher\")\n+\n+ # Step 3: Pairing\n+ nd_pmk = run_nan_pairing(sub, pub, pid, sid, pairing_type, password)\n+\n+ logger.info(f\"Pairing completed successfully: nd_pmk={nd_pmk}, now setting up NDP\")\n+\n+ # Step 4: NDP setup with security using the derived nd_pmk\n+ ndp_id, init_ndi = _nan_ndp_request_and_accept(pub, sub, pid, sid, paddr, saddr,\n+ req_ssi=\"aabbcc\", resp_ssi=\"ddeeff\", csid=csid,\n+ pmk=nd_pmk, configure_schedule=True)\n+\n+ logger.info(\"NDP connection established successfully after pairing\")\n+\n+ _nan_test_connectivity(pub, sub)\n+ _nan_ndp_terminate(pub, sub, paddr, init_ndi, ndp_id)\n+\n+ # Once the NDP is removed, verify that service discovery still works\n+ pub.wpas.dump_monitor()\n+ sub.wpas.dump_monitor()\n+ time.sleep(1)\n+\n+ ev = sub.wpas.wait_event([\"NAN-DISCOVERY-RESULT\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-DISCOVERY-RESULT event not seen\")\n+\n+ nan_sync_verify_event(ev, paddr, pid, sid, pssi)\n+\n+ ev = pub.wpas.wait_event([\"NAN-REPLIED\"], timeout=2)\n+ if ev is None:\n+ raise Exception(\"NAN-REPLIED event not seen\")\n+\n+ nan_sync_verify_event(ev, saddr, pid, sid, sssi)\n+\n+def test_nan_pairing_bootstrap_ndp_sk_ccmp128(dev, apdev, params):\n+ \"\"\"NAN Pairing bootstrap followed by NDP with SK CCMP-128 security\"\"\"\n+ set_country(\"US\")\n+ try:\n+ _run_nan_pairing_bootstrap_ndp(\"SAE\", password=\"password123\", csid=1)\n+ finally:\n+ set_country(\"00\")\n", "prefixes": [ "RFC", "02/97" ] }