| /* |
| * |
| * Wireless daemon for Linux |
| * |
| * Copyright (C) 2013-2014 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 <errno.h> |
| #include <ell/ell.h> |
| #include "util.h" |
| #include "crypto.h" |
| |
| #include "ie.h" |
| |
| static const uint8_t ieee_oui[3] = { 0x00, 0x0f, 0xac }; |
| static const uint8_t microsoft_oui[3] = { 0x00, 0x50, 0xf2 }; |
| |
| void ie_tlv_iter_init(struct ie_tlv_iter *iter, const unsigned char *tlv, |
| unsigned int len) |
| { |
| iter->tlv = tlv; |
| iter->max = len; |
| iter->pos = 0; |
| } |
| |
| void ie_tlv_iter_recurse(struct ie_tlv_iter *iter, |
| struct ie_tlv_iter *recurse) |
| { |
| recurse->tlv = iter->data; |
| recurse->max = iter->len; |
| recurse->pos = 0; |
| } |
| |
| bool ie_tlv_iter_next(struct ie_tlv_iter *iter) |
| { |
| const unsigned char *tlv = iter->tlv + iter->pos; |
| const unsigned char *end = iter->tlv + iter->max; |
| unsigned int tag; |
| unsigned int len; |
| |
| if (iter->pos + 1 >= iter->max) |
| return false; |
| |
| tag = *tlv++; |
| len = *tlv++; |
| |
| if (tag == IE_TYPE_EXTENSION) { |
| if (iter->pos + 2 >= iter->max || len < 1) |
| return false; |
| |
| tag = 256 + *tlv++; |
| len--; |
| } |
| |
| if (tlv + len > end) |
| return false; |
| |
| iter->tag = tag; |
| iter->len = len; |
| iter->data = tlv; |
| |
| iter->pos = tlv + len - iter->tlv; |
| |
| return true; |
| } |
| |
| /* |
| * Concatenate all vendor IEs with a given OUI + type. |
| * |
| * Returns a newly allocated buffer with the contents of the matching ies |
| * copied into it. @out_len is set to the overall size of the contents. |
| * If no matching elements were found, NULL is returned and @out_len is |
| * set to -ENOENT. |
| */ |
| static void *ie_tlv_vendor_ie_concat(const unsigned char oui[], |
| unsigned char type, |
| const unsigned char *ies, |
| unsigned int len, |
| ssize_t *out_len) |
| { |
| struct ie_tlv_iter iter; |
| const unsigned char *data; |
| unsigned int ie_len; |
| unsigned int concat_len = 0; |
| unsigned char *ret; |
| |
| ie_tlv_iter_init(&iter, ies, len); |
| |
| while (ie_tlv_iter_next(&iter)) { |
| if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC) |
| continue; |
| |
| ie_len = ie_tlv_iter_get_length(&iter); |
| if (ie_len < 4) |
| continue; |
| |
| data = ie_tlv_iter_get_data(&iter); |
| |
| if (memcmp(data, oui, 3)) |
| continue; |
| |
| if (data[3] != type) |
| continue; |
| |
| concat_len += ie_len - 4; |
| } |
| |
| if (concat_len == 0) { |
| if (out_len) |
| *out_len = -ENOENT; |
| |
| return NULL; |
| } |
| |
| ie_tlv_iter_init(&iter, ies, len); |
| ret = l_malloc(concat_len); |
| |
| concat_len = 0; |
| |
| while (ie_tlv_iter_next(&iter)) { |
| if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC) |
| continue; |
| |
| ie_len = ie_tlv_iter_get_length(&iter); |
| if (ie_len < 4) |
| continue; |
| |
| data = ie_tlv_iter_get_data(&iter); |
| |
| if (memcmp(data, oui, 3)) |
| continue; |
| |
| if (data[3] != type) |
| continue; |
| |
| memcpy(ret + concat_len, data + 4, ie_len - 4); |
| concat_len += ie_len - 4; |
| } |
| |
| if (out_len) |
| *out_len = concat_len; |
| |
| return ret; |
| } |
| |
| /* |
| * Wi-Fi Simple Configuration v2.0.5, Section 8.2: |
| * "There may be more than one instance of the Wi-Fi Simple Configuration |
| * Information Element in a single 802.11 management frame. If multiple |
| * Information Elements are present, the Wi-Fi Simple Configuration data |
| * consists of the concatenation of the Data components of those Information |
| * Elements (the order of these elements in the original packet shall be |
| * preserved when concatenating Data components)." |
| */ |
| void *ie_tlv_extract_wsc_payload(const unsigned char *ies, size_t len, |
| ssize_t *out_len) |
| { |
| return ie_tlv_vendor_ie_concat(microsoft_oui, 0x04, ies, len, out_len); |
| } |
| |
| /* |
| * Encapsulate & Fragment data into Vendor IE with a given OUI + type |
| * |
| * Returns a newly allocated buffer with the contents of encapsulated into |
| * multiple vendor IE. @out_len is set to the overall size of the contents. |
| */ |
| static void *ie_tlv_vendor_ie_encapsulate(const unsigned char oui[], |
| uint8_t type, |
| const void *data, size_t len, |
| size_t *out_len) |
| { |
| size_t overhead; |
| size_t ie_len; |
| size_t offset; |
| uint8_t *ret; |
| |
| /* |
| * Each Vendor IE can contain up to 251 bytes of data. |
| * 255 byte maximum length - 3 for oui and 1 for type |
| */ |
| overhead = (len + 250) / 251 * 6; |
| |
| ret = l_malloc(len + overhead); |
| |
| if (out_len) |
| *out_len = len + overhead; |
| |
| offset = 0; |
| |
| while (len) { |
| ie_len = len <= 251 ? len : 251; |
| ret[offset++] = IE_TYPE_VENDOR_SPECIFIC; |
| ret[offset++] = ie_len + 4; |
| memcpy(ret + offset, oui, 3); |
| offset += 3; |
| ret[offset++] = type; |
| memcpy(ret + offset, data, ie_len); |
| |
| data += ie_len; |
| len -= ie_len; |
| } |
| |
| return ret; |
| } |
| |
| void *ie_tlv_encapsulate_wsc_payload(const uint8_t *data, size_t len, |
| size_t *out_len) |
| { |
| return ie_tlv_vendor_ie_encapsulate(microsoft_oui, 0x04, |
| data, len, out_len); |
| } |
| |
| #define TLV_HEADER_LEN 2 |
| |
| static bool ie_tlv_builder_init_recurse(struct ie_tlv_builder *builder, |
| unsigned char *tlv, unsigned int size) |
| { |
| if (!builder) |
| return false; |
| |
| if (!tlv) { |
| memset(builder->buf, 0, MAX_BUILDER_SIZE); |
| builder->tlv = builder->buf; |
| builder->max = MAX_BUILDER_SIZE; |
| } else { |
| builder->tlv = tlv; |
| builder->max = size; |
| } |
| |
| builder->pos = 0; |
| builder->parent = NULL; |
| builder->tag = 0xffff; |
| builder->len = 0; |
| |
| return true; |
| } |
| |
| bool ie_tlv_builder_init(struct ie_tlv_builder *builder) |
| { |
| return ie_tlv_builder_init_recurse(builder, NULL, 0); |
| } |
| |
| static void ie_tlv_builder_write_header(struct ie_tlv_builder *builder) |
| { |
| unsigned char *tlv = builder->tlv + builder->pos; |
| |
| if (builder->tag < 256) { |
| tlv[0] = builder->tag; |
| tlv[1] = builder->len; |
| } else { |
| tlv[0] = IE_TYPE_EXTENSION; |
| tlv[1] = builder->len + 1; |
| tlv[2] = builder->tag - 256; |
| } |
| } |
| |
| bool ie_tlv_builder_set_length(struct ie_tlv_builder *builder, |
| unsigned int new_len) |
| { |
| unsigned int new_pos = builder->pos + TLV_HEADER_LEN + new_len; |
| |
| if (builder->tag >= 256) |
| new_pos += 1; |
| |
| if (new_pos > builder->max) |
| return false; |
| |
| if (builder->parent) |
| ie_tlv_builder_set_length(builder->parent, new_pos); |
| |
| builder->len = new_len; |
| |
| return true; |
| } |
| |
| bool ie_tlv_builder_next(struct ie_tlv_builder *builder, unsigned int new_tag) |
| { |
| if (new_tag > 0x1ff) |
| return false; |
| |
| if (builder->tag != 0xffff) { |
| ie_tlv_builder_write_header(builder); |
| builder->pos += TLV_HEADER_LEN + builder->tlv[builder->pos + 1]; |
| } |
| |
| builder->tag = new_tag; |
| |
| return ie_tlv_builder_set_length(builder, 0); |
| } |
| |
| unsigned char *ie_tlv_builder_get_data(struct ie_tlv_builder *builder) |
| { |
| return builder->tlv + TLV_HEADER_LEN + builder->pos + |
| (builder->tag >= 256 ? 1 : 0); |
| } |
| |
| bool ie_tlv_builder_recurse(struct ie_tlv_builder *builder, |
| struct ie_tlv_builder *recurse) |
| { |
| unsigned char *end = builder->buf + builder->max; |
| unsigned char *data = ie_tlv_builder_get_data(builder); |
| |
| if (!ie_tlv_builder_init_recurse(recurse, data, end - data)) |
| return false; |
| |
| recurse->parent = builder; |
| |
| return true; |
| } |
| |
| void ie_tlv_builder_finalize(struct ie_tlv_builder *builder, |
| unsigned int *out_len) |
| { |
| unsigned int len = 0; |
| |
| if (builder->tag != 0xffff) { |
| ie_tlv_builder_write_header(builder); |
| |
| len = builder->pos + TLV_HEADER_LEN + |
| builder->tlv[builder->pos + 1]; |
| } |
| |
| if (out_len) |
| *out_len = len; |
| } |
| |
| /* |
| * Converts RSN cipher suite into an unsigned integer suitable to be used |
| * by nl80211. The enumeration is the same as found in crypto.h |
| * |
| * If the suite value is invalid, this function returns 0. |
| */ |
| uint32_t ie_rsn_cipher_suite_to_cipher(enum ie_rsn_cipher_suite suite) |
| { |
| switch (suite) { |
| case IE_RSN_CIPHER_SUITE_CCMP: |
| return CRYPTO_CIPHER_CCMP; |
| case IE_RSN_CIPHER_SUITE_TKIP: |
| return CRYPTO_CIPHER_TKIP; |
| case IE_RSN_CIPHER_SUITE_WEP40: |
| return CRYPTO_CIPHER_WEP40; |
| case IE_RSN_CIPHER_SUITE_WEP104: |
| return CRYPTO_CIPHER_WEP104; |
| case IE_RSN_CIPHER_SUITE_BIP: |
| return CRYPTO_CIPHER_BIP; |
| default: |
| return 0; |
| } |
| } |
| |
| /* 802.11, Section 8.4.2.27.2 */ |
| static bool ie_parse_cipher_suite(const uint8_t *data, |
| enum ie_rsn_cipher_suite *out) |
| { |
| /* |
| * Compare the OUI to the ones we know. OUI Format is found in |
| * Figure 8-187 of 802.11 |
| */ |
| if (!memcmp(data, ieee_oui, 3)) { |
| /* Suite type from Table 8-99 */ |
| switch (data[3]) { |
| case 0: |
| *out = IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER; |
| return true; |
| case 1: |
| *out = IE_RSN_CIPHER_SUITE_WEP40; |
| return true; |
| case 2: |
| *out = IE_RSN_CIPHER_SUITE_TKIP; |
| return true; |
| case 4: |
| *out = IE_RSN_CIPHER_SUITE_CCMP; |
| return true; |
| case 5: |
| *out = IE_RSN_CIPHER_SUITE_WEP104; |
| return true; |
| case 6: |
| *out = IE_RSN_CIPHER_SUITE_BIP; |
| return true; |
| case 7: |
| *out = IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC; |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* 802.11, Section 8.4.2.27.2 */ |
| static bool ie_parse_akm_suite(const uint8_t *data, |
| enum ie_rsn_akm_suite *out) |
| { |
| /* |
| * Compare the OUI to the ones we know. OUI Format is found in |
| * Figure 8-187 of 802.11 |
| */ |
| if (!memcmp(data, ieee_oui, 3)) { |
| /* Suite type from Table 8-101 */ |
| switch (data[3]) { |
| case 1: |
| *out = IE_RSN_AKM_SUITE_8021X; |
| return true; |
| case 2: |
| *out = IE_RSN_AKM_SUITE_PSK; |
| return true; |
| case 3: |
| *out = IE_RSN_AKM_SUITE_FT_OVER_8021X; |
| return true; |
| case 4: |
| *out = IE_RSN_AKM_SUITE_FT_USING_PSK; |
| return true; |
| case 5: |
| *out = IE_RSN_AKM_SUITE_8021X_SHA256; |
| return true; |
| case 6: |
| *out = IE_RSN_AKM_SUITE_PSK_SHA256; |
| return true; |
| case 7: |
| *out = IE_RSN_AKM_SUITE_TDLS; |
| return true; |
| case 8: |
| *out = IE_RSN_AKM_SUITE_SAE_SHA256; |
| return true; |
| case 9: |
| *out = IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256; |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool ie_parse_group_cipher(const uint8_t *data, |
| enum ie_rsn_cipher_suite *out) |
| { |
| enum ie_rsn_cipher_suite tmp; |
| |
| bool r = ie_parse_cipher_suite(data, &tmp); |
| |
| if (!r) |
| return r; |
| |
| switch (tmp) { |
| case IE_RSN_CIPHER_SUITE_CCMP: |
| case IE_RSN_CIPHER_SUITE_TKIP: |
| case IE_RSN_CIPHER_SUITE_WEP104: |
| case IE_RSN_CIPHER_SUITE_WEP40: |
| case IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC: |
| break; |
| default: |
| return false; |
| } |
| |
| *out = tmp; |
| return true; |
| } |
| |
| static bool ie_parse_pairwise_cipher(const uint8_t *data, |
| enum ie_rsn_cipher_suite *out) |
| { |
| enum ie_rsn_cipher_suite tmp; |
| |
| bool r = ie_parse_cipher_suite(data, &tmp); |
| |
| if (!r) |
| return r; |
| |
| switch (tmp) { |
| case IE_RSN_CIPHER_SUITE_CCMP: |
| case IE_RSN_CIPHER_SUITE_TKIP: |
| case IE_RSN_CIPHER_SUITE_WEP104: |
| case IE_RSN_CIPHER_SUITE_WEP40: |
| case IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER: |
| break; |
| default: |
| return false; |
| } |
| |
| *out = tmp; |
| return true; |
| } |
| |
| static bool ie_parse_group_management_cipher(const uint8_t *data, |
| enum ie_rsn_cipher_suite *out) |
| { |
| enum ie_rsn_cipher_suite tmp; |
| |
| bool r = ie_parse_cipher_suite(data, &tmp); |
| |
| if (!r) |
| return r; |
| |
| switch (tmp) { |
| case IE_RSN_CIPHER_SUITE_BIP: |
| break; |
| default: |
| return false; |
| } |
| |
| *out = tmp; |
| return true; |
| } |
| |
| #define RSNE_ADVANCE(data, len, step) \ |
| data += step; \ |
| len -= step; \ |
| \ |
| if (len == 0) \ |
| goto done \ |
| |
| int ie_parse_rsne(struct ie_tlv_iter *iter, struct ie_rsn_info *out_info) |
| { |
| const uint8_t *data = iter->data; |
| size_t len = iter->len; |
| uint16_t version; |
| struct ie_rsn_info info; |
| uint16_t count; |
| uint16_t i; |
| |
| memset(&info, 0, sizeof(info)); |
| info.group_cipher = IE_RSN_CIPHER_SUITE_CCMP; |
| info.pairwise_ciphers = IE_RSN_CIPHER_SUITE_CCMP; |
| info.akm_suites = IE_RSN_AKM_SUITE_8021X; |
| |
| /* Parse Version field */ |
| if (len < 2) |
| return -EMSGSIZE; |
| |
| version = l_get_le16(data); |
| if (version != 0x01) |
| return -EBADMSG; |
| |
| RSNE_ADVANCE(data, len, 2); |
| |
| /* Parse Group Cipher Suite field */ |
| if (len < 4) |
| return -EBADMSG; |
| |
| if (!ie_parse_group_cipher(data, &info.group_cipher)) |
| return -ERANGE; |
| |
| RSNE_ADVANCE(data, len, 4); |
| |
| /* Parse Pairwise Cipher Suite Count field */ |
| if (len < 2) |
| return -EBADMSG; |
| |
| count = l_get_le16(data); |
| |
| /* |
| * The spec doesn't seem to explicitly say what to do in this case, |
| * so we assume this situation is invalid. |
| */ |
| if (count == 0) |
| return -EINVAL; |
| |
| data += 2; |
| len -= 2; |
| |
| if (len < 4 * count) |
| return -EBADMSG; |
| |
| /* Parse Pairwise Cipher Suite List field */ |
| for (i = 0, info.pairwise_ciphers = 0; i < count; i++) { |
| enum ie_rsn_cipher_suite suite; |
| |
| if (!ie_parse_pairwise_cipher(data + i * 4, &suite)) |
| return -ERANGE; |
| |
| info.pairwise_ciphers |= suite; |
| } |
| |
| RSNE_ADVANCE(data, len, count * 4); |
| |
| /* Parse AKM Suite Count field */ |
| if (len < 2) |
| return -EBADMSG; |
| |
| count = l_get_le16(data); |
| if (count == 0) |
| return -EINVAL; |
| |
| data += 2; |
| len -= 2; |
| |
| if (len < 4 * count) |
| return -EBADMSG; |
| |
| /* Parse AKM Suite List field */ |
| for (i = 0, info.akm_suites = 0; i < count; i++) { |
| enum ie_rsn_akm_suite suite; |
| |
| if (!ie_parse_akm_suite(data + i * 4, &suite)) |
| return -ERANGE; |
| |
| info.akm_suites |= suite; |
| } |
| |
| RSNE_ADVANCE(data, len, count * 4); |
| |
| if (len < 2) |
| return -EBADMSG; |
| |
| info.preauthentication = util_is_bit_set(data[0], 0); |
| info.no_pairwise = util_is_bit_set(data[0], 1); |
| info.ptksa_replay_counter = util_bit_field(data[0], 2, 2); |
| info.gtksa_replay_counter = util_bit_field(data[0], 4, 2); |
| info.mfpr = util_is_bit_set(data[0], 6); |
| info.mfpc = util_is_bit_set(data[0], 7); |
| info.peerkey_enabled = util_is_bit_set(data[1], 1); |
| info.spp_a_msdu_capable = util_is_bit_set(data[1], 2); |
| info.spp_a_msdu_required = util_is_bit_set(data[1], 3); |
| info.pbac = util_is_bit_set(data[1], 4); |
| info.extended_key_id = util_is_bit_set(data[1], 5); |
| |
| /* |
| * BIP—default group management cipher suite in an RSNA with |
| * management frame protection enabled |
| */ |
| if (info.mfpc) |
| info.group_management_cipher = IE_RSN_CIPHER_SUITE_BIP; |
| |
| RSNE_ADVANCE(data, len, 2); |
| |
| /* Parse PMKID Count field */ |
| if (len < 2) |
| return -EBADMSG; |
| |
| info.num_pmkids = l_get_le16(data); |
| RSNE_ADVANCE(data, len, 2); |
| |
| if (info.num_pmkids > 0) { |
| if (len < 16 * info.num_pmkids) |
| return -EBADMSG; |
| |
| /* |
| * Parse PMKID List field. |
| * |
| * We simply assign the pointer to the PMKIDs to the structure. |
| * The PMKIDs are fixed size, 16 bytes each. |
| */ |
| info.pmkids = data; |
| RSNE_ADVANCE(data, len, info.num_pmkids * 16); |
| } |
| |
| /* Parse Group Management Cipher Suite field */ |
| if (len < 4) |
| return -EBADMSG; |
| |
| if (!ie_parse_group_management_cipher(data, |
| &info.group_management_cipher)) |
| return -ERANGE; |
| |
| RSNE_ADVANCE(data, len, 4); |
| |
| return -EBADMSG; |
| |
| done: |
| if (out_info) |
| memcpy(out_info, &info, sizeof(info)); |
| |
| return 0; |
| } |
| |
| int ie_parse_rsne_from_data(const uint8_t *data, size_t len, |
| struct ie_rsn_info *info) |
| { |
| struct ie_tlv_iter iter; |
| |
| ie_tlv_iter_init(&iter, data, len); |
| |
| if (!ie_tlv_iter_next(&iter)) |
| return -EMSGSIZE; |
| |
| if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_RSN) |
| return -EPROTOTYPE; |
| |
| return ie_parse_rsne(&iter, info); |
| } |
| |
| /* |
| * 802.11, Section 8.4.2.27.2 |
| * 802.11i, Section 7.3.2.25.1 and WPA_80211_v3_1 Section 2.1 |
| */ |
| static bool ie_build_cipher_suite(uint8_t *data, const uint8_t *oui, |
| const enum ie_rsn_cipher_suite suite) |
| { |
| switch (suite) { |
| case IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER: |
| memcpy(data, oui, 3); |
| data[3] = 0; |
| return true; |
| case IE_RSN_CIPHER_SUITE_WEP40: |
| memcpy(data, oui, 3); |
| data[3] = 1; |
| return true; |
| case IE_RSN_CIPHER_SUITE_TKIP: |
| memcpy(data, oui, 3); |
| data[3] = 2; |
| return true; |
| case IE_RSN_CIPHER_SUITE_CCMP: |
| memcpy(data, oui, 3); |
| data[3] = 4; |
| return true; |
| case IE_RSN_CIPHER_SUITE_WEP104: |
| memcpy(data, oui, 3); |
| data[3] = 5; |
| return true; |
| case IE_RSN_CIPHER_SUITE_BIP: |
| memcpy(data, oui, 3); |
| data[3] = 6; |
| return true; |
| case IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC: |
| memcpy(data, oui, 3); |
| data[3] = 7; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * 802.11, Section 8.4.2.27.2 |
| * 802.11i, Section 7.3.2.25.2 and WPA_80211_v3_1 Section 2.1 |
| */ |
| static bool ie_build_akm_suite(uint8_t *data, const uint8_t *oui, |
| enum ie_rsn_akm_suite suite) |
| { |
| switch (suite) { |
| case IE_RSN_AKM_SUITE_8021X: |
| memcpy(data, oui, 3); |
| data[3] = 1; |
| return true; |
| case IE_RSN_AKM_SUITE_PSK: |
| memcpy(data, oui, 3); |
| data[3] = 2; |
| return true; |
| case IE_RSN_AKM_SUITE_FT_OVER_8021X: |
| memcpy(data, oui, 3); |
| data[3] = 3; |
| return true; |
| case IE_RSN_AKM_SUITE_FT_USING_PSK: |
| memcpy(data, oui, 3); |
| data[3] = 4; |
| return true; |
| case IE_RSN_AKM_SUITE_8021X_SHA256: |
| memcpy(data, oui, 3); |
| data[3] = 5; |
| return true; |
| case IE_RSN_AKM_SUITE_PSK_SHA256: |
| memcpy(data, oui, 3); |
| data[3] = 6; |
| return true; |
| case IE_RSN_AKM_SUITE_TDLS: |
| memcpy(data, oui, 3); |
| data[3] = 7; |
| return true; |
| case IE_RSN_AKM_SUITE_SAE_SHA256: |
| memcpy(data, oui, 3); |
| data[3] = 8; |
| return true; |
| case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256: |
| memcpy(data, oui, 3); |
| data[3] = 9; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * Generate an RSNE IE based on the information found in info. |
| * The to array must be 256 bytes in size |
| * |
| * In theory it is possible to generate 257 byte IE RSNs (1 byte for IE Type, |
| * 1 byte for Length and 255 bytes of data) but we don't support this |
| * possibility. |
| */ |
| bool ie_build_rsne(const struct ie_rsn_info *info, uint8_t *to) |
| { |
| /* These are the only valid pairwise suites */ |
| static enum ie_rsn_cipher_suite pairwise_suites[] = { |
| IE_RSN_CIPHER_SUITE_CCMP, |
| IE_RSN_CIPHER_SUITE_TKIP, |
| IE_RSN_CIPHER_SUITE_WEP104, |
| IE_RSN_CIPHER_SUITE_WEP40, |
| IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER, |
| }; |
| unsigned int pos; |
| unsigned int i; |
| uint8_t *countptr; |
| uint16_t count; |
| enum ie_rsn_akm_suite akm_suite; |
| |
| to[0] = IE_TYPE_RSN; |
| |
| /* Version field, always 1 */ |
| pos = 2; |
| l_put_le16(1, to + pos); |
| pos += 2; |
| |
| /* Group Data Cipher Suite */ |
| if (!ie_build_cipher_suite(to + pos, ieee_oui, info->group_cipher)) |
| return false; |
| |
| pos += 4; |
| |
| /* Save position for Pairwise Cipher Suite Count field */ |
| countptr = to + pos; |
| pos += 2; |
| |
| for (i = 0, count = 0; i < L_ARRAY_SIZE(pairwise_suites); i++) { |
| enum ie_rsn_cipher_suite suite = pairwise_suites[i]; |
| |
| if (!(info->pairwise_ciphers & suite)) |
| continue; |
| |
| if (pos + 4 > 242) |
| return false; |
| |
| if (!ie_build_cipher_suite(to + pos, ieee_oui, suite)) |
| return false; |
| |
| pos += 4; |
| count += 1; |
| } |
| |
| l_put_le16(count, countptr); |
| |
| /* Save position for AKM Suite Count field */ |
| countptr = to + pos; |
| pos += 2; |
| |
| akm_suite = IE_RSN_AKM_SUITE_8021X; |
| count = 0; |
| |
| for (count = 0, akm_suite = IE_RSN_AKM_SUITE_8021X; |
| akm_suite <= IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256; |
| akm_suite <<= 1) { |
| if (!(info->akm_suites & akm_suite)) |
| continue; |
| |
| if (pos + 4 > 248) |
| return false; |
| |
| if (!ie_build_akm_suite(to + pos, ieee_oui, akm_suite)) |
| return false; |
| |
| pos += 4; |
| count += 1; |
| } |
| |
| l_put_le16(count, countptr); |
| |
| /* Bits 0 - 7 of RSNE Capabilities field */ |
| to[pos] = 0; |
| |
| if (info->preauthentication) |
| to[pos] |= 0x1; |
| |
| if (info->no_pairwise) |
| to[pos] |= 0x2; |
| |
| to[pos] |= info->ptksa_replay_counter << 2; |
| to[pos] |= info->gtksa_replay_counter << 4; |
| |
| if (info->mfpr) |
| to[pos] |= 0x40; |
| |
| if (info->mfpc) |
| to[pos] |= 0x80; |
| |
| pos += 1; |
| |
| /* Bits 8 - 15 of RSNE Capabilities field */ |
| to[pos] = 0; |
| |
| if (info->peerkey_enabled) |
| to[pos] |= 0x2; |
| |
| if (info->spp_a_msdu_capable) |
| to[pos] |= 0x4; |
| |
| if (info->spp_a_msdu_required) |
| to[pos] |= 0x8; |
| |
| if (info->pbac) |
| to[pos] |= 0x10; |
| |
| if (info->extended_key_id) |
| to[pos] |= 0x20; |
| |
| pos += 1; |
| |
| /* Short hand the generated RSNE if possible */ |
| if (info->num_pmkids == 0) { |
| /* No Group Management Cipher Suite */ |
| if (to[pos - 2] == 0 && to[pos - 1] == 0) { |
| pos -= 2; |
| goto done; |
| } else if (!info->mfpc) |
| goto done; |
| else if (info->group_management_cipher == |
| IE_RSN_CIPHER_SUITE_BIP) |
| goto done; |
| } |
| |
| /* PMKID Count */ |
| l_put_le16(info->num_pmkids, to + pos); |
| pos += 2; |
| |
| if (pos + info->num_pmkids * 16 > 252) |
| return false; |
| |
| /* PMKID List */ |
| memcpy(to + pos, info->pmkids, 16 * info->num_pmkids); |
| pos += 16 * info->num_pmkids; |
| |
| if (!info->mfpc) |
| goto done; |
| |
| if (info->group_management_cipher == IE_RSN_CIPHER_SUITE_BIP) |
| goto done; |
| |
| /* Group Management Cipher Suite */ |
| if (!ie_build_cipher_suite(to, ieee_oui, info->group_management_cipher)) |
| return false; |
| |
| pos += 4; |
| |
| done: |
| to[1] = pos - 2; |
| |
| return true; |
| } |
| |
| /* 802.11i-2004, Section 7.3.2.25.1 and WPA_80211_v3_1 Section 2.1 */ |
| static bool ie_parse_wpa_cipher_suite(const uint8_t *data, |
| enum ie_rsn_cipher_suite *out) |
| { |
| /* |
| * Compare the OUI to the ones we know. OUI Format is found in |
| * Figure 8-187 of 802.11 |
| */ |
| if (!memcmp(data, microsoft_oui, 3)) { |
| /* Suite type from 802.11i-2004, Table 20da */ |
| switch (data[3]) { |
| case 0: |
| *out = IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER; |
| return true; |
| case 1: |
| *out = IE_RSN_CIPHER_SUITE_WEP40; |
| return true; |
| case 2: |
| *out = IE_RSN_CIPHER_SUITE_TKIP; |
| return true; |
| case 4: |
| *out = IE_RSN_CIPHER_SUITE_CCMP; |
| return true; |
| case 5: |
| *out = IE_RSN_CIPHER_SUITE_WEP104; |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* 802.11i-2004, Section 7.3.2.25.2 and WPA_80211_v3_1 Section 2.1 */ |
| static bool ie_parse_wpa_akm_suite(const uint8_t *data, |
| enum ie_rsn_akm_suite *out) |
| { |
| /* |
| * Compare the OUI to the ones we know. OUI Format is found in |
| * Figure 8-187 of 802.11 |
| */ |
| if (!memcmp(data, microsoft_oui, 3)) { |
| /* Suite type from 802.11i-2004, Table 20dc */ |
| switch (data[3]) { |
| case 1: |
| *out = IE_RSN_AKM_SUITE_8021X; |
| return true; |
| case 2: |
| *out = IE_RSN_AKM_SUITE_PSK; |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool ie_parse_wpa_group_cipher(const uint8_t *data, |
| enum ie_rsn_cipher_suite *out) |
| { |
| enum ie_rsn_cipher_suite tmp; |
| |
| bool r = ie_parse_wpa_cipher_suite(data, &tmp); |
| |
| if (!r) |
| return r; |
| |
| switch (tmp) { |
| case IE_RSN_CIPHER_SUITE_CCMP: |
| case IE_RSN_CIPHER_SUITE_TKIP: |
| case IE_RSN_CIPHER_SUITE_WEP104: |
| case IE_RSN_CIPHER_SUITE_WEP40: |
| break; |
| default: |
| return false; |
| } |
| |
| *out = tmp; |
| return true; |
| } |
| |
| static bool ie_parse_wpa_pairwise_cipher(const uint8_t *data, |
| enum ie_rsn_cipher_suite *out) |
| { |
| enum ie_rsn_cipher_suite tmp; |
| |
| bool r = ie_parse_wpa_cipher_suite(data, &tmp); |
| |
| if (!r) |
| return r; |
| |
| switch (tmp) { |
| case IE_RSN_CIPHER_SUITE_CCMP: |
| case IE_RSN_CIPHER_SUITE_TKIP: |
| case IE_RSN_CIPHER_SUITE_WEP104: |
| case IE_RSN_CIPHER_SUITE_WEP40: |
| /* TODO : not sure about GROUP_CIPHER */ |
| break; |
| default: |
| return false; |
| } |
| |
| *out = tmp; |
| return true; |
| } |
| |
| bool is_ie_wpa_ie(const uint8_t *data, uint8_t len) |
| { |
| if (!data || len < 6) |
| return false; |
| |
| if ((!memcmp(data, microsoft_oui, 3) && data[3] == 1 && |
| l_get_le16(data + 4) == 1)) |
| return true; |
| |
| return false; |
| } |
| |
| int ie_parse_wpa(struct ie_tlv_iter *iter, struct ie_rsn_info *out_info) |
| { |
| const uint8_t *data = iter->data; |
| size_t len = iter->len; |
| struct ie_rsn_info info; |
| uint16_t count; |
| uint16_t i; |
| |
| if (!is_ie_wpa_ie(iter->data, iter->len)) |
| return -EINVAL; |
| |
| info.group_cipher = IE_RSN_CIPHER_SUITE_TKIP; |
| info.pairwise_ciphers = IE_RSN_CIPHER_SUITE_TKIP; |
| info.akm_suites = IE_RSN_AKM_SUITE_PSK; |
| |
| memset(&info, 0, sizeof(info)); |
| RSNE_ADVANCE(data, len, 6); |
| |
| /* Parse Group Cipher Suite field */ |
| if (len < 4) |
| return -EBADMSG; |
| |
| if (!ie_parse_wpa_group_cipher(data, &info.group_cipher)) |
| return -ERANGE; |
| |
| RSNE_ADVANCE(data, len, 4); |
| |
| /* Parse Pairwise Cipher Suite Count field */ |
| if (len < 2) |
| return -EBADMSG; |
| |
| count = l_get_le16(data); |
| |
| /* |
| * The spec doesn't seem to explicitly say what to do in this case, |
| * so we assume this situation is invalid. |
| */ |
| if (count == 0) |
| return -EINVAL; |
| |
| data += 2; |
| len -= 2; |
| |
| if (len < 4 * count) |
| return -EBADMSG; |
| |
| /* Parse Pairwise Cipher Suite List field */ |
| for (i = 0, info.pairwise_ciphers = 0; i < count; i++) { |
| enum ie_rsn_cipher_suite suite; |
| |
| if (!ie_parse_wpa_pairwise_cipher(data + i * 4, &suite)) |
| return -ERANGE; |
| |
| info.pairwise_ciphers |= suite; |
| } |
| |
| RSNE_ADVANCE(data, len, count * 4); |
| |
| /* Parse AKM Suite Count field */ |
| if (len < 2) |
| return -EBADMSG; |
| |
| count = l_get_le16(data); |
| if (count == 0) |
| return -EINVAL; |
| |
| data += 2; |
| len -= 2; |
| |
| if (len < 4 * count) |
| return -EBADMSG; |
| |
| /* Parse AKM Suite List field */ |
| for (i = 0, info.akm_suites = 0; i < count; i++) { |
| enum ie_rsn_akm_suite suite; |
| |
| if (!ie_parse_wpa_akm_suite(data + i * 4, &suite)) |
| return -ERANGE; |
| |
| info.akm_suites |= suite; |
| } |
| |
| RSNE_ADVANCE(data, len, count * 4); |
| |
| return -EBADMSG; |
| |
| done: |
| /* |
| * 802.11i, Section 7.3.2.25.1 |
| * Use of CCMP as the group cipher suite with TKIP as the |
| * pairwise cipher suite shall not be supported. |
| */ |
| if (info.group_cipher & IE_RSN_CIPHER_SUITE_CCMP && |
| info.pairwise_ciphers & IE_RSN_CIPHER_SUITE_TKIP) |
| return -EBADMSG; |
| |
| if (out_info) |
| memcpy(out_info, &info, sizeof(info)); |
| |
| return 0; |
| } |
| |
| int ie_parse_wpa_from_data(const uint8_t *data, size_t len, |
| struct ie_rsn_info *info) |
| { |
| struct ie_tlv_iter iter; |
| |
| ie_tlv_iter_init(&iter, data, len); |
| |
| if (!ie_tlv_iter_next(&iter)) |
| return -EMSGSIZE; |
| |
| if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC) |
| return -EPROTOTYPE; |
| |
| return ie_parse_wpa(&iter, info); |
| } |
| |
| /* |
| * Generate an WPA IE based on the information found in info. |
| * The to array must be minimum of 19 bytes in size |
| */ |
| bool ie_build_wpa(const struct ie_rsn_info *info, uint8_t *to) |
| { |
| /* These are the only valid pairwise suites */ |
| static enum ie_rsn_cipher_suite pairwise_suites[] = { |
| IE_RSN_CIPHER_SUITE_CCMP, |
| IE_RSN_CIPHER_SUITE_TKIP, |
| IE_RSN_CIPHER_SUITE_WEP104, |
| IE_RSN_CIPHER_SUITE_WEP40, |
| /* TODO: not sure about USE_GROUP_CIPHER,*/ |
| }; |
| /* These are the only valid AKM suites */ |
| static enum ie_rsn_akm_suite akm_suites[] = { |
| IE_RSN_AKM_SUITE_8021X, |
| IE_RSN_AKM_SUITE_PSK, |
| }; |
| unsigned int pos; |
| unsigned int i; |
| uint8_t *countptr; |
| uint16_t count; |
| |
| /* |
| * 802.11i, Section 7.3.2.25.1 |
| * Use of CCMP as the group cipher suite with TKIP as the |
| * pairwise cipher suite shall not be supported. |
| */ |
| if (info->group_cipher == IE_RSN_CIPHER_SUITE_CCMP && |
| info->pairwise_ciphers & IE_RSN_CIPHER_SUITE_TKIP) |
| return false; |
| |
| to[0] = IE_TYPE_VENDOR_SPECIFIC; |
| |
| /* Vendor OUI and Type */ |
| pos = 2; |
| memcpy(to + pos, microsoft_oui, 3); |
| pos += 3; |
| to[pos] = 1; /* OUI type 1 means WPA element */ |
| pos++; |
| |
| /* Version field, always 1 */ |
| l_put_le16(1, to + pos); |
| pos += 2; |
| |
| /* Group Data Cipher Suite */ |
| if (!ie_build_cipher_suite(to + pos, microsoft_oui, |
| info->group_cipher)) |
| return false; |
| |
| pos += 4; |
| |
| /* Save position for Pairwise Cipher Suite Count field */ |
| countptr = to + pos; |
| pos += 2; |
| |
| for (i = 0, count = 0; i < L_ARRAY_SIZE(pairwise_suites); i++) { |
| enum ie_rsn_cipher_suite suite = pairwise_suites[i]; |
| |
| if (!(info->pairwise_ciphers & suite)) |
| continue; |
| |
| if (!ie_build_cipher_suite(to + pos, microsoft_oui, suite)) |
| return false; |
| |
| pos += 4; |
| count += 1; |
| } |
| |
| l_put_le16(count, countptr); |
| |
| /* Save position for AKM Suite Count field */ |
| countptr = to + pos; |
| pos += 2; |
| |
| for (i = 0, count = 0; i < L_ARRAY_SIZE(akm_suites); i++) { |
| enum ie_rsn_akm_suite suite = akm_suites[i]; |
| |
| if (!(info->akm_suites & suite)) |
| continue; |
| |
| if (!ie_build_akm_suite(to + pos, microsoft_oui, suite)) |
| return false; |
| |
| pos += 4; |
| count += 1; |
| } |
| |
| l_put_le16(count, countptr); |
| |
| to[1] = pos - 2; |
| |
| return true; |
| } |
| |
| int ie_parse_bss_load(struct ie_tlv_iter *iter, uint16_t *out_sta_count, |
| uint8_t *out_channel_utilization, |
| uint16_t *out_admission_capacity) |
| { |
| const uint8_t *data; |
| |
| if (ie_tlv_iter_get_length(iter) != 5) |
| return -EINVAL; |
| |
| data = ie_tlv_iter_get_data(iter); |
| |
| if (out_sta_count) |
| *out_sta_count = data[0] | data[1] << 8; |
| |
| if (out_channel_utilization) |
| *out_channel_utilization = data[2]; |
| |
| if (out_admission_capacity) |
| *out_admission_capacity = data[3] | data[4] << 8; |
| |
| return 0; |
| } |
| |
| int ie_parse_bss_load_from_data(const uint8_t *data, uint8_t len, |
| uint16_t *out_sta_count, |
| uint8_t *out_channel_utilization, |
| uint16_t *out_admission_capacity) |
| { |
| struct ie_tlv_iter iter; |
| |
| ie_tlv_iter_init(&iter, data, len); |
| |
| if (!ie_tlv_iter_next(&iter)) |
| return -EMSGSIZE; |
| |
| if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_BSS_LOAD) |
| return -EPROTOTYPE; |
| |
| return ie_parse_bss_load(&iter, out_sta_count, |
| out_channel_utilization, out_admission_capacity); |
| } |
| |
| int ie_parse_supported_rates(struct ie_tlv_iter *iter, |
| struct l_uintset **set) |
| { |
| const uint8_t *rates; |
| unsigned int len; |
| unsigned int i; |
| |
| len = ie_tlv_iter_get_length(iter); |
| |
| if (ie_tlv_iter_get_tag(iter) == IE_TYPE_SUPPORTED_RATES && |
| (len == 0 || len > 8)) |
| return -EINVAL; |
| |
| rates = ie_tlv_iter_get_data(iter); |
| |
| if (!*set) |
| *set = l_uintset_new(108); |
| |
| for (i = 0; i < len; i++) { |
| if (rates[i] == 0xff) |
| continue; |
| |
| l_uintset_put(*set, rates[i] & 0x7f); |
| } |
| |
| return 0; |
| } |
| |
| int ie_parse_supported_rates_from_data(const uint8_t *data, uint8_t len, |
| struct l_uintset **set) |
| { |
| struct ie_tlv_iter iter; |
| uint8_t tag; |
| |
| ie_tlv_iter_init(&iter, data, len); |
| |
| if (!ie_tlv_iter_next(&iter)) |
| return -EMSGSIZE; |
| |
| tag = ie_tlv_iter_get_tag(&iter); |
| |
| if (tag != IE_TYPE_SUPPORTED_RATES && |
| tag != IE_TYPE_EXTENDED_SUPPORTED_RATES) |
| return -EPROTOTYPE; |
| |
| return ie_parse_supported_rates(&iter, set); |
| } |
| |
| int ie_parse_mobility_domain(struct ie_tlv_iter *iter, uint16_t *mdid, |
| bool *ft_over_ds, bool *resource_req) |
| { |
| const uint8_t *data; |
| |
| if (ie_tlv_iter_get_length(iter) != 3) |
| return -EINVAL; |
| |
| data = ie_tlv_iter_get_data(iter); |
| |
| if (mdid) |
| *mdid = l_get_le16(data); |
| |
| if (ft_over_ds) |
| *ft_over_ds = (data[2] & 0x01) > 0; |
| |
| if (resource_req) |
| *resource_req = (data[2] & 0x02) > 0; |
| |
| return 0; |
| } |
| |
| int ie_parse_mobility_domain_from_data(const uint8_t *data, uint8_t len, |
| uint16_t *mdid, |
| bool *ft_over_ds, bool *resource_req) |
| { |
| struct ie_tlv_iter iter; |
| |
| ie_tlv_iter_init(&iter, data, len); |
| |
| if (!ie_tlv_iter_next(&iter)) |
| return -EMSGSIZE; |
| |
| if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_MOBILITY_DOMAIN) |
| return -EPROTOTYPE; |
| |
| return ie_parse_mobility_domain(&iter, mdid, ft_over_ds, resource_req); |
| } |
| |
| bool ie_build_mobility_domain(uint16_t mdid, bool ft_over_ds, bool resource_req, |
| uint8_t *to) |
| { |
| *to++ = IE_TYPE_MOBILITY_DOMAIN; |
| |
| *to++ = 3; |
| |
| l_put_le16(mdid, to); |
| to[2] = |
| (ft_over_ds ? 0x01 : 0) | |
| (resource_req ? 0x02 : 0); |
| |
| return true; |
| } |
| |
| int ie_parse_fast_bss_transition(struct ie_tlv_iter *iter, |
| struct ie_ft_info *info) |
| { |
| const uint8_t *data; |
| uint8_t len, subelem_id, subelem_len; |
| |
| len = ie_tlv_iter_get_length(iter); |
| if (len < 82) |
| return -EINVAL; |
| |
| data = ie_tlv_iter_get_data(iter); |
| |
| memset(info, 0, sizeof(*info)); |
| |
| info->mic_element_count = data[1]; |
| |
| memcpy(info->mic, data + 2, 16); |
| |
| memcpy(info->anonce, data + 18, 32); |
| |
| memcpy(info->snonce, data + 50, 32); |
| |
| len -= 82; |
| data += 82; |
| |
| while (len >= 2) { |
| subelem_id = *data++; |
| subelem_len = *data++; |
| |
| switch (subelem_id) { |
| case 1: |
| if (subelem_len != 6) |
| return -EINVAL; |
| |
| memcpy(info->r1khid, data, 6); |
| info->r1khid_present = true; |
| |
| break; |
| |
| case 2: |
| if (subelem_len < 35 || subelem_len > 51) |
| return -EINVAL; |
| |
| info->gtk_key_id = util_bit_field(data[0], 0, 2); |
| info->gtk_len = data[2]; |
| |
| /* |
| * Check Wrapped Key field length is Key Length plus |
| * padding (0 - 7 bytes) plus 8 bytes for AES key wrap. |
| */ |
| if (align_len(info->gtk_len, 8) + 8 != subelem_len - 11) |
| return -EINVAL; |
| |
| memcpy(info->gtk_rsc, data + 3, 8); |
| memcpy(info->gtk, data + 11, subelem_len - 11); |
| |
| break; |
| case 3: |
| |
| if (subelem_len < 1 || subelem_len > 48) |
| return -EINVAL; |
| |
| memcpy(info->r0khid, data, subelem_len); |
| info->r0khid_len = subelem_len; |
| |
| break; |
| |
| case 4: |
| if (subelem_len != 33) |
| return -EINVAL; |
| |
| info->igtk_key_id = l_get_le16(data); |
| memcpy(info->igtk_ipn, data + 2, 6); |
| info->igtk_len = data[8]; |
| |
| if (info->igtk_len > 16) |
| return -EINVAL; |
| |
| memcpy(info->igtk, data + 9, subelem_len - 9); |
| |
| break; |
| } |
| |
| data += subelem_len; |
| len -= subelem_len + 2; |
| } |
| |
| if (len) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| int ie_parse_fast_bss_transition_from_data(const uint8_t *data, uint8_t len, |
| struct ie_ft_info *info) |
| { |
| struct ie_tlv_iter iter; |
| |
| ie_tlv_iter_init(&iter, data, len); |
| |
| if (!ie_tlv_iter_next(&iter)) |
| return -EMSGSIZE; |
| |
| if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_FAST_BSS_TRANSITION) |
| return -EPROTOTYPE; |
| |
| return ie_parse_fast_bss_transition(&iter, info); |
| } |
| |
| bool ie_build_fast_bss_transition(const struct ie_ft_info *info, uint8_t *to) |
| { |
| uint8_t *len; |
| |
| *to++ = IE_TYPE_FAST_BSS_TRANSITION; |
| |
| len = to++; |
| *len = 82; |
| |
| to[0] = 0x00; |
| to[1] = info->mic_element_count; |
| |
| memcpy(to + 2, info->mic, 16); |
| |
| memcpy(to + 18, info->anonce, 32); |
| |
| memcpy(to + 50, info->snonce, 32); |
| |
| to += 82; |
| |
| if (info->r1khid_present) { |
| to[0] = 1; |
| to[1] = 6; |
| memcpy(to + 2, info->r1khid, 6); |
| to += 8; |
| *len += 8; |
| } |
| |
| L_WARN_ON(info->gtk_len); /* Not implemented */ |
| |
| if (info->r0khid_len) { |
| to[0] = 3; |
| to[1] = info->r0khid_len; |
| memcpy(to + 2, info->r0khid, info->r0khid_len); |
| to += 2 + info->r0khid_len; |
| *len += 2 + info->r0khid_len; |
| } |
| |
| L_WARN_ON(info->igtk_len); /* Not implemented */ |
| |
| return true; |
| } |
| |
| enum nr_subelem_id { |
| NR_SUBELEM_ID_TSF_INFO = 1, |
| NR_SUBELEM_ID_CONDENSED_COUNTRY_STR = 2, |
| NR_SUBELEM_ID_BSS_TRANSITION_PREF = 3, |
| NR_SUBELEM_ID_BSS_TERMINATION_DURATION = 4, |
| NR_SUBELEM_ID_BEARING = 5, |
| NR_SUBELEM_ID_WIDE_BW_CHANNEL = 6, |
| /* Remaining defined subelements use the IE_TYPE_* ID values */ |
| }; |
| |
| int ie_parse_neighbor_report(struct ie_tlv_iter *iter, |
| struct ie_neighbor_report_info *info) |
| { |
| unsigned int len = ie_tlv_iter_get_length(iter); |
| const uint8_t *data = ie_tlv_iter_get_data(iter); |
| struct ie_tlv_iter opt_iter; |
| |
| if (len < 13) |
| return -EINVAL; |
| |
| memset(info, 0, sizeof(*info)); |
| |
| memcpy(info->addr, data + 0, 6); |
| |
| info->ht = util_is_bit_set(data[8], 3); |
| info->md = util_is_bit_set(data[8], 2); |
| info->immediate_block_ack = util_is_bit_set(data[8], 1); |
| info->delayed_block_ack = util_is_bit_set(data[8], 0); |
| info->rm = util_is_bit_set(data[9], 7); |
| info->apsd = util_is_bit_set(data[9], 6); |
| info->qos = util_is_bit_set(data[9], 5); |
| info->spectrum_mgmt = util_is_bit_set(data[9], 4); |
| info->key_scope = util_is_bit_set(data[9], 3); |
| info->security = util_is_bit_set(data[9], 2); |
| info->reachable = util_bit_field(data[9], 0, 2); |
| |
| info->oper_class = data[10]; |
| |
| info->channel_num = data[11]; |
| |
| info->phy_type = data[12]; |
| |
| ie_tlv_iter_init(&opt_iter, data + 13, len - 13); |
| |
| while (ie_tlv_iter_next(&opt_iter)) { |
| if (ie_tlv_iter_get_tag(&opt_iter) != |
| NR_SUBELEM_ID_BSS_TRANSITION_PREF) |
| continue; |
| |
| if (ie_tlv_iter_get_length(&opt_iter) != 1) |
| continue; |
| |
| info->bss_transition_pref = ie_tlv_iter_get_data(&opt_iter)[0]; |
| info->bss_transition_pref_present = true; |
| } |
| |
| return 0; |
| } |