taxonomy: implement v3 signature format.

The TX Power levels from the Association Request
appear to be very useful for disambiguating
devices which are otherwise very similar, for example:
+ Apple iPhone 5 from iPad 4th gen from iPad Mini 2nd gen
+ Samsung Galaxy Note 4 from Galaxy S5

Also include the capabilities field from the Association
Request, as that is more distinctive than we believed
at first.

Extended Capabilities can be longer than 4 bytes.
Include whatever is there.

Change-Id: Ife1fa3f69c1c93be5745edd0347707171faedad9
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index bab2e91..438cf81 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -359,7 +359,7 @@
 		wpa_printf(MSG_ERROR, "open %s failed", tmpfile);
 		return;
 	}
-	fprintf(f, "wifi|probe:%s|assoc:%s", sta->probe_ie_taxonomy,
+	fprintf(f, "wifi3|probe:%s|assoc:%s", sta->probe_ie_taxonomy,
 	        sta->assoc_ie_taxonomy);
 	fclose(f);
 	if (rename(tmpfile, filename)) {
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index bd88c91..d0e04da 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -169,7 +169,7 @@
 	u8 last_subtype;
 
 #ifdef CONFIG_CLIENT_TAXONOMY
-#define TAXONOMY_STRING_LEN 768
+#define TAXONOMY_STRING_LEN 1536
 	char probe_ie_taxonomy[TAXONOMY_STRING_LEN];
 	char assoc_ie_taxonomy[TAXONOMY_STRING_LEN];
 #endif /* CONFIG_CLIENT_TAXONOMY */
diff --git a/src/ap/taxonomy.c b/src/ap/taxonomy.c
index b70dcb7..5e1fd2a 100644
--- a/src/ap/taxonomy.c
+++ b/src/ap/taxonomy.c
@@ -75,6 +75,7 @@
 }
 
 static void ie_to_string(char *fstr, size_t fstr_len,
+                         const char *capability,
                          const u8 *ie, size_t ie_len)
 {
 	size_t flen = fstr_len - 1;
@@ -85,7 +86,9 @@
 	char vhtrxmcs[10 + 8 + 1];  // ",vhtrxmcs:" + %08x + trailing NUL
 	char vhttxmcs[10 + 8 + 1];  // ",vhttxmcs:" + %08x + trailing NUL
 	char intwrk[8 + 2 + 1];  // ",intwrk:" + %02hx + trailing NUL
-	char extcap[8 + 8 + 1];  // ",extcap:" + %08x + trailing NUL
+	#define MAX_EXTCAP	254
+	char extcap[8 + (2 * MAX_EXTCAP) + 1];  // ",extcap:" + hex + trailing NUL
+	char txpow[7 + 4 + 1];  // ",txpow:" + %04hx + trailing NUL
 	#define WPS_NAME_LEN		32
 	char wps[WPS_NAME_LEN + 5 + 1];  // room to prepend ",wps:" + trailing NUL
 	int num = 0;
@@ -98,6 +101,7 @@
 	memset(vhttxmcs, 0, sizeof(vhttxmcs));
 	memset(intwrk, 0, sizeof(intwrk));
 	memset(extcap, 0, sizeof(extcap));
+	memset(txpow, 0, sizeof(txpow));
 	memset(wps, 0, sizeof(wps));
 	fstr[0] = '\0';
 
@@ -176,12 +180,24 @@
 				/* Interworking */
 				snprintf(intwrk, sizeof(intwrk), ",intwrk:%02hx", *ie);
 			}
-			if ((id == 127) && (elen >= 4)) {
+			if (id == 127) {
 				/* Extended Capabilities */
-				u32 ext;
-				memcpy(&ext, ie, sizeof(ext));
-				snprintf(extcap, sizeof(extcap), ",extcap:%08x",
-				         le_to_host32(ext));
+				int i;
+				int len = (elen < MAX_EXTCAP) ? elen : MAX_EXTCAP;
+				char *p = extcap;
+
+				p += snprintf(extcap, sizeof(extcap), ",extcap:");
+				for (i = 0; i < len; ++i) {
+					int lim = sizeof(extcap) - strlen(extcap);
+					p += snprintf(p, lim, "%02x", *(ie + i));
+				}
+			}
+			if ((id == 33) && (elen == 2)) {
+				/* TX Power */
+				u16 p;
+				memcpy(&p, ie, sizeof(p));
+				snprintf(txpow, sizeof(txpow), ",txpow:%04hx",
+				         le_to_host16(p));
 			}
 
 			snprintf(tagbuf, sizeof(tagbuf), "%s%d", sep, id);
@@ -194,6 +210,10 @@
 		ie_len -= elen;
 	}
 
+	if (capability) {
+		strncat(fstr, capability, flen);
+		flen = fstr_len - strlen(fstr) - 1;
+	}
 	if (strlen(htcap)) {
 		strncat(fstr, htcap, flen);
 		flen = fstr_len - strlen(fstr) - 1;
@@ -218,6 +238,10 @@
 		strncat(fstr, vhttxmcs, flen);
 		flen = fstr_len - strlen(fstr) - 1;
 	}
+	if (strlen(txpow)) {
+		strncat(fstr, txpow, flen);
+		flen = fstr_len - strlen(fstr) - 1;
+	}
 	if (strlen(intwrk)) {
 		strncat(fstr, intwrk, flen);
 		flen = fstr_len - strlen(fstr) - 1;
@@ -238,16 +262,18 @@
 	const u8 *ie, size_t ie_len)
 {
 	ie_to_string(sta->probe_ie_taxonomy,
-			     sizeof(sta->probe_ie_taxonomy),
-				 ie, ie_len);
+	             sizeof(sta->probe_ie_taxonomy),
+	             NULL, ie, ie_len);
 }
 
 void hostapd_taxonomy_assoc_req(struct sta_info *sta,
 	const u8 *ie, size_t ie_len)
 {
+	char cap[5 + 4 + 1];  // ",cap:" + %04x + trailing NUL
+	snprintf(cap, sizeof(cap), ",cap:%04hx", sta->capability);
 	ie_to_string(sta->assoc_ie_taxonomy,
 	             sizeof(sta->assoc_ie_taxonomy),
-	             ie, ie_len);
+	             cap, ie, ie_len);
 }
 
 /* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab : */