blob: 6ea18b74812fb1ab87e6b4ae925e4deb5783e7e4 [file] [log] [blame]
/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdbool.h>
#include <stdarg.h>
#include <errno.h>
#include <stdio.h>
#include <ell/ell.h>
#include "src/p2putil.h"
#include "src/ie.h"
void p2p_attr_iter_init(struct p2p_attr_iter *iter, const uint8_t *pdu,
size_t len)
{
iter->pos = pdu;
iter->end = pdu + len;
iter->type = -1;
}
/* Wi-Fi P2P Technical Specification v1.7 Section 4.1.1 */
bool p2p_attr_iter_next(struct p2p_attr_iter *iter)
{
if (iter->type != (enum p2p_attr) -1)
iter->pos += 3 + iter->len;
if (iter->pos + 3 > iter->end ||
iter->pos + 3 + l_get_le16(iter->pos + 1) > iter->end)
return false;
iter->type = iter->pos[0];
iter->len = l_get_le16(iter->pos + 1);
return true;
}
void wfd_subelem_iter_init(struct wfd_subelem_iter *iter, const uint8_t *pdu,
size_t len)
{
iter->pos = pdu;
iter->end = pdu + len;
iter->type = -1;
}
bool wfd_subelem_iter_next(struct wfd_subelem_iter *iter)
{
if (iter->type != (enum wfd_subelem_type) -1)
iter->pos += 3 + iter->len;
if (iter->pos + 3 > iter->end ||
iter->pos + 3 + l_get_be16(iter->pos + 1) > iter->end)
return false;
iter->type = iter->pos[0];
iter->len = l_get_be16(iter->pos + 1);
return true;
}
enum attr_flag {
ATTR_FLAG_REQUIRED = 0x1, /* Always required */
};
typedef bool (*attr_handler)(const uint8_t *, size_t, void *);
static bool extract_p2p_byte(const uint8_t *attr, size_t len,
void *data)
{
uint8_t *out = data;
if (len != 1)
return false;
*out = attr[0];
return true;
}
/* Section 4.1.2 */
static bool extract_p2p_status(const uint8_t *attr, size_t len,
void *data)
{
enum p2p_attr_status_code *out = data;
if (len != 1)
return false;
*out = attr[0];
return true;
}
/* Section 4.1.4 */
static bool extract_p2p_capability(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_capability_attr *out = data;
if (len != 2)
return false;
out->device_caps = attr[0];
out->group_caps = attr[1];
return true;
}
/* Section 4.1.5, 4.1.9, 4.1.11, ... */
static bool extract_p2p_addr(const uint8_t *attr, size_t len,
void *data)
{
uint8_t *out = data;
if (len != 6)
return false;
memcpy(out, attr, 6);
return true;
}
struct p2p_go_intent_attr {
uint8_t intent;
bool tie_breaker;
};
/* Section 4.1.6 */
static bool extract_p2p_go_intent(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_go_intent_attr *out = data;
uint8_t intent;
if (len != 1)
return false;
intent = attr[0] >> 1;
if (intent & ~15)
return false;
out->intent = intent;
out->tie_breaker = attr[0] & 1;
return true;
}
/* Section 4.1.7 */
static bool extract_p2p_config_timeout(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_config_timeout_attr *out = data;
if (len != 2)
return false;
out->go_config_timeout = attr[0];
out->client_config_timeout = attr[1];
return true;
}
/* Section 4.1.8, 4.1.19, ... */
static bool extract_p2p_channel(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_channel_attr *out = data;
if (len != 5)
return false;
out->country[0] = attr[0];
out->country[1] = attr[1];
out->country[2] = attr[2];
out->oper_class = attr[3];
out->channel_num = attr[4];
return true;
}
/* Section 4.1.10 */
static bool extract_p2p_listen_timing(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_extended_listen_timing_attr *out = data;
if (len != 4)
return false;
out->avail_period_ms = l_get_le16(attr + 0);
out->avail_interval_ms = l_get_le16(attr + 2);
return true;
}
/* Section 4.1.13 */
static bool extract_p2p_channel_list(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_channel_list_attr *out = data;
/*
* Some devices reply with an empty Channel Entry List inside the
* Channel List attribute of a GO Negotiation Response (status 1),
* so tolerate a length of 3.
*/
if (len < 3)
return false;
out->country[0] = *attr++;
out->country[1] = *attr++;
out->country[2] = *attr++;
len -= 3;
out->channel_entries = l_queue_new();
while (len) {
struct p2p_channel_entries *entries;
if (len < 2 || len < (size_t) 2 + attr[1]) {
l_queue_destroy(out->channel_entries, l_free);
out->channel_entries = NULL;
return false;
}
entries = l_malloc(sizeof(struct p2p_channel_entries) + attr[1]);
entries->oper_class = *attr++;
entries->n_channels = *attr++;
memcpy(entries->channels, attr, entries->n_channels);
l_queue_push_tail(out->channel_entries, entries);
attr += entries->n_channels;
len -= 2 + entries->n_channels;
}
return true;
}
/* Section 4.1.14 */
static bool extract_p2p_notice_of_absence(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_notice_of_absence_attr *out = data;
uint8_t index;
uint8_t ct_window;
bool opp_ps;
if (len % 13 != 2)
return false;
index = *attr++;
ct_window = *attr & 127;
opp_ps = *attr++ >> 7;
len -= 2;
if (opp_ps && !ct_window)
return false;
out->index = index;
out->opp_ps = opp_ps;
out->ct_window = ct_window;
out->descriptors = l_queue_new();
while (len) {
struct p2p_notice_of_absence_desc *desc;
desc = l_new(struct p2p_notice_of_absence_desc, 1);
desc->count_type = attr[0];
desc->duration = l_get_le32(attr + 1);
desc->interval = l_get_le32(attr + 5);
desc->start_time = l_get_le32(attr + 9);
l_queue_push_tail(out->descriptors, desc);
attr += 13;
len -= 13;
}
return true;
}
/* Section 4.1.15 */
static bool extract_p2p_device_info(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_device_info_attr *out = data;
int r;
int name_len;
int i;
int types_num;
if (len < 21)
return false;
memcpy(out->device_addr, attr + 0, 6);
out->wsc_config_methods = l_get_be16(attr + 6);
r = wsc_parse_primary_device_type(attr + 8, 8,
&out->primary_device_type);
if (r < 0)
return false;
types_num = attr[16];
if (len < 17u + types_num * 8 + 4)
return false;
if (l_get_be16(attr + 17 + types_num * 8) != WSC_ATTR_DEVICE_NAME)
return false;
name_len = l_get_be16(attr + 17 + types_num * 8 + 2);
if (len < 17u + types_num * 8 + 4 + name_len || name_len > 32)
return false;
out->secondary_device_types = l_queue_new();
for (i = 0; i < types_num; i++) {
struct wsc_primary_device_type *device_type =
l_new(struct wsc_primary_device_type, 1);
l_queue_push_tail(out->secondary_device_types, device_type);
r = wsc_parse_primary_device_type(attr + 17 + i * 8, 8,
device_type);
if (r < 0) {
l_queue_destroy(out->secondary_device_types, l_free);
out->secondary_device_types = NULL;
return false;
}
}
memcpy(out->device_name, attr + 17 + types_num * 8 + 4, name_len);
return true;
}
static void p2p_clear_client_info_descriptor(void *data)
{
struct p2p_client_info_descriptor *desc = data;
l_queue_destroy(desc->secondary_device_types, l_free);
l_free(desc);
}
/* Section 4.1.16 */
static bool extract_p2p_group_info(const uint8_t *attr, size_t len,
void *data)
{
struct l_queue **out = data;
while (len) {
uint8_t desc_len = *attr++;
struct p2p_client_info_descriptor *desc;
int r, name_len, i, types_num;
if (len < 1u + desc_len)
goto error;
if (!*out)
*out = l_queue_new();
desc = l_new(struct p2p_client_info_descriptor, 1);
l_queue_push_tail(*out, desc);
memcpy(desc->device_addr, attr + 0, 6);
memcpy(desc->interface_addr, attr + 6, 6);
desc->device_caps = attr[12];
desc->wsc_config_methods = l_get_be16(attr + 13);
r = wsc_parse_primary_device_type(attr + 15, 8,
&desc->primary_device_type);
if (r < 0)
goto error;
types_num = attr[23];
if (desc_len < 24 + types_num * 8 + 4)
goto error;
if (l_get_be16(attr + 24 + types_num * 8) !=
WSC_ATTR_DEVICE_NAME)
goto error;
name_len = l_get_be16(attr + 24 + types_num * 8 + 2);
if (desc_len < 24 + types_num * 8 + 4 + name_len ||
name_len > 32)
goto error;
desc->secondary_device_types = l_queue_new();
for (i = 0; i < types_num; i++) {
struct wsc_primary_device_type *device_type =
l_new(struct wsc_primary_device_type, 1);
l_queue_push_tail(desc->secondary_device_types,
device_type);
r = wsc_parse_primary_device_type(attr + 24 + i * 8, 8,
device_type);
if (r < 0)
goto error;
}
memcpy(desc->device_name, attr + 24 + types_num * 8 + 4,
name_len);
attr += 24 + types_num * 8 + 4 + name_len;
len -= 1 + desc_len;
}
return true;
error:
l_queue_destroy(*out, p2p_clear_client_info_descriptor);
*out = NULL;
return false;
}
/* Section 4.1.17, 4.1.29, ... */
static bool extract_p2p_group_id(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_group_id_attr *out = data;
if (len < 6 || len > 38)
return false;
memcpy(out->device_addr, attr + 0, 6);
memcpy(out->ssid, attr + 6, len - 6);
return true;
}
/* Section 4.1.18 */
static bool extract_p2p_interface(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_interface_attr *out = data;
int addr_count;
if (len < 7)
return false;
addr_count = attr[6];
if (len < 7u + addr_count * 6)
return false;
memcpy(out->device_addr, attr + 0, 6);
out->interface_addrs = l_queue_new();
attr += 7;
while (addr_count--) {
l_queue_push_tail(out->interface_addrs, l_memdup(attr, 6));
attr += 6;
}
return true;
}
/* Section 4.1.20 */
static bool extract_p2p_invitation_flags(const uint8_t *attr, size_t len,
void *data)
{
bool *out = data;
if (len != 1)
return false;
*out = attr[0] & 1; /* Invitation Type flag */
return true;
}
/* Section 4.1.22 */
static bool extract_p2p_service_hashes(const uint8_t *attr, size_t len,
void *data)
{
struct l_queue **out = data;
if (len % 6 != 0)
return false;
*out = l_queue_new();
while (len) {
l_queue_push_tail(*out, l_memdup(attr, 6));
attr += 6;
len -= 6;
}
return true;
}
/* Section 4.1.23 */
static bool extract_p2p_session_info(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_session_info_data_attr *out = data;
out->data_len = len;
memcpy(out->data, data, len);
return true;
}
/* Section 4.1.25 */
static bool extract_p2p_advertisement_id(const uint8_t *attr, size_t len,
void *data)
{
struct p2p_advertisement_id_info_attr *out = data;
if (len != 10)
return false;
out->advertisement_id = l_get_le32(attr + 0);
memcpy(out->service_mac_addr, attr + 4, 6);
return true;
}
static void p2p_clear_advertised_service_descriptor(void *data)
{
struct p2p_advertised_service_descriptor *desc = data;
l_free(desc->service_name);
l_free(desc);
}
/* Section 4.1.26 */
static bool extract_p2p_advertised_service_info(const uint8_t *attr, size_t len,
void *data)
{
struct l_queue **out = data;
while (len) {
struct p2p_advertised_service_descriptor *desc;
int name_len;
if (len < 7)
goto error;
name_len = attr[6];
if (len < 7u + name_len)
goto error;
if (!l_utf8_validate((const char *) attr + 7, name_len, NULL))
goto error;
if (!*out)
*out = l_queue_new();
desc = l_new(struct p2p_advertised_service_descriptor, 1);
l_queue_push_tail(*out, desc);
desc->advertisement_id = l_get_le32(attr + 0);
desc->wsc_config_methods = l_get_be16(attr + 4);
desc->service_name = l_strndup((const char *) attr + 7,
name_len);
attr += 7 + name_len;
len -= 7 + name_len;
}
return true;
error:
l_queue_destroy(*out, p2p_clear_advertised_service_descriptor);
return false;
}
/* Section 4.1.27 */
static bool extract_p2p_session_id(const uint8_t *attr, size_t len, void *data)
{
struct p2p_session_id_info_attr *out = data;
if (len != 10)
return false;
out->session_id = l_get_le32(attr + 0);
memcpy(out->session_mac_addr, attr + 4, 6);
return true;
}
/* Section 4.1.28 */
static bool extract_p2p_feature_capability(const uint8_t *attr, size_t len,
void *data)
{
enum p2p_asp_coordination_transport_protocol *out = data;
if (len != 2)
return false;
if (attr[0] == 0x01)
*out = P2P_ASP_TRANSPORT_UDP;
else
*out = P2P_ASP_TRANSPORT_UNKNOWN;
return true;
}
static attr_handler handler_for_type(enum p2p_attr type)
{
switch (type) {
case P2P_ATTR_STATUS:
return extract_p2p_status;
case P2P_ATTR_MINOR_REASON_CODE:
return extract_p2p_byte;
case P2P_ATTR_P2P_CAPABILITY:
return extract_p2p_capability;
case P2P_ATTR_P2P_DEVICE_ID:
case P2P_ATTR_P2P_GROUP_BSSID:
case P2P_ATTR_INTENDED_P2P_INTERFACE_ADDR:
return extract_p2p_addr;
case P2P_ATTR_GO_INTENT:
return extract_p2p_go_intent;
case P2P_ATTR_CONFIGURATION_TIMEOUT:
return extract_p2p_config_timeout;
case P2P_ATTR_LISTEN_CHANNEL:
case P2P_ATTR_OPERATING_CHANNEL:
return extract_p2p_channel;
case P2P_ATTR_EXTENDED_LISTEN_TIMING:
return extract_p2p_listen_timing;
case P2P_ATTR_P2P_MANAGEABILITY:
break;
case P2P_ATTR_CHANNEL_LIST:
return extract_p2p_channel_list;
case P2P_ATTR_NOTICE_OF_ABSENCE:
return extract_p2p_notice_of_absence;
case P2P_ATTR_P2P_DEVICE_INFO:
return extract_p2p_device_info;
case P2P_ATTR_P2P_GROUP_INFO:
return extract_p2p_group_info;
case P2P_ATTR_P2P_GROUP_ID:
case P2P_ATTR_PERSISTENT_GROUP_INFO:
return extract_p2p_group_id;
case P2P_ATTR_P2P_INTERFACE:
return extract_p2p_interface;
case P2P_ATTR_INVITATION_FLAGS:
return extract_p2p_invitation_flags;
case P2P_ATTR_OOB_GO_NEGOTIATION_CHANNEL:
break;
case P2P_ATTR_SVC_HASH:
return extract_p2p_service_hashes;
case P2P_ATTR_SESSION_INFO_DATA_INFO:
return extract_p2p_session_info;
case P2P_ATTR_CONNECTION_CAPABILITY_INFO:
return extract_p2p_byte;
case P2P_ATTR_ADVERTISEMENT_ID_INFO:
return extract_p2p_advertisement_id;
case P2P_ATTR_ADVERTISED_SVC_INFO:
return extract_p2p_advertised_service_info;
case P2P_ATTR_SESSION_ID_INFO:
return extract_p2p_session_id;
case P2P_ATTR_FEATURE_CAPABILITY:
return extract_p2p_feature_capability;
case P2P_ATTR_VENDOR_SPECIFIC_ATTR:
break;
}
return NULL;
}
struct attr_handler_entry {
enum p2p_attr type;
unsigned int flags;
void *data;
bool present;
};
/*
* This function may find an error after having parsed part of the message
* and may have allocated memory so the output needs to be deallocated
* properly even on error return values.
*/
static int p2p_parse_attrs(const uint8_t *pdu, size_t len, int type, ...)
{
struct p2p_attr_iter iter;
uint8_t *p2p_data;
ssize_t p2p_len;
struct l_queue *entries;
va_list args;
bool have_required = true;
bool parse_error = false;
struct attr_handler_entry *entry;
const struct l_queue_entry *e;
p2p_data = ie_tlv_extract_p2p_payload(pdu, len, &p2p_len);
if (!p2p_data)
return p2p_len;
p2p_attr_iter_init(&iter, p2p_data, p2p_len);
va_start(args, type);
entries = l_queue_new();
while (type != -1) {
entry = l_new(struct attr_handler_entry, 1);
entry->type = type;
entry->flags = va_arg(args, unsigned int);
entry->data = va_arg(args, void *);
type = va_arg(args, enum p2p_attr);
l_queue_push_tail(entries, entry);
}
va_end(args);
while (p2p_attr_iter_next(&iter)) {
attr_handler handler;
for (e = l_queue_get_entries(entries); e; e = e->next) {
entry = e->data;
if (p2p_attr_iter_get_type(&iter) == entry->type)
break;
}
if (!e || entry->present) {
parse_error = true;
goto done;
}
entry->present = true;
handler = handler_for_type(entry->type);
if (!handler(p2p_attr_iter_get_data(&iter),
p2p_attr_iter_get_length(&iter), entry->data)) {
parse_error = true;
goto done;
}
}
for (e = l_queue_get_entries(entries); e; e = e->next) {
entry = e->data;
if (!entry->present && (entry->flags & ATTR_FLAG_REQUIRED)) {
have_required = false;
goto done;
}
}
done:
l_free(p2p_data);
l_queue_destroy(entries, l_free);
if (!have_required)
return -EINVAL;
if (parse_error)
return -EBADMSG;
return 0;
}
#define REQUIRED(attr, out) \
P2P_ATTR_ ## attr, ATTR_FLAG_REQUIRED, out
#define OPTIONAL(attr, out) \
P2P_ATTR_ ## attr, 0, out
/* Section 4.2.1 */
int p2p_parse_beacon(const uint8_t *pdu, size_t len, struct p2p_beacon *out)
{
struct p2p_beacon d = {};
int r;
r = p2p_parse_attrs(pdu, len,
REQUIRED(P2P_CAPABILITY, &d.capability),
REQUIRED(P2P_DEVICE_ID, &d.device_addr),
OPTIONAL(NOTICE_OF_ABSENCE, &d.notice_of_absence),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
else
p2p_clear_beacon(&d);
return r;
}
/* Section 4.2.2 */
int p2p_parse_probe_req(const uint8_t *pdu, size_t len,
struct p2p_probe_req *out)
{
struct p2p_probe_req d = {};
int r;
r = p2p_parse_attrs(pdu, len,
REQUIRED(P2P_CAPABILITY, &d.capability),
OPTIONAL(P2P_DEVICE_ID, &d.device_addr),
OPTIONAL(LISTEN_CHANNEL, &d.listen_channel),
OPTIONAL(EXTENDED_LISTEN_TIMING,
&d.listen_availability),
OPTIONAL(P2P_DEVICE_INFO, &d.device_info),
OPTIONAL(OPERATING_CHANNEL, &d.operating_channel),
OPTIONAL(SVC_HASH, &d.service_hashes),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
else
p2p_clear_probe_req(&d);
/*
* The additional WSC IE attributes are already covered by
* wsc_parse_probe_request.
*/
return r;
}
/* Section 4.2.3 */
int p2p_parse_probe_resp(const uint8_t *pdu, size_t len,
struct p2p_probe_resp *out)
{
struct p2p_probe_resp d = {};
int r;
r = p2p_parse_attrs(pdu, len,
REQUIRED(P2P_CAPABILITY, &d.capability),
OPTIONAL(EXTENDED_LISTEN_TIMING,
&d.listen_availability),
OPTIONAL(NOTICE_OF_ABSENCE, &d.notice_of_absence),
REQUIRED(P2P_DEVICE_INFO, &d.device_info),
OPTIONAL(P2P_GROUP_INFO, &d.group_clients),
OPTIONAL(ADVERTISED_SVC_INFO, &d.advertised_svcs),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
else
p2p_clear_probe_resp(&d);
return r;
}
/* Section 4.2.4 */
int p2p_parse_association_req(const uint8_t *pdu, size_t len,
struct p2p_association_req *out)
{
struct p2p_association_req d = {};
int r;
r = p2p_parse_attrs(pdu, len,
REQUIRED(P2P_CAPABILITY, &d.capability),
OPTIONAL(EXTENDED_LISTEN_TIMING,
&d.listen_availability),
OPTIONAL(P2P_DEVICE_INFO, &d.device_info),
OPTIONAL(P2P_INTERFACE, &d.interface),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
else
p2p_clear_association_req(&d);
return r;
}
/* Section 4.2.5 */
int p2p_parse_association_resp(const uint8_t *pdu, size_t len,
struct p2p_association_resp *out)
{
struct p2p_association_resp d = {};
int r;
r = p2p_parse_attrs(pdu, len,
OPTIONAL(STATUS, &d.status),
OPTIONAL(EXTENDED_LISTEN_TIMING,
&d.listen_availability),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
return r;
}
/* Section 4.2.6 */
int p2p_parse_deauthentication(const uint8_t *pdu, size_t len,
struct p2p_deauthentication *out)
{
int r;
uint8_t reason = 0;
r = p2p_parse_attrs(pdu, len,
REQUIRED(MINOR_REASON_CODE, &reason),
-1);
/* The P2P IE is optional */
if (r < 0 && r != -ENOENT)
return r;
out->minor_reason_code = reason;
return 0;
}
/* Section 4.2.7 */
int p2p_parse_disassociation(const uint8_t *pdu, size_t len,
struct p2p_disassociation *out)
{
int r;
uint8_t reason = 0;
r = p2p_parse_attrs(pdu, len,
REQUIRED(MINOR_REASON_CODE, &reason),
-1);
/* The P2P IE is optional */
if (r < 0 && r != -ENOENT)
return r;
out->minor_reason_code = reason;
return 0;
}
#define WSC_REQUIRED(attr, out) \
WSC_ATTR_ ## attr, WSC_ATTR_FLAG_REQUIRED, out
#define WSC_OPTIONAL(attr, out) \
WSC_ATTR_ ## attr, 0, out
/* Section 4.2.9.2 */
int p2p_parse_go_negotiation_req(const uint8_t *pdu, size_t len,
struct p2p_go_negotiation_req *out)
{
struct p2p_go_negotiation_req d = {};
int r;
struct p2p_go_intent_attr go_intent;
uint8_t *wsc_data;
ssize_t wsc_len;
uint8_t wsc_version;
if (len < 1)
return -EINVAL;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(P2P_CAPABILITY, &d.capability),
REQUIRED(GO_INTENT, &go_intent),
REQUIRED(CONFIGURATION_TIMEOUT, &d.config_timeout),
REQUIRED(LISTEN_CHANNEL, &d.listen_channel),
OPTIONAL(EXTENDED_LISTEN_TIMING,
&d.listen_availability),
REQUIRED(INTENDED_P2P_INTERFACE_ADDR,
&d.intended_interface_addr),
REQUIRED(CHANNEL_LIST, &d.channel_list),
REQUIRED(P2P_DEVICE_INFO, &d.device_info),
REQUIRED(OPERATING_CHANNEL, &d.operating_channel),
-1);
if (r < 0)
goto error;
wsc_data = ie_tlv_extract_wsc_payload(pdu + 1, len - 1, &wsc_len);
if (!wsc_data) {
r = wsc_len;
goto error;
}
r = wsc_parse_attrs(wsc_data, wsc_len, NULL, NULL, 0, NULL,
WSC_REQUIRED(VERSION, &wsc_version),
WSC_REQUIRED(DEVICE_PASSWORD_ID, &d.device_password_id),
WSC_ATTR_INVALID);
l_free(wsc_data);
if (r < 0)
goto error;
d.go_intent = go_intent.intent;
d.go_tie_breaker = go_intent.tie_breaker;
d.wfd = ie_tlv_extract_wfd_payload(pdu + 1, len - 1, &d.wfd_size);
memcpy(out, &d, sizeof(d));
return 0;
error:
p2p_clear_go_negotiation_req(&d);
return r;
}
/* Section 4.2.9.3 */
int p2p_parse_go_negotiation_resp(const uint8_t *pdu, size_t len,
struct p2p_go_negotiation_resp *out)
{
struct p2p_go_negotiation_resp d = {};
int r;
struct p2p_go_intent_attr go_intent;
uint8_t *wsc_data;
ssize_t wsc_len;
uint8_t wsc_version;
if (len < 1)
return -EINVAL;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(STATUS, &d.status),
REQUIRED(P2P_CAPABILITY, &d.capability),
REQUIRED(GO_INTENT, &go_intent),
REQUIRED(CONFIGURATION_TIMEOUT, &d.config_timeout),
OPTIONAL(OPERATING_CHANNEL, &d.operating_channel),
REQUIRED(INTENDED_P2P_INTERFACE_ADDR,
&d.intended_interface_addr),
REQUIRED(CHANNEL_LIST, &d.channel_list),
REQUIRED(P2P_DEVICE_INFO, &d.device_info),
OPTIONAL(P2P_GROUP_ID, &d.group_id),
-1);
if (r < 0)
goto error;
wsc_data = ie_tlv_extract_wsc_payload(pdu + 1, len - 1, &wsc_len);
if (!wsc_data) {
r = wsc_len;
goto error;
}
r = wsc_parse_attrs(wsc_data, wsc_len, NULL, NULL, 0, NULL,
WSC_REQUIRED(VERSION, &wsc_version),
WSC_REQUIRED(DEVICE_PASSWORD_ID, &d.device_password_id),
WSC_ATTR_INVALID);
l_free(wsc_data);
if (r < 0)
goto error;
d.go_intent = go_intent.intent;
d.go_tie_breaker = go_intent.tie_breaker;
d.wfd = ie_tlv_extract_wfd_payload(pdu + 1, len - 1, &d.wfd_size);
memcpy(out, &d, sizeof(d));
return 0;
error:
p2p_clear_go_negotiation_resp(&d);
return r;
}
/* Section 4.2.9.4 */
int p2p_parse_go_negotiation_confirmation(const uint8_t *pdu, size_t len,
struct p2p_go_negotiation_confirmation *out)
{
struct p2p_go_negotiation_confirmation d = {};
int r;
if (len < 1)
return -EINVAL;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(STATUS, &d.status),
REQUIRED(P2P_CAPABILITY, &d.capability),
REQUIRED(OPERATING_CHANNEL, &d.operating_channel),
REQUIRED(CHANNEL_LIST, &d.channel_list),
OPTIONAL(P2P_GROUP_ID, &d.group_id),
-1);
if (r < 0)
goto error;
d.wfd = ie_tlv_extract_wfd_payload(pdu + 1, len - 1, &d.wfd_size);
memcpy(out, &d, sizeof(d));
return 0;
error:
p2p_clear_go_negotiation_confirmation(&d);
return r;
}
/* Section 4.2.9.5 */
int p2p_parse_invitation_req(const uint8_t *pdu, size_t len,
struct p2p_invitation_req *out)
{
struct p2p_invitation_req d = {};
int r;
uint8_t *wsc_data;
ssize_t wsc_len;
bool wsc_version2;
if (len < 1)
return -EINVAL;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(CONFIGURATION_TIMEOUT, &d.config_timeout),
REQUIRED(INVITATION_FLAGS,
&d.reinvoke_persistent_group),
OPTIONAL(OPERATING_CHANNEL, &d.operating_channel),
OPTIONAL(P2P_GROUP_BSSID, &d.group_bssid),
REQUIRED(CHANNEL_LIST, &d.channel_list),
REQUIRED(P2P_GROUP_ID, &d.group_id),
REQUIRED(P2P_DEVICE_INFO, &d.device_info),
-1);
if (r < 0)
goto error;
/* A WSC IE is optional */
wsc_data = ie_tlv_extract_wsc_payload(pdu + 1, len - 1, &wsc_len);
if (wsc_data) {
r = wsc_parse_attrs(
wsc_data, wsc_len, &wsc_version2, NULL, 0, NULL,
WSC_REQUIRED(DEVICE_PASSWORD_ID, &d.device_password_id),
WSC_ATTR_INVALID);
l_free(wsc_data);
if (r >= 0 && !wsc_version2) {
r = -EINVAL;
goto error;
}
}
d.wfd = ie_tlv_extract_wfd_payload(pdu + 1, len - 1, &d.wfd_size);
memcpy(out, &d, sizeof(d));
return 0;
error:
p2p_clear_invitation_req(&d);
return r;
}
/* Section 4.2.9.6 */
int p2p_parse_invitation_resp(const uint8_t *pdu, size_t len,
struct p2p_invitation_resp *out)
{
struct p2p_invitation_resp d = {};
int r;
if (len < 1)
return -EINVAL;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(STATUS, &d.status),
REQUIRED(CONFIGURATION_TIMEOUT, &d.config_timeout),
OPTIONAL(OPERATING_CHANNEL, &d.operating_channel),
OPTIONAL(P2P_GROUP_BSSID, &d.group_bssid),
OPTIONAL(CHANNEL_LIST, &d.channel_list),
-1);
if (r < 0)
goto error;
d.wfd = ie_tlv_extract_wfd_payload(pdu + 1, len - 1, &d.wfd_size);
memcpy(out, &d, sizeof(d));
return 0;
error:
p2p_clear_invitation_resp(&d);
return r;
}
/* Section 4.2.9.7 */
int p2p_parse_device_disc_req(const uint8_t *pdu, size_t len,
struct p2p_device_discoverability_req *out)
{
struct p2p_device_discoverability_req d = {};
int r;
if (len < 1)
return -EINVAL;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(P2P_DEVICE_ID, &d.device_addr),
REQUIRED(P2P_GROUP_ID, &d.group_id),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
return r;
}
/* Section 4.2.9.8 */
int p2p_parse_device_disc_resp(const uint8_t *pdu, size_t len,
struct p2p_device_discoverability_resp *out)
{
struct p2p_device_discoverability_resp d = {};
int r;
if (len < 1)
return -EINVAL;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(STATUS, &d.status),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
return r;
}
/* Section 4.2.9.9 */
int p2p_parse_provision_disc_req(const uint8_t *pdu, size_t len,
struct p2p_provision_discovery_req *out)
{
struct p2p_provision_discovery_req d = {};
int r;
uint8_t *wsc_data;
ssize_t wsc_len;
if (len < 1)
return -EINVAL;
d.status = -1;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(P2P_CAPABILITY, &d.capability),
REQUIRED(P2P_DEVICE_INFO, &d.device_info),
OPTIONAL(P2P_GROUP_ID, &d.group_id),
OPTIONAL(INTENDED_P2P_INTERFACE_ADDR,
&d.intended_interface_addr),
OPTIONAL(STATUS, &d.status),
OPTIONAL(OPERATING_CHANNEL, &d.operating_channel),
OPTIONAL(CHANNEL_LIST, &d.channel_list),
OPTIONAL(SESSION_INFO_DATA_INFO, &d.session_info),
OPTIONAL(CONNECTION_CAPABILITY_INFO,
&d.connection_capability),
OPTIONAL(ADVERTISEMENT_ID_INFO, &d.advertisement_id),
OPTIONAL(CONFIGURATION_TIMEOUT, &d.config_timeout),
OPTIONAL(LISTEN_CHANNEL, &d.listen_channel),
OPTIONAL(SESSION_ID_INFO, &d.session_id),
OPTIONAL(FEATURE_CAPABILITY, &d.transport_protocol),
OPTIONAL(PERSISTENT_GROUP_INFO,
&d.persistent_group_info),
-1);
if (r < 0)
goto error;
wsc_data = ie_tlv_extract_wsc_payload(pdu + 1, len - 1, &wsc_len);
if (wsc_len < 0) {
r = wsc_len;
goto error;
}
r = wsc_parse_attrs(wsc_data, wsc_len, NULL, NULL, 0, NULL,
WSC_REQUIRED(CONFIGURATION_METHODS,
&d.wsc_config_method),
WSC_ATTR_INVALID);
l_free(wsc_data);
if (r < 0)
goto error;
/*
* 4.2.9.9: "A single method shall be set in the Config Methods
* attribute."
*/
if (__builtin_popcount(d.wsc_config_method) != 1) {
r = -EINVAL;
goto error;
}
d.wfd = ie_tlv_extract_wfd_payload(pdu + 1, len - 1, &d.wfd_size);
memcpy(out, &d, sizeof(d));
return 0;
error:
p2p_clear_provision_disc_req(&d);
return r;
}
/* Section 4.2.9.10 */
int p2p_parse_provision_disc_resp(const uint8_t *pdu, size_t len,
struct p2p_provision_discovery_resp *out)
{
struct p2p_provision_discovery_resp d = {};
int r;
uint8_t *wsc_data;
ssize_t wsc_len;
if (len < 1)
return -EINVAL;
d.status = -1;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
/*
* The P2P IE is optional but, if present, some of the attributes
* are required for this frame type.
*/
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(STATUS, &d.status),
REQUIRED(P2P_CAPABILITY, &d.capability),
REQUIRED(P2P_DEVICE_INFO, &d.device_info),
OPTIONAL(P2P_GROUP_ID, &d.group_id),
OPTIONAL(INTENDED_P2P_INTERFACE_ADDR,
&d.intended_interface_addr),
OPTIONAL(OPERATING_CHANNEL, &d.operating_channel),
OPTIONAL(CHANNEL_LIST, &d.channel_list),
OPTIONAL(CONNECTION_CAPABILITY_INFO,
&d.connection_capability),
REQUIRED(ADVERTISEMENT_ID_INFO, &d.advertisement_id),
OPTIONAL(CONFIGURATION_TIMEOUT, &d.config_timeout),
REQUIRED(SESSION_ID_INFO, &d.session_id),
REQUIRED(FEATURE_CAPABILITY, &d.transport_protocol),
OPTIONAL(PERSISTENT_GROUP_INFO,
&d.persistent_group_info),
REQUIRED(SESSION_INFO_DATA_INFO, &d.session_info),
-1);
if (r < 0 && r != -ENOENT)
goto error;
wsc_data = ie_tlv_extract_wsc_payload(pdu + 1, len - 1, &wsc_len);
if (wsc_len < 0) {
r = wsc_len;
goto error;
}
r = wsc_parse_attrs(wsc_data, wsc_len, NULL, NULL, 0, NULL,
WSC_REQUIRED(CONFIGURATION_METHODS,
&d.wsc_config_method),
WSC_ATTR_INVALID);
l_free(wsc_data);
if (r < 0)
goto error;
/*
* 4.2.9.10: "The value of the Config Methods attribute shall be
* set to the same value received in the Provision Discovery
* Request frame to indicate success or shall be null to indicate
* failure of the request."
*/
if (__builtin_popcount(d.wsc_config_method) > 1) {
r = -EINVAL;
goto error;
}
d.wfd = ie_tlv_extract_wfd_payload(pdu + 1, len - 1, &d.wfd_size);
memcpy(out, &d, sizeof(d));
return 0;
error:
p2p_clear_provision_disc_resp(&d);
return r;
}
/* Section 4.2.10.2 */
int p2p_parse_notice_of_absence(const uint8_t *pdu, size_t len,
struct p2p_notice_of_absence *out)
{
struct p2p_notice_of_absence d = {};
int r;
if (len < 1)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(NOTICE_OF_ABSENCE, &d.notice_of_absence),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
else
p2p_clear_notice_of_absence(&d);
return r;
}
/* Section 4.2.10.3 */
int p2p_parse_presence_req(const uint8_t *pdu, size_t len,
struct p2p_presence_req *out)
{
struct p2p_presence_req d = {};
int r;
if (len < 1)
return -EINVAL;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(NOTICE_OF_ABSENCE, &d.notice_of_absence),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
else
p2p_clear_presence_req(&d);
return r;
}
/* Section 4.2.10.4 */
int p2p_parse_presence_resp(const uint8_t *pdu, size_t len,
struct p2p_presence_resp *out)
{
struct p2p_presence_resp d = {};
int r;
if (len < 1)
return -EINVAL;
d.dialog_token = pdu[0];
if (d.dialog_token == 0)
return -EINVAL;
r = p2p_parse_attrs(pdu + 1, len - 1,
REQUIRED(STATUS, &d.status),
REQUIRED(NOTICE_OF_ABSENCE, &d.notice_of_absence),
-1);
if (r >= 0)
memcpy(out, &d, sizeof(d));
else
p2p_clear_presence_resp(&d);
return r;
}
/* Section 4.2.10.5 */
int p2p_parse_go_disc_req(const uint8_t *pdu, size_t len)
{
if (len != 1 || pdu[0] != 0)
return -EINVAL;
return 0;
}
static void p2p_clear_channel_list_attr(struct p2p_channel_list_attr *attr)
{
l_queue_destroy(attr->channel_entries, l_free);
attr->channel_entries = NULL;
}
static void p2p_clear_notice_of_absence_attr(
struct p2p_notice_of_absence_attr *attr)
{
l_queue_destroy(attr->descriptors, l_free);
attr->descriptors = NULL;
}
static void p2p_clear_device_info_attr(struct p2p_device_info_attr *attr)
{
l_queue_destroy(attr->secondary_device_types, l_free);
attr->secondary_device_types = NULL;
}
static void p2p_clear_group_info_attr(struct l_queue **group_clients)
{
l_queue_destroy(*group_clients, p2p_clear_client_info_descriptor);
*group_clients = NULL;
}
static void p2p_clear_interface_attr(struct p2p_interface_attr *attr)
{
l_queue_destroy(attr->interface_addrs, l_free);
attr->interface_addrs = NULL;
}
static void p2p_clear_svc_hash_attr(struct l_queue **hashes)
{
l_queue_destroy(*hashes, l_free);
*hashes = NULL;
}
static void p2p_clear_advertised_service_info_attr(struct l_queue **descriptors)
{
l_queue_destroy(*descriptors, p2p_clear_advertised_service_descriptor);
*descriptors = NULL;
}
void p2p_clear_beacon(struct p2p_beacon *data)
{
p2p_clear_notice_of_absence_attr(&data->notice_of_absence);
}
void p2p_clear_probe_req(struct p2p_probe_req *data)
{
p2p_clear_device_info_attr(&data->device_info);
p2p_clear_svc_hash_attr(&data->service_hashes);
}
void p2p_clear_probe_resp(struct p2p_probe_resp *data)
{
p2p_clear_notice_of_absence_attr(&data->notice_of_absence);
p2p_clear_device_info_attr(&data->device_info);
p2p_clear_group_info_attr(&data->group_clients);
p2p_clear_advertised_service_info_attr(&data->advertised_svcs);
}
void p2p_clear_association_req(struct p2p_association_req *data)
{
p2p_clear_device_info_attr(&data->device_info);
p2p_clear_interface_attr(&data->interface);
}
void p2p_clear_association_resp(struct p2p_association_resp *data)
{
}
void p2p_clear_go_negotiation_req(struct p2p_go_negotiation_req *data)
{
p2p_clear_channel_list_attr(&data->channel_list);
p2p_clear_device_info_attr(&data->device_info);
l_free(data->wfd);
data->wfd = NULL;
}
void p2p_clear_go_negotiation_resp(struct p2p_go_negotiation_resp *data)
{
p2p_clear_channel_list_attr(&data->channel_list);
p2p_clear_device_info_attr(&data->device_info);
l_free(data->wfd);
data->wfd = NULL;
}
void p2p_clear_go_negotiation_confirmation(
struct p2p_go_negotiation_confirmation *data)
{
p2p_clear_channel_list_attr(&data->channel_list);
l_free(data->wfd);
data->wfd = NULL;
}
void p2p_clear_invitation_req(struct p2p_invitation_req *data)
{
p2p_clear_channel_list_attr(&data->channel_list);
p2p_clear_device_info_attr(&data->device_info);
l_free(data->wfd);
data->wfd = NULL;
}
void p2p_clear_invitation_resp(struct p2p_invitation_resp *data)
{
p2p_clear_channel_list_attr(&data->channel_list);
l_free(data->wfd);
data->wfd = NULL;
}
void p2p_clear_provision_disc_req(struct p2p_provision_discovery_req *data)
{
p2p_clear_channel_list_attr(&data->channel_list);
p2p_clear_device_info_attr(&data->device_info);
l_free(data->wfd);
data->wfd = NULL;
}
void p2p_clear_provision_disc_resp(struct p2p_provision_discovery_resp *data)
{
p2p_clear_channel_list_attr(&data->channel_list);
p2p_clear_device_info_attr(&data->device_info);
l_free(data->wfd);
data->wfd = NULL;
}
void p2p_clear_notice_of_absence(struct p2p_notice_of_absence *data)
{
p2p_clear_notice_of_absence_attr(&data->notice_of_absence);
}
void p2p_clear_presence_req(struct p2p_presence_req *data)
{
p2p_clear_notice_of_absence_attr(&data->notice_of_absence);
}
void p2p_clear_presence_resp(struct p2p_presence_resp *data)
{
p2p_clear_notice_of_absence_attr(&data->notice_of_absence);
}
struct p2p_attr_builder {
size_t capacity;
uint8_t *buf;
uint16_t offset;
uint16_t curlen;
};
static void p2p_attr_builder_grow(struct p2p_attr_builder *builder)
{
builder->buf = l_realloc(builder->buf, builder->capacity * 2);
builder->capacity *= 2;
}
/* Section 4.1.1 */
static void p2p_attr_builder_start_attr(struct p2p_attr_builder *builder,
enum p2p_attr type)
{
/* Record previous attribute's length */
if (builder->curlen || builder->offset) {
l_put_le16(builder->curlen, builder->buf + builder->offset + 1);
builder->offset += 3 + builder->curlen;
builder->curlen = 0;
}
if (builder->offset + 3u >= builder->capacity)
p2p_attr_builder_grow(builder);
builder->buf[builder->offset] = type;
}
static void p2p_attr_builder_put_u8(struct p2p_attr_builder *builder, uint8_t v)
{
if (builder->offset + 3u + builder->curlen + 1u >= builder->capacity)
p2p_attr_builder_grow(builder);
builder->buf[builder->offset + 3 + builder->curlen] = v;
builder->curlen += 1;
}
static void p2p_attr_builder_put_u16(struct p2p_attr_builder *builder,
uint16_t v)
{
if (builder->offset + 3u + builder->curlen + 2u >= builder->capacity)
p2p_attr_builder_grow(builder);
l_put_le16(v, builder->buf + builder->offset + 3 + builder->curlen);
builder->curlen += 2;
}
static void p2p_attr_builder_put_be16(struct p2p_attr_builder *builder,
uint16_t v)
{
if (builder->offset + 3u + builder->curlen + 2u >= builder->capacity)
p2p_attr_builder_grow(builder);
l_put_be16(v, builder->buf + builder->offset + 3 + builder->curlen);
builder->curlen += 2;
}
static void p2p_attr_builder_put_u32(struct p2p_attr_builder *builder,
uint32_t v)
{
if (builder->offset + 3u + builder->curlen + 4u >= builder->capacity)
p2p_attr_builder_grow(builder);
l_put_le32(v, builder->buf + builder->offset + 3 + builder->curlen);
builder->curlen += 4;
}
static void p2p_attr_builder_put_bytes(struct p2p_attr_builder *builder,
const void *bytes, size_t size)
{
while (builder->offset + 3u + builder->curlen + size >=
builder->capacity)
p2p_attr_builder_grow(builder);
memcpy(builder->buf + builder->offset + 3 + builder->curlen,
bytes, size);
builder->curlen += size;
}
static void p2p_attr_builder_put_oui(struct p2p_attr_builder *builder,
const uint8_t *oui)
{
if (builder->offset + 3u + builder->curlen + 3u >= builder->capacity)
p2p_attr_builder_grow(builder);
memcpy(builder->buf + builder->offset + 3 + builder->curlen, oui, 3);
builder->curlen += 3;
}
static struct p2p_attr_builder *p2p_attr_builder_new(size_t initial_capacity)
{
struct p2p_attr_builder *builder;
if (initial_capacity == 0)
return NULL;
builder = l_new(struct p2p_attr_builder, 1);
builder->buf = l_malloc(initial_capacity);
builder->capacity = initial_capacity;
return builder;
}
static uint8_t *p2p_attr_builder_free(struct p2p_attr_builder *builder,
bool free_contents, size_t *out_size)
{
uint8_t *ret;
if (builder->curlen > 0 || builder->offset) {
l_put_le16(builder->curlen, builder->buf + builder->offset + 1);
builder->offset += 3 + builder->curlen;
}
if (free_contents) {
l_free(builder->buf);
ret = NULL;
} else
ret = builder->buf;
if (out_size)
*out_size = builder->offset;
l_free(builder);
return ret;
}
static void p2p_build_u8_attr(struct p2p_attr_builder *builder,
enum p2p_attr type, uint8_t value)
{
p2p_attr_builder_start_attr(builder, type);
p2p_attr_builder_put_u8(builder, value);
}
/* Section 4.1.4 */
static void p2p_build_capability(struct p2p_attr_builder *builder,
const struct p2p_capability_attr *attr)
{
/* Always required */
p2p_attr_builder_start_attr(builder, P2P_ATTR_P2P_CAPABILITY);
p2p_attr_builder_put_u8(builder, attr->device_caps);
p2p_attr_builder_put_u8(builder, attr->group_caps);
}
static const uint8_t zero_addr[6];
/* Section 4.1.5, 4.1.9, 4.1.11, ... */
static void p2p_build_addr(struct p2p_attr_builder *builder, bool optional,
enum p2p_attr type, const uint8_t *addr)
{
if (optional && !memcmp(addr, zero_addr, 6))
return;
p2p_attr_builder_start_attr(builder, type);
p2p_attr_builder_put_bytes(builder, addr, 6);
}
/* Section 4.1.6 */
static void p2p_build_go_intent(struct p2p_attr_builder *builder,
uint8_t intent, bool tie_breaker)
{
/* Always required */
p2p_attr_builder_start_attr(builder, P2P_ATTR_GO_INTENT);
p2p_attr_builder_put_u8(builder, tie_breaker | (intent << 1));
}
/* Section 4.1.7 */
static void p2p_build_config_timeout(struct p2p_attr_builder *builder,
bool optional,
const struct p2p_config_timeout_attr *attr)
{
if (optional && !attr->go_config_timeout &&
!attr->client_config_timeout)
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_CONFIGURATION_TIMEOUT);
p2p_attr_builder_put_u8(builder, attr->go_config_timeout);
p2p_attr_builder_put_u8(builder, attr->client_config_timeout);
}
/* Section 4.1.8, 4.1.19, ... */
static void p2p_build_channel(struct p2p_attr_builder *builder, bool optional,
enum p2p_attr type,
const struct p2p_channel_attr *attr)
{
if (optional && !attr->country[0])
return;
p2p_attr_builder_start_attr(builder, type);
p2p_attr_builder_put_bytes(builder, attr->country, 3);
p2p_attr_builder_put_u8(builder, attr->oper_class);
p2p_attr_builder_put_u8(builder, attr->channel_num);
}
/* Section 4.1.10 */
static void p2p_build_extended_listen_timing(struct p2p_attr_builder *builder,
const struct p2p_extended_listen_timing_attr *attr)
{
/* Always optional */
if (!attr->avail_period_ms && !attr->avail_interval_ms)
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_EXTENDED_LISTEN_TIMING);
p2p_attr_builder_put_u16(builder, attr->avail_period_ms);
p2p_attr_builder_put_u16(builder, attr->avail_interval_ms);
}
/* Section 4.1.13 */
static void p2p_build_channel_list(struct p2p_attr_builder *builder,
bool optional,
const struct p2p_channel_list_attr *attr)
{
const struct l_queue_entry *entry;
if (optional && !attr->channel_entries)
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_CHANNEL_LIST);
p2p_attr_builder_put_bytes(builder, attr->country, 3);
for (entry = l_queue_get_entries(attr->channel_entries); entry;
entry = entry->next) {
const struct p2p_channel_entries *entries = entry->data;
p2p_attr_builder_put_u8(builder, entries->oper_class);
p2p_attr_builder_put_u8(builder, entries->n_channels);
p2p_attr_builder_put_bytes(builder, entries->channels,
entries->n_channels);
}
}
/* Section 4.1.14 */
static void p2p_build_notice_of_absence_attr(struct p2p_attr_builder *builder,
bool optional,
const struct p2p_notice_of_absence_attr *attr)
{
const struct l_queue_entry *entry;
if (optional && !attr->ct_window && !attr->descriptors)
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_NOTICE_OF_ABSENCE);
p2p_attr_builder_put_u8(builder, attr->index);
p2p_attr_builder_put_u8(builder,
attr->ct_window | (attr->opp_ps ? 0x80 : 0));
for (entry = l_queue_get_entries(attr->descriptors); entry;
entry = entry->next) {
const struct p2p_notice_of_absence_desc *desc = entry->data;
p2p_attr_builder_put_u8(builder, desc->count_type);
p2p_attr_builder_put_u32(builder, desc->duration);
p2p_attr_builder_put_u32(builder, desc->interval);
p2p_attr_builder_put_u32(builder, desc->start_time);
}
}
static void p2p_build_wsc_device_type(struct p2p_attr_builder *builder,
const struct wsc_primary_device_type *pdt)
{
p2p_attr_builder_put_be16(builder, pdt->category);
p2p_attr_builder_put_oui(builder, pdt->oui);
p2p_attr_builder_put_u8(builder, pdt->oui_type);
p2p_attr_builder_put_be16(builder, pdt->subcategory);
}
/* Section 4.1.15 */
static void p2p_build_device_info(struct p2p_attr_builder *builder,
bool optional,
const struct p2p_device_info_attr *attr)
{
const struct l_queue_entry *entry;
if (optional && !memcmp(attr->device_addr, zero_addr, 6))
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_P2P_DEVICE_INFO);
p2p_attr_builder_put_bytes(builder, attr->device_addr, 6);
p2p_attr_builder_put_be16(builder, attr->wsc_config_methods);
p2p_build_wsc_device_type(builder, &attr->primary_device_type);
p2p_attr_builder_put_u8(builder,
l_queue_length(attr->secondary_device_types));
for (entry = l_queue_get_entries(attr->secondary_device_types); entry;
entry = entry->next)
p2p_build_wsc_device_type(builder, entry->data);
p2p_attr_builder_put_be16(builder, WSC_ATTR_DEVICE_NAME);
p2p_attr_builder_put_be16(builder, strlen(attr->device_name));
p2p_attr_builder_put_bytes(builder, attr->device_name,
strlen(attr->device_name));
}
/* Section 4.1.16 */
static void p2p_build_group_info(struct p2p_attr_builder *builder,
struct l_queue *clients)
{
const struct l_queue_entry *entry;
/* Always optional */
if (!clients)
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_P2P_GROUP_INFO);
for (entry = l_queue_get_entries(clients); entry; entry = entry->next) {
const struct l_queue_entry *entry2;
const struct p2p_client_info_descriptor *desc = entry->data;
p2p_attr_builder_put_bytes(builder, desc->device_addr, 6);
p2p_attr_builder_put_bytes(builder, desc->interface_addr, 6);
p2p_attr_builder_put_u8(builder, desc->device_caps);
p2p_attr_builder_put_be16(builder, desc->wsc_config_methods);
p2p_build_wsc_device_type(builder, &desc->primary_device_type);
p2p_attr_builder_put_u8(builder,
l_queue_length(desc->secondary_device_types));
for (entry2 = l_queue_get_entries(desc->secondary_device_types);
entry2; entry2 = entry->next)
p2p_build_wsc_device_type(builder, entry2->data);
p2p_attr_builder_put_be16(builder, WSC_ATTR_DEVICE_NAME);
p2p_attr_builder_put_be16(builder, strlen(desc->device_name));
p2p_attr_builder_put_bytes(builder, desc->device_name,
strlen(desc->device_name));
}
}
/* Section 4.1.17, 4.1.29 */
static void p2p_build_group_id(struct p2p_attr_builder *builder, bool optional,
enum p2p_attr type,
const struct p2p_group_id_attr *attr)
{
if (optional && !memcmp(attr->device_addr, zero_addr, 6))
return;
p2p_attr_builder_start_attr(builder, type);
p2p_attr_builder_put_bytes(builder, attr->device_addr, 6);
p2p_attr_builder_put_bytes(builder, attr->ssid, strlen(attr->ssid));
}
/* Section 4.1.18 */
static void p2p_build_interface(struct p2p_attr_builder *builder,
const struct p2p_interface_attr *attr)
{
const struct l_queue_entry *entry;
/* Always optional */
if (!memcmp(attr->device_addr, zero_addr, 6))
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_P2P_INTERFACE);
p2p_attr_builder_put_bytes(builder, attr->device_addr, 6);
p2p_attr_builder_put_u8(builder, l_queue_length(attr->interface_addrs));
for (entry = l_queue_get_entries(attr->interface_addrs); entry;
entry = entry->next)
p2p_attr_builder_put_bytes(builder, entry->data, 6);
}
/* Section 4.1.22 */
static void p2p_build_svc_hash(struct p2p_attr_builder *builder,
struct l_queue *service_hashes)
{
const struct l_queue_entry *entry;
/* Always optional */
if (!service_hashes)
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_SVC_HASH);
for (entry = l_queue_get_entries(service_hashes); entry;
entry = entry->next)
p2p_attr_builder_put_bytes(builder, entry->data, 6);
}
/* Section 4.1.23 */
static void p2p_build_session_data(struct p2p_attr_builder *builder,
const struct p2p_session_info_data_attr *attr)
{
/* Always optional */
if (!attr->data_len)
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_SESSION_INFO_DATA_INFO);
p2p_attr_builder_put_bytes(builder, attr->data, attr->data_len);
}
/* Section 4.1.25 */
static void p2p_build_advertisement_id(struct p2p_attr_builder *builder,
bool optional,
const struct p2p_advertisement_id_info_attr *attr)
{
if (optional && !memcmp(attr->service_mac_addr, zero_addr, 6))
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_ADVERTISEMENT_ID_INFO);
p2p_attr_builder_put_u32(builder, attr->advertisement_id);
p2p_attr_builder_put_bytes(builder, attr->service_mac_addr, 6);
}
/* Section 4.1.26 */
static void p2p_build_advertised_service_info(struct p2p_attr_builder *builder,
struct l_queue *services)
{
const struct l_queue_entry *entry;
/* Always optional */
if (!services)
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_ADVERTISED_SVC_INFO);
for (entry = l_queue_get_entries(services); entry;
entry = entry->next) {
const struct p2p_advertised_service_descriptor *desc =
entry->data;
p2p_attr_builder_put_u32(builder, desc->advertisement_id);
p2p_attr_builder_put_be16(builder, desc->wsc_config_methods);
p2p_attr_builder_put_u8(builder, strlen(desc->service_name));
p2p_attr_builder_put_bytes(builder, desc->service_name,
strlen(desc->service_name));
}
}
/* Section 4.1.27 */
static void p2p_build_session_id(struct p2p_attr_builder *builder,
bool optional,
const struct p2p_session_id_info_attr *attr)
{
if (optional && !memcmp(attr->session_mac_addr, zero_addr, 6))
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_SESSION_ID_INFO);
p2p_attr_builder_put_u32(builder, attr->session_id);
p2p_attr_builder_put_bytes(builder, attr->session_mac_addr, 6);
}
/* Section 4.1.28 */
static void p2p_build_feature_capability(struct p2p_attr_builder *builder,
bool optional,
enum p2p_asp_coordination_transport_protocol attr)
{
if (optional && attr == P2P_ASP_TRANSPORT_UNKNOWN)
return;
p2p_attr_builder_start_attr(builder, P2P_ATTR_FEATURE_CAPABILITY);
p2p_attr_builder_put_u8(builder, 0x01); /* P2P_ASP_TRANSPORT_UDP */
p2p_attr_builder_put_u8(builder, 0x00); /* Reserved */
}
/* Section 4.2.1 */
uint8_t *p2p_build_beacon(const struct p2p_beacon *data, size_t *out_len)
{
struct p2p_attr_builder *builder;
uint8_t *ret;
uint8_t *tlv;
size_t tlv_len;
builder = p2p_attr_builder_new(512);
p2p_build_capability(builder, &data->capability);
p2p_build_addr(builder, false, P2P_ATTR_P2P_DEVICE_ID,
data->device_addr);
p2p_build_notice_of_absence_attr(builder, true,
&data->notice_of_absence);
tlv = p2p_attr_builder_free(builder, false, &tlv_len);
ret = ie_tlv_encapsulate_p2p_payload(tlv, tlv_len, out_len);
l_free(tlv);
return ret;
}
/* Section 4.2.2 */
uint8_t *p2p_build_probe_req(const struct p2p_probe_req *data, size_t *out_len)
{
struct p2p_attr_builder *builder;
uint8_t *ret;
uint8_t *tlv;
size_t tlv_len;
builder = p2p_attr_builder_new(512);
p2p_build_capability(builder, &data->capability);
p2p_build_addr(builder, true, P2P_ATTR_P2P_DEVICE_ID,
data->device_addr);
p2p_build_channel(builder, true, P2P_ATTR_LISTEN_CHANNEL,
&data->listen_channel);
p2p_build_extended_listen_timing(builder, &data->listen_availability);
p2p_build_device_info(builder, true, &data->device_info);
p2p_build_channel(builder, true, P2P_ATTR_OPERATING_CHANNEL,
&data->operating_channel);
p2p_build_svc_hash(builder, data->service_hashes);
tlv = p2p_attr_builder_free(builder, false, &tlv_len);
ret = ie_tlv_encapsulate_p2p_payload(tlv, tlv_len, out_len);
l_free(tlv);
return ret;
}
/* Section 4.2.3 */
uint8_t *p2p_build_probe_resp(const struct p2p_probe_resp *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
uint8_t *ret;
uint8_t *tlv;
size_t tlv_len;
builder = p2p_attr_builder_new(512);
p2p_build_capability(builder, &data->capability);
p2p_build_extended_listen_timing(builder, &data->listen_availability);
p2p_build_notice_of_absence_attr(builder, true,
&data->notice_of_absence);
p2p_build_device_info(builder, false, &data->device_info);
p2p_build_group_info(builder, data->group_clients);
p2p_build_advertised_service_info(builder, data->advertised_svcs);
tlv = p2p_attr_builder_free(builder, false, &tlv_len);
ret = ie_tlv_encapsulate_p2p_payload(tlv, tlv_len, out_len);
l_free(tlv);
return ret;
}
/* Section 4.2.4 */
uint8_t *p2p_build_association_req(const struct p2p_association_req *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
uint8_t *ret;
uint8_t *tlv;
size_t tlv_len;
builder = p2p_attr_builder_new(512);
p2p_build_capability(builder, &data->capability);
p2p_build_extended_listen_timing(builder, &data->listen_availability);
p2p_build_device_info(builder, true, &data->device_info);
p2p_build_interface(builder, &data->interface);
tlv = p2p_attr_builder_free(builder, false, &tlv_len);
ret = ie_tlv_encapsulate_p2p_payload(tlv, tlv_len, out_len);
l_free(tlv);
return ret;
}
/* Section 4.2.5 */
uint8_t *p2p_build_association_resp(const struct p2p_association_resp *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
uint8_t *ret;
uint8_t *tlv;
size_t tlv_len;
builder = p2p_attr_builder_new(32);
/*
* 4.2.5: "The Status attribute shall be present [...] when a
* (Re) association Request frame is denied."
*
* Note the P2P IE may end up being empty but is required for
* this frame type nevertheless.
*/
if (data->status != P2P_STATUS_SUCCESS &&
data->status != P2P_STATUS_SUCCESS_ACCEPTED_BY_USER)
p2p_build_u8_attr(builder, P2P_ATTR_STATUS, data->status);
p2p_build_extended_listen_timing(builder, &data->listen_availability);
tlv = p2p_attr_builder_free(builder, false, &tlv_len);
ret = ie_tlv_encapsulate_p2p_payload(tlv, tlv_len, out_len);
l_free(tlv);
return ret;
}
/* Section 4.2.6 */
uint8_t *p2p_build_deauthentication(const struct p2p_deauthentication *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
uint8_t *ret;
uint8_t *tlv;
size_t tlv_len;
if (!data->minor_reason_code) {
*out_len = 0;
return (uint8_t *) "";
}
builder = p2p_attr_builder_new(512);
p2p_build_u8_attr(builder, P2P_ATTR_MINOR_REASON_CODE,
data->minor_reason_code);
tlv = p2p_attr_builder_free(builder, false, &tlv_len);
ret = ie_tlv_encapsulate_p2p_payload(tlv, tlv_len, out_len);
l_free(tlv);
return ret;
}
/* Section 4.2.7 */
uint8_t *p2p_build_disassociation(const struct p2p_disassociation *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
uint8_t *ret;
uint8_t *tlv;
size_t tlv_len;
if (!data->minor_reason_code) {
*out_len = 0;
return (uint8_t *) "";
}
builder = p2p_attr_builder_new(512);
p2p_build_u8_attr(builder, P2P_ATTR_MINOR_REASON_CODE,
data->minor_reason_code);
tlv = p2p_attr_builder_free(builder, false, &tlv_len);
ret = ie_tlv_encapsulate_p2p_payload(tlv, tlv_len, out_len);
l_free(tlv);
return ret;
}
/* Sections 4.2.9.1, 4.2.10.1. Note: consumes @p2p_attrs */
static uint8_t *p2p_build_action_frame(bool public, uint8_t frame_subtype,
uint8_t dialog_token,
struct p2p_attr_builder *p2p_attrs,
const struct wsc_p2p_attrs *wsc_attrs,
const uint8_t *wfd_ie,
size_t wfd_ie_len, size_t *out_len)
{
uint8_t *p2p_ie, *wsc_ie, *ret;
size_t p2p_ie_len, wsc_ie_len;
int pos = 0;
if (p2p_attrs) {
uint8_t *payload;
size_t payload_len;
payload = p2p_attr_builder_free(p2p_attrs, false, &payload_len);
p2p_ie = ie_tlv_encapsulate_p2p_payload(payload, payload_len,
&p2p_ie_len);
l_free(payload);
} else
p2p_ie = NULL;
if (wsc_attrs) {
uint8_t *payload;
size_t payload_len;
payload = wsc_build_p2p_attrs(wsc_attrs, &payload_len);
wsc_ie = ie_tlv_encapsulate_wsc_payload(payload, payload_len,
&wsc_ie_len);
l_free(payload);
} else
wsc_ie = NULL;
*out_len = (public ? 8 : 7) + (p2p_ie ? p2p_ie_len : 0) +
(wsc_ie ? wsc_ie_len : 0) + (wfd_ie ? wfd_ie_len : 0);
ret = l_malloc(*out_len);
if (public) {
ret[pos++] = 0x04; /* Category: Public Action */
ret[pos++] = 0x09; /* Action: Vendor Specific */
} else
ret[pos++] = 0x7f; /* Category: Vendor Specific */
ret[pos++] = wifi_alliance_oui[0];
ret[pos++] = wifi_alliance_oui[1];
ret[pos++] = wifi_alliance_oui[2];
ret[pos++] = 0x09; /* OUI type: Wi-Fi Alliance P2P v1.0 */
ret[pos++] = frame_subtype;
ret[pos++] = dialog_token;
if (p2p_ie) {
memcpy(ret + pos, p2p_ie, p2p_ie_len);
l_free(p2p_ie);
pos += p2p_ie_len;
}
if (wsc_ie) {
memcpy(ret + pos, wsc_ie, wsc_ie_len);
l_free(wsc_ie);
pos += wsc_ie_len;
}
if (wfd_ie)
memcpy(ret + pos, wfd_ie, wfd_ie_len);
return ret;
}
/* Section 4.2.9.2 */
uint8_t *p2p_build_go_negotiation_req(const struct p2p_go_negotiation_req *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
struct wsc_p2p_attrs wsc_attrs = {};
builder = p2p_attr_builder_new(512);
p2p_build_capability(builder, &data->capability);
p2p_build_go_intent(builder, data->go_intent, data->go_tie_breaker);
p2p_build_config_timeout(builder, false, &data->config_timeout);
p2p_build_channel(builder, true, P2P_ATTR_LISTEN_CHANNEL,
&data->listen_channel);
p2p_build_extended_listen_timing(builder, &data->listen_availability);
p2p_build_addr(builder, false, P2P_ATTR_INTENDED_P2P_INTERFACE_ADDR,
data->intended_interface_addr);
p2p_build_channel_list(builder, false, &data->channel_list);
p2p_build_device_info(builder, false, &data->device_info);
p2p_build_channel(builder, true, P2P_ATTR_OPERATING_CHANNEL,
&data->operating_channel);
wsc_attrs.version = true;
wsc_attrs.device_password_id = data->device_password_id;
return p2p_build_action_frame(true, P2P_ACTION_GO_NEGOTIATION_REQ,
data->dialog_token, builder, &wsc_attrs,
data->wfd, data->wfd_size, out_len);
}
/* Section 4.2.9.3 */
uint8_t *p2p_build_go_negotiation_resp(
const struct p2p_go_negotiation_resp *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
struct wsc_p2p_attrs wsc_attrs = {};
builder = p2p_attr_builder_new(512);
p2p_build_u8_attr(builder, P2P_ATTR_STATUS, data->status);
p2p_build_capability(builder, &data->capability);
p2p_build_go_intent(builder, data->go_intent, data->go_tie_breaker);
p2p_build_config_timeout(builder, false, &data->config_timeout);
p2p_build_channel(builder, true, P2P_ATTR_OPERATING_CHANNEL,
&data->operating_channel);
p2p_build_addr(builder, false, P2P_ATTR_INTENDED_P2P_INTERFACE_ADDR,
data->intended_interface_addr);
p2p_build_channel_list(builder, false, &data->channel_list);
p2p_build_device_info(builder, false, &data->device_info);
p2p_build_group_id(builder, true, P2P_ATTR_P2P_GROUP_ID,
&data->group_id);
wsc_attrs.version = true;
wsc_attrs.device_password_id = data->device_password_id;
return p2p_build_action_frame(true, P2P_ACTION_GO_NEGOTIATION_RESP,
data->dialog_token, builder, &wsc_attrs,
data->wfd, data->wfd_size, out_len);
}
/* Section 4.2.9.4 */
uint8_t *p2p_build_go_negotiation_confirmation(
const struct p2p_go_negotiation_confirmation *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
builder = p2p_attr_builder_new(512);
p2p_build_u8_attr(builder, P2P_ATTR_STATUS, data->status);
p2p_build_capability(builder, &data->capability);
p2p_build_channel(builder, false, P2P_ATTR_OPERATING_CHANNEL,
&data->operating_channel);
p2p_build_channel_list(builder, false, &data->channel_list);
p2p_build_group_id(builder, true, P2P_ATTR_P2P_GROUP_ID,
&data->group_id);
return p2p_build_action_frame(true, P2P_ACTION_GO_NEGOTIATION_CONFIRM,
data->dialog_token, builder, NULL,
data->wfd, data->wfd_size, out_len);
}
/* Section 4.2.9.5 */
uint8_t *p2p_build_invitation_req(const struct p2p_invitation_req *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
struct wsc_p2p_attrs wsc_attrs = {};
builder = p2p_attr_builder_new(512);
p2p_build_config_timeout(builder, false, &data->config_timeout);
p2p_build_u8_attr(builder, P2P_ATTR_INVITATION_FLAGS,
data->reinvoke_persistent_group ? 0x01 : 0x00);
p2p_build_channel(builder, true, P2P_ATTR_OPERATING_CHANNEL,
&data->operating_channel);
p2p_build_addr(builder, true, P2P_ATTR_P2P_GROUP_BSSID,
data->group_bssid);
p2p_build_channel_list(builder, false, &data->channel_list);
p2p_build_group_id(builder, false, P2P_ATTR_P2P_GROUP_ID,
&data->group_id);
p2p_build_device_info(builder, false, &data->device_info);
/* Optional WSC IE for NFC Static Handover */
wsc_attrs.version2 = true;
wsc_attrs.device_password_id = data->device_password_id;
return p2p_build_action_frame(true, P2P_ACTION_INVITATION_REQ,
data->dialog_token, builder,
data->device_password_id ?
&wsc_attrs : NULL,
data->wfd, data->wfd_size, out_len);
}
/* Section 4.2.9.6 */
uint8_t *p2p_build_invitation_resp(const struct p2p_invitation_resp *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
builder = p2p_attr_builder_new(512);
p2p_build_u8_attr(builder, P2P_ATTR_STATUS, data->status);
p2p_build_config_timeout(builder, false, &data->config_timeout);
if (data->status == P2P_STATUS_SUCCESS ||
data->status == P2P_STATUS_SUCCESS_ACCEPTED_BY_USER) {
p2p_build_channel(builder, false, P2P_ATTR_OPERATING_CHANNEL,
&data->operating_channel);
p2p_build_addr(builder, false, P2P_ATTR_P2P_GROUP_BSSID,
data->group_bssid);
p2p_build_channel_list(builder, false, &data->channel_list);
}
return p2p_build_action_frame(true, P2P_ACTION_INVITATION_RESP,
data->dialog_token, builder, NULL,
data->wfd, data->wfd_size, out_len);
}
/* Section 4.2.9.7 */
uint8_t *p2p_build_device_disc_req(
const struct p2p_device_discoverability_req *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
builder = p2p_attr_builder_new(64);
p2p_build_addr(builder, false, P2P_ATTR_P2P_DEVICE_ID,
data->device_addr);
p2p_build_group_id(builder, false, P2P_ATTR_P2P_GROUP_ID,
&data->group_id);
return p2p_build_action_frame(true,
P2P_ACTION_DEVICE_DISCOVERABILITY_REQ,
data->dialog_token, builder, NULL,
NULL, 0, out_len);
}
/* Section 4.2.9.8 */
uint8_t *p2p_build_device_disc_resp(
const struct p2p_device_discoverability_resp *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
builder = p2p_attr_builder_new(16);
p2p_build_u8_attr(builder, P2P_ATTR_STATUS, data->status);
return p2p_build_action_frame(true,
P2P_ACTION_DEVICE_DISCOVERABILITY_RESP,
data->dialog_token, builder, NULL,
NULL, 0, out_len);
}
/* Section 4.2.9.9 */
uint8_t *p2p_build_provision_disc_req(
const struct p2p_provision_discovery_req *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
struct wsc_p2p_attrs wsc_attrs = {};
builder = p2p_attr_builder_new(512);
p2p_build_capability(builder, &data->capability);
p2p_build_device_info(builder, false, &data->device_info);
p2p_build_group_id(builder, true, P2P_ATTR_P2P_GROUP_ID,
&data->group_id);
p2p_build_addr(builder, true, P2P_ATTR_INTENDED_P2P_INTERFACE_ADDR,
data->intended_interface_addr);
if (data->status != (enum p2p_attr_status_code) -1)
p2p_build_u8_attr(builder, P2P_ATTR_STATUS, data->status);
p2p_build_channel(builder, true, P2P_ATTR_OPERATING_CHANNEL,
&data->operating_channel);
p2p_build_channel_list(builder, true, &data->channel_list);
p2p_build_session_data(builder, &data->session_info);
if (data->connection_capability)
p2p_build_u8_attr(builder, P2P_ATTR_CONNECTION_CAPABILITY_INFO,
data->connection_capability);
p2p_build_advertisement_id(builder, true, &data->advertisement_id);
p2p_build_config_timeout(builder, true, &data->config_timeout);
p2p_build_channel(builder, true, P2P_ATTR_LISTEN_CHANNEL,
&data->listen_channel);
p2p_build_session_id(builder, true, &data->session_id);
p2p_build_feature_capability(builder, true, data->transport_protocol);
p2p_build_group_id(builder, true, P2P_ATTR_PERSISTENT_GROUP_INFO,
&data->persistent_group_info);
wsc_attrs.config_methods = data->wsc_config_method;
return p2p_build_action_frame(true, P2P_ACTION_PROVISION_DISCOVERY_REQ,
data->dialog_token, builder, &wsc_attrs,
data->wfd, data->wfd_size, out_len);
}
/* Section 4.2.9.10 */
uint8_t *p2p_build_provision_disc_resp(
const struct p2p_provision_discovery_resp *data,
size_t *out_len)
{
struct p2p_attr_builder *builder = NULL;
struct wsc_p2p_attrs wsc_attrs = {};
if (data->status != (enum p2p_attr_status_code) -1) {
builder = p2p_attr_builder_new(512);
p2p_build_u8_attr(builder, P2P_ATTR_STATUS, data->status);
p2p_build_capability(builder, &data->capability);
p2p_build_device_info(builder, false, &data->device_info);
p2p_build_group_id(builder, true, P2P_ATTR_P2P_GROUP_ID,
&data->group_id);
p2p_build_addr(builder, true,
P2P_ATTR_INTENDED_P2P_INTERFACE_ADDR,
data->intended_interface_addr);
p2p_build_channel(builder, true, P2P_ATTR_OPERATING_CHANNEL,
&data->operating_channel);
p2p_build_channel_list(builder, true, &data->channel_list);
if (data->connection_capability)
p2p_build_u8_attr(builder,
P2P_ATTR_CONNECTION_CAPABILITY_INFO,
data->connection_capability);
p2p_build_advertisement_id(builder, false,
&data->advertisement_id);
p2p_build_config_timeout(builder, true, &data->config_timeout);
p2p_build_session_id(builder, false, &data->session_id);
p2p_build_feature_capability(builder, false,
data->transport_protocol);
p2p_build_group_id(builder, true,
P2P_ATTR_PERSISTENT_GROUP_INFO,
&data->persistent_group_info);
p2p_build_session_data(builder, &data->session_info);
}
wsc_attrs.config_methods = data->wsc_config_method;
return p2p_build_action_frame(true, P2P_ACTION_PROVISION_DISCOVERY_RESP,
data->dialog_token, builder, &wsc_attrs,
data->wfd, data->wfd_size, out_len);
}
/* Section 4.2.10.2 */
uint8_t *p2p_build_notice_of_absence(const struct p2p_notice_of_absence *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
builder = p2p_attr_builder_new(128);
p2p_build_notice_of_absence_attr(builder, false,
&data->notice_of_absence);
return p2p_build_action_frame(false, P2P_ACTION_NOTICE_OF_ABSENCE,
0, builder, NULL, NULL, 0, out_len);
}
/* Section 4.2.10.3 */
uint8_t *p2p_build_presence_req(const struct p2p_presence_req *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
builder = p2p_attr_builder_new(128);
p2p_build_notice_of_absence_attr(builder, false,
&data->notice_of_absence);
return p2p_build_action_frame(false, P2P_ACTION_PRESENCE_REQ,
0, builder, NULL, NULL, 0, out_len);
}
/* Section 4.2.10.4 */
uint8_t *p2p_build_presence_resp(const struct p2p_presence_resp *data,
size_t *out_len)
{
struct p2p_attr_builder *builder;
builder = p2p_attr_builder_new(128);
p2p_build_u8_attr(builder, P2P_ATTR_STATUS, data->status);
p2p_build_notice_of_absence_attr(builder, false,
&data->notice_of_absence);
return p2p_build_action_frame(false, P2P_ACTION_PRESENCE_RESP,
0, builder, NULL, NULL, 0, out_len);
}
/* Section 4.2.10.5 */
uint8_t *p2p_build_go_disc_req(size_t *out_len)
{
return p2p_build_action_frame(false, P2P_ACTION_GO_DISCOVERABILITY_REQ,
0, NULL, NULL, NULL, 0, out_len);
}
/*
* Select a string of random characters from the set defined in section
* 3.2.1:
*
* "Following "DIRECT-" the SSID shall contain two ASCII characters "xy",
* randomly selected with a uniform distribution from the following
* character set: upper case letters, lower case letters and numbers."
*
* "The WPA2-Personal pass-phrase shall contain at least eight ASCII
* characters randomly selected with a uniform distribution from the
* following character set: upper case letters, lower case letters and
* numbers."
*
* Our random distribution is not uniform but close enough for use in
* the SSID.
*/
void p2p_get_random_string(char *buf, size_t len)
{
#define CHARSET "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" \
"0123456789"
static const int set_size = strlen(CHARSET);
l_getrandom(buf, len);
while (len) {
int index = *buf % set_size;
*buf++ = CHARSET[index];
len--;
}
#undef CHARSET
}