86bd36f0d5
This adds following new control interface commands to allow arbitrary vendor elements to be added into number of frames: VENDOR_ELEM_ADD <frame id> <hexdump of elem(s)> VENDOR_ELEM_GET <frame id> VENDOR_ELEM_REMOVE <frame id> <hexdump of elem(s)> VENDOR_ELEM_REMOVE <frame id> * The following frames are supported in this commit (additional frames can be added in the future): 0 = Probe Request frame in P2P device discovery 1 = Probe Response frame from P2P Device role 2 = Probe Response frame from P2P GO 3 = Beacon frame from P2P GO 4 = PD Req 5 = PD Resp 6 = GO Neg Req 7 = GO Neg Resp 8 = GO Neg Conf 9 = Invitation Request 10 = Invitation Response 11 = P2P Association Request 12 = P2P Association Response One or more vendor element can be added/removed with the commands. The hexdump of the element(s) needs to contain the full element (id, len, payload) and the buffer needs to pass IE parsing requirements to be accepted. Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
1061 lines
24 KiB
C
1061 lines
24 KiB
C
/*
|
|
* Wi-Fi Direct - P2P group operations
|
|
* Copyright (c) 2009-2010, Atheros Communications
|
|
*
|
|
* This software may be distributed under the terms of the BSD license.
|
|
* See README for more details.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#include "common.h"
|
|
#include "common/ieee802_11_defs.h"
|
|
#include "common/ieee802_11_common.h"
|
|
#include "common/wpa_ctrl.h"
|
|
#include "wps/wps_defs.h"
|
|
#include "wps/wps_i.h"
|
|
#include "p2p_i.h"
|
|
#include "p2p.h"
|
|
|
|
|
|
struct p2p_group_member {
|
|
struct p2p_group_member *next;
|
|
u8 addr[ETH_ALEN]; /* P2P Interface Address */
|
|
u8 dev_addr[ETH_ALEN]; /* P2P Device Address */
|
|
struct wpabuf *p2p_ie;
|
|
struct wpabuf *wfd_ie;
|
|
struct wpabuf *client_info;
|
|
u8 dev_capab;
|
|
};
|
|
|
|
/**
|
|
* struct p2p_group - Internal P2P module per-group data
|
|
*/
|
|
struct p2p_group {
|
|
struct p2p_data *p2p;
|
|
struct p2p_group_config *cfg;
|
|
struct p2p_group_member *members;
|
|
unsigned int num_members;
|
|
int group_formation;
|
|
int beacon_update;
|
|
struct wpabuf *noa;
|
|
struct wpabuf *wfd_ie;
|
|
};
|
|
|
|
|
|
struct p2p_group * p2p_group_init(struct p2p_data *p2p,
|
|
struct p2p_group_config *config)
|
|
{
|
|
struct p2p_group *group, **groups;
|
|
|
|
group = os_zalloc(sizeof(*group));
|
|
if (group == NULL)
|
|
return NULL;
|
|
|
|
groups = os_realloc_array(p2p->groups, p2p->num_groups + 1,
|
|
sizeof(struct p2p_group *));
|
|
if (groups == NULL) {
|
|
os_free(group);
|
|
return NULL;
|
|
}
|
|
groups[p2p->num_groups++] = group;
|
|
p2p->groups = groups;
|
|
|
|
group->p2p = p2p;
|
|
group->cfg = config;
|
|
group->group_formation = 1;
|
|
group->beacon_update = 1;
|
|
p2p_group_update_ies(group);
|
|
group->cfg->idle_update(group->cfg->cb_ctx, 1);
|
|
|
|
return group;
|
|
}
|
|
|
|
|
|
static void p2p_group_free_member(struct p2p_group_member *m)
|
|
{
|
|
wpabuf_free(m->wfd_ie);
|
|
wpabuf_free(m->p2p_ie);
|
|
wpabuf_free(m->client_info);
|
|
os_free(m);
|
|
}
|
|
|
|
|
|
static void p2p_group_free_members(struct p2p_group *group)
|
|
{
|
|
struct p2p_group_member *m, *prev;
|
|
m = group->members;
|
|
group->members = NULL;
|
|
group->num_members = 0;
|
|
while (m) {
|
|
prev = m;
|
|
m = m->next;
|
|
p2p_group_free_member(prev);
|
|
}
|
|
}
|
|
|
|
|
|
void p2p_group_deinit(struct p2p_group *group)
|
|
{
|
|
size_t g;
|
|
struct p2p_data *p2p;
|
|
|
|
if (group == NULL)
|
|
return;
|
|
|
|
p2p = group->p2p;
|
|
|
|
for (g = 0; g < p2p->num_groups; g++) {
|
|
if (p2p->groups[g] == group) {
|
|
while (g + 1 < p2p->num_groups) {
|
|
p2p->groups[g] = p2p->groups[g + 1];
|
|
g++;
|
|
}
|
|
p2p->num_groups--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
p2p_group_free_members(group);
|
|
os_free(group->cfg);
|
|
wpabuf_free(group->noa);
|
|
wpabuf_free(group->wfd_ie);
|
|
os_free(group);
|
|
}
|
|
|
|
|
|
static void p2p_client_info(struct wpabuf *ie, struct p2p_group_member *m)
|
|
{
|
|
if (m->client_info == NULL)
|
|
return;
|
|
if (wpabuf_tailroom(ie) < wpabuf_len(m->client_info) + 1)
|
|
return;
|
|
wpabuf_put_buf(ie, m->client_info);
|
|
}
|
|
|
|
|
|
static void p2p_group_add_common_ies(struct p2p_group *group,
|
|
struct wpabuf *ie)
|
|
{
|
|
u8 dev_capab = group->p2p->dev_capab, group_capab = 0;
|
|
|
|
/* P2P Capability */
|
|
dev_capab &= ~P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY;
|
|
group_capab |= P2P_GROUP_CAPAB_GROUP_OWNER;
|
|
if (group->cfg->persistent_group) {
|
|
group_capab |= P2P_GROUP_CAPAB_PERSISTENT_GROUP;
|
|
if (group->cfg->persistent_group == 2)
|
|
group_capab |= P2P_GROUP_CAPAB_PERSISTENT_RECONN;
|
|
}
|
|
if (group->p2p->cfg->p2p_intra_bss)
|
|
group_capab |= P2P_GROUP_CAPAB_INTRA_BSS_DIST;
|
|
if (group->group_formation)
|
|
group_capab |= P2P_GROUP_CAPAB_GROUP_FORMATION;
|
|
if (group->p2p->cross_connect)
|
|
group_capab |= P2P_GROUP_CAPAB_CROSS_CONN;
|
|
if (group->num_members >= group->cfg->max_clients)
|
|
group_capab |= P2P_GROUP_CAPAB_GROUP_LIMIT;
|
|
group_capab |= P2P_GROUP_CAPAB_IP_ADDR_ALLOCATION;
|
|
p2p_buf_add_capability(ie, dev_capab, group_capab);
|
|
}
|
|
|
|
|
|
static void p2p_group_add_noa(struct wpabuf *ie, struct wpabuf *noa)
|
|
{
|
|
if (noa == NULL)
|
|
return;
|
|
/* Notice of Absence */
|
|
wpabuf_put_u8(ie, P2P_ATTR_NOTICE_OF_ABSENCE);
|
|
wpabuf_put_le16(ie, wpabuf_len(noa));
|
|
wpabuf_put_buf(ie, noa);
|
|
}
|
|
|
|
|
|
static struct wpabuf * p2p_group_encaps_probe_resp(struct wpabuf *subelems)
|
|
{
|
|
struct wpabuf *ie;
|
|
const u8 *pos, *end;
|
|
size_t len;
|
|
|
|
if (subelems == NULL)
|
|
return NULL;
|
|
|
|
len = wpabuf_len(subelems) + 100;
|
|
|
|
ie = wpabuf_alloc(len);
|
|
if (ie == NULL)
|
|
return NULL;
|
|
|
|
pos = wpabuf_head(subelems);
|
|
end = pos + wpabuf_len(subelems);
|
|
|
|
while (end > pos) {
|
|
size_t frag_len = end - pos;
|
|
if (frag_len > 251)
|
|
frag_len = 251;
|
|
wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC);
|
|
wpabuf_put_u8(ie, 4 + frag_len);
|
|
wpabuf_put_be32(ie, P2P_IE_VENDOR_TYPE);
|
|
wpabuf_put_data(ie, pos, frag_len);
|
|
pos += frag_len;
|
|
}
|
|
|
|
return ie;
|
|
}
|
|
|
|
|
|
static struct wpabuf * p2p_group_build_beacon_ie(struct p2p_group *group)
|
|
{
|
|
struct wpabuf *ie;
|
|
u8 *len;
|
|
size_t extra = 0;
|
|
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
if (group->p2p->wfd_ie_beacon)
|
|
extra = wpabuf_len(group->p2p->wfd_ie_beacon);
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
if (group->p2p->vendor_elem &&
|
|
group->p2p->vendor_elem[VENDOR_ELEM_BEACON_P2P_GO])
|
|
extra += wpabuf_len(group->p2p->vendor_elem[VENDOR_ELEM_BEACON_P2P_GO]);
|
|
|
|
ie = wpabuf_alloc(257 + extra);
|
|
if (ie == NULL)
|
|
return NULL;
|
|
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
if (group->p2p->wfd_ie_beacon)
|
|
wpabuf_put_buf(ie, group->p2p->wfd_ie_beacon);
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
if (group->p2p->vendor_elem &&
|
|
group->p2p->vendor_elem[VENDOR_ELEM_BEACON_P2P_GO])
|
|
wpabuf_put_buf(ie,
|
|
group->p2p->vendor_elem[VENDOR_ELEM_BEACON_P2P_GO]);
|
|
|
|
len = p2p_buf_add_ie_hdr(ie);
|
|
p2p_group_add_common_ies(group, ie);
|
|
p2p_buf_add_device_id(ie, group->p2p->cfg->dev_addr);
|
|
p2p_group_add_noa(ie, group->noa);
|
|
p2p_buf_update_ie_hdr(ie, len);
|
|
|
|
return ie;
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
|
|
struct wpabuf * p2p_group_get_wfd_ie(struct p2p_group *g)
|
|
{
|
|
return g->wfd_ie;
|
|
}
|
|
|
|
|
|
struct wpabuf * wifi_display_encaps(struct wpabuf *subelems)
|
|
{
|
|
struct wpabuf *ie;
|
|
const u8 *pos, *end;
|
|
|
|
if (subelems == NULL)
|
|
return NULL;
|
|
|
|
ie = wpabuf_alloc(wpabuf_len(subelems) + 100);
|
|
if (ie == NULL)
|
|
return NULL;
|
|
|
|
pos = wpabuf_head(subelems);
|
|
end = pos + wpabuf_len(subelems);
|
|
|
|
while (end > pos) {
|
|
size_t frag_len = end - pos;
|
|
if (frag_len > 251)
|
|
frag_len = 251;
|
|
wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC);
|
|
wpabuf_put_u8(ie, 4 + frag_len);
|
|
wpabuf_put_be32(ie, WFD_IE_VENDOR_TYPE);
|
|
wpabuf_put_data(ie, pos, frag_len);
|
|
pos += frag_len;
|
|
}
|
|
|
|
return ie;
|
|
}
|
|
|
|
|
|
static int wifi_display_add_dev_info_descr(struct wpabuf *buf,
|
|
struct p2p_group_member *m)
|
|
{
|
|
const u8 *pos, *end;
|
|
const u8 *dev_info = NULL;
|
|
const u8 *assoc_bssid = NULL;
|
|
const u8 *coupled_sink = NULL;
|
|
u8 zero_addr[ETH_ALEN];
|
|
|
|
if (m->wfd_ie == NULL)
|
|
return 0;
|
|
|
|
os_memset(zero_addr, 0, ETH_ALEN);
|
|
pos = wpabuf_head_u8(m->wfd_ie);
|
|
end = pos + wpabuf_len(m->wfd_ie);
|
|
while (pos + 1 < end) {
|
|
u8 id;
|
|
u16 len;
|
|
|
|
id = *pos++;
|
|
len = WPA_GET_BE16(pos);
|
|
pos += 2;
|
|
if (pos + len > end)
|
|
break;
|
|
|
|
switch (id) {
|
|
case WFD_SUBELEM_DEVICE_INFO:
|
|
if (len < 6)
|
|
break;
|
|
dev_info = pos;
|
|
break;
|
|
case WFD_SUBELEM_ASSOCIATED_BSSID:
|
|
if (len < ETH_ALEN)
|
|
break;
|
|
assoc_bssid = pos;
|
|
break;
|
|
case WFD_SUBELEM_COUPLED_SINK:
|
|
if (len < 1 + ETH_ALEN)
|
|
break;
|
|
coupled_sink = pos;
|
|
break;
|
|
}
|
|
|
|
pos += len;
|
|
}
|
|
|
|
if (dev_info == NULL)
|
|
return 0;
|
|
|
|
wpabuf_put_u8(buf, 23);
|
|
wpabuf_put_data(buf, m->dev_addr, ETH_ALEN);
|
|
if (assoc_bssid)
|
|
wpabuf_put_data(buf, assoc_bssid, ETH_ALEN);
|
|
else
|
|
wpabuf_put_data(buf, zero_addr, ETH_ALEN);
|
|
wpabuf_put_data(buf, dev_info, 2); /* WFD Device Info */
|
|
wpabuf_put_data(buf, dev_info + 4, 2); /* WFD Device Max Throughput */
|
|
if (coupled_sink) {
|
|
wpabuf_put_data(buf, coupled_sink, 1 + ETH_ALEN);
|
|
} else {
|
|
wpabuf_put_u8(buf, 0);
|
|
wpabuf_put_data(buf, zero_addr, ETH_ALEN);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static struct wpabuf *
|
|
wifi_display_build_go_ie(struct p2p_group *group)
|
|
{
|
|
struct wpabuf *wfd_subelems, *wfd_ie;
|
|
struct p2p_group_member *m;
|
|
u8 *len;
|
|
unsigned int count = 0;
|
|
|
|
if (!group->p2p->wfd_ie_probe_resp)
|
|
return NULL;
|
|
|
|
wfd_subelems = wpabuf_alloc(wpabuf_len(group->p2p->wfd_ie_probe_resp) +
|
|
group->num_members * 24 + 100);
|
|
if (wfd_subelems == NULL)
|
|
return NULL;
|
|
if (group->p2p->wfd_dev_info)
|
|
wpabuf_put_buf(wfd_subelems, group->p2p->wfd_dev_info);
|
|
if (group->p2p->wfd_assoc_bssid)
|
|
wpabuf_put_buf(wfd_subelems,
|
|
group->p2p->wfd_assoc_bssid);
|
|
if (group->p2p->wfd_coupled_sink_info)
|
|
wpabuf_put_buf(wfd_subelems,
|
|
group->p2p->wfd_coupled_sink_info);
|
|
|
|
/* Build WFD Session Info */
|
|
wpabuf_put_u8(wfd_subelems, WFD_SUBELEM_SESSION_INFO);
|
|
len = wpabuf_put(wfd_subelems, 2);
|
|
m = group->members;
|
|
while (m) {
|
|
if (wifi_display_add_dev_info_descr(wfd_subelems, m))
|
|
count++;
|
|
m = m->next;
|
|
}
|
|
|
|
if (count == 0) {
|
|
/* No Wi-Fi Display clients - do not include subelement */
|
|
wfd_subelems->used -= 3;
|
|
} else {
|
|
WPA_PUT_BE16(len, (u8 *) wpabuf_put(wfd_subelems, 0) - len -
|
|
2);
|
|
p2p_dbg(group->p2p, "WFD: WFD Session Info: %u descriptors",
|
|
count);
|
|
}
|
|
|
|
wfd_ie = wifi_display_encaps(wfd_subelems);
|
|
wpabuf_free(wfd_subelems);
|
|
|
|
return wfd_ie;
|
|
}
|
|
|
|
static void wifi_display_group_update(struct p2p_group *group)
|
|
{
|
|
wpabuf_free(group->wfd_ie);
|
|
group->wfd_ie = wifi_display_build_go_ie(group);
|
|
}
|
|
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
|
|
void p2p_buf_add_group_info(struct p2p_group *group, struct wpabuf *buf,
|
|
int max_clients)
|
|
{
|
|
u8 *group_info;
|
|
int count = 0;
|
|
struct p2p_group_member *m;
|
|
|
|
p2p_dbg(group->p2p, "* P2P Group Info");
|
|
group_info = wpabuf_put(buf, 0);
|
|
wpabuf_put_u8(buf, P2P_ATTR_GROUP_INFO);
|
|
wpabuf_put_le16(buf, 0); /* Length to be filled */
|
|
for (m = group->members; m; m = m->next) {
|
|
p2p_client_info(buf, m);
|
|
count++;
|
|
if (max_clients >= 0 && count >= max_clients)
|
|
break;
|
|
}
|
|
WPA_PUT_LE16(group_info + 1,
|
|
(u8 *) wpabuf_put(buf, 0) - group_info - 3);
|
|
}
|
|
|
|
|
|
void p2p_group_buf_add_id(struct p2p_group *group, struct wpabuf *buf)
|
|
{
|
|
p2p_buf_add_group_id(buf, group->p2p->cfg->dev_addr, group->cfg->ssid,
|
|
group->cfg->ssid_len);
|
|
}
|
|
|
|
|
|
static struct wpabuf * p2p_group_build_probe_resp_ie(struct p2p_group *group)
|
|
{
|
|
struct wpabuf *p2p_subelems, *ie;
|
|
|
|
p2p_subelems = wpabuf_alloc(500);
|
|
if (p2p_subelems == NULL)
|
|
return NULL;
|
|
|
|
p2p_group_add_common_ies(group, p2p_subelems);
|
|
p2p_group_add_noa(p2p_subelems, group->noa);
|
|
|
|
/* P2P Device Info */
|
|
p2p_buf_add_device_info(p2p_subelems, group->p2p, NULL);
|
|
|
|
/* P2P Group Info: Only when at least one P2P Client is connected */
|
|
if (group->members)
|
|
p2p_buf_add_group_info(group, p2p_subelems, -1);
|
|
|
|
ie = p2p_group_encaps_probe_resp(p2p_subelems);
|
|
wpabuf_free(p2p_subelems);
|
|
|
|
if (group->p2p->vendor_elem &&
|
|
group->p2p->vendor_elem[VENDOR_ELEM_PROBE_RESP_P2P_GO]) {
|
|
struct wpabuf *extra;
|
|
extra = wpabuf_dup(group->p2p->vendor_elem[VENDOR_ELEM_PROBE_RESP_P2P_GO]);
|
|
ie = wpabuf_concat(extra, ie);
|
|
}
|
|
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
if (group->wfd_ie) {
|
|
struct wpabuf *wfd = wpabuf_dup(group->wfd_ie);
|
|
ie = wpabuf_concat(wfd, ie);
|
|
}
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
return ie;
|
|
}
|
|
|
|
|
|
void p2p_group_update_ies(struct p2p_group *group)
|
|
{
|
|
struct wpabuf *beacon_ie;
|
|
struct wpabuf *probe_resp_ie;
|
|
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
wifi_display_group_update(group);
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
probe_resp_ie = p2p_group_build_probe_resp_ie(group);
|
|
if (probe_resp_ie == NULL)
|
|
return;
|
|
wpa_hexdump_buf(MSG_MSGDUMP, "P2P: Update GO Probe Response P2P IE",
|
|
probe_resp_ie);
|
|
|
|
if (group->beacon_update) {
|
|
beacon_ie = p2p_group_build_beacon_ie(group);
|
|
if (beacon_ie)
|
|
group->beacon_update = 0;
|
|
wpa_hexdump_buf(MSG_MSGDUMP, "P2P: Update GO Beacon P2P IE",
|
|
beacon_ie);
|
|
} else
|
|
beacon_ie = NULL;
|
|
|
|
group->cfg->ie_update(group->cfg->cb_ctx, beacon_ie, probe_resp_ie);
|
|
}
|
|
|
|
|
|
/**
|
|
* p2p_build_client_info - Build P2P Client Info Descriptor
|
|
* @addr: MAC address of the peer device
|
|
* @p2p_ie: P2P IE from (Re)Association Request
|
|
* @dev_capab: Buffer for returning Device Capability
|
|
* @dev_addr: Buffer for returning P2P Device Address
|
|
* Returns: P2P Client Info Descriptor or %NULL on failure
|
|
*
|
|
* This function builds P2P Client Info Descriptor based on the information
|
|
* available from (Re)Association Request frame. Group owner can use this to
|
|
* build the P2P Group Info attribute for Probe Response frames.
|
|
*/
|
|
static struct wpabuf * p2p_build_client_info(const u8 *addr,
|
|
struct wpabuf *p2p_ie,
|
|
u8 *dev_capab, u8 *dev_addr)
|
|
{
|
|
const u8 *spos;
|
|
struct p2p_message msg;
|
|
u8 *len_pos;
|
|
struct wpabuf *buf;
|
|
|
|
if (p2p_ie == NULL)
|
|
return NULL;
|
|
|
|
os_memset(&msg, 0, sizeof(msg));
|
|
if (p2p_parse_p2p_ie(p2p_ie, &msg) ||
|
|
msg.capability == NULL || msg.p2p_device_info == NULL)
|
|
return NULL;
|
|
|
|
buf = wpabuf_alloc(ETH_ALEN + 1 + 1 + msg.p2p_device_info_len);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
|
|
*dev_capab = msg.capability[0];
|
|
os_memcpy(dev_addr, msg.p2p_device_addr, ETH_ALEN);
|
|
|
|
spos = msg.p2p_device_info; /* P2P Device address */
|
|
|
|
/* P2P Client Info Descriptor */
|
|
/* Length to be set */
|
|
len_pos = wpabuf_put(buf, 1);
|
|
/* P2P Device address */
|
|
wpabuf_put_data(buf, spos, ETH_ALEN);
|
|
/* P2P Interface address */
|
|
wpabuf_put_data(buf, addr, ETH_ALEN);
|
|
/* Device Capability Bitmap */
|
|
wpabuf_put_u8(buf, msg.capability[0]);
|
|
/*
|
|
* Config Methods, Primary Device Type, Number of Secondary Device
|
|
* Types, Secondary Device Type List, Device Name copied from
|
|
* Device Info
|
|
*/
|
|
wpabuf_put_data(buf, spos + ETH_ALEN,
|
|
msg.p2p_device_info_len - ETH_ALEN);
|
|
|
|
*len_pos = wpabuf_len(buf) - 1;
|
|
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
static int p2p_group_remove_member(struct p2p_group *group, const u8 *addr)
|
|
{
|
|
struct p2p_group_member *m, *prev;
|
|
|
|
if (group == NULL)
|
|
return 0;
|
|
|
|
m = group->members;
|
|
prev = NULL;
|
|
while (m) {
|
|
if (os_memcmp(m->addr, addr, ETH_ALEN) == 0)
|
|
break;
|
|
prev = m;
|
|
m = m->next;
|
|
}
|
|
|
|
if (m == NULL)
|
|
return 0;
|
|
|
|
if (prev)
|
|
prev->next = m->next;
|
|
else
|
|
group->members = m->next;
|
|
p2p_group_free_member(m);
|
|
group->num_members--;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int p2p_group_notif_assoc(struct p2p_group *group, const u8 *addr,
|
|
const u8 *ie, size_t len)
|
|
{
|
|
struct p2p_group_member *m;
|
|
|
|
if (group == NULL)
|
|
return -1;
|
|
|
|
p2p_add_device(group->p2p, addr, 0, NULL, 0, ie, len, 0);
|
|
|
|
m = os_zalloc(sizeof(*m));
|
|
if (m == NULL)
|
|
return -1;
|
|
os_memcpy(m->addr, addr, ETH_ALEN);
|
|
m->p2p_ie = ieee802_11_vendor_ie_concat(ie, len, P2P_IE_VENDOR_TYPE);
|
|
if (m->p2p_ie) {
|
|
m->client_info = p2p_build_client_info(addr, m->p2p_ie,
|
|
&m->dev_capab,
|
|
m->dev_addr);
|
|
}
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
m->wfd_ie = ieee802_11_vendor_ie_concat(ie, len, WFD_IE_VENDOR_TYPE);
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
p2p_group_remove_member(group, addr);
|
|
|
|
m->next = group->members;
|
|
group->members = m;
|
|
group->num_members++;
|
|
p2p_dbg(group->p2p, "Add client " MACSTR
|
|
" to group (p2p=%d wfd=%d client_info=%d); num_members=%u/%u",
|
|
MAC2STR(addr), m->p2p_ie ? 1 : 0, m->wfd_ie ? 1 : 0,
|
|
m->client_info ? 1 : 0,
|
|
group->num_members, group->cfg->max_clients);
|
|
if (group->num_members == group->cfg->max_clients)
|
|
group->beacon_update = 1;
|
|
p2p_group_update_ies(group);
|
|
if (group->num_members == 1)
|
|
group->cfg->idle_update(group->cfg->cb_ctx, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct wpabuf * p2p_group_assoc_resp_ie(struct p2p_group *group, u8 status)
|
|
{
|
|
struct wpabuf *resp;
|
|
u8 *rlen;
|
|
size_t extra = 0;
|
|
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
if (group->wfd_ie)
|
|
extra = wpabuf_len(group->wfd_ie);
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
if (group->p2p->vendor_elem &&
|
|
group->p2p->vendor_elem[VENDOR_ELEM_P2P_ASSOC_RESP])
|
|
extra += wpabuf_len(group->p2p->vendor_elem[VENDOR_ELEM_P2P_ASSOC_RESP]);
|
|
|
|
/*
|
|
* (Re)Association Response - P2P IE
|
|
* Status attribute (shall be present when association request is
|
|
* denied)
|
|
* Extended Listen Timing (may be present)
|
|
*/
|
|
resp = wpabuf_alloc(20 + extra);
|
|
if (resp == NULL)
|
|
return NULL;
|
|
|
|
#ifdef CONFIG_WIFI_DISPLAY
|
|
if (group->wfd_ie)
|
|
wpabuf_put_buf(resp, group->wfd_ie);
|
|
#endif /* CONFIG_WIFI_DISPLAY */
|
|
|
|
if (group->p2p->vendor_elem &&
|
|
group->p2p->vendor_elem[VENDOR_ELEM_P2P_ASSOC_RESP])
|
|
wpabuf_put_buf(resp,
|
|
group->p2p->vendor_elem[VENDOR_ELEM_P2P_ASSOC_RESP]);
|
|
|
|
rlen = p2p_buf_add_ie_hdr(resp);
|
|
if (status != P2P_SC_SUCCESS)
|
|
p2p_buf_add_status(resp, status);
|
|
p2p_buf_update_ie_hdr(resp, rlen);
|
|
|
|
return resp;
|
|
}
|
|
|
|
|
|
void p2p_group_notif_disassoc(struct p2p_group *group, const u8 *addr)
|
|
{
|
|
if (p2p_group_remove_member(group, addr)) {
|
|
p2p_dbg(group->p2p, "Remove client " MACSTR
|
|
" from group; num_members=%u/%u",
|
|
MAC2STR(addr), group->num_members,
|
|
group->cfg->max_clients);
|
|
if (group->num_members == group->cfg->max_clients - 1)
|
|
group->beacon_update = 1;
|
|
p2p_group_update_ies(group);
|
|
if (group->num_members == 0)
|
|
group->cfg->idle_update(group->cfg->cb_ctx, 1);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* p2p_match_dev_type_member - Match client device type with requested type
|
|
* @m: Group member
|
|
* @wps: WPS TLVs from Probe Request frame (concatenated WPS IEs)
|
|
* Returns: 1 on match, 0 on mismatch
|
|
*
|
|
* This function can be used to match the Requested Device Type attribute in
|
|
* WPS IE with the device types of a group member for deciding whether a GO
|
|
* should reply to a Probe Request frame.
|
|
*/
|
|
static int p2p_match_dev_type_member(struct p2p_group_member *m,
|
|
struct wpabuf *wps)
|
|
{
|
|
const u8 *pos, *end;
|
|
struct wps_parse_attr attr;
|
|
u8 num_sec;
|
|
|
|
if (m->client_info == NULL || wps == NULL)
|
|
return 0;
|
|
|
|
pos = wpabuf_head(m->client_info);
|
|
end = pos + wpabuf_len(m->client_info);
|
|
|
|
pos += 1 + 2 * ETH_ALEN + 1 + 2;
|
|
if (end - pos < WPS_DEV_TYPE_LEN + 1)
|
|
return 0;
|
|
|
|
if (wps_parse_msg(wps, &attr))
|
|
return 1; /* assume no Requested Device Type attributes */
|
|
|
|
if (attr.num_req_dev_type == 0)
|
|
return 1; /* no Requested Device Type attributes -> match */
|
|
|
|
if (dev_type_list_match(pos, attr.req_dev_type, attr.num_req_dev_type))
|
|
return 1; /* Match with client Primary Device Type */
|
|
|
|
pos += WPS_DEV_TYPE_LEN;
|
|
num_sec = *pos++;
|
|
if (end - pos < num_sec * WPS_DEV_TYPE_LEN)
|
|
return 0;
|
|
while (num_sec > 0) {
|
|
num_sec--;
|
|
if (dev_type_list_match(pos, attr.req_dev_type,
|
|
attr.num_req_dev_type))
|
|
return 1; /* Match with client Secondary Device Type */
|
|
pos += WPS_DEV_TYPE_LEN;
|
|
}
|
|
|
|
/* No matching device type found */
|
|
return 0;
|
|
}
|
|
|
|
|
|
int p2p_group_match_dev_type(struct p2p_group *group, struct wpabuf *wps)
|
|
{
|
|
struct p2p_group_member *m;
|
|
|
|
if (p2p_match_dev_type(group->p2p, wps))
|
|
return 1; /* Match with own device type */
|
|
|
|
for (m = group->members; m; m = m->next) {
|
|
if (p2p_match_dev_type_member(m, wps))
|
|
return 1; /* Match with group client device type */
|
|
}
|
|
|
|
/* No match with Requested Device Type */
|
|
return 0;
|
|
}
|
|
|
|
|
|
int p2p_group_match_dev_id(struct p2p_group *group, struct wpabuf *p2p)
|
|
{
|
|
struct p2p_group_member *m;
|
|
struct p2p_message msg;
|
|
|
|
os_memset(&msg, 0, sizeof(msg));
|
|
if (p2p_parse_p2p_ie(p2p, &msg))
|
|
return 1; /* Failed to parse - assume no filter on Device ID */
|
|
|
|
if (!msg.device_id)
|
|
return 1; /* No filter on Device ID */
|
|
|
|
if (os_memcmp(msg.device_id, group->p2p->cfg->dev_addr, ETH_ALEN) == 0)
|
|
return 1; /* Match with our P2P Device Address */
|
|
|
|
for (m = group->members; m; m = m->next) {
|
|
if (os_memcmp(msg.device_id, m->dev_addr, ETH_ALEN) == 0)
|
|
return 1; /* Match with group client P2P Device Address */
|
|
}
|
|
|
|
/* No match with Device ID */
|
|
return 0;
|
|
}
|
|
|
|
|
|
void p2p_group_notif_formation_done(struct p2p_group *group)
|
|
{
|
|
if (group == NULL)
|
|
return;
|
|
group->group_formation = 0;
|
|
group->beacon_update = 1;
|
|
p2p_group_update_ies(group);
|
|
}
|
|
|
|
|
|
int p2p_group_notif_noa(struct p2p_group *group, const u8 *noa,
|
|
size_t noa_len)
|
|
{
|
|
if (noa == NULL) {
|
|
wpabuf_free(group->noa);
|
|
group->noa = NULL;
|
|
} else {
|
|
if (group->noa) {
|
|
if (wpabuf_size(group->noa) >= noa_len) {
|
|
group->noa->used = 0;
|
|
wpabuf_put_data(group->noa, noa, noa_len);
|
|
} else {
|
|
wpabuf_free(group->noa);
|
|
group->noa = NULL;
|
|
}
|
|
}
|
|
|
|
if (!group->noa) {
|
|
group->noa = wpabuf_alloc_copy(noa, noa_len);
|
|
if (group->noa == NULL)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
group->beacon_update = 1;
|
|
p2p_group_update_ies(group);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct p2p_group_member * p2p_group_get_client(struct p2p_group *group,
|
|
const u8 *dev_id)
|
|
{
|
|
struct p2p_group_member *m;
|
|
|
|
for (m = group->members; m; m = m->next) {
|
|
if (os_memcmp(dev_id, m->dev_addr, ETH_ALEN) == 0)
|
|
return m;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static struct p2p_group_member * p2p_group_get_client_iface(
|
|
struct p2p_group *group, const u8 *interface_addr)
|
|
{
|
|
struct p2p_group_member *m;
|
|
|
|
for (m = group->members; m; m = m->next) {
|
|
if (os_memcmp(interface_addr, m->addr, ETH_ALEN) == 0)
|
|
return m;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const u8 * p2p_group_get_dev_addr(struct p2p_group *group, const u8 *addr)
|
|
{
|
|
struct p2p_group_member *m;
|
|
|
|
if (group == NULL)
|
|
return NULL;
|
|
m = p2p_group_get_client_iface(group, addr);
|
|
if (m && !is_zero_ether_addr(m->dev_addr))
|
|
return m->dev_addr;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static struct wpabuf * p2p_build_go_disc_req(void)
|
|
{
|
|
struct wpabuf *buf;
|
|
|
|
buf = wpabuf_alloc(100);
|
|
if (buf == NULL)
|
|
return NULL;
|
|
|
|
p2p_buf_add_action_hdr(buf, P2P_GO_DISC_REQ, 0);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
int p2p_group_go_discover(struct p2p_group *group, const u8 *dev_id,
|
|
const u8 *searching_dev, int rx_freq)
|
|
{
|
|
struct p2p_group_member *m;
|
|
struct wpabuf *req;
|
|
struct p2p_data *p2p = group->p2p;
|
|
int freq;
|
|
|
|
m = p2p_group_get_client(group, dev_id);
|
|
if (m == NULL || m->client_info == NULL) {
|
|
p2p_dbg(group->p2p, "Requested client was not in this group "
|
|
MACSTR, MAC2STR(group->cfg->interface_addr));
|
|
return -1;
|
|
}
|
|
|
|
if (!(m->dev_capab & P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY)) {
|
|
p2p_dbg(group->p2p, "Requested client does not support client discoverability");
|
|
return -1;
|
|
}
|
|
|
|
p2p_dbg(group->p2p, "Schedule GO Discoverability Request to be sent to "
|
|
MACSTR, MAC2STR(dev_id));
|
|
|
|
req = p2p_build_go_disc_req();
|
|
if (req == NULL)
|
|
return -1;
|
|
|
|
/* TODO: Should really use group operating frequency here */
|
|
freq = rx_freq;
|
|
|
|
p2p->pending_action_state = P2P_PENDING_GO_DISC_REQ;
|
|
if (p2p->cfg->send_action(p2p->cfg->cb_ctx, freq, m->addr,
|
|
group->cfg->interface_addr,
|
|
group->cfg->interface_addr,
|
|
wpabuf_head(req), wpabuf_len(req), 200) < 0)
|
|
{
|
|
p2p_dbg(p2p, "Failed to send Action frame");
|
|
}
|
|
|
|
wpabuf_free(req);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
const u8 * p2p_group_get_interface_addr(struct p2p_group *group)
|
|
{
|
|
return group->cfg->interface_addr;
|
|
}
|
|
|
|
|
|
u8 p2p_group_presence_req(struct p2p_group *group,
|
|
const u8 *client_interface_addr,
|
|
const u8 *noa, size_t noa_len)
|
|
{
|
|
struct p2p_group_member *m;
|
|
u8 curr_noa[50];
|
|
int curr_noa_len;
|
|
|
|
m = p2p_group_get_client_iface(group, client_interface_addr);
|
|
if (m == NULL || m->client_info == NULL) {
|
|
p2p_dbg(group->p2p, "Client was not in this group");
|
|
return P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE;
|
|
}
|
|
|
|
wpa_hexdump(MSG_DEBUG, "P2P: Presence Request NoA", noa, noa_len);
|
|
|
|
if (group->p2p->cfg->get_noa)
|
|
curr_noa_len = group->p2p->cfg->get_noa(
|
|
group->p2p->cfg->cb_ctx, group->cfg->interface_addr,
|
|
curr_noa, sizeof(curr_noa));
|
|
else
|
|
curr_noa_len = -1;
|
|
if (curr_noa_len < 0)
|
|
p2p_dbg(group->p2p, "Failed to fetch current NoA");
|
|
else if (curr_noa_len == 0)
|
|
p2p_dbg(group->p2p, "No NoA being advertized");
|
|
else
|
|
wpa_hexdump(MSG_DEBUG, "P2P: Current NoA", curr_noa,
|
|
curr_noa_len);
|
|
|
|
/* TODO: properly process request and store copy */
|
|
if (curr_noa_len > 0 || curr_noa_len == -1)
|
|
return P2P_SC_FAIL_UNABLE_TO_ACCOMMODATE;
|
|
|
|
return P2P_SC_SUCCESS;
|
|
}
|
|
|
|
|
|
unsigned int p2p_get_group_num_members(struct p2p_group *group)
|
|
{
|
|
return group->num_members;
|
|
}
|
|
|
|
|
|
const u8 * p2p_iterate_group_members(struct p2p_group *group, void **next)
|
|
{
|
|
struct p2p_group_member *iter = *next;
|
|
|
|
if (!iter)
|
|
iter = group->members;
|
|
else
|
|
iter = iter->next;
|
|
|
|
*next = iter;
|
|
|
|
if (!iter)
|
|
return NULL;
|
|
|
|
return iter->dev_addr;
|
|
}
|
|
|
|
|
|
int p2p_group_is_client_connected(struct p2p_group *group, const u8 *dev_addr)
|
|
{
|
|
struct p2p_group_member *m;
|
|
|
|
for (m = group->members; m; m = m->next) {
|
|
if (os_memcmp(m->dev_addr, dev_addr, ETH_ALEN) == 0)
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int p2p_group_is_group_id_match(struct p2p_group *group, const u8 *group_id,
|
|
size_t group_id_len)
|
|
{
|
|
if (group_id_len != ETH_ALEN + group->cfg->ssid_len)
|
|
return 0;
|
|
if (os_memcmp(group_id, group->p2p->cfg->dev_addr, ETH_ALEN) != 0)
|
|
return 0;
|
|
return os_memcmp(group_id + ETH_ALEN, group->cfg->ssid,
|
|
group->cfg->ssid_len) == 0;
|
|
}
|
|
|
|
|
|
void p2p_group_force_beacon_update_ies(struct p2p_group *group)
|
|
{
|
|
group->beacon_update = 1;
|
|
p2p_group_update_ies(group);
|
|
}
|
|
|
|
|
|
int p2p_group_get_freq(struct p2p_group *group)
|
|
{
|
|
return group->cfg->freq;
|
|
}
|
|
|
|
|
|
const struct p2p_group_config * p2p_group_get_config(struct p2p_group *group)
|
|
{
|
|
return group->cfg;
|
|
}
|
|
|
|
|
|
void p2p_loop_on_all_groups(struct p2p_data *p2p,
|
|
int (*group_callback)(struct p2p_group *group,
|
|
void *user_data),
|
|
void *user_data)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < p2p->num_groups; i++) {
|
|
if (!group_callback(p2p->groups[i], user_data))
|
|
break;
|
|
}
|
|
}
|