+static int gwi_sta_cb(struct nl_msg *msg, void *data) {
+ wireless_info_t *info = data;
+
+ 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]) {