- if (stats.qual.level != 0 || (stats.qual.updated & (IW_QUAL_DBM | IW_QUAL_RCPI))) {
- if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
- info->quality = stats.qual.qual;
- info->quality_max = range.max_qual.qual;
- info->quality_average = range.avg_qual.qual;
- info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
- }
-
- if (stats.qual.updated & IW_QUAL_RCPI) {
- if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
- info->signal_level = stats.qual.level / 2.0 - 110 + 0.5;
- info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
- }
- if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
- info->noise_level = stats.qual.noise / 2.0 - 110 + 0.5;
- info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
- }
- }
- else {
- if ((stats.qual.updated & IW_QUAL_DBM) || stats.qual.level > range.max_qual.level) {
- if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
- info->signal_level = stats.qual.level;
- if (info->signal_level > 63)
- info->signal_level -= 256;
- info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
- }
- if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
- info->noise_level = stats.qual.noise;
- if (info->noise_level > 63)
- info->noise_level -= 256;
- info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
- }
- }
- else {
- if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
- info->signal_level = stats.qual.level;
- info->signal_level_max = range.max_qual.level;
- info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
- }
- if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
- info->noise_level = stats.qual.noise;
- info->noise_level_max = range.max_qual.noise;
- info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
- }
- }
- }
- }
- else {
- if (!(stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
- info->quality = stats.qual.qual;
- info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
- }
- if (!(stats.qual.updated & IW_QUAL_LEVEL_INVALID)) {
- info->quality = stats.qual.level;
- info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
- }
- if (!(stats.qual.updated & IW_QUAL_NOISE_INVALID)) {
- info->quality = stats.qual.noise;
- info->flags |= WIRELESS_INFO_FLAG_HAS_NOISE;
- }
+ struct nlattr *tb[NL80211_ATTR_MAX + 1];
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];
+ struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1];
+ static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
+ [NL80211_STA_INFO_RX_BITRATE] = {.type = NLA_NESTED},
+ };
+
+ static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
+ [NL80211_RATE_INFO_BITRATE] = {.type = NLA_U16},
+ };
+
+ if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0)
+ return NL_SKIP;
+
+ if (tb[NL80211_ATTR_STA_INFO] == NULL)
+ return NL_SKIP;
+
+ if (nla_parse_nested(sinfo, NL80211_STA_INFO_MAX, tb[NL80211_ATTR_STA_INFO], stats_policy))
+ return NL_SKIP;
+
+ if (sinfo[NL80211_STA_INFO_RX_BITRATE] == NULL)
+ return NL_SKIP;
+
+ if (nla_parse_nested(rinfo, NL80211_RATE_INFO_MAX, sinfo[NL80211_STA_INFO_RX_BITRATE], rate_policy))
+ return NL_SKIP;
+
+ if (rinfo[NL80211_RATE_INFO_BITRATE] == NULL)
+ return NL_SKIP;
+
+ // NL80211_RATE_INFO_BITRATE is specified in units of 100 kbit/s, but iw
+ // used to specify bit/s, so we convert to use the same code path.
+ info->bitrate = (int)nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]) * 100 * 1000;
+
+ return NL_SKIP;
+}
+
+static int gwi_scan_cb(struct nl_msg *msg, void *data) {
+ wireless_info_t *info = data;
+ struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
+ struct nlattr *tb[NL80211_ATTR_MAX + 1];
+ struct nlattr *bss[NL80211_BSS_MAX + 1];
+ struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = {
+ [NL80211_BSS_FREQUENCY] = {.type = NLA_U32},
+ [NL80211_BSS_BSSID] = {.type = NLA_UNSPEC},
+ [NL80211_BSS_INFORMATION_ELEMENTS] = {.type = NLA_UNSPEC},
+ [NL80211_BSS_SIGNAL_MBM] = {.type = NLA_U32},
+ [NL80211_BSS_SIGNAL_UNSPEC] = {.type = NLA_U8},
+ [NL80211_BSS_STATUS] = {.type = NLA_U32},
+ };
+
+ if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0)
+ return NL_SKIP;
+
+ if (tb[NL80211_ATTR_BSS] == NULL)
+ return NL_SKIP;
+
+ if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy))
+ return NL_SKIP;
+
+ if (bss[NL80211_BSS_STATUS] == NULL)
+ return NL_SKIP;
+
+ const uint32_t status = nla_get_u32(bss[NL80211_BSS_STATUS]);
+
+ if (status != NL80211_BSS_STATUS_ASSOCIATED &&
+ status != NL80211_BSS_STATUS_IBSS_JOINED)
+ return NL_SKIP;
+
+ if (bss[NL80211_BSS_BSSID] == NULL)
+ return NL_SKIP;
+
+ memcpy(info->bssid, nla_data(bss[NL80211_BSS_BSSID]), ETH_ALEN);
+
+ if (bss[NL80211_BSS_FREQUENCY]) {
+ info->flags |= WIRELESS_INFO_FLAG_HAS_FREQUENCY;
+ info->frequency = (double)nla_get_u32(bss[NL80211_BSS_FREQUENCY]) * 1e6;
+ }
+
+ if (bss[NL80211_BSS_SIGNAL_UNSPEC]) {
+ info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
+ info->signal_level = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
+ info->signal_level_max = 100;
+
+ info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
+ info->quality = info->signal_level;
+ info->quality_max = 100;
+ info->quality_average = 50;
+ }
+
+ if (bss[NL80211_BSS_SIGNAL_MBM]) {
+ info->flags |= WIRELESS_INFO_FLAG_HAS_SIGNAL;
+ info->signal_level = (int)nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]) / 100;
+
+ info->flags |= WIRELESS_INFO_FLAG_HAS_QUALITY;
+ info->quality = nl80211_xbm_to_percent(nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]), 100);
+ info->quality_max = 100;
+ info->quality_average = 50;
+ }
+
+ if (bss[NL80211_BSS_INFORMATION_ELEMENTS]) {
+ uint8_t *ssid;
+ uint32_t ssid_len;
+
+ find_ssid(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]),
+ nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]),
+ &ssid, &ssid_len);
+ if (ssid && ssid_len) {
+ info->flags |= WIRELESS_INFO_FLAG_HAS_ESSID;
+ snprintf(info->essid, sizeof(info->essid), "%.*s", ssid_len, ssid);