/* * * 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 #endif #include #include #include #include #include #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; } 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; if (len < 6) 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_free_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_free_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_free_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_free_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_free_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_free_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_free_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_free_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; memcpy(out, &d, sizeof(d)); return 0; error: p2p_free_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; memcpy(out, &d, sizeof(d)); return 0; error: p2p_free_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) memcpy(out, &d, sizeof(d)); else p2p_free_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 done; /* A WSC IE is optional */ wsc_data = ie_tlv_extract_wsc_payload(pdu + 1, len - 1, &wsc_len); if (!wsc_data) goto done; 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; done: if (r >= 0) memcpy(out, &d, sizeof(d)); else p2p_free_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) memcpy(out, &d, sizeof(d)); else p2p_free_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; } memcpy(out, &d, sizeof(d)); return 0; error: p2p_free_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; } memcpy(out, &d, sizeof(d)); return 0; error: p2p_free_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_free_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_free_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_free_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_free_channel_list_attr(struct p2p_channel_list_attr *attr) { l_queue_destroy(attr->channel_entries, l_free); attr->channel_entries = NULL; } static void p2p_free_notice_of_absence_attr( struct p2p_notice_of_absence_attr *attr) { l_queue_destroy(attr->descriptors, l_free); attr->descriptors = NULL; } static void p2p_free_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_free_group_info_attr(struct l_queue **group_clients) { l_queue_destroy(*group_clients, p2p_free_client_info_descriptor); *group_clients = NULL; } static void p2p_free_interface_attr(struct p2p_interface_attr *attr) { l_queue_destroy(attr->interface_addrs, l_free); attr->interface_addrs = NULL; } static void p2p_free_svc_hash_attr(struct l_queue **hashes) { l_queue_destroy(*hashes, l_free); *hashes = NULL; } static void p2p_free_advertised_service_info_attr(struct l_queue **descriptors) { l_queue_destroy(*descriptors, p2p_free_advertised_service_descriptor); *descriptors = NULL; } void p2p_free_beacon(struct p2p_beacon *data) { p2p_free_notice_of_absence_attr(&data->notice_of_absence); } void p2p_free_probe_req(struct p2p_probe_req *data) { p2p_free_device_info_attr(&data->device_info); p2p_free_svc_hash_attr(&data->service_hashes); } void p2p_free_probe_resp(struct p2p_probe_resp *data) { p2p_free_notice_of_absence_attr(&data->notice_of_absence); p2p_free_device_info_attr(&data->device_info); p2p_free_group_info_attr(&data->group_clients); p2p_free_advertised_service_info_attr(&data->advertised_svcs); } void p2p_free_association_req(struct p2p_association_req *data) { p2p_free_device_info_attr(&data->device_info); p2p_free_interface_attr(&data->interface); } void p2p_free_association_resp(struct p2p_association_resp *data) { } void p2p_free_go_negotiation_req(struct p2p_go_negotiation_req *data) { p2p_free_channel_list_attr(&data->channel_list); p2p_free_device_info_attr(&data->device_info); } void p2p_free_go_negotiation_resp(struct p2p_go_negotiation_resp *data) { p2p_free_channel_list_attr(&data->channel_list); p2p_free_device_info_attr(&data->device_info); } void p2p_free_go_negotiation_confirmation( struct p2p_go_negotiation_confirmation *data) { p2p_free_channel_list_attr(&data->channel_list); } void p2p_free_invitation_req(struct p2p_invitation_req *data) { p2p_free_channel_list_attr(&data->channel_list); p2p_free_device_info_attr(&data->device_info); } void p2p_free_invitation_resp(struct p2p_invitation_resp *data) { p2p_free_channel_list_attr(&data->channel_list); } void p2p_free_provision_disc_req(struct p2p_provision_discovery_req *data) { p2p_free_channel_list_attr(&data->channel_list); p2p_free_device_info_attr(&data->device_info); } void p2p_free_provision_disc_resp(struct p2p_provision_discovery_resp *data) { p2p_free_channel_list_attr(&data->channel_list); p2p_free_device_info_attr(&data->device_info); } void p2p_free_notice_of_absence(struct p2p_notice_of_absence *data) { p2p_free_notice_of_absence_attr(&data->notice_of_absence); } void p2p_free_presence_req(struct p2p_presence_req *data) { p2p_free_notice_of_absence_attr(&data->notice_of_absence); } void p2p_free_presence_resp(struct p2p_presence_resp *data) { p2p_free_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, 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); 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); } 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, out_len); }