diff mbox series

[1/2] DBus: add a method to get ANQP fields.

Message ID 20240220121140.519027-1-damiendejean@chromium.org
State Accepted
Headers show
Series [1/2] DBus: add a method to get ANQP fields. | expand

Commit Message

Damien Dejean Feb. 20, 2024, 12:11 p.m. UTC
Adds a D-Bus method to perform ANQP get requests. The new method is
equivalent to the "anqp_get" command available in wpa_cli.

Signed-off-by: Damien Dejean <damiendejean@chromium.org>
---
 doc/dbus.doxygen                        |  18 +++++
 tests/hwsim/test_dbus.py                |  69 ++++++++++++++++
 wpa_supplicant/dbus/dbus_dict_helpers.c |  62 +++++++++++++++
 wpa_supplicant/dbus/dbus_dict_helpers.h |   1 +
 wpa_supplicant/dbus/dbus_new.c          |   7 ++
 wpa_supplicant/dbus/dbus_new_handlers.c | 100 ++++++++++++++++++++++++
 wpa_supplicant/dbus/dbus_new_handlers.h |   4 +
 7 files changed, 261 insertions(+)

Comments

Jouni Malinen March 9, 2024, 5:02 p.m. UTC | #1
On Tue, Feb 20, 2024 at 12:11:40PM +0000, Damien Dejean wrote:
> Adds a D-Bus method to perform ANQP get requests. The new method is
> equivalent to the "anqp_get" command available in wpa_cli.

Thanks, both patches applied with some cleanup.
diff mbox series

Patch

diff --git a/doc/dbus.doxygen b/doc/dbus.doxygen
index 4c5f5f9e9..93d387def 100644
--- a/doc/dbus.doxygen
+++ b/doc/dbus.doxygen
@@ -583,6 +583,24 @@  fi.w1.wpa_supplicant1.CreateInterface.
 	<h3>InterworkingSelect ( ) --> nothing</h3>
 	<p>Perform Interworking (Hotspot 2.0) network selection.</p>
       </li>
+      <li>
+	<h3>ANQPGet ( a{sv} : args) --> nothing</h3>
+	<p>Send an ANQP request.</p>
+	<h4>Arguments</h4>
+	<dl>
+	  <dt>a{sv} : args</dt>
+	  <dd>
+	    <table>
+	      <tr><th>Key</th><th>Value type</th><th>Description</th><th>Required</th>
+	      <tr><td>addr</td><td>s</td><td>Address of the BSS</td><td>Yes</td>
+	      <tr><td>freq</td><td>u</td><td>Frequency of the BSS</td><td>No</td>
+	      <tr><td>ids</td><td>aq</td><td>List of ANQP information IDs to query</td><td>No</td>
+	      <tr><td>hs20_ids</td><td>ay</td><td>List of Hotspot 2.0 ANQP information IDs to query</td><td>No</td>
+	      <tr><td>mbo_ids</td><td>ay</td><td>List of MBO ANQP information IDs to query</td><td>No</td>
+	    </table>
+	  </dd>
+	</dl>
+      </li>
       <li>
 	<h3>EAPLogoff ( ) --> nothing</h3>
 	<p>IEEE 802.1X EAPOL state machine logoff.</p>
diff --git a/tests/hwsim/test_dbus.py b/tests/hwsim/test_dbus.py
index 3ff911364..8bae4b546 100644
--- a/tests/hwsim/test_dbus.py
+++ b/tests/hwsim/test_dbus.py
@@ -6267,3 +6267,72 @@  def test_dbus_hs20_terms_and_conditions(dev, apdev):
     with TestDbusInterworking(bus) as t:
         if not t.success():
             raise Exception("Expected signals not seen")
+
+def test_dbus_anqp_get(dev, apdev):
+    "D-Bus ANQP get test"
+
+    (bus, wpa_obj, path, if_obj) = prepare_dbus(dev[0])
+    iface = dbus.Interface(if_obj, WPAS_DBUS_IFACE)
+
+    venue_group = 1
+    venue_type = 13
+    venue_info = struct.pack('BB', venue_group, venue_type)
+    lang1 = "eng"
+    name1 = "Example venue"
+    lang2 = "fin"
+    name2 = "Esimerkkipaikka"
+    venue1 = struct.pack('B', len(lang1 + name1)) + lang1.encode() + name1.encode()
+    venue2 = struct.pack('B', len(lang2 + name2)) + lang2.encode() + name2.encode()
+
+    url1 = b"http://example.com/venue"
+    url2 = b"https://example.org/venue-info/"
+    duple1 = struct.pack('BB', 1 + len(url1), 1) + url1
+    duple2 = struct.pack('BB', 1 + len(url2), 2) + url2
+    venue_url = binascii.hexlify(duple1 + duple2).decode()
+
+    bssid = apdev[0]['bssid']
+    params = {"ssid": "test-anqp", "hessid": bssid, "wpa": "2",
+              "rsn_pairwise": "CCMP", "wpa_key_mgmt": "WPA-EAP",
+              "ieee80211w": "1", "ieee8021x": "1",
+              "auth_server_addr": "127.0.0.1", "auth_server_port": "1812",
+              "auth_server_shared_secret": "radius",
+              "interworking": "1",
+              "venue_group": str(venue_group),
+              "venue_type": str(venue_type),
+              "venue_name": [lang1 + ":" + name1, lang2 + ":" + name2],
+              "anqp_elem": ["277:" + venue_url],
+              "roaming_consortium": ["112233", "1020304050", "010203040506", "fedcba"],
+              "domain_name": "example.com,another.example.com",
+              "nai_realm": ["0,example.com,13[5:6],21[2:4][5:7]", "0,another.example.com"],
+              'mbo': '1',
+              'mbo_cell_data_conn_pref': '1',
+              'hs20': '1',
+              'hs20_oper_friendly_name': ["eng:Example operator", "fin:Esimerkkioperaattori"]}
+
+    hapd = hostapd.add_ap(apdev[0], params)
+
+    dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
+    iface.ANQPGet({"addr": bssid,
+                   "ids": dbus.Array([257], dbus.Signature("q")),
+                   "mbo_ids": dbus.Array([2], dbus.Signature("y")),
+                   "hs20_ids": dbus.Array([3, 4], dbus.Signature("y"))})
+
+    ev = dev[0].wait_event(["GAS-QUERY-DONE"], timeout=10)
+    if ev is None:
+        raise Exception("GAS query timed out")
+
+    ev = dev[0].wait_event(["RX-ANQP"], timeout=1)
+    if ev is None or "ANQP Capability list" not in ev:
+        raise Exception("Did not receive Capability list")
+
+    ev = dev[0].wait_event(["RX-HS20-ANQP"], timeout=1)
+    if ev is None or "Operator Friendly Name" not in ev:
+        raise Exception("Did not receive Operator Friendly Name")
+
+    ev = dev[0].wait_event(["RX-MBO-ANQP"], timeout=1)
+    if ev is None or "cell_conn_pref" not in ev:
+        raise Exception("Did not receive MBO Cellular Data Connection Preference")
+
+    bss = dev[0].get_bss(bssid)
+    if 'anqp_capability_list' not in bss:
+        raise Exception("Capability List ANQP-element not seen")
diff --git a/wpa_supplicant/dbus/dbus_dict_helpers.c b/wpa_supplicant/dbus/dbus_dict_helpers.c
index fdf7b1258..090f2b1fb 100644
--- a/wpa_supplicant/dbus/dbus_dict_helpers.c
+++ b/wpa_supplicant/dbus/dbus_dict_helpers.c
@@ -705,6 +705,61 @@  done:
 	return success;
 }
 
+#define UINT16_ARRAY_CHUNK_SIZE 18
+#define UINT16_ARRAY_ITEM_SIZE (sizeof(dbus_uint16_t))
+
+static dbus_bool_t _wpa_dbus_dict_entry_get_uint16_array(
+	DBusMessageIter *iter, struct wpa_dbus_dict_entry *entry)
+{
+	dbus_uint32_t count = 0;
+	dbus_bool_t success = FALSE;
+	dbus_uint16_t *buffer, *nbuffer;
+
+	entry->uint16array_value = NULL;
+	entry->array_type = DBUS_TYPE_UINT16;
+
+	buffer = os_calloc(UINT16_ARRAY_CHUNK_SIZE, UINT16_ARRAY_ITEM_SIZE);
+	if (!buffer)
+		return FALSE;
+
+	entry->array_len = 0;
+	while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_UINT16) {
+		dbus_uint16_t value;
+
+		if ((count % UINT16_ARRAY_CHUNK_SIZE) == 0 && count != 0) {
+			nbuffer = os_realloc_array(
+				buffer, count + UINT16_ARRAY_CHUNK_SIZE,
+				UINT16_ARRAY_ITEM_SIZE);
+			if (nbuffer == NULL) {
+				os_free(buffer);
+				wpa_printf(MSG_ERROR,
+					   "dbus: %s out of memory trying to retrieve the uint16 array",
+					   __func__);
+				goto done;
+			}
+			buffer = nbuffer;
+		}
+
+		dbus_message_iter_get_basic(iter, &value);
+		buffer[count] = value;
+		entry->array_len = ++count;
+		dbus_message_iter_next(iter);
+	}
+	entry->uint16array_value = buffer;
+	wpa_hexdump_key(MSG_MSGDUMP, "dbus: uint16 array contents",
+			entry->bytearray_value, entry->array_len);
+
+	/* Zero-length arrays are valid. */
+	if (entry->array_len == 0) {
+		os_free(entry->uint16array_value);
+		entry->uint16array_value = NULL;
+	}
+
+	success = TRUE;
+
+done:
+	return success;
+}
 
 #define STR_ARRAY_CHUNK_SIZE 8
 #define STR_ARRAY_ITEM_SIZE (sizeof(char *))
@@ -873,6 +928,10 @@  static dbus_bool_t _wpa_dbus_dict_entry_get_array(
 		success = _wpa_dbus_dict_entry_get_byte_array(&iter_array,
 							      entry);
 		break;
+	case DBUS_TYPE_UINT16:
+		success = _wpa_dbus_dict_entry_get_uint16_array(&iter_array,
+								entry);
+		break;
 	case DBUS_TYPE_STRING:
 		success = _wpa_dbus_dict_entry_get_string_array(&iter_array,
 								array_type,
@@ -1081,6 +1140,9 @@  void wpa_dbus_dict_entry_clear(struct wpa_dbus_dict_entry *entry)
 		case DBUS_TYPE_BYTE:
 			os_free(entry->bytearray_value);
 			break;
+		case DBUS_TYPE_UINT16:
+			os_free(entry->uint16array_value);
+			break;
 		case DBUS_TYPE_STRING:
 			if (!entry->strarray_value)
 				break;
diff --git a/wpa_supplicant/dbus/dbus_dict_helpers.h b/wpa_supplicant/dbus/dbus_dict_helpers.h
index cc9e26fa9..1d33689a8 100644
--- a/wpa_supplicant/dbus/dbus_dict_helpers.h
+++ b/wpa_supplicant/dbus/dbus_dict_helpers.h
@@ -139,6 +139,7 @@  struct wpa_dbus_dict_entry {
 		dbus_uint64_t uint64_value;
 		double double_value;
 		char *bytearray_value;
+		dbus_uint16_t *uint16array_value;
 		char **strarray_value;
 		struct wpabuf **binarray_value;
 	};
diff --git a/wpa_supplicant/dbus/dbus_new.c b/wpa_supplicant/dbus/dbus_new.c
index 00b38edf5..25b5919c0 100644
--- a/wpa_supplicant/dbus/dbus_new.c
+++ b/wpa_supplicant/dbus/dbus_new.c
@@ -3716,6 +3716,13 @@  static const struct wpa_dbus_method_desc wpas_dbus_interface_methods[] = {
 		  END_ARGS
 	  }
 	},
+	{"ANQPGet", WPAS_DBUS_NEW_IFACE_INTERFACE,
+	  (WPADBusMethodHandler) wpas_dbus_handler_anqp_get,
+	  {
+		  { "args", "a{sv}", ARG_IN },
+		  END_ARGS
+	  },
+	},
 #endif /* CONFIG_INTERWORKING */
 	{ NULL, NULL, NULL, { END_ARGS } }
 };
diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c b/wpa_supplicant/dbus/dbus_new_handlers.c
index 6ad49a136..e19a7bc8b 100644
--- a/wpa_supplicant/dbus/dbus_new_handlers.c
+++ b/wpa_supplicant/dbus/dbus_new_handlers.c
@@ -1977,6 +1977,106 @@  wpas_dbus_handler_interworking_select(DBusMessage *message,
 
 	return reply;
 }
+
+DBusMessage *
+wpas_dbus_handler_anqp_get(DBusMessage *message,
+			   struct wpa_supplicant *wpa_s)
+{
+	DBusMessageIter	iter, iter_dict;
+	struct wpa_dbus_dict_entry entry;
+	int ret;
+	u8 dst_addr[ETH_ALEN];
+	int is_addr_present = 0;
+	unsigned int freq = 0;
+// Max info ID count from CLI implementation.
+#define MAX_ANQP_INFO_ID 100
+	u16 id[MAX_ANQP_INFO_ID];
+	size_t num_id = 0;
+	u32 subtypes = 0;
+	u32 mbo_subtypes = 0;
+
+
+	dbus_message_iter_init(message, &iter);
+
+	if (!wpa_dbus_dict_open_read(&iter, &iter_dict, NULL))
+		return wpas_dbus_error_invalid_args(message, NULL);
+
+	while (wpa_dbus_dict_has_dict_entry(&iter_dict)) {
+		if (!wpa_dbus_dict_get_entry(&iter_dict, &entry))
+			return wpas_dbus_error_invalid_args(message, NULL);
+
+		if (os_strcmp(entry.key, "addr") == 0 &&
+		    entry.type == DBUS_TYPE_STRING) {
+			if (hwaddr_aton(entry.str_value, dst_addr)) {
+				wpa_printf(MSG_DEBUG,
+					   "%s[dbus]: Invalid address '%s'",
+					   __func__, entry.str_value);
+				wpa_dbus_dict_entry_clear(&entry);
+				return wpas_dbus_error_invalid_args(message,
+								    "invalid address");
+			}
+
+			is_addr_present = 1;
+		} else if (os_strcmp(entry.key, "freq") == 0 &&
+			   entry.type == DBUS_TYPE_UINT32) {
+			freq = entry.uint32_value;
+		} else if (os_strcmp(entry.key, "ids") == 0 &&
+			   entry.type == DBUS_TYPE_ARRAY &&
+			   entry.array_type == DBUS_TYPE_UINT16) {
+			for (size_t i = 0; i < entry.array_len && num_id < MAX_ANQP_INFO_ID; i++) {
+				id[num_id] = entry.uint16array_value[i];
+				num_id++;
+			}
+		} else if (os_strcmp(entry.key, "hs20_ids") == 0 &&
+			   entry.type == DBUS_TYPE_ARRAY &&
+			   entry.array_type == DBUS_TYPE_BYTE) {
+			for (size_t i = 0; i < entry.array_len; i++) {
+				int num = entry.bytearray_value[i];
+				if (num <= 0 || num > 31) {
+					wpa_dbus_dict_entry_clear(&entry);
+					return wpas_dbus_error_invalid_args(message, "invalid HS20 ANQP id");
+				}
+				subtypes |= BIT(num);
+			}
+
+		} else if (os_strcmp(entry.key, "mbo_ids") == 0 &&
+			   entry.type == DBUS_TYPE_ARRAY &&
+			   entry.array_type == DBUS_TYPE_BYTE) {
+			for (size_t i = 0; i < entry.array_len; i++) {
+				int num = entry.bytearray_value[i];
+				if (num <= 0 || num > MAX_MBO_ANQP_SUBTYPE) {
+					wpa_dbus_dict_entry_clear(&entry);
+					return wpas_dbus_error_invalid_args(message, "invalid MBO ANQP id");
+				}
+				mbo_subtypes |= BIT(num);
+			}
+
+		} else {
+			wpa_dbus_dict_entry_clear(&entry);
+			return wpas_dbus_error_invalid_args(message, "unsupported parameter");
+		}
+
+		wpa_dbus_dict_entry_clear(&entry);
+	}
+
+	if (is_addr_present == 0) {
+		wpa_printf(MSG_DEBUG,
+			   "%s[dbus]: address not provided", __func__);
+		return wpas_dbus_error_invalid_args(
+			message, "address not provided");
+	}
+
+	ret = anqp_send_req(wpa_s, dst_addr, freq, id, num_id, subtypes, mbo_subtypes);
+	if (ret < 0) {
+		wpa_printf(MSG_ERROR,
+			   "%s[dbus]: failed to send ANQP request",
+			   __func__);
+		return wpas_dbus_error_unknown_error(
+			message, "error sending ANQP request.");
+	}
+
+	return NULL;
+}
 #endif /* CONFIG_INTERWORKING */
 
 
diff --git a/wpa_supplicant/dbus/dbus_new_handlers.h b/wpa_supplicant/dbus/dbus_new_handlers.h
index 97fa337bd..5a69ec6de 100644
--- a/wpa_supplicant/dbus/dbus_new_handlers.h
+++ b/wpa_supplicant/dbus/dbus_new_handlers.h
@@ -157,6 +157,10 @@  DBusMessage *
 wpas_dbus_handler_interworking_select(DBusMessage *message,
 				      struct wpa_supplicant *wpa_s);
 
+DBusMessage *
+wpas_dbus_handler_anqp_get(DBusMessage *message,
+			   struct wpa_supplicant *wpa_s);
+
 DECLARE_ACCESSOR(wpas_dbus_getter_capabilities);
 DECLARE_ACCESSOR(wpas_dbus_getter_state);
 DECLARE_ACCESSOR(wpas_dbus_getter_scanning);