]> git.ipfire.org Git - thirdparty/hostap.git/blobdiff - wpa_supplicant/rrm.c
Enhance get_mode() to return correct hw_mode with 6 GHz support
[thirdparty/hostap.git] / wpa_supplicant / rrm.c
index 942eea79da192f674669d8ca6b7db263d5e61613..66855b8773624ce8c44ae270ddd3c2444490e0bc 100644 (file)
@@ -79,7 +79,7 @@ void wpas_rrm_process_neighbor_rep(struct wpa_supplicant *wpa_s,
                             NULL);
 
        if (!wpa_s->rrm.notify_neighbor_rep) {
-               wpa_printf(MSG_ERROR, "RRM: Unexpected neighbor report");
+               wpa_msg(wpa_s, MSG_INFO, "RRM: Unexpected neighbor report");
                return;
        }
 
@@ -90,8 +90,8 @@ void wpas_rrm_process_neighbor_rep(struct wpa_supplicant *wpa_s,
                return;
        }
        wpabuf_put_data(neighbor_rep, report + 1, report_len - 1);
-       wpa_printf(MSG_DEBUG, "RRM: Notifying neighbor report (token = %d)",
-                  report[0]);
+       wpa_dbg(wpa_s, MSG_DEBUG, "RRM: Notifying neighbor report (token = %d)",
+               report[0]);
        wpa_s->rrm.notify_neighbor_rep(wpa_s->rrm.neighbor_rep_cb_ctx,
                                       neighbor_rep);
        wpa_s->rrm.notify_neighbor_rep = NULL;
@@ -101,10 +101,16 @@ void wpas_rrm_process_neighbor_rep(struct wpa_supplicant *wpa_s,
 
 #if defined(__CYGWIN__) || defined(CONFIG_NATIVE_WINDOWS)
 /* Workaround different, undefined for Windows, error codes used here */
+#ifndef ENOTCONN
 #define ENOTCONN -1
+#endif
+#ifndef EOPNOTSUPP
 #define EOPNOTSUPP -1
+#endif
+#ifndef ECANCELED
 #define ECANCELED -1
 #endif
+#endif
 
 /* Measurement Request element + Location Subject + Maximum Age subelement */
 #define MEASURE_REQUEST_LCI_LEN (3 + 1 + 4)
@@ -142,12 +148,12 @@ int wpas_rrm_send_neighbor_rep_request(struct wpa_supplicant *wpa_s,
        const u8 *rrm_ie;
 
        if (wpa_s->wpa_state != WPA_COMPLETED || wpa_s->current_ssid == NULL) {
-               wpa_printf(MSG_DEBUG, "RRM: No connection, no RRM.");
+               wpa_dbg(wpa_s, MSG_DEBUG, "RRM: No connection, no RRM.");
                return -ENOTCONN;
        }
 
        if (!wpa_s->rrm.rrm_used) {
-               wpa_printf(MSG_DEBUG, "RRM: No RRM in current connection.");
+               wpa_dbg(wpa_s, MSG_DEBUG, "RRM: No RRM in current connection.");
                return -EOPNOTSUPP;
        }
 
@@ -155,21 +161,15 @@ int wpas_rrm_send_neighbor_rep_request(struct wpa_supplicant *wpa_s,
                                WLAN_EID_RRM_ENABLED_CAPABILITIES);
        if (!rrm_ie || !(wpa_s->current_bss->caps & IEEE80211_CAP_RRM) ||
            !(rrm_ie[2] & WLAN_RRM_CAPS_NEIGHBOR_REPORT)) {
-               wpa_printf(MSG_DEBUG,
-                          "RRM: No network support for Neighbor Report.");
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "RRM: No network support for Neighbor Report.");
                return -EOPNOTSUPP;
        }
 
-       if (!cb) {
-               wpa_printf(MSG_DEBUG,
-                          "RRM: Neighbor Report request must provide a callback.");
-               return -EINVAL;
-       }
-
        /* Refuse if there's a live request */
        if (wpa_s->rrm.notify_neighbor_rep) {
-               wpa_printf(MSG_DEBUG,
-                          "RRM: Currently handling previous Neighbor Report.");
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "RRM: Currently handling previous Neighbor Report.");
                return -EBUSY;
        }
 
@@ -178,14 +178,15 @@ int wpas_rrm_send_neighbor_rep_request(struct wpa_supplicant *wpa_s,
                           (lci ? 2 + MEASURE_REQUEST_LCI_LEN : 0) +
                           (civic ? 2 + MEASURE_REQUEST_CIVIC_LEN : 0));
        if (buf == NULL) {
-               wpa_printf(MSG_DEBUG,
-                          "RRM: Failed to allocate Neighbor Report Request");
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "RRM: Failed to allocate Neighbor Report Request");
                return -ENOMEM;
        }
 
-       wpa_printf(MSG_DEBUG, "RRM: Neighbor report request (for %s), token=%d",
-                  (ssid ? wpa_ssid_txt(ssid->ssid, ssid->ssid_len) : ""),
-                  wpa_s->rrm.next_neighbor_rep_token);
+       wpa_dbg(wpa_s, MSG_DEBUG,
+               "RRM: Neighbor report request (for %s), token=%d",
+               (ssid ? wpa_ssid_txt(ssid->ssid, ssid->ssid_len) : ""),
+               wpa_s->rrm.next_neighbor_rep_token);
 
        wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
        wpabuf_put_u8(buf, WLAN_RRM_NEIGHBOR_REPORT_REQUEST);
@@ -267,8 +268,8 @@ int wpas_rrm_send_neighbor_rep_request(struct wpa_supplicant *wpa_s,
        if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
                                wpa_s->own_addr, wpa_s->bssid,
                                wpabuf_head(buf), wpabuf_len(buf), 0) < 0) {
-               wpa_printf(MSG_DEBUG,
-                          "RRM: Failed to send Neighbor Report Request");
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "RRM: Failed to send Neighbor Report Request");
                wpabuf_free(buf);
                return -ECANCELED;
        }
@@ -284,20 +285,20 @@ int wpas_rrm_send_neighbor_rep_request(struct wpa_supplicant *wpa_s,
 }
 
 
-static int wpas_rrm_report_elem(struct wpabuf *buf, u8 token, u8 mode, u8 type,
+static int wpas_rrm_report_elem(struct wpabuf **buf, u8 token, u8 mode, u8 type,
                                const u8 *data, size_t data_len)
 {
-       if (wpabuf_tailroom(buf) < 5 + data_len)
+       if (wpabuf_resize(buf, 5 + data_len))
                return -1;
 
-       wpabuf_put_u8(buf, WLAN_EID_MEASURE_REPORT);
-       wpabuf_put_u8(buf, 3 + data_len);
-       wpabuf_put_u8(buf, token);
-       wpabuf_put_u8(buf, mode);
-       wpabuf_put_u8(buf, type);
+       wpabuf_put_u8(*buf, WLAN_EID_MEASURE_REPORT);
+       wpabuf_put_u8(*buf, 3 + data_len);
+       wpabuf_put_u8(*buf, token);
+       wpabuf_put_u8(*buf, mode);
+       wpabuf_put_u8(*buf, type);
 
        if (data_len)
-               wpabuf_put_data(buf, data, data_len);
+               wpabuf_put_data(*buf, data, data_len);
 
        return 0;
 }
@@ -350,10 +351,7 @@ wpas_rrm_build_lci_report(struct wpa_supplicant *wpa_s,
        if (max_age != 0xffff && max_age < diff_l)
                goto reject;
 
-       if (wpabuf_resize(buf, 5 + wpabuf_len(wpa_s->lci)))
-               return -1;
-
-       if (wpas_rrm_report_elem(*buf, req->token,
+       if (wpas_rrm_report_elem(buf, req->token,
                                 MEASUREMENT_REPORT_MODE_ACCEPT, req->type,
                                 wpabuf_head_u8(wpa_s->lci),
                                 wpabuf_len(wpa_s->lci)) < 0) {
@@ -364,12 +362,8 @@ wpas_rrm_build_lci_report(struct wpa_supplicant *wpa_s,
        return 0;
 
 reject:
-       if (wpabuf_resize(buf, sizeof(struct rrm_measurement_report_element))) {
-               wpa_printf(MSG_DEBUG, "RRM: Memory allocation failed");
-               return -1;
-       }
-
-       if (wpas_rrm_report_elem(*buf, req->token,
+       if (!is_multicast_ether_addr(wpa_s->rrm.dst_addr) &&
+           wpas_rrm_report_elem(buf, req->token,
                                 MEASUREMENT_REPORT_MODE_REJECT_INCAPABLE,
                                 req->type, NULL, 0) < 0) {
                wpa_printf(MSG_DEBUG, "RRM: Failed to add report element");
@@ -405,17 +399,66 @@ static void wpas_rrm_send_msr_report_mpdu(struct wpa_supplicant *wpa_s,
 }
 
 
+static int wpas_rrm_beacon_rep_update_last_frame(u8 *pos, size_t len)
+{
+       struct rrm_measurement_report_element *msr_rep;
+       u8 *end = pos + len;
+       u8 *msr_rep_end;
+       struct rrm_measurement_beacon_report *rep = NULL;
+       u8 *subelem;
+
+       /* Find the last beacon report element */
+       while (end - pos >= (int) sizeof(*msr_rep)) {
+               msr_rep = (struct rrm_measurement_report_element *) pos;
+               msr_rep_end = pos + msr_rep->len + 2;
+
+               if (msr_rep->eid != WLAN_EID_MEASURE_REPORT ||
+                   msr_rep_end > end) {
+                       /* Should not happen. This indicates a bug. */
+                       wpa_printf(MSG_ERROR,
+                                  "RRM: non-measurement report element in measurement report frame");
+                       return -1;
+               }
+
+               if (msr_rep->type == MEASURE_TYPE_BEACON)
+                       rep = (struct rrm_measurement_beacon_report *)
+                               msr_rep->variable;
+
+               pos += pos[1] + 2;
+       }
+
+       if (!rep)
+               return 0;
+
+       subelem = rep->variable;
+       while (subelem + 2 < msr_rep_end &&
+              subelem[0] != WLAN_BEACON_REPORT_SUBELEM_LAST_INDICATION)
+               subelem += 2 + subelem[1];
+
+       if (subelem + 2 < msr_rep_end &&
+           subelem[0] == WLAN_BEACON_REPORT_SUBELEM_LAST_INDICATION &&
+           subelem[1] == 1 &&
+           subelem + BEACON_REPORT_LAST_INDICATION_SUBELEM_LEN <= end)
+               subelem[2] = 1;
+
+       return 0;
+}
+
+
 static void wpas_rrm_send_msr_report(struct wpa_supplicant *wpa_s,
                                     struct wpabuf *buf)
 {
        int len = wpabuf_len(buf);
-       const u8 *pos = wpabuf_head_u8(buf), *next = pos;
+       u8 *pos = wpabuf_mhead_u8(buf), *next = pos;
 
 #define MPDU_REPORT_LEN (int) (IEEE80211_MAX_MMPDU_SIZE - IEEE80211_HDRLEN - 3)
 
        while (len) {
                int send_len = (len > MPDU_REPORT_LEN) ? next - pos : len;
 
+               if (send_len == len)
+                       wpas_rrm_beacon_rep_update_last_frame(pos, len);
+
                if (send_len == len ||
                    (send_len + next[1] + 2) > MPDU_REPORT_LEN) {
                        wpas_rrm_send_msr_report_mpdu(wpa_s, pos, send_len);
@@ -423,7 +466,8 @@ static void wpas_rrm_send_msr_report(struct wpa_supplicant *wpa_s,
                        pos = next;
                }
 
-               next += next[1] + 2;
+               if (len)
+                       next += next[1] + 2;
        }
 #undef MPDU_REPORT_LEN
 }
@@ -438,6 +482,12 @@ static int wpas_add_channel(u8 op_class, u8 chan, u8 num_primary_channels,
                u8 primary_chan = chan - (2 * num_primary_channels - 2) + i * 4;
 
                freqs[i] = ieee80211_chan_to_freq(NULL, op_class, primary_chan);
+               /* ieee80211_chan_to_freq() is not really meant for this
+                * conversion of 20 MHz primary channel numbers for wider VHT
+                * channels, so handle those as special cases here for now. */
+               if (freqs[i] < 0 &&
+                   (op_class == 128 || op_class == 129 || op_class == 130))
+                       freqs[i] = 5000 + 5 * primary_chan;
                if (freqs[i] < 0) {
                        wpa_printf(MSG_DEBUG,
                                   "Beacon Report: Invalid channel %u",
@@ -479,7 +529,8 @@ static int * wpas_add_channels(const struct oper_class_map *op,
        next_freq = freqs;
        for  (i = 0; i < num_chans; i++) {
                u8 chan = channels ? channels[i] : op->min_chan + i * op->inc;
-               enum chan_allowed res = verify_channel(mode, chan, op->bw);
+               enum chan_allowed res = verify_channel(mode, op->op_class, chan,
+                                                      op->bw);
 
                if (res == NOT_ALLOWED || (res == NO_IR && active))
                        continue;
@@ -558,7 +609,8 @@ static int * wpas_channel_report_freqs(struct wpa_supplicant *wpa_s, int active,
                pos++;
                left--;
 
-               mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, op->mode);
+               mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, op->mode,
+                               is_6ghz_op_class(op->op_class));
                if (!mode)
                        continue;
 
@@ -610,7 +662,8 @@ static int * wpas_beacon_request_freqs(struct wpa_supplicant *wpa_s,
                return NULL;
        }
 
-       mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, op->mode);
+       mode = get_mode(wpa_s->hw.modes, wpa_s->hw.num_modes, op->mode,
+                       is_6ghz_op_class(op->op_class));
        if (!mode)
                return NULL;
 
@@ -635,14 +688,15 @@ static int * wpas_beacon_request_freqs(struct wpa_supplicant *wpa_s,
        if (ext_freqs) {
                int_array_concat(&freqs, ext_freqs);
                os_free(ext_freqs);
+               int_array_sort_unique(freqs);
        }
 
        return freqs;
 }
 
 
-int wpas_get_op_chan_phy(int freq, const u8 *ies, size_t ies_len,
-                        u8 *op_class, u8 *chan, u8 *phy_type)
+static int wpas_get_op_chan_phy(int freq, const u8 *ies, size_t ies_len,
+                               u8 *op_class, u8 *chan, u8 *phy_type)
 {
        const u8 *ie;
        int sec_chan = 0, vht = 0;
@@ -673,20 +727,20 @@ int wpas_get_op_chan_phy(int freq, const u8 *ies, size_t ies_len,
                        seg0 = vht_oper->vht_op_info_chan_center_freq_seg0_idx;
                        seg1 = vht_oper->vht_op_info_chan_center_freq_seg1_idx;
                        if (seg1 && abs(seg1 - seg0) == 8)
-                               vht = VHT_CHANWIDTH_160MHZ;
+                               vht = CHANWIDTH_160MHZ;
                        else if (seg1)
-                               vht = VHT_CHANWIDTH_80P80MHZ;
+                               vht = CHANWIDTH_80P80MHZ;
                        else
-                               vht = VHT_CHANWIDTH_80MHZ;
+                               vht = CHANWIDTH_80MHZ;
                        break;
                case 2:
-                       vht = VHT_CHANWIDTH_160MHZ;
+                       vht = CHANWIDTH_160MHZ;
                        break;
                case 3:
-                       vht = VHT_CHANWIDTH_80P80MHZ;
+                       vht = CHANWIDTH_80P80MHZ;
                        break;
                default:
-                       vht = VHT_CHANWIDTH_USE_HT;
+                       vht = CHANWIDTH_USE_HT;
                        break;
                }
        }
@@ -712,15 +766,17 @@ int wpas_get_op_chan_phy(int freq, const u8 *ies, size_t ies_len,
 static int wpas_beacon_rep_add_frame_body(struct bitfield *eids,
                                          enum beacon_report_detail detail,
                                          struct wpa_bss *bss, u8 *buf,
-                                         size_t buf_len)
+                                         size_t buf_len, u8 **ies_buf,
+                                         size_t *ie_len, int add_fixed)
 {
-       u8 *ies = (u8 *) (bss + 1);
-       size_t ies_len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len;
+       u8 *ies = *ies_buf;
+       size_t ies_len = *ie_len;
        u8 *pos = buf;
        int rem_len;
 
        rem_len = 255 - sizeof(struct rrm_measurement_beacon_report) -
-               sizeof(struct rrm_measurement_report_element) - 2;
+               sizeof(struct rrm_measurement_report_element) - 2 -
+               REPORTED_FRAME_BODY_SUBELEM_LEN;
 
        if (detail > BEACON_REPORT_DETAIL_ALL_FIELDS_AND_ELEMENTS) {
                wpa_printf(MSG_DEBUG,
@@ -736,18 +792,21 @@ static int wpas_beacon_rep_add_frame_body(struct bitfield *eids,
         * Minimal frame body subelement size: EID(1) + length(1) + TSF(8) +
         * beacon interval(2) + capabilities(2) = 14 bytes
         */
-       if (buf_len < 14)
-               return 0;
+       if (add_fixed && buf_len < 14)
+               return -1;
 
        *pos++ = WLAN_BEACON_REPORT_SUBELEM_FRAME_BODY;
        /* The length will be filled later */
        pos++;
-       WPA_PUT_LE64(pos, bss->tsf);
-       pos += sizeof(bss->tsf);
-       WPA_PUT_LE16(pos, bss->beacon_int);
-       pos += 2;
-       WPA_PUT_LE16(pos, bss->caps);
-       pos += 2;
+
+       if (add_fixed) {
+               WPA_PUT_LE64(pos, bss->tsf);
+               pos += sizeof(bss->tsf);
+               WPA_PUT_LE16(pos, bss->beacon_int);
+               pos += 2;
+               WPA_PUT_LE16(pos, bss->caps);
+               pos += 2;
+       }
 
        rem_len -= pos - buf;
 
@@ -762,15 +821,7 @@ static int wpas_beacon_rep_add_frame_body(struct bitfield *eids,
        while (ies_len > 2 && 2U + ies[1] <= ies_len && rem_len > 0) {
                if (detail == BEACON_REPORT_DETAIL_ALL_FIELDS_AND_ELEMENTS ||
                    (eids && bitfield_is_set(eids, ies[0]))) {
-                       u8 eid = ies[0], elen = ies[1];
-
-                       if ((eid == WLAN_EID_TIM || eid == WLAN_EID_RSN) &&
-                           elen > 4)
-                               elen = 4;
-                       /*
-                        * TODO: Truncate IBSS DFS element as described in
-                        * IEEE Std 802.11-2016, 9.4.2.22.7.
-                        */
+                       u8 elen = ies[1];
 
                        if (2 + elen > buf + buf_len - pos ||
                            2 + elen > rem_len)
@@ -787,22 +838,91 @@ static int wpas_beacon_rep_add_frame_body(struct bitfield *eids,
                ies += 2 + ies[1];
        }
 
+       *ie_len = ies_len;
+       *ies_buf = ies;
+
        /* Now the length is known */
        buf[1] = pos - buf - 2;
        return pos - buf;
 }
 
 
+static int wpas_add_beacon_rep_elem(struct beacon_rep_data *data,
+                                   struct wpa_bss *bss,
+                                   struct wpabuf **wpa_buf,
+                                   struct rrm_measurement_beacon_report *rep,
+                                   u8 **ie, size_t *ie_len, u8 idx)
+{
+       int ret;
+       u8 *buf, *pos;
+       u32 subelems_len = REPORTED_FRAME_BODY_SUBELEM_LEN +
+               (data->last_indication ?
+                BEACON_REPORT_LAST_INDICATION_SUBELEM_LEN : 0);
+
+       /* Maximum element length: Beacon Report element + Reported Frame Body
+        * subelement + all IEs of the reported Beacon frame + Reported Frame
+        * Body Fragment ID subelement */
+       buf = os_malloc(sizeof(*rep) + 14 + *ie_len + subelems_len);
+       if (!buf)
+               return -1;
+
+       os_memcpy(buf, rep, sizeof(*rep));
+
+       ret = wpas_beacon_rep_add_frame_body(data->eids, data->report_detail,
+                                            bss, buf + sizeof(*rep),
+                                            14 + *ie_len, ie, ie_len,
+                                            idx == 0);
+       if (ret < 0)
+               goto out;
+
+       pos = buf + ret + sizeof(*rep);
+       pos[0] = WLAN_BEACON_REPORT_SUBELEM_FRAME_BODY_FRAGMENT_ID;
+       pos[1] = 2;
+
+       /*
+        * Only one Beacon Report Measurement is supported at a time, so
+        * the Beacon Report ID can always be set to 1.
+        */
+       pos[2] = 1;
+
+       /* Fragment ID Number (bits 0..6) and More Frame Body Fragments (bit 7)
+ */
+       pos[3] = idx;
+       if (data->report_detail != BEACON_REPORT_DETAIL_NONE && *ie_len)
+               pos[3] |= REPORTED_FRAME_BODY_MORE_FRAGMENTS;
+       else
+               pos[3] &= ~REPORTED_FRAME_BODY_MORE_FRAGMENTS;
+
+       pos += REPORTED_FRAME_BODY_SUBELEM_LEN;
+
+       if (data->last_indication) {
+               pos[0] = WLAN_BEACON_REPORT_SUBELEM_LAST_INDICATION;
+               pos[1] = 1;
+
+               /* This field will be updated later if this is the last frame */
+               pos[2] = 0;
+       }
+
+       ret = wpas_rrm_report_elem(wpa_buf, data->token,
+                                  MEASUREMENT_REPORT_MODE_ACCEPT,
+                                  MEASURE_TYPE_BEACON, buf,
+                                  ret + sizeof(*rep) + subelems_len);
+out:
+       os_free(buf);
+       return ret;
+}
+
+
 static int wpas_add_beacon_rep(struct wpa_supplicant *wpa_s,
                               struct wpabuf **wpa_buf, struct wpa_bss *bss,
                               u64 start, u64 parent_tsf)
 {
        struct beacon_rep_data *data = &wpa_s->beacon_rep_data;
-       u8 *ie = (u8 *) (bss + 1);
-       size_t ie_len = bss->ie_len + bss->beacon_ie_len;
-       int ret;
-       u8 buf[2000];
-       struct rrm_measurement_beacon_report *rep;
+       u8 *ies = (u8 *) (bss + 1);
+       u8 *pos = ies;
+       size_t ies_len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len;
+       struct rrm_measurement_beacon_report rep;
+       u8 idx = 0;
 
        if (os_memcmp(data->bssid, broadcast_ether_addr, ETH_ALEN) != 0 &&
            os_memcmp(data->bssid, bss->bssid, ETH_ALEN) != 0)
@@ -813,48 +933,36 @@ static int wpas_add_beacon_rep(struct wpa_supplicant *wpa_s,
             os_memcmp(data->ssid, bss->ssid, bss->ssid_len) != 0))
                return 0;
 
-       rep = (struct rrm_measurement_beacon_report *) buf;
-       if (wpas_get_op_chan_phy(bss->freq, ie, ie_len, &rep->op_class,
-                                &rep->channel, &rep->report_info) < 0)
+       if (wpas_get_op_chan_phy(bss->freq, ies, ies_len, &rep.op_class,
+                                &rep.channel, &rep.report_info) < 0)
                return 0;
 
-       rep->start_time = host_to_le64(start);
-       rep->duration = host_to_le16(data->scan_params.duration);
-       rep->rcpi = rssi_to_rcpi(bss->level);
-       rep->rsni = 255; /* 255 indicates that RSNI is not available */
-       os_memcpy(rep->bssid, bss->bssid, ETH_ALEN);
-       rep->antenna_id = 0; /* unknown */
-       rep->parent_tsf = host_to_le32(parent_tsf);
+       rep.start_time = host_to_le64(start);
+       rep.duration = host_to_le16(data->scan_params.duration);
+       rep.rcpi = rssi_to_rcpi(bss->level);
+       rep.rsni = 255; /* 255 indicates that RSNI is not available */
+       os_memcpy(rep.bssid, bss->bssid, ETH_ALEN);
+       rep.antenna_id = 0; /* unknown */
+       rep.parent_tsf = host_to_le32(parent_tsf);
 
-       ret = wpas_beacon_rep_add_frame_body(data->eids, data->report_detail,
-                                            bss, rep->variable,
-                                            sizeof(buf) - sizeof(*rep));
-       if (ret < 0)
-               return -1;
+       do {
+               int ret;
 
-       if (wpabuf_resize(wpa_buf,
-                         sizeof(struct rrm_measurement_report_element) +
-                         sizeof(*rep) + ret)) {
-               wpa_printf(MSG_ERROR, "RRM: Memory allocation failed");
-               return -1;
-       }
+               ret = wpas_add_beacon_rep_elem(data, bss, wpa_buf, &rep,
+                                              &pos, &ies_len, idx++);
+               if (ret)
+                       return ret;
+       } while (data->report_detail != BEACON_REPORT_DETAIL_NONE &&
+                ies_len >= 2);
 
-       return wpas_rrm_report_elem(*wpa_buf, wpa_s->beacon_rep_data.token,
-                                   MEASUREMENT_REPORT_MODE_ACCEPT,
-                                   MEASURE_TYPE_BEACON, buf,
-                                   ret + sizeof(*rep));
+       return 0;
 }
 
 
 static int wpas_beacon_rep_no_results(struct wpa_supplicant *wpa_s,
                                      struct wpabuf **buf)
 {
-       if (wpabuf_resize(buf, 5)) {
-               wpa_printf(MSG_DEBUG, "RRM: Memory allocation failed");
-               return -1;
-       }
-
-       return wpas_rrm_report_elem(*buf, wpa_s->beacon_rep_data.token,
+       return wpas_rrm_report_elem(buf, wpa_s->beacon_rep_data.token,
                                    MEASUREMENT_REPORT_MODE_ACCEPT,
                                    MEASURE_TYPE_BEACON, NULL, 0);
 }
@@ -878,23 +986,24 @@ static void wpas_beacon_rep_table(struct wpa_supplicant *wpa_s,
 }
 
 
-static void wpas_rrm_refuse_request(struct wpa_supplicant *wpa_s)
+void wpas_rrm_refuse_request(struct wpa_supplicant *wpa_s)
 {
-       struct wpabuf *buf;
+       if (!is_multicast_ether_addr(wpa_s->rrm.dst_addr)) {
+               struct wpabuf *buf = NULL;
+
+               if (wpas_rrm_report_elem(&buf, wpa_s->beacon_rep_data.token,
+                                        MEASUREMENT_REPORT_MODE_REJECT_REFUSED,
+                                        MEASURE_TYPE_BEACON, NULL, 0)) {
+                       wpa_printf(MSG_ERROR, "RRM: Memory allocation failed");
+                       wpabuf_free(buf);
+                       return;
+               }
 
-       buf = wpabuf_alloc(sizeof(struct rrm_measurement_beacon_report));
-       if (!buf ||
-           wpas_rrm_report_elem(buf, wpa_s->beacon_rep_data.token,
-                                MEASUREMENT_REPORT_MODE_REJECT_REFUSED,
-                                MEASURE_TYPE_BEACON, NULL, 0)) {
-               wpa_printf(MSG_ERROR, "RRM: Memory allocation failed");
+               wpas_rrm_send_msr_report(wpa_s, buf);
                wpabuf_free(buf);
-               return;
        }
 
-       wpas_rrm_send_msr_report(wpa_s, buf);
        wpas_clear_beacon_rep_data(wpa_s);
-       wpabuf_free(buf);
 }
 
 
@@ -974,7 +1083,7 @@ static int wpas_rm_handle_beacon_req_subelem(struct wpa_supplicant *wpa_s,
                    BEACON_REPORT_DETAIL_ALL_FIELDS_AND_ELEMENTS) {
                        wpa_printf(MSG_DEBUG, "Invalid reporting detail: %u",
                                   subelem[0]);
-                       return 0;
+                       return -1;
                }
 
                break;
@@ -1012,6 +1121,16 @@ static int wpas_rm_handle_beacon_req_subelem(struct wpa_supplicant *wpa_s,
        case WLAN_BEACON_REQUEST_SUBELEM_AP_CHANNEL:
                /* Skip - it will be processed when freqs are added */
                break;
+       case WLAN_BEACON_REQUEST_SUBELEM_LAST_INDICATION:
+               if (slen != 1) {
+                       wpa_printf(MSG_DEBUG,
+                                  "Beacon request: Invalid last indication request subelement length: %u",
+                                  slen);
+                       return -1;
+               }
+
+               data->last_indication = subelem[0];
+               break;
        default:
                wpa_printf(MSG_DEBUG,
                           "Beacon request: Unknown subelement id %u", sid);
@@ -1041,6 +1160,7 @@ wpas_rm_handle_beacon_req(struct wpa_supplicant *wpa_s,
        u32 interval_usec;
        u32 _rand;
        int ret = 0, res;
+       u8 reject_mode;
 
        if (len < sizeof(*req))
                return -1;
@@ -1054,6 +1174,7 @@ wpas_rm_handle_beacon_req(struct wpa_supplicant *wpa_s,
        elems_len = len - sizeof(*req);
        rand_interval = le_to_host16(req->rand_interval);
 
+       os_free(params->freqs);
        os_memset(params, 0, sizeof(*params));
 
        data->token = elem_token;
@@ -1073,9 +1194,12 @@ wpas_rm_handle_beacon_req(struct wpa_supplicant *wpa_s,
 
                res = wpas_rm_handle_beacon_req_subelem(
                        wpa_s, data, subelems[0], subelems[1], &subelems[2]);
-               if (res != 1) {
+               if (res < 0) {
                        ret = res;
                        goto out;
+               } else if (!res) {
+                       reject_mode = MEASUREMENT_REPORT_MODE_REJECT_INCAPABLE;
+                       goto out_reject;
                }
 
                elems_len -= 2 + subelems[1];
@@ -1093,7 +1217,8 @@ wpas_rm_handle_beacon_req(struct wpa_supplicant *wpa_s,
                req->variable, len - sizeof(*req));
        if (!params->freqs) {
                wpa_printf(MSG_DEBUG, "Beacon request: No valid channels");
-               goto out;
+               reject_mode = MEASUREMENT_REPORT_MODE_REJECT_REFUSED;
+               goto out_reject;
        }
 
        params->duration = le_to_host16(req->duration);
@@ -1117,6 +1242,13 @@ wpas_rm_handle_beacon_req(struct wpa_supplicant *wpa_s,
        eloop_register_timeout(0, interval_usec, wpas_rrm_scan_timeout, wpa_s,
                               NULL);
        return 1;
+out_reject:
+       if (!is_multicast_ether_addr(wpa_s->rrm.dst_addr) &&
+           wpas_rrm_report_elem(buf, elem_token, reject_mode,
+                                MEASURE_TYPE_BEACON, NULL, 0) < 0) {
+               wpa_printf(MSG_DEBUG, "RRM: Failed to add report element");
+               ret = -1;
+       }
 out:
        wpas_clear_beacon_rep_data(wpa_s);
        return ret;
@@ -1174,12 +1306,8 @@ wpas_rrm_handle_msr_req_element(
        }
 
 reject:
-       if (wpabuf_resize(buf, sizeof(struct rrm_measurement_report_element))) {
-               wpa_printf(MSG_DEBUG, "RRM: Memory allocation failed");
-               return -1;
-       }
-
-       if (wpas_rrm_report_elem(*buf, req->token,
+       if (!is_multicast_ether_addr(wpa_s->rrm.dst_addr) &&
+           wpas_rrm_report_elem(buf, req->token,
                                 MEASUREMENT_REPORT_MODE_REJECT_INCAPABLE,
                                 req->type, NULL, 0) < 0) {
                wpa_printf(MSG_DEBUG, "RRM: Failed to add report element");
@@ -1240,7 +1368,7 @@ out:
 
 
 void wpas_rrm_handle_radio_measurement_request(struct wpa_supplicant *wpa_s,
-                                              const u8 *src,
+                                              const u8 *src, const u8 *dst,
                                               const u8 *frame, size_t len)
 {
        struct wpabuf *report;
@@ -1264,6 +1392,7 @@ void wpas_rrm_handle_radio_measurement_request(struct wpa_supplicant *wpa_s,
        }
 
        wpa_s->rrm.token = *frame;
+       os_memcpy(wpa_s->rrm.dst_addr, dst, ETH_ALEN);
 
        /* Number of repetitions is not supported */
 
@@ -1311,10 +1440,14 @@ void wpas_rrm_handle_link_measurement_request(struct wpa_supplicant *wpa_s,
        }
 
        os_memset(&report, 0, sizeof(report));
+       report.dialog_token = req->dialog_token;
        report.tpc.eid = WLAN_EID_TPC_REPORT;
        report.tpc.len = 2;
+       /* Note: The driver is expected to update report.tpc.tx_power and
+        * report.tpc.link_margin subfields when sending out this frame.
+        * Similarly, the driver would need to update report.rx_ant_id and
+        * report.tx_ant_id subfields. */
        report.rsni = 255; /* 255 indicates that RSNI is not available */
-       report.dialog_token = req->dialog_token;
        report.rcpi = rssi_to_rcpi(rssi);
 
        /* action_category + action_code */
@@ -1328,8 +1461,7 @@ void wpas_rrm_handle_link_measurement_request(struct wpa_supplicant *wpa_s,
        wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
        wpabuf_put_u8(buf, WLAN_RRM_LINK_MEASUREMENT_REPORT);
        wpabuf_put_data(buf, &report, sizeof(report));
-       wpa_hexdump(MSG_DEBUG, "RRM: Link measurement report:",
-                   wpabuf_head(buf), wpabuf_len(buf));
+       wpa_hexdump_buf(MSG_DEBUG, "RRM: Link measurement report", buf);
 
        if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, src,
                                wpa_s->own_addr, wpa_s->bssid,
@@ -1388,6 +1520,10 @@ int wpas_beacon_rep_scan_process(struct wpa_supplicant *wpa_s,
                        continue;
                }
 
+               /*
+                * Don't report results that were not received during the
+                * current measurement.
+                */
                if (!(wpa_s->drv_rrm_flags &
                      WPA_DRIVER_FLAGS_SUPPORT_BEACON_REPORT)) {
                        struct os_reltime update_time, diff;
@@ -1414,14 +1550,10 @@ int wpas_beacon_rep_scan_process(struct wpa_supplicant *wpa_s,
                                           (unsigned int) diff.usec);
                                continue;
                        }
-               }
-
-               /*
-                * Don't report results that were not received during the
-                * current measurement.
-                */
-               if (info->scan_start_tsf > scan_res->res[i]->parent_tsf)
+               } else if (info->scan_start_tsf >
+                          scan_res->res[i]->parent_tsf) {
                        continue;
+               }
 
                if (wpas_add_beacon_rep(wpa_s, &buf, bss, info->scan_start_tsf,
                                        scan_res->res[i]->parent_tsf) < 0)