| /* |
| * wpa_supplicant - Wi-Fi Display |
| * Copyright (c) 2011, Atheros Communications, Inc. |
| * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| |
| #include "common.h" |
| #include "p2p/p2p.h" |
| #include "common/ieee802_11_defs.h" |
| #include "wpa_supplicant_i.h" |
| #include "wifi_display.h" |
| |
| |
| #define WIFI_DISPLAY_SUBELEM_HEADER_LEN 3 |
| |
| |
| int wifi_display_init(struct wpa_global *global) |
| { |
| global->wifi_display = 1; |
| return 0; |
| } |
| |
| |
| void wifi_display_deinit(struct wpa_global *global) |
| { |
| int i; |
| for (i = 0; i < MAX_WFD_SUBELEMS; i++) { |
| wpabuf_free(global->wfd_subelem[i]); |
| global->wfd_subelem[i] = NULL; |
| } |
| } |
| |
| |
| struct wpabuf * wifi_display_get_wfd_ie(struct wpa_global *global) |
| { |
| struct wpabuf *ie; |
| size_t len; |
| int i; |
| |
| if (global->p2p == NULL) |
| return NULL; |
| |
| len = 0; |
| for (i = 0; i < MAX_WFD_SUBELEMS; i++) { |
| if (global->wfd_subelem[i]) |
| len += wpabuf_len(global->wfd_subelem[i]); |
| } |
| |
| ie = wpabuf_alloc(len); |
| if (ie == NULL) |
| return NULL; |
| |
| for (i = 0; i < MAX_WFD_SUBELEMS; i++) { |
| if (global->wfd_subelem[i]) |
| wpabuf_put_buf(ie, global->wfd_subelem[i]); |
| } |
| |
| return ie; |
| } |
| |
| |
| static int wifi_display_update_wfd_ie(struct wpa_global *global) |
| { |
| struct wpabuf *ie, *buf; |
| size_t len, plen; |
| |
| if (global->p2p == NULL) |
| return 0; |
| |
| wpa_printf(MSG_DEBUG, "WFD: Update WFD IE"); |
| |
| if (!global->wifi_display) { |
| wpa_printf(MSG_DEBUG, "WFD: Wi-Fi Display disabled - do not " |
| "include WFD IE"); |
| p2p_set_wfd_ie_beacon(global->p2p, NULL); |
| p2p_set_wfd_ie_probe_req(global->p2p, NULL); |
| p2p_set_wfd_ie_probe_resp(global->p2p, NULL); |
| p2p_set_wfd_ie_assoc_req(global->p2p, NULL); |
| p2p_set_wfd_ie_invitation(global->p2p, NULL); |
| p2p_set_wfd_ie_prov_disc_req(global->p2p, NULL); |
| p2p_set_wfd_ie_prov_disc_resp(global->p2p, NULL); |
| p2p_set_wfd_ie_go_neg(global->p2p, NULL); |
| p2p_set_wfd_dev_info(global->p2p, NULL); |
| p2p_set_wfd_r2_dev_info(global->p2p, NULL); |
| p2p_set_wfd_assoc_bssid(global->p2p, NULL); |
| p2p_set_wfd_coupled_sink_info(global->p2p, NULL); |
| return 0; |
| } |
| |
| p2p_set_wfd_dev_info(global->p2p, |
| global->wfd_subelem[WFD_SUBELEM_DEVICE_INFO]); |
| p2p_set_wfd_r2_dev_info( |
| global->p2p, global->wfd_subelem[WFD_SUBELEM_R2_DEVICE_INFO]); |
| p2p_set_wfd_assoc_bssid( |
| global->p2p, |
| global->wfd_subelem[WFD_SUBELEM_ASSOCIATED_BSSID]); |
| p2p_set_wfd_coupled_sink_info( |
| global->p2p, global->wfd_subelem[WFD_SUBELEM_COUPLED_SINK]); |
| |
| /* |
| * WFD IE is included in number of management frames. Two different |
| * sets of subelements are included depending on the frame: |
| * |
| * Beacon, (Re)Association Request, GO Negotiation Req/Resp/Conf, |
| * Provision Discovery Req: |
| * WFD Device Info |
| * [Associated BSSID] |
| * [Coupled Sink Info] |
| * |
| * Probe Request: |
| * WFD Device Info |
| * [Associated BSSID] |
| * [Coupled Sink Info] |
| * [WFD Extended Capability] |
| * |
| * Probe Response: |
| * WFD Device Info |
| * [Associated BSSID] |
| * [Coupled Sink Info] |
| * [WFD Extended Capability] |
| * [WFD Session Info] |
| * |
| * (Re)Association Response, P2P Invitation Req/Resp, |
| * Provision Discovery Resp: |
| * WFD Device Info |
| * [Associated BSSID] |
| * [Coupled Sink Info] |
| * [WFD Session Info] |
| */ |
| len = 0; |
| if (global->wfd_subelem[WFD_SUBELEM_DEVICE_INFO]) |
| len += wpabuf_len(global->wfd_subelem[ |
| WFD_SUBELEM_DEVICE_INFO]); |
| |
| if (global->wfd_subelem[WFD_SUBELEM_R2_DEVICE_INFO]) |
| len += wpabuf_len(global->wfd_subelem[ |
| WFD_SUBELEM_R2_DEVICE_INFO]); |
| |
| if (global->wfd_subelem[WFD_SUBELEM_ASSOCIATED_BSSID]) |
| len += wpabuf_len(global->wfd_subelem[ |
| WFD_SUBELEM_ASSOCIATED_BSSID]); |
| if (global->wfd_subelem[WFD_SUBELEM_COUPLED_SINK]) |
| len += wpabuf_len(global->wfd_subelem[ |
| WFD_SUBELEM_COUPLED_SINK]); |
| if (global->wfd_subelem[WFD_SUBELEM_SESSION_INFO]) |
| len += wpabuf_len(global->wfd_subelem[ |
| WFD_SUBELEM_SESSION_INFO]); |
| if (global->wfd_subelem[WFD_SUBELEM_EXT_CAPAB]) |
| len += wpabuf_len(global->wfd_subelem[WFD_SUBELEM_EXT_CAPAB]); |
| buf = wpabuf_alloc(len); |
| if (buf == NULL) |
| return -1; |
| |
| if (global->wfd_subelem[WFD_SUBELEM_DEVICE_INFO]) |
| wpabuf_put_buf(buf, |
| global->wfd_subelem[WFD_SUBELEM_DEVICE_INFO]); |
| |
| if (global->wfd_subelem[WFD_SUBELEM_R2_DEVICE_INFO]) |
| wpabuf_put_buf(buf, |
| global->wfd_subelem[WFD_SUBELEM_R2_DEVICE_INFO]); |
| |
| if (global->wfd_subelem[WFD_SUBELEM_ASSOCIATED_BSSID]) |
| wpabuf_put_buf(buf, global->wfd_subelem[ |
| WFD_SUBELEM_ASSOCIATED_BSSID]); |
| if (global->wfd_subelem[WFD_SUBELEM_COUPLED_SINK]) |
| wpabuf_put_buf(buf, |
| global->wfd_subelem[WFD_SUBELEM_COUPLED_SINK]); |
| |
| ie = wifi_display_encaps(buf); |
| wpa_hexdump_buf(MSG_DEBUG, "WFD: WFD IE for Beacon", ie); |
| p2p_set_wfd_ie_beacon(global->p2p, ie); |
| |
| ie = wifi_display_encaps(buf); |
| wpa_hexdump_buf(MSG_DEBUG, "WFD: WFD IE for (Re)Association Request", |
| ie); |
| p2p_set_wfd_ie_assoc_req(global->p2p, ie); |
| |
| ie = wifi_display_encaps(buf); |
| wpa_hexdump_buf(MSG_DEBUG, "WFD: WFD IE for GO Negotiation", ie); |
| p2p_set_wfd_ie_go_neg(global->p2p, ie); |
| |
| ie = wifi_display_encaps(buf); |
| wpa_hexdump_buf(MSG_DEBUG, "WFD: WFD IE for Provision Discovery " |
| "Request", ie); |
| p2p_set_wfd_ie_prov_disc_req(global->p2p, ie); |
| |
| plen = buf->used; |
| if (global->wfd_subelem[WFD_SUBELEM_EXT_CAPAB]) |
| wpabuf_put_buf(buf, |
| global->wfd_subelem[WFD_SUBELEM_EXT_CAPAB]); |
| |
| ie = wifi_display_encaps(buf); |
| wpa_hexdump_buf(MSG_DEBUG, "WFD: WFD IE for Probe Request", ie); |
| p2p_set_wfd_ie_probe_req(global->p2p, ie); |
| |
| if (global->wfd_subelem[WFD_SUBELEM_SESSION_INFO]) |
| wpabuf_put_buf(buf, |
| global->wfd_subelem[WFD_SUBELEM_SESSION_INFO]); |
| ie = wifi_display_encaps(buf); |
| wpa_hexdump_buf(MSG_DEBUG, "WFD: WFD IE for Probe Response", ie); |
| p2p_set_wfd_ie_probe_resp(global->p2p, ie); |
| |
| /* Remove WFD Extended Capability from buffer */ |
| buf->used = plen; |
| if (global->wfd_subelem[WFD_SUBELEM_SESSION_INFO]) |
| wpabuf_put_buf(buf, |
| global->wfd_subelem[WFD_SUBELEM_SESSION_INFO]); |
| |
| ie = wifi_display_encaps(buf); |
| wpa_hexdump_buf(MSG_DEBUG, "WFD: WFD IE for P2P Invitation", ie); |
| p2p_set_wfd_ie_invitation(global->p2p, ie); |
| |
| ie = wifi_display_encaps(buf); |
| wpa_hexdump_buf(MSG_DEBUG, "WFD: WFD IE for Provision Discovery " |
| "Response", ie); |
| p2p_set_wfd_ie_prov_disc_resp(global->p2p, ie); |
| |
| wpabuf_free(buf); |
| |
| return 0; |
| } |
| |
| |
| void wifi_display_enable(struct wpa_global *global, int enabled) |
| { |
| wpa_printf(MSG_DEBUG, "WFD: Wi-Fi Display %s", |
| enabled ? "enabled" : "disabled"); |
| global->wifi_display = enabled; |
| wifi_display_update_wfd_ie(global); |
| } |
| |
| |
| int wifi_display_subelem_set(struct wpa_global *global, char *cmd) |
| { |
| char *pos; |
| int subelem; |
| size_t len; |
| struct wpabuf *e; |
| |
| pos = os_strchr(cmd, ' '); |
| if (pos == NULL) |
| return -1; |
| *pos++ = '\0'; |
| |
| len = os_strlen(pos); |
| if (len & 1) |
| return -1; |
| len /= 2; |
| |
| if (os_strcmp(cmd, "all") == 0) { |
| int res; |
| |
| e = wpabuf_alloc(len); |
| if (e == NULL) |
| return -1; |
| if (hexstr2bin(pos, wpabuf_put(e, len), len) < 0) { |
| wpabuf_free(e); |
| return -1; |
| } |
| res = wifi_display_subelem_set_from_ies(global, e); |
| wpabuf_free(e); |
| return res; |
| } |
| |
| subelem = atoi(cmd); |
| if (subelem < 0 || subelem >= MAX_WFD_SUBELEMS) |
| return -1; |
| |
| if (len == 0) { |
| /* Clear subelement */ |
| e = NULL; |
| wpa_printf(MSG_DEBUG, "WFD: Clear subelement %d", subelem); |
| } else { |
| e = wpabuf_alloc(1 + len); |
| if (e == NULL) |
| return -1; |
| wpabuf_put_u8(e, subelem); |
| if (hexstr2bin(pos, wpabuf_put(e, len), len) < 0) { |
| wpabuf_free(e); |
| return -1; |
| } |
| wpa_printf(MSG_DEBUG, "WFD: Set subelement %d", subelem); |
| } |
| |
| wpabuf_free(global->wfd_subelem[subelem]); |
| global->wfd_subelem[subelem] = e; |
| wifi_display_update_wfd_ie(global); |
| |
| return 0; |
| } |
| |
| |
| int wifi_display_subelem_set_from_ies(struct wpa_global *global, |
| struct wpabuf *ie) |
| { |
| int subelements[MAX_WFD_SUBELEMS] = {}; |
| const u8 *pos, *end; |
| unsigned int len, subelem; |
| struct wpabuf *e; |
| |
| wpa_printf(MSG_DEBUG, "WFD IEs set: %p - %lu", |
| ie, ie ? (unsigned long) wpabuf_len(ie) : 0); |
| |
| if (ie == NULL || wpabuf_len(ie) < 6) |
| return -1; |
| |
| pos = wpabuf_head(ie); |
| end = pos + wpabuf_len(ie); |
| |
| while (end > pos) { |
| if (pos + 3 > end) |
| break; |
| |
| len = WPA_GET_BE16(pos + 1) + 3; |
| |
| wpa_printf(MSG_DEBUG, "WFD Sub-Element ID %d - len %d", |
| *pos, len - 3); |
| |
| if (len > (unsigned int) (end - pos)) |
| break; |
| |
| subelem = *pos; |
| if (subelem < MAX_WFD_SUBELEMS && subelements[subelem] == 0) { |
| e = wpabuf_alloc_copy(pos, len); |
| if (e == NULL) |
| return -1; |
| |
| wpabuf_free(global->wfd_subelem[subelem]); |
| global->wfd_subelem[subelem] = e; |
| subelements[subelem] = 1; |
| } |
| |
| pos += len; |
| } |
| |
| for (subelem = 0; subelem < MAX_WFD_SUBELEMS; subelem++) { |
| if (subelements[subelem] == 0) { |
| wpabuf_free(global->wfd_subelem[subelem]); |
| global->wfd_subelem[subelem] = NULL; |
| } |
| } |
| |
| return wifi_display_update_wfd_ie(global); |
| } |
| |
| |
| int wifi_display_subelem_get(struct wpa_global *global, char *cmd, |
| char *buf, size_t buflen) |
| { |
| int subelem; |
| |
| if (os_strcmp(cmd, "all") == 0) { |
| struct wpabuf *ie; |
| int res; |
| |
| ie = wifi_display_get_wfd_ie(global); |
| if (ie == NULL) |
| return 0; |
| res = wpa_snprintf_hex(buf, buflen, wpabuf_head(ie), |
| wpabuf_len(ie)); |
| wpabuf_free(ie); |
| return res; |
| } |
| |
| subelem = atoi(cmd); |
| if (subelem < 0 || subelem >= MAX_WFD_SUBELEMS) |
| return -1; |
| |
| if (global->wfd_subelem[subelem] == NULL) |
| return 0; |
| |
| return wpa_snprintf_hex(buf, buflen, |
| wpabuf_head_u8(global->wfd_subelem[subelem]) + |
| 1, |
| wpabuf_len(global->wfd_subelem[subelem]) - 1); |
| } |
| |
| |
| char * wifi_display_subelem_hex(const struct wpabuf *wfd_subelems, u8 id) |
| { |
| char *subelem = NULL; |
| const u8 *buf; |
| size_t buflen; |
| size_t i = 0; |
| u16 elen; |
| |
| if (!wfd_subelems) |
| return NULL; |
| |
| buf = wpabuf_head_u8(wfd_subelems); |
| if (!buf) |
| return NULL; |
| |
| buflen = wpabuf_len(wfd_subelems); |
| |
| while (i + WIFI_DISPLAY_SUBELEM_HEADER_LEN < buflen) { |
| elen = WPA_GET_BE16(buf + i + 1); |
| if (i + WIFI_DISPLAY_SUBELEM_HEADER_LEN + elen > buflen) |
| break; /* truncated subelement */ |
| |
| if (buf[i] == id) { |
| /* |
| * Limit explicitly to an arbitrary length to avoid |
| * unnecessarily large allocations. In practice, this |
| * is limited to maximum frame length anyway, so the |
| * maximum memory allocation here is not really that |
| * large. Anyway, the Wi-Fi Display subelements that |
| * are fetched with this function are even shorter. |
| */ |
| if (elen > 1000) |
| break; |
| subelem = os_zalloc(2 * elen + 1); |
| if (!subelem) |
| return NULL; |
| wpa_snprintf_hex(subelem, 2 * elen + 1, |
| buf + i + |
| WIFI_DISPLAY_SUBELEM_HEADER_LEN, |
| elen); |
| break; |
| } |
| |
| i += elen + WIFI_DISPLAY_SUBELEM_HEADER_LEN; |
| } |
| |
| return subelem; |
| } |