diff --git a/src/ie.c b/src/ie.c index cf04e9f3..4700a8a1 100644 --- a/src/ie.c +++ b/src/ie.c @@ -1491,6 +1491,224 @@ int ie_parse_supported_rates_from_data(const uint8_t *supp_rates_ie, rssi, data_rate); } +enum ht_vht_channel_width { + HT_VHT_CHANNEL_WIDTH_20MHZ = 0, + HT_VHT_CHANNEL_WIDTH_40MHZ, + HT_VHT_CHANNEL_WIDTH_80MHZ, + HT_VHT_CHANNEL_WIDTH_160MHZ, +}; + +/* + * Base RSSI values for 20MHz (both HT and VHT) channel. These values can be + * used to calculate the minimum RSSI values for all other channel widths. HT + * MCS indexes are grouped into ranges of 8 (per spatial stream) where VHT are + * grouped in chunks of 10. This just means HT will not use the last two + * index's of this array. + */ +static const int32_t ht_vht_base_rssi[] = { + -82, -79, -77, -74, -70, -66, -65, -64, -59, -57 +}; + +struct ht_vht_rate { + uint64_t rate; + uint64_t sgi_rate; +}; + +static const struct ht_vht_rate ht_vht_rates[] = { + [HT_VHT_CHANNEL_WIDTH_20MHZ] = { .rate = 6500000, + .sgi_rate = 7200000 }, + [HT_VHT_CHANNEL_WIDTH_40MHZ] = { .rate = 13500000, + .sgi_rate = 15000000 }, + [HT_VHT_CHANNEL_WIDTH_80MHZ] = { .rate = 29300000, + .sgi_rate = 32500000 }, + [HT_VHT_CHANNEL_WIDTH_160MHZ] = { .rate = 58500000, + .sgi_rate = 65000000 }, +}; + +/* + * Both HT and VHT rates are calculated in the same fashion. The only difference + * is a relative MCS index is used for HT since, for each NSS, the formula + * is the same with relative index's. This is why this is called with index % 8 + * for HT, but not VHT. + */ +static bool calculate_ht_vht_data_rate(uint8_t index, + enum ht_vht_channel_width width, + int32_t rssi, uint8_t nss, bool sgi, + uint64_t *data_rate) +{ + const struct ht_vht_rate *rate = &ht_vht_rates[width]; + int32_t width_adjust = width * 3; + + if (rssi < ht_vht_base_rssi[index] + width_adjust) + return false; + + if (sgi) + *data_rate = rate->sgi_rate; + else + *data_rate = rate->rate; + + /* adjust base for spatial streams */ + *data_rate *= nss; + + /* + * As with HT, the VHT rates multiplier jumps up + * by 2 after MCS index 4 + */ + if (index < 4) + *data_rate *= index + 1; + else + *data_rate *= index + 3; + + return true; +} + +static int ie_parse_ht_capability(struct ie_tlv_iter *iter, int32_t rssi, + uint64_t *data_rate) +{ + unsigned int len; + const uint8_t *data; + uint8_t ht_cap; + int i; + uint64_t highest_rate = 0; + bool support_40mhz; + bool short_gi_20mhz; + bool short_gi_40mhz; + + len = ie_tlv_iter_get_length(iter); + + if (len < 26) + return -EINVAL; + + if (ie_tlv_iter_get_tag(iter) != IE_TYPE_HT_CAPABILITIES) + return -EINVAL; + + data = ie_tlv_iter_get_data(iter); + + /* Parse out channel width set and short GI */ + ht_cap = l_get_u8(data++); + + support_40mhz = util_is_bit_set(ht_cap, 1); + short_gi_20mhz = util_is_bit_set(ht_cap, 5); + short_gi_40mhz = util_is_bit_set(ht_cap, 6); + + data += 2; + + /* + * TODO: Support MCS values 32 - 76 + * + * The MCS values > 31 do not follow the same pattern since they use + * unequal modulation per spatial stream. These higher MCS values + * actually don't follow a pattern at all, since each stream can have a + * different modulation a higher MCS value does not mean higher + * throughput. For this reason these MCS indexes are left out. + */ + for (i = 31; i >= 0; i--) { + uint64_t drate; + uint8_t byte = i / 8; + uint8_t bit = i % 8; + + if (!util_is_bit_set(data[byte], bit)) + continue; + + if (!support_40mhz) + goto check_20; + + if (calculate_ht_vht_data_rate(i % 8, + HT_VHT_CHANNEL_WIDTH_40MHZ, + rssi, (i / 8) + 1, + short_gi_40mhz, &drate)) { + *data_rate = drate; + return 0; + } + +check_20: + if (!calculate_ht_vht_data_rate(i % 8, + HT_VHT_CHANNEL_WIDTH_20MHZ, + rssi, (i / 8) + 1, + short_gi_20mhz, &drate)) + continue; + + if (!support_40mhz) { + *data_rate = drate; + return 0; + } + + if (drate > highest_rate) + highest_rate = drate; + } + + if (!highest_rate) + return -ENOTSUP; + + *data_rate = highest_rate; + + return 0; +} + +static int ie_parse_ht_capability_from_data(const uint8_t *data, uint8_t len, + int32_t rssi, uint64_t *data_rate) +{ + 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_HT_CAPABILITIES) + return -EPROTOTYPE; + + return ie_parse_ht_capability(&iter, rssi, data_rate); +} + +/* + * Calculates the theoretical maximum data rates out of the provided + * supported rates IE, HT IE, and VHT IE. All 3 parsing functions are allowed + * to return -ENOTSUP, which indicates that a data rate was not found given + * the provided data. This is not fatal, it most likely means our RSSI was too + * low. + */ +int ie_parse_data_rates(const uint8_t *supp_rates_ie, + const uint8_t *ext_supp_rates_ie, + const uint8_t *ht_ie, + int32_t rssi, + uint64_t *data_rate) +{ + int ret = -ENOTSUP; + uint64_t rate = 0; + + /* An RSSI this low will not yield any rate results */ + if (rssi < -82) + return -ENOTSUP; + + if (ht_ie) { + ret = ie_parse_ht_capability_from_data(ht_ie, IE_LEN(ht_ie), + rssi, &rate); + if (ret == 0) + goto done; + } + + if (supp_rates_ie || ext_supp_rates_ie) { + ret = ie_parse_supported_rates_from_data(supp_rates_ie, + IE_LEN(supp_rates_ie), + ext_supp_rates_ie, + IE_LEN(supp_rates_ie), + rssi, &rate); + if (ret == 0) + goto done; + } + + return ret; + +done: + *data_rate = rate; + + return 0; +} + int ie_parse_mobility_domain(struct ie_tlv_iter *iter, uint16_t *mdid, bool *ft_over_ds, bool *resource_req) { diff --git a/src/ie.h b/src/ie.h index 9901b063..19b5405b 100644 --- a/src/ie.h +++ b/src/ie.h @@ -419,6 +419,12 @@ int ie_parse_supported_rates_from_data(const uint8_t *supp_rates_ie, uint8_t ext_supp_rates_len, int32_t rssi, uint64_t *data_rate); +int ie_parse_data_rates(const uint8_t *supp_rates_ie, + const uint8_t *ext_supp_rates_ie, + const uint8_t *ht_ie, + int32_t rssi, + uint64_t *data_rate); + int ie_parse_mobility_domain(struct ie_tlv_iter *iter, uint16_t *mdid, bool *ft_over_ds, bool *resource_req); int ie_parse_mobility_domain_from_data(const uint8_t *data, uint8_t len, diff --git a/src/scan.c b/src/scan.c index e44a6d4f..990c4cd0 100644 --- a/src/scan.c +++ b/src/scan.c @@ -865,6 +865,14 @@ static bool scan_parse_bss_information_elements(struct scan_bss *bss, bss->cc[2] = iter.data[2]; bss->cc_present = true; + break; + case IE_TYPE_HT_CAPABILITIES: + if (iter.len != 26) + return false; + + bss->ht_capable = true; + memcpy(bss->ht_ie, iter.data - 2, iter.len + 2); + break; } } @@ -1049,20 +1057,19 @@ static void scan_bss_compute_rank(struct scan_bss *bss) if (bss->has_sup_rates || bss->ext_supp_rates_ie) { uint64_t data_rate; - if (ie_parse_supported_rates_from_data(bss->has_sup_rates ? - bss->supp_rates_ie : NULL, - IE_LEN(bss->supp_rates_ie), - bss->ext_supp_rates_ie, - IE_LEN(bss->ext_supp_rates_ie), - bss->signal_strength / 100, - &data_rate) == 0) { + if (ie_parse_data_rates(bss->has_sup_rates ? + bss->supp_rates_ie : NULL, + bss->ext_supp_rates_ie, + bss->ht_capable ? bss->ht_ie : NULL, + bss->signal_strength / 100, + &data_rate) == 0) { double factor = RANK_MAX_SUPPORTED_RATE_FACTOR - RANK_MIN_SUPPORTED_RATE_FACTOR; /* - * Maximum rate is 54 Mbps + * Maximum rate is 600 Mbps (HT) */ - factor = factor * data_rate / 54000000 + + factor = factor * data_rate / 600000000 + RANK_MIN_SUPPORTED_RATE_FACTOR; rank *= factor; } else diff --git a/src/scan.h b/src/scan.h index 25717ede..a917aa3e 100644 --- a/src/scan.h +++ b/src/scan.h @@ -59,10 +59,12 @@ struct scan_bss { uint8_t utilization; uint8_t cc[3]; uint16_t rank; + uint8_t ht_ie[28]; bool mde_present : 1; bool cc_present : 1; bool cap_rm_neighbor_report : 1; bool has_sup_rates : 1; + bool ht_capable : 1; }; struct scan_parameters {