]> git.ipfire.org Git - thirdparty/hostap.git/blobdiff - wpa_supplicant/scan.c
EAP: Increase the maximum number of message exchanges
[thirdparty/hostap.git] / wpa_supplicant / scan.c
index 86fe2ba292705c630a5e8639fa66b7de6b9a8632..7abb028dd34448ca0b5978382613d95012dcae0f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * WPA Supplicant - Scanning
- * Copyright (c) 2003-2014, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2019, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -36,8 +36,7 @@ static void wpa_supplicant_gen_assoc_event(struct wpa_supplicant *wpa_s)
 
        if (wpa_s->current_ssid == NULL) {
                wpa_s->current_ssid = ssid;
-               if (wpa_s->current_ssid != NULL)
-                       wpas_notify_network_changed(wpa_s);
+               wpas_notify_network_changed(wpa_s);
        }
        wpa_supplicant_initiate_eapol(wpa_s);
        wpa_dbg(wpa_s, MSG_DEBUG, "Already associated with a configured "
@@ -60,10 +59,7 @@ static int wpas_wps_in_use(struct wpa_supplicant *wpa_s,
 
                wps = 1;
                *req_type = wpas_wps_get_req_type(ssid);
-               if (!ssid->eap.phase1)
-                       continue;
-
-               if (os_strstr(ssid->eap.phase1, "pbc=1"))
+               if (ssid->eap.phase1 && os_strstr(ssid->eap.phase1, "pbc=1"))
                        return 2;
        }
 
@@ -96,6 +92,10 @@ int wpa_supplicant_enabled_networks(struct wpa_supplicant *wpa_s)
 {
        struct wpa_ssid *ssid = wpa_s->conf->ssid;
        int count = 0, disabled = 0;
+
+       if (wpa_s->p2p_mgmt)
+               return 0; /* no normal network profiles on p2p_mgmt interface */
+
        while (ssid) {
                if (!wpas_network_disabled(wpa_s, ssid))
                        count++;
@@ -117,9 +117,19 @@ int wpa_supplicant_enabled_networks(struct wpa_supplicant *wpa_s)
 static void wpa_supplicant_assoc_try(struct wpa_supplicant *wpa_s,
                                     struct wpa_ssid *ssid)
 {
+       int min_temp_disabled = 0;
+
        while (ssid) {
-               if (!wpas_network_disabled(wpa_s, ssid))
-                       break;
+               if (!wpas_network_disabled(wpa_s, ssid)) {
+                       int temp_disabled = wpas_temp_disabled(wpa_s, ssid);
+
+                       if (temp_disabled <= 0)
+                               break;
+
+                       if (!min_temp_disabled ||
+                           temp_disabled < min_temp_disabled)
+                               min_temp_disabled = temp_disabled;
+               }
                ssid = ssid->next;
        }
 
@@ -128,7 +138,7 @@ static void wpa_supplicant_assoc_try(struct wpa_supplicant *wpa_s,
                wpa_dbg(wpa_s, MSG_DEBUG, "wpa_supplicant_assoc_try: Reached "
                        "end of scan list - go back to beginning");
                wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
-               wpa_supplicant_req_scan(wpa_s, 0, 0);
+               wpa_supplicant_req_scan(wpa_s, min_temp_disabled, 0);
                return;
        }
        if (ssid->next) {
@@ -162,23 +172,61 @@ static void wpas_trigger_scan_cb(struct wpa_radio_work *work, int deinit)
        if (wpas_update_random_addr_disassoc(wpa_s) < 0) {
                wpa_msg(wpa_s, MSG_INFO,
                        "Failed to assign random MAC address for a scan");
+               wpa_scan_free_params(params);
+               wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_SCAN_FAILED "ret=-1");
                radio_work_done(work);
                return;
        }
 
        wpa_supplicant_notify_scanning(wpa_s, 1);
 
-       if (wpa_s->clear_driver_scan_cache)
+       if (wpa_s->clear_driver_scan_cache) {
+               wpa_printf(MSG_DEBUG,
+                          "Request driver to clear scan cache due to local BSS flush");
                params->only_new_results = 1;
+       }
        ret = wpa_drv_scan(wpa_s, params);
+       /*
+        * Store the obtained vendor scan cookie (if any) in wpa_s context.
+        * The current design is to allow only one scan request on each
+        * interface, hence having this scan cookie stored in wpa_s context is
+        * fine for now.
+        *
+        * Revisit this logic if concurrent scan operations per interface
+        * is supported.
+        */
+       if (ret == 0)
+               wpa_s->curr_scan_cookie = params->scan_cookie;
        wpa_scan_free_params(params);
        work->ctx = NULL;
        if (ret) {
+               int retry = wpa_s->last_scan_req != MANUAL_SCAN_REQ &&
+                       !wpa_s->beacon_rep_data.token;
+
+               if (wpa_s->disconnected)
+                       retry = 0;
+
                wpa_supplicant_notify_scanning(wpa_s, 0);
                wpas_notify_scan_done(wpa_s, 0);
-               wpa_msg_ctrl(wpa_s, MSG_INFO, WPA_EVENT_SCAN_FAILED "ret=%d",
-                            ret);
+               if (wpa_s->wpa_state == WPA_SCANNING)
+                       wpa_supplicant_set_state(wpa_s,
+                                                wpa_s->scan_prev_wpa_state);
+               wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_SCAN_FAILED "ret=%d%s",
+                       ret, retry ? " retry=1" : "");
                radio_work_done(work);
+
+               if (retry) {
+                       /* Restore scan_req since we will try to scan again */
+                       wpa_s->scan_req = wpa_s->last_scan_req;
+                       wpa_supplicant_req_scan(wpa_s, 1, 0);
+               } else if (wpa_s->scan_res_handler) {
+                       /* Clear the scan_res_handler */
+                       wpa_s->scan_res_handler = NULL;
+               }
+
+               if (wpa_s->beacon_rep_data.token)
+                       wpas_rrm_refuse_request(wpa_s);
+
                return;
        }
 
@@ -208,12 +256,11 @@ int wpa_supplicant_trigger_scan(struct wpa_supplicant *wpa_s,
        }
 
        ctx = wpa_scan_clone_params(params);
-       if (ctx == NULL)
-               return -1;
-
-       if (radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx) < 0)
+       if (!ctx ||
+           radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx) < 0)
        {
                wpa_scan_free_params(ctx);
+               wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_SCAN_FAILED "ret=-1");
                return -1;
        }
 
@@ -245,14 +292,14 @@ wpa_supplicant_sched_scan_timeout(void *eloop_ctx, void *timeout_ctx)
 }
 
 
-int wpa_supplicant_start_sched_scan(struct wpa_supplicant *wpa_s,
-                                   struct wpa_driver_scan_params *params,
-                                   int interval)
+static int
+wpa_supplicant_start_sched_scan(struct wpa_supplicant *wpa_s,
+                               struct wpa_driver_scan_params *params)
 {
        int ret;
 
        wpa_supplicant_notify_scanning(wpa_s, 1);
-       ret = wpa_drv_sched_scan(wpa_s, params, interval * 1000);
+       ret = wpa_drv_sched_scan(wpa_s, params);
        if (ret)
                wpa_supplicant_notify_scanning(wpa_s, 0);
        else
@@ -262,7 +309,7 @@ int wpa_supplicant_start_sched_scan(struct wpa_supplicant *wpa_s,
 }
 
 
-int wpa_supplicant_stop_sched_scan(struct wpa_supplicant *wpa_s)
+static int wpa_supplicant_stop_sched_scan(struct wpa_supplicant *wpa_s)
 {
        int ret;
 
@@ -397,22 +444,6 @@ static void wpa_supplicant_optimize_freqs(
 static void wpas_add_interworking_elements(struct wpa_supplicant *wpa_s,
                                           struct wpabuf *buf)
 {
-       if (wpa_s->conf->interworking == 0)
-               return;
-
-       wpabuf_put_u8(buf, WLAN_EID_EXT_CAPAB);
-       wpabuf_put_u8(buf, 6);
-       wpabuf_put_u8(buf, 0x00);
-       wpabuf_put_u8(buf, 0x00);
-       wpabuf_put_u8(buf, 0x00);
-       wpabuf_put_u8(buf, 0x80); /* Bit 31 - Interworking */
-       wpabuf_put_u8(buf, 0x00);
-#ifdef CONFIG_HS20
-       wpabuf_put_u8(buf, 0x40); /* Bit 46 - WNM-Notification */
-#else /* CONFIG_HS20 */
-       wpabuf_put_u8(buf, 0x00);
-#endif /* CONFIG_HS20 */
-
        wpabuf_put_u8(buf, WLAN_EID_INTERWORKING);
        wpabuf_put_u8(buf, is_zero_ether_addr(wpa_s->conf->hessid) ? 1 :
                      1 + ETH_ALEN);
@@ -424,20 +455,102 @@ static void wpas_add_interworking_elements(struct wpa_supplicant *wpa_s,
 #endif /* CONFIG_INTERWORKING */
 
 
+#ifdef CONFIG_MBO
+static void wpas_fils_req_param_add_max_channel(struct wpa_supplicant *wpa_s,
+                                               struct wpabuf **ie)
+{
+       if (wpabuf_resize(ie, 5)) {
+               wpa_printf(MSG_DEBUG,
+                          "Failed to allocate space for FILS Request Parameters element");
+               return;
+       }
+
+       /* FILS Request Parameters element */
+       wpabuf_put_u8(*ie, WLAN_EID_EXTENSION);
+       wpabuf_put_u8(*ie, 3); /* FILS Request attribute length */
+       wpabuf_put_u8(*ie, WLAN_EID_EXT_FILS_REQ_PARAMS);
+       /* Parameter control bitmap */
+       wpabuf_put_u8(*ie, 0);
+       /* Max Channel Time field - contains the value of MaxChannelTime
+        * parameter of the MLME-SCAN.request primitive represented in units of
+        * TUs, as an unsigned integer. A Max Channel Time field value of 255
+        * is used to indicate any duration of more than 254 TUs, or an
+        * unspecified or unknown duration. (IEEE Std 802.11ai-2016, 9.4.2.178)
+        */
+       wpabuf_put_u8(*ie, 255);
+}
+#endif /* CONFIG_MBO */
+
+
+void wpa_supplicant_set_default_scan_ies(struct wpa_supplicant *wpa_s)
+{
+       struct wpabuf *default_ies = NULL;
+       u8 ext_capab[18];
+       int ext_capab_len;
+       enum wpa_driver_if_type type = WPA_IF_STATION;
+
+#ifdef CONFIG_P2P
+       if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_CLIENT)
+               type = WPA_IF_P2P_CLIENT;
+#endif /* CONFIG_P2P */
+
+       wpa_drv_get_ext_capa(wpa_s, type);
+
+       ext_capab_len = wpas_build_ext_capab(wpa_s, ext_capab,
+                                            sizeof(ext_capab));
+       if (ext_capab_len > 0 &&
+           wpabuf_resize(&default_ies, ext_capab_len) == 0)
+               wpabuf_put_data(default_ies, ext_capab, ext_capab_len);
+
+#ifdef CONFIG_MBO
+       if (wpa_s->enable_oce & OCE_STA)
+               wpas_fils_req_param_add_max_channel(wpa_s, &default_ies);
+       /* Send MBO and OCE capabilities */
+       if (wpabuf_resize(&default_ies, 12) == 0)
+               wpas_mbo_scan_ie(wpa_s, default_ies);
+#endif /* CONFIG_MBO */
+
+       if (default_ies)
+               wpa_drv_set_default_scan_ies(wpa_s, wpabuf_head(default_ies),
+                                            wpabuf_len(default_ies));
+       wpabuf_free(default_ies);
+}
+
+
 static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s)
 {
        struct wpabuf *extra_ie = NULL;
+       u8 ext_capab[18];
+       int ext_capab_len;
 #ifdef CONFIG_WPS
        int wps = 0;
        enum wps_request_type req_type = WPS_REQ_ENROLLEE_INFO;
 #endif /* CONFIG_WPS */
 
+#ifdef CONFIG_P2P
+       if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_CLIENT)
+               wpa_drv_get_ext_capa(wpa_s, WPA_IF_P2P_CLIENT);
+       else
+#endif /* CONFIG_P2P */
+               wpa_drv_get_ext_capa(wpa_s, WPA_IF_STATION);
+
+       ext_capab_len = wpas_build_ext_capab(wpa_s, ext_capab,
+                                            sizeof(ext_capab));
+       if (ext_capab_len > 0 &&
+           wpabuf_resize(&extra_ie, ext_capab_len) == 0)
+               wpabuf_put_data(extra_ie, ext_capab, ext_capab_len);
+
 #ifdef CONFIG_INTERWORKING
        if (wpa_s->conf->interworking &&
            wpabuf_resize(&extra_ie, 100) == 0)
                wpas_add_interworking_elements(wpa_s, extra_ie);
 #endif /* CONFIG_INTERWORKING */
 
+#ifdef CONFIG_MBO
+       if (wpa_s->enable_oce & OCE_STA)
+               wpas_fils_req_param_add_max_channel(wpa_s, &extra_ie);
+#endif /* CONFIG_MBO */
+
 #ifdef CONFIG_WPS
        wps = wpas_wps_in_use(wpa_s, &req_type);
 
@@ -468,10 +581,29 @@ static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s)
 #endif /* CONFIG_WPS */
 
 #ifdef CONFIG_HS20
-       if (wpa_s->conf->hs20 && wpabuf_resize(&extra_ie, 7) == 0)
-               wpas_hs20_add_indication(extra_ie, -1);
+       if (wpa_s->conf->hs20 && wpabuf_resize(&extra_ie, 9) == 0)
+               wpas_hs20_add_indication(extra_ie, -1, 0);
 #endif /* CONFIG_HS20 */
 
+#ifdef CONFIG_FST
+       if (wpa_s->fst_ies &&
+           wpabuf_resize(&extra_ie, wpabuf_len(wpa_s->fst_ies)) == 0)
+               wpabuf_put_buf(extra_ie, wpa_s->fst_ies);
+#endif /* CONFIG_FST */
+
+#ifdef CONFIG_MBO
+       /* Send MBO and OCE capabilities */
+       if (wpabuf_resize(&extra_ie, 12) == 0)
+               wpas_mbo_scan_ie(wpa_s, extra_ie);
+#endif /* CONFIG_MBO */
+
+       if (wpa_s->vendor_elem[VENDOR_ELEM_PROBE_REQ]) {
+               struct wpabuf *buf = wpa_s->vendor_elem[VENDOR_ELEM_PROBE_REQ];
+
+               if (wpabuf_resize(&extra_ie, wpabuf_len(buf)) == 0)
+                       wpabuf_put_buf(extra_ie, buf);
+       }
+
        return extra_ie;
 }
 
@@ -503,21 +635,6 @@ static int non_p2p_network_enabled(struct wpa_supplicant *wpa_s)
 #endif /* CONFIG_P2P */
 
 
-static struct hostapd_hw_modes * get_mode(struct hostapd_hw_modes *modes,
-                                         u16 num_modes,
-                                         enum hostapd_hw_mode mode)
-{
-       u16 i;
-
-       for (i = 0; i < num_modes; i++) {
-               if (modes[i].mode == mode)
-                       return &modes[i];
-       }
-
-       return NULL;
-}
-
-
 static void wpa_setband_scan_freqs_list(struct wpa_supplicant *wpa_s,
                                        enum hostapd_hw_mode band,
                                        struct wpa_driver_scan_params *params)
@@ -560,6 +677,87 @@ static void wpa_setband_scan_freqs(struct wpa_supplicant *wpa_s,
 }
 
 
+static void wpa_add_scan_ssid(struct wpa_supplicant *wpa_s,
+                             struct wpa_driver_scan_params *params,
+                             size_t max_ssids, const u8 *ssid, size_t ssid_len)
+{
+       unsigned int j;
+
+       for (j = 0; j < params->num_ssids; j++) {
+               if (params->ssids[j].ssid_len == ssid_len &&
+                   params->ssids[j].ssid &&
+                   os_memcmp(params->ssids[j].ssid, ssid, ssid_len) == 0)
+                       return; /* already in the list */
+       }
+
+       if (params->num_ssids + 1 > max_ssids) {
+               wpa_printf(MSG_DEBUG, "Over max scan SSIDs for manual request");
+               return;
+       }
+
+       wpa_printf(MSG_DEBUG, "Scan SSID (manual request): %s",
+                  wpa_ssid_txt(ssid, ssid_len));
+
+       params->ssids[params->num_ssids].ssid = ssid;
+       params->ssids[params->num_ssids].ssid_len = ssid_len;
+       params->num_ssids++;
+}
+
+
+static void wpa_add_owe_scan_ssid(struct wpa_supplicant *wpa_s,
+                                 struct wpa_driver_scan_params *params,
+                                 struct wpa_ssid *ssid, size_t max_ssids)
+{
+#ifdef CONFIG_OWE
+       struct wpa_bss *bss;
+
+       if (!(ssid->key_mgmt & WPA_KEY_MGMT_OWE))
+               return;
+
+       wpa_printf(MSG_DEBUG, "OWE: Look for transition mode AP. ssid=%s",
+                  wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
+
+       dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
+               const u8 *owe, *pos, *end;
+               const u8 *owe_ssid;
+               size_t owe_ssid_len;
+
+               if (bss->ssid_len != ssid->ssid_len ||
+                   os_memcmp(bss->ssid, ssid->ssid, ssid->ssid_len) != 0)
+                       continue;
+
+               owe = wpa_bss_get_vendor_ie(bss, OWE_IE_VENDOR_TYPE);
+               if (!owe || owe[1] < 4)
+                       continue;
+
+               pos = owe + 6;
+               end = owe + 2 + owe[1];
+
+               /* Must include BSSID and ssid_len */
+               if (end - pos < ETH_ALEN + 1)
+                       return;
+
+               /* Skip BSSID */
+               pos += ETH_ALEN;
+               owe_ssid_len = *pos++;
+               owe_ssid = pos;
+
+               if ((size_t) (end - pos) < owe_ssid_len ||
+                   owe_ssid_len > SSID_MAX_LEN)
+                       return;
+
+               wpa_printf(MSG_DEBUG,
+                          "OWE: scan_ssids: transition mode OWE ssid=%s",
+                          wpa_ssid_txt(owe_ssid, owe_ssid_len));
+
+               wpa_add_scan_ssid(wpa_s, params, max_ssids,
+                                 owe_ssid, owe_ssid_len);
+               return;
+       }
+#endif /* CONFIG_OWE */
+}
+
+
 static void wpa_set_scan_ssids(struct wpa_supplicant *wpa_s,
                               struct wpa_driver_scan_params *params,
                               size_t max_ssids)
@@ -567,37 +765,58 @@ static void wpa_set_scan_ssids(struct wpa_supplicant *wpa_s,
        unsigned int i;
        struct wpa_ssid *ssid;
 
-       for (i = 0; i < wpa_s->scan_id_count; i++) {
-               unsigned int j;
+       /*
+        * For devices with max_ssids greater than 1, leave the last slot empty
+        * for adding the wildcard scan entry.
+        */
+       max_ssids = max_ssids > 1 ? max_ssids - 1 : max_ssids;
 
+       for (i = 0; i < wpa_s->scan_id_count; i++) {
                ssid = wpa_config_get_network(wpa_s->conf, wpa_s->scan_id[i]);
-               if (!ssid || !ssid->scan_ssid)
+               if (!ssid)
                        continue;
+               if (ssid->scan_ssid)
+                       wpa_add_scan_ssid(wpa_s, params, max_ssids,
+                                         ssid->ssid, ssid->ssid_len);
+               /*
+                * Also add the SSID of the OWE BSS, to allow discovery of
+                * transition mode APs more quickly.
+                */
+               wpa_add_owe_scan_ssid(wpa_s, params, ssid, max_ssids);
+       }
 
-               for (j = 0; j < params->num_ssids; j++) {
-                       if (params->ssids[j].ssid_len == ssid->ssid_len &&
-                           params->ssids[j].ssid &&
-                           os_memcmp(params->ssids[j].ssid, ssid->ssid,
-                                     ssid->ssid_len) == 0)
-                               break;
-               }
-               if (j < params->num_ssids)
-                       continue; /* already in the list */
+       wpa_s->scan_id_count = 0;
+}
 
-               if (params->num_ssids + 1 > max_ssids) {
-                       wpa_printf(MSG_DEBUG,
-                                  "Over max scan SSIDs for manual request");
-                       break;
-               }
 
-               wpa_printf(MSG_DEBUG, "Scan SSID (manual request): %s",
-                          wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
-               params->ssids[params->num_ssids].ssid = ssid->ssid;
-               params->ssids[params->num_ssids].ssid_len = ssid->ssid_len;
-               params->num_ssids++;
+static int wpa_set_ssids_from_scan_req(struct wpa_supplicant *wpa_s,
+                                      struct wpa_driver_scan_params *params,
+                                      size_t max_ssids)
+{
+       unsigned int i;
+
+       if (wpa_s->ssids_from_scan_req == NULL ||
+           wpa_s->num_ssids_from_scan_req == 0)
+               return 0;
+
+       if (wpa_s->num_ssids_from_scan_req > max_ssids) {
+               wpa_s->num_ssids_from_scan_req = max_ssids;
+               wpa_printf(MSG_DEBUG, "Over max scan SSIDs from scan req: %u",
+                          (unsigned int) max_ssids);
        }
 
-       wpa_s->scan_id_count = 0;
+       for (i = 0; i < wpa_s->num_ssids_from_scan_req; i++) {
+               params->ssids[i].ssid = wpa_s->ssids_from_scan_req[i].ssid;
+               params->ssids[i].ssid_len =
+                       wpa_s->ssids_from_scan_req[i].ssid_len;
+               wpa_hexdump_ascii(MSG_DEBUG, "specific SSID",
+                                 params->ssids[i].ssid,
+                                 params->ssids[i].ssid_len);
+       }
+
+       params->num_ssids = wpa_s->num_ssids_from_scan_req;
+       wpa_s->num_ssids_from_scan_req = 0;
+       return 1;
 }
 
 
@@ -610,12 +829,9 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
        struct wpa_driver_scan_params params;
        struct wpa_driver_scan_params *scan_params;
        size_t max_ssids;
-       enum wpa_states prev_state;
+       int connect_without_scan = 0;
 
-       if (wpa_s->pno || wpa_s->pno_sched_pending) {
-               wpa_dbg(wpa_s, MSG_DEBUG, "Skip scan - PNO is in progress");
-               return;
-       }
+       wpa_s->ignore_post_flush_scan_res = 0;
 
        if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Skip scan - interface disabled");
@@ -658,13 +874,40 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                return;
        }
 
+       ssid = NULL;
+       if (wpa_s->scan_req != MANUAL_SCAN_REQ &&
+           wpa_s->connect_without_scan) {
+               connect_without_scan = 1;
+               for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
+                       if (ssid == wpa_s->connect_without_scan)
+                               break;
+               }
+       }
+
        p2p_in_prog = wpas_p2p_in_progress(wpa_s);
-       if (p2p_in_prog && p2p_in_prog != 2) {
+       if (p2p_in_prog && p2p_in_prog != 2 &&
+           (!ssid ||
+            (ssid->mode != WPAS_MODE_AP && ssid->mode != WPAS_MODE_P2P_GO))) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Delay station mode scan while P2P operation is in progress");
                wpa_supplicant_req_scan(wpa_s, 5, 0);
                return;
        }
 
+       /*
+        * Don't cancel the scan based on ongoing PNO; defer it. Some scans are
+        * used for changing modes inside wpa_supplicant (roaming,
+        * auto-reconnect, etc). Discarding the scan might hurt these processes.
+        * The normal use case for PNO is to suspend the host immediately after
+        * starting PNO, so the periodic 100 ms attempts to run the scan do not
+        * normally happen in practice multiple times, i.e., this is simply
+        * restarting scanning once the host is woken up and PNO stopped.
+        */
+       if (wpa_s->pno || wpa_s->pno_sched_pending) {
+               wpa_dbg(wpa_s, MSG_DEBUG, "Defer scan - PNO is in progress");
+               wpa_supplicant_req_scan(wpa_s, 0, 100000);
+               return;
+       }
+
        if (wpa_s->conf->ap_scan == 2)
                max_ssids = 1;
        else {
@@ -676,9 +919,19 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
        wpa_s->last_scan_req = wpa_s->scan_req;
        wpa_s->scan_req = NORMAL_SCAN_REQ;
 
+       if (connect_without_scan) {
+               wpa_s->connect_without_scan = NULL;
+               if (ssid) {
+                       wpa_printf(MSG_DEBUG, "Start a pre-selected network "
+                                  "without scan step");
+                       wpa_supplicant_associate(wpa_s, NULL, ssid);
+                       return;
+               }
+       }
+
        os_memset(&params, 0, sizeof(params));
 
-       prev_state = wpa_s->wpa_state;
+       wpa_s->scan_prev_wpa_state = wpa_s->wpa_state;
        if (wpa_s->wpa_state == WPA_DISCONNECTED ||
            wpa_s->wpa_state == WPA_INACTIVE)
                wpa_supplicant_set_state(wpa_s, WPA_SCANNING);
@@ -691,24 +944,15 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                goto scan;
        }
 
-       if (wpa_s->last_scan_req != MANUAL_SCAN_REQ &&
-           wpa_s->connect_without_scan) {
-               for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
-                       if (ssid == wpa_s->connect_without_scan)
-                               break;
-               }
-               wpa_s->connect_without_scan = NULL;
-               if (ssid) {
-                       wpa_printf(MSG_DEBUG, "Start a pre-selected network "
-                                  "without scan step");
-                       wpa_supplicant_associate(wpa_s, NULL, ssid);
-                       return;
-               }
+       if (wpa_s->last_scan_req == MANUAL_SCAN_REQ &&
+           wpa_set_ssids_from_scan_req(wpa_s, &params, max_ssids)) {
+               wpa_printf(MSG_DEBUG, "Use specific SSIDs from SCAN command");
+               goto ssid_list_set;
        }
 
 #ifdef CONFIG_P2P
        if ((wpa_s->p2p_in_provisioning || wpa_s->show_group_started) &&
-           wpa_s->go_params) {
+           wpa_s->go_params && !wpa_s->conf->passive_scan) {
                wpa_printf(MSG_DEBUG, "P2P: Use specific SSID for scan during P2P group formation (p2p_in_provisioning=%d show_group_started=%d)",
                           wpa_s->p2p_in_provisioning,
                           wpa_s->show_group_started);
@@ -745,6 +989,9 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
        }
 
        if (wpa_s->last_scan_req != MANUAL_SCAN_REQ &&
+#ifdef CONFIG_AP
+           !wpa_s->ap_iface &&
+#endif /* CONFIG_AP */
            wpa_s->conf->ap_scan == 2) {
                wpa_s->connect_without_scan = NULL;
                wpa_s->prev_scan_wildcard = 0;
@@ -774,12 +1021,10 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                 * slot for the zero-terminator.
                 */
                params.freqs = os_malloc(sizeof(int) * 2);
-               if (params.freqs == NULL) {
-                       wpa_dbg(wpa_s, MSG_ERROR, "Memory allocation failed");
-                       return;
+               if (params.freqs) {
+                       params.freqs[0] = wpa_s->assoc_freq;
+                       params.freqs[1] = 0;
                }
-               params.freqs[0] = wpa_s->assoc_freq;
-               params.freqs[1] = 0;
 
                /*
                 * Reset the reattach flag so that we fall back to full scan if
@@ -804,6 +1049,17 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
                                if (params.num_ssids + 1 >= max_ssids)
                                        break;
                        }
+
+                       if (!wpas_network_disabled(wpa_s, ssid)) {
+                               /*
+                                * Also add the SSID of the OWE BSS, to allow
+                                * discovery of transition mode APs more
+                                * quickly.
+                                */
+                               wpa_add_owe_scan_ssid(wpa_s, &params, ssid,
+                                                     max_ssids);
+                       }
+
                        ssid = ssid->next;
                        if (ssid == start)
                                break;
@@ -862,22 +1118,26 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx)
        } else if (wpa_s->last_scan_req == MANUAL_SCAN_REQ &&
                   wpa_s->manual_scan_passive && params.num_ssids == 0) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Use passive scan based on manual request");
+       } else if (wpa_s->conf->passive_scan) {
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Use passive scan based on configuration");
        } else {
                wpa_s->prev_scan_ssid = WILDCARD_SSID_SCAN;
                params.num_ssids++;
                wpa_dbg(wpa_s, MSG_DEBUG, "Starting AP scan for wildcard "
                        "SSID");
        }
-#ifdef CONFIG_P2P
-ssid_list_set:
-#endif /* CONFIG_P2P */
 
+ssid_list_set:
        wpa_supplicant_optimize_freqs(wpa_s, &params);
        extra_ie = wpa_supplicant_extra_ies(wpa_s);
 
        if (wpa_s->last_scan_req == MANUAL_SCAN_REQ &&
-           wpa_s->manual_scan_only_new)
+           wpa_s->manual_scan_only_new) {
+               wpa_printf(MSG_DEBUG,
+                          "Request driver to clear scan cache due to manual only_new=1 scan");
                params.only_new_results = 1;
+       }
 
        if (wpa_s->last_scan_req == MANUAL_SCAN_REQ && params.freqs == NULL &&
            wpa_s->manual_scan_freqs) {
@@ -886,6 +1146,13 @@ ssid_list_set:
                wpa_s->manual_scan_freqs = NULL;
        }
 
+       if (params.freqs == NULL && wpa_s->select_network_scan_freqs) {
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Limit select_network scan to specified channels");
+               params.freqs = wpa_s->select_network_scan_freqs;
+               wpa_s->select_network_scan_freqs = NULL;
+       }
+
        if (params.freqs == NULL && wpa_s->next_scan_freqs) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Optimize scan based on previously "
                        "generated frequency list");
@@ -920,6 +1187,11 @@ ssid_list_set:
                }
        }
 
+#ifdef CONFIG_MBO
+       if (wpa_s->enable_oce & OCE_STA)
+               params.oce_scan = 1;
+#endif /* CONFIG_MBO */
+
        params.filter_ssids = wpa_supplicant_build_filter_ssids(
                wpa_s->conf, &params.num_filter_ssids);
        if (extra_ie) {
@@ -938,7 +1210,8 @@ ssid_list_set:
        }
 #endif /* CONFIG_P2P */
 
-       if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) {
+       if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCAN) &&
+           wpa_s->wpa_state <= WPA_SCANNING) {
                params.mac_addr_rand = 1;
                if (wpa_s->mac_addr_scan) {
                        params.mac_addr = wpa_s->mac_addr_scan;
@@ -946,6 +1219,27 @@ ssid_list_set:
                }
        }
 
+       if (!is_zero_ether_addr(wpa_s->next_scan_bssid)) {
+               struct wpa_bss *bss;
+
+               params.bssid = wpa_s->next_scan_bssid;
+               bss = wpa_bss_get_bssid_latest(wpa_s, params.bssid);
+               if (bss && bss->ssid_len && params.num_ssids == 1 &&
+                   params.ssids[0].ssid_len == 0) {
+                       params.ssids[0].ssid = bss->ssid;
+                       params.ssids[0].ssid_len = bss->ssid_len;
+                       wpa_dbg(wpa_s, MSG_DEBUG,
+                               "Scan a previously specified BSSID " MACSTR
+                               " and SSID %s",
+                               MAC2STR(params.bssid),
+                               wpa_ssid_txt(bss->ssid, bss->ssid_len));
+               } else {
+                       wpa_dbg(wpa_s, MSG_DEBUG,
+                               "Scan a previously specified BSSID " MACSTR,
+                               MAC2STR(params.bssid));
+               }
+       }
+
        scan_params = &params;
 
 scan:
@@ -995,8 +1289,9 @@ scan:
 
        if (ret) {
                wpa_msg(wpa_s, MSG_WARNING, "Failed to initiate AP scan");
-               if (prev_state != wpa_s->wpa_state)
-                       wpa_supplicant_set_state(wpa_s, prev_state);
+               if (wpa_s->scan_prev_wpa_state != wpa_s->wpa_state)
+                       wpa_supplicant_set_state(wpa_s,
+                                                wpa_s->scan_prev_wpa_state);
                /* Restore scan_req since we will try to scan again */
                wpa_s->scan_req = wpa_s->last_scan_req;
                wpa_supplicant_req_scan(wpa_s, 1, 0);
@@ -1005,6 +1300,8 @@ scan:
 #ifdef CONFIG_INTERWORKING
                wpa_s->interworking_fast_assoc_tried = 0;
 #endif /* CONFIG_INTERWORKING */
+               if (params.bssid)
+                       os_memset(wpa_s->next_scan_bssid, 0, ETH_ALEN);
        }
 }
 
@@ -1043,8 +1340,17 @@ void wpa_supplicant_update_scan_int(struct wpa_supplicant *wpa_s, int sec)
  */
 void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec)
 {
-       int res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,
-                                       NULL);
+       int res;
+
+       if (wpa_s->p2p_mgmt) {
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Ignore scan request (%d.%06d sec) on p2p_mgmt interface",
+                       sec, usec);
+               return;
+       }
+
+       res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,
+                                   NULL);
        if (res == 1) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d.%06d sec",
                        sec, usec);
@@ -1083,6 +1389,26 @@ int wpa_supplicant_delayed_sched_scan(struct wpa_supplicant *wpa_s,
 }
 
 
+static void
+wpa_scan_set_relative_rssi_params(struct wpa_supplicant *wpa_s,
+                                 struct wpa_driver_scan_params *params)
+{
+       if (wpa_s->wpa_state != WPA_COMPLETED ||
+           !(wpa_s->drv_flags & WPA_DRIVER_FLAGS_SCHED_SCAN_RELATIVE_RSSI) ||
+           wpa_s->srp.relative_rssi_set == 0)
+               return;
+
+       params->relative_rssi_set = 1;
+       params->relative_rssi = wpa_s->srp.relative_rssi;
+
+       if (wpa_s->srp.relative_adjust_rssi == 0)
+               return;
+
+       params->relative_adjust_band = wpa_s->srp.relative_adjust_band;
+       params->relative_adjust_rssi = wpa_s->srp.relative_adjust_rssi;
+}
+
+
 /**
  * wpa_supplicant_req_sched_scan - Start a periodic scheduled scan
  * @wpa_s: Pointer to wpa_supplicant data
@@ -1102,6 +1428,7 @@ int wpa_supplicant_req_sched_scan(struct wpa_supplicant *wpa_s)
        unsigned int max_sched_scan_ssids;
        int wildcard = 0;
        int need_ssids;
+       struct sched_scan_plan scan_plan;
 
        if (!wpa_s->sched_scan_supported)
                return -1;
@@ -1113,6 +1440,8 @@ int wpa_supplicant_req_sched_scan(struct wpa_supplicant *wpa_s)
        if (max_sched_scan_ssids < 1 || wpa_s->conf->disable_scan_offload)
                return -1;
 
+       wpa_s->sched_scan_stop_req = 0;
+
        if (wpa_s->sched_scanning) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Already sched scanning");
                return 0;
@@ -1191,11 +1520,6 @@ int wpa_supplicant_req_sched_scan(struct wpa_supplicant *wpa_s)
 
        if (!ssid || !wpa_s->prev_sched_ssid) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Beginning of SSID list");
-               if (wpa_s->conf->sched_scan_interval)
-                       wpa_s->sched_scan_interval =
-                               wpa_s->conf->sched_scan_interval;
-               if (wpa_s->sched_scan_interval == 0)
-                       wpa_s->sched_scan_interval = 10;
                wpa_s->sched_scan_timeout = max_sched_scan_ssids * 2;
                wpa_s->first_sched_scan = 1;
                ssid = wpa_s->conf->ssid;
@@ -1277,22 +1601,70 @@ int wpa_supplicant_req_sched_scan(struct wpa_supplicant *wpa_s)
                int_array_concat(&params.freqs, wpa_s->conf->freq_list);
        }
 
+#ifdef CONFIG_MBO
+       if (wpa_s->enable_oce & OCE_STA)
+               params.oce_scan = 1;
+#endif /* CONFIG_MBO */
+
        scan_params = &params;
 
 scan:
+       wpa_s->sched_scan_timed_out = 0;
+
+       /*
+        * We cannot support multiple scan plans if the scan request includes
+        * too many SSID's, so in this case use only the last scan plan and make
+        * it run infinitely. It will be stopped by the timeout.
+        */
+       if (wpa_s->sched_scan_plans_num == 1 ||
+           (wpa_s->sched_scan_plans_num && !ssid && wpa_s->first_sched_scan)) {
+               params.sched_scan_plans = wpa_s->sched_scan_plans;
+               params.sched_scan_plans_num = wpa_s->sched_scan_plans_num;
+       } else if (wpa_s->sched_scan_plans_num > 1) {
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Too many SSIDs. Default to using single scheduled_scan plan");
+               params.sched_scan_plans =
+                       &wpa_s->sched_scan_plans[wpa_s->sched_scan_plans_num -
+                                                1];
+               params.sched_scan_plans_num = 1;
+       } else {
+               if (wpa_s->conf->sched_scan_interval)
+                       scan_plan.interval = wpa_s->conf->sched_scan_interval;
+               else
+                       scan_plan.interval = 10;
+
+               if (scan_plan.interval > wpa_s->max_sched_scan_plan_interval) {
+                       wpa_printf(MSG_WARNING,
+                                  "Scan interval too long(%u), use the maximum allowed(%u)",
+                                  scan_plan.interval,
+                                  wpa_s->max_sched_scan_plan_interval);
+                       scan_plan.interval =
+                               wpa_s->max_sched_scan_plan_interval;
+               }
+
+               scan_plan.iterations = 0;
+               params.sched_scan_plans = &scan_plan;
+               params.sched_scan_plans_num = 1;
+       }
+
+       params.sched_scan_start_delay = wpa_s->conf->sched_scan_start_delay;
+
        if (ssid || !wpa_s->first_sched_scan) {
                wpa_dbg(wpa_s, MSG_DEBUG,
-                       "Starting sched scan: interval %d timeout %d",
-                       wpa_s->sched_scan_interval, wpa_s->sched_scan_timeout);
+                       "Starting sched scan after %u seconds: interval %u timeout %d",
+                       params.sched_scan_start_delay,
+                       params.sched_scan_plans[0].interval,
+                       wpa_s->sched_scan_timeout);
        } else {
                wpa_dbg(wpa_s, MSG_DEBUG,
-                       "Starting sched scan: interval %d (no timeout)",
-                       wpa_s->sched_scan_interval);
+                       "Starting sched scan after %u seconds (no timeout)",
+                       params.sched_scan_start_delay);
        }
 
        wpa_setband_scan_freqs(wpa_s, scan_params);
 
-       if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCHED_SCAN) {
+       if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_SCHED_SCAN) &&
+           wpa_s->wpa_state <= WPA_SCANNING) {
                params.mac_addr_rand = 1;
                if (wpa_s->mac_addr_sched_scan) {
                        params.mac_addr = wpa_s->mac_addr_sched_scan;
@@ -1301,8 +1673,9 @@ scan:
                }
        }
 
-       ret = wpa_supplicant_start_sched_scan(wpa_s, scan_params,
-                                             wpa_s->sched_scan_interval);
+       wpa_scan_set_relative_rssi_params(wpa_s, scan_params);
+
+       ret = wpa_supplicant_start_sched_scan(wpa_s, scan_params);
        wpabuf_free(extra_ie);
        os_free(params.filter_ssids);
        if (ret) {
@@ -1320,9 +1693,12 @@ scan:
                                       wpa_s, NULL);
                wpa_s->first_sched_scan = 0;
                wpa_s->sched_scan_timeout /= 2;
-               wpa_s->sched_scan_interval *= 2;
-               if (wpa_s->sched_scan_timeout < wpa_s->sched_scan_interval) {
-                       wpa_s->sched_scan_interval = 10;
+               params.sched_scan_plans[0].interval *= 2;
+               if ((unsigned int) wpa_s->sched_scan_timeout <
+                   params.sched_scan_plans[0].interval ||
+                   params.sched_scan_plans[0].interval >
+                   wpa_s->max_sched_scan_plan_interval) {
+                       params.sched_scan_plans[0].interval = 10;
                        wpa_s->sched_scan_timeout = max_sched_scan_ssids * 2;
                }
        }
@@ -1377,6 +1753,9 @@ void wpa_supplicant_cancel_sched_scan(struct wpa_supplicant *wpa_s)
        if (!wpa_s->sched_scanning)
                return;
 
+       if (wpa_s->sched_scanning)
+               wpa_s->sched_scan_stop_req = 1;
+
        wpa_dbg(wpa_s, MSG_DEBUG, "Cancelling sched scan");
        eloop_cancel_timeout(wpa_supplicant_sched_scan_timeout, wpa_s, NULL);
        wpa_supplicant_stop_sched_scan(wpa_s);
@@ -1436,20 +1815,13 @@ static int wpa_scan_get_max_rate(const struct wpa_scan_res *res)
  */
 const u8 * wpa_scan_get_ie(const struct wpa_scan_res *res, u8 ie)
 {
-       const u8 *end, *pos;
-
-       pos = (const u8 *) (res + 1);
-       end = pos + res->ie_len;
+       size_t ie_len = res->ie_len;
 
-       while (pos + 1 < end) {
-               if (pos + 2 + pos[1] > end)
-                       break;
-               if (pos[0] == ie)
-                       return pos;
-               pos += 2 + pos[1];
-       }
+       /* Use the Beacon frame IEs if res->ie_len is not available */
+       if (!ie_len)
+               ie_len = res->beacon_ie_len;
 
-       return NULL;
+       return get_ie((const u8 *) (res + 1), ie_len, ie);
 }
 
 
@@ -1470,8 +1842,8 @@ const u8 * wpa_scan_get_vendor_ie(const struct wpa_scan_res *res,
        pos = (const u8 *) (res + 1);
        end = pos + res->ie_len;
 
-       while (pos + 1 < end) {
-               if (pos + 2 + pos[1] > end)
+       while (end - pos > 1) {
+               if (2 + pos[1] > end - pos)
                        break;
                if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 &&
                    vendor_type == WPA_GET_BE32(&pos[2]))
@@ -1507,8 +1879,8 @@ const u8 * wpa_scan_get_vendor_ie_beacon(const struct wpa_scan_res *res,
        pos += res->ie_len;
        end = pos + res->beacon_ie_len;
 
-       while (pos + 1 < end) {
-               if (pos + 2 + pos[1] > end)
+       while (end - pos > 1) {
+               if (2 + pos[1] > end - pos)
                        break;
                if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 &&
                    vendor_type == WPA_GET_BE32(&pos[2]))
@@ -1543,8 +1915,8 @@ struct wpabuf * wpa_scan_get_vendor_ie_multi(const struct wpa_scan_res *res,
        pos = (const u8 *) (res + 1);
        end = pos + res->ie_len;
 
-       while (pos + 1 < end) {
-               if (pos + 2 + pos[1] > end)
+       while (end - pos > 1) {
+               if (2 + pos[1] > end - pos)
                        break;
                if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && pos[1] >= 4 &&
                    vendor_type == WPA_GET_BE32(&pos[2]))
@@ -1566,23 +1938,26 @@ struct wpabuf * wpa_scan_get_vendor_ie_multi(const struct wpa_scan_res *res,
  * This doc https://supportforums.cisco.com/docs/DOC-12954 says, "the general
  * rule of thumb is that any SNR above 20 is good." This one
  * http://www.cisco.com/en/US/tech/tk722/tk809/technologies_q_and_a_item09186a00805e9a96.shtml#qa23
- * recommends 25 as a minimum SNR for 54 Mbps data rate. 30 is chosen here as a
- * conservative value.
+ * recommends 25 as a minimum SNR for 54 Mbps data rate. The estimates used in
+ * scan_est_throughput() allow even smaller SNR values for the maximum rates
+ * (21 for 54 Mbps, 22 for VHT80 MCS9, 24 for HT40 and HT20 MCS7). Use 25 as a
+ * somewhat conservative value here.
  */
-#define GREAT_SNR 30
+#define GREAT_SNR 25
+
+#define IS_5GHZ(n) (n > 4000)
 
 /* Compare function for sorting scan results. Return >0 if @b is considered
  * better. */
 static int wpa_scan_result_compar(const void *a, const void *b)
 {
-#define IS_5GHZ(n) (n > 4000)
 #define MIN(a,b) a < b ? a : b
        struct wpa_scan_res **_wa = (void *) a;
        struct wpa_scan_res **_wb = (void *) b;
        struct wpa_scan_res *wa = *_wa;
        struct wpa_scan_res *wb = *_wb;
-       int wpa_a, wpa_b, maxrate_a, maxrate_b;
-       int snr_a, snr_b;
+       int wpa_a, wpa_b;
+       int snr_a, snr_b, snr_a_full, snr_b_full;
 
        /* WPA/WPA2 support preferred */
        wpa_a = wpa_scan_get_vendor_ie(wa, WPA_IE_VENDOR_TYPE) != NULL ||
@@ -1603,37 +1978,37 @@ static int wpa_scan_result_compar(const void *a, const void *b)
            (wb->caps & IEEE80211_CAP_PRIVACY) == 0)
                return -1;
 
-       if ((wa->flags & wb->flags & WPA_SCAN_LEVEL_DBM) &&
-           !((wa->flags | wb->flags) & WPA_SCAN_NOISE_INVALID)) {
-               snr_a = MIN(wa->level - wa->noise, GREAT_SNR);
-               snr_b = MIN(wb->level - wb->noise, GREAT_SNR);
+       if (wa->flags & wb->flags & WPA_SCAN_LEVEL_DBM) {
+               snr_a_full = wa->snr;
+               snr_a = MIN(wa->snr, GREAT_SNR);
+               snr_b_full = wb->snr;
+               snr_b = MIN(wb->snr, GREAT_SNR);
        } else {
-               /* Not suitable information to calculate SNR, so use level */
-               snr_a = wa->level;
-               snr_b = wb->level;
+               /* Level is not in dBm, so we can't calculate
+                * SNR. Just use raw level (units unknown). */
+               snr_a = snr_a_full = wa->level;
+               snr_b = snr_b_full = wb->level;
        }
 
-       /* best/max rate preferred if SNR close enough */
-        if ((snr_a && snr_b && abs(snr_b - snr_a) < 5) ||
+       /* if SNR is close, decide by max rate or frequency band */
+       if (snr_a && snr_b && abs(snr_b - snr_a) < 7) {
+               if (wa->est_throughput != wb->est_throughput)
+                       return (int) wb->est_throughput -
+                               (int) wa->est_throughput;
+       }
+       if ((snr_a && snr_b && abs(snr_b - snr_a) < 5) ||
            (wa->qual && wb->qual && abs(wb->qual - wa->qual) < 10)) {
-               maxrate_a = wpa_scan_get_max_rate(wa);
-               maxrate_b = wpa_scan_get_max_rate(wb);
-               if (maxrate_a != maxrate_b)
-                       return maxrate_b - maxrate_a;
                if (IS_5GHZ(wa->freq) ^ IS_5GHZ(wb->freq))
                        return IS_5GHZ(wa->freq) ? -1 : 1;
        }
 
-       /* use freq for channel preference */
-
        /* all things being equal, use SNR; if SNRs are
         * identical, use quality values since some drivers may only report
         * that value and leave the signal level zero */
-       if (snr_b == snr_a)
+       if (snr_b_full == snr_a_full)
                return wb->qual - wa->qual;
-       return snr_b - snr_a;
+       return snr_b_full - snr_a_full;
 #undef MIN
-#undef IS_5GHZ
 }
 
 
@@ -1698,21 +2073,22 @@ static void dump_scan_res(struct wpa_scan_results *scan_res)
        for (i = 0; i < scan_res->num; i++) {
                struct wpa_scan_res *r = scan_res->res[i];
                u8 *pos;
-               if ((r->flags & (WPA_SCAN_LEVEL_DBM | WPA_SCAN_NOISE_INVALID))
-                   == WPA_SCAN_LEVEL_DBM) {
-                       int snr = r->level - r->noise;
+               if (r->flags & WPA_SCAN_LEVEL_DBM) {
+                       int noise_valid = !(r->flags & WPA_SCAN_NOISE_INVALID);
+
                        wpa_printf(MSG_EXCESSIVE, MACSTR " freq=%d qual=%d "
-                                  "noise=%d level=%d snr=%d%s flags=0x%x "
-                                  "age=%u",
+                                  "noise=%d%s level=%d snr=%d%s flags=0x%x age=%u est=%u",
                                   MAC2STR(r->bssid), r->freq, r->qual,
-                                  r->noise, r->level, snr,
-                                  snr >= GREAT_SNR ? "*" : "", r->flags,
-                                  r->age);
+                                  r->noise, noise_valid ? "" : "~", r->level,
+                                  r->snr, r->snr >= GREAT_SNR ? "*" : "",
+                                  r->flags,
+                                  r->age, r->est_throughput);
                } else {
                        wpa_printf(MSG_EXCESSIVE, MACSTR " freq=%d qual=%d "
-                                  "noise=%d level=%d flags=0x%x age=%u",
+                                  "noise=%d level=%d flags=0x%x age=%u est=%u",
                                   MAC2STR(r->bssid), r->freq, r->qual,
-                                  r->noise, r->level, r->flags, r->age);
+                                  r->noise, r->level, r->flags, r->age,
+                                  r->est_throughput);
                }
                pos = (u8 *) (r + 1);
                if (r->ie_len)
@@ -1753,8 +2129,8 @@ int wpa_supplicant_filter_bssid_match(struct wpa_supplicant *wpa_s,
 }
 
 
-static void filter_scan_res(struct wpa_supplicant *wpa_s,
-                           struct wpa_scan_results *res)
+void filter_scan_res(struct wpa_supplicant *wpa_s,
+                    struct wpa_scan_results *res)
 {
        size_t i, j;
 
@@ -1779,6 +2155,188 @@ static void filter_scan_res(struct wpa_supplicant *wpa_s,
 }
 
 
+/*
+ * Noise floor values to use when we have signal strength
+ * measurements, but no noise floor measurements. These values were
+ * measured in an office environment with many APs.
+ */
+#define DEFAULT_NOISE_FLOOR_2GHZ (-89)
+#define DEFAULT_NOISE_FLOOR_5GHZ (-92)
+
+void scan_snr(struct wpa_scan_res *res)
+{
+       if (res->flags & WPA_SCAN_NOISE_INVALID) {
+               res->noise = IS_5GHZ(res->freq) ?
+                       DEFAULT_NOISE_FLOOR_5GHZ :
+                       DEFAULT_NOISE_FLOOR_2GHZ;
+       }
+
+       if (res->flags & WPA_SCAN_LEVEL_DBM) {
+               res->snr = res->level - res->noise;
+       } else {
+               /* Level is not in dBm, so we can't calculate
+                * SNR. Just use raw level (units unknown). */
+               res->snr = res->level;
+       }
+}
+
+
+static unsigned int max_ht20_rate(int snr)
+{
+       if (snr < 6)
+               return 6500; /* HT20 MCS0 */
+       if (snr < 8)
+               return 13000; /* HT20 MCS1 */
+       if (snr < 13)
+               return 19500; /* HT20 MCS2 */
+       if (snr < 17)
+               return 26000; /* HT20 MCS3 */
+       if (snr < 20)
+               return 39000; /* HT20 MCS4 */
+       if (snr < 23)
+               return 52000; /* HT20 MCS5 */
+       if (snr < 24)
+               return 58500; /* HT20 MCS6 */
+       return 65000; /* HT20 MCS7 */
+}
+
+
+static unsigned int max_ht40_rate(int snr)
+{
+       if (snr < 3)
+               return 13500; /* HT40 MCS0 */
+       if (snr < 6)
+               return 27000; /* HT40 MCS1 */
+       if (snr < 10)
+               return 40500; /* HT40 MCS2 */
+       if (snr < 15)
+               return 54000; /* HT40 MCS3 */
+       if (snr < 17)
+               return 81000; /* HT40 MCS4 */
+       if (snr < 22)
+               return 108000; /* HT40 MCS5 */
+       if (snr < 24)
+               return 121500; /* HT40 MCS6 */
+       return 135000; /* HT40 MCS7 */
+}
+
+
+static unsigned int max_vht80_rate(int snr)
+{
+       if (snr < 1)
+               return 0;
+       if (snr < 2)
+               return 29300; /* VHT80 MCS0 */
+       if (snr < 5)
+               return 58500; /* VHT80 MCS1 */
+       if (snr < 9)
+               return 87800; /* VHT80 MCS2 */
+       if (snr < 11)
+               return 117000; /* VHT80 MCS3 */
+       if (snr < 15)
+               return 175500; /* VHT80 MCS4 */
+       if (snr < 16)
+               return 234000; /* VHT80 MCS5 */
+       if (snr < 18)
+               return 263300; /* VHT80 MCS6 */
+       if (snr < 20)
+               return 292500; /* VHT80 MCS7 */
+       if (snr < 22)
+               return 351000; /* VHT80 MCS8 */
+       return 390000; /* VHT80 MCS9 */
+}
+
+
+void scan_est_throughput(struct wpa_supplicant *wpa_s,
+                        struct wpa_scan_res *res)
+{
+       enum local_hw_capab capab = wpa_s->hw_capab;
+       int rate; /* max legacy rate in 500 kb/s units */
+       const u8 *ie;
+       unsigned int est, tmp;
+       int snr = res->snr;
+
+       if (res->est_throughput)
+               return;
+
+       /* Get maximum legacy rate */
+       rate = wpa_scan_get_max_rate(res);
+
+       /* Limit based on estimated SNR */
+       if (rate > 1 * 2 && snr < 1)
+               rate = 1 * 2;
+       else if (rate > 2 * 2 && snr < 4)
+               rate = 2 * 2;
+       else if (rate > 6 * 2 && snr < 5)
+               rate = 6 * 2;
+       else if (rate > 9 * 2 && snr < 6)
+               rate = 9 * 2;
+       else if (rate > 12 * 2 && snr < 7)
+               rate = 12 * 2;
+       else if (rate > 18 * 2 && snr < 10)
+               rate = 18 * 2;
+       else if (rate > 24 * 2 && snr < 11)
+               rate = 24 * 2;
+       else if (rate > 36 * 2 && snr < 15)
+               rate = 36 * 2;
+       else if (rate > 48 * 2 && snr < 19)
+               rate = 48 * 2;
+       else if (rate > 54 * 2 && snr < 21)
+               rate = 54 * 2;
+       est = rate * 500;
+
+       if (capab == CAPAB_HT || capab == CAPAB_HT40 || capab == CAPAB_VHT) {
+               ie = wpa_scan_get_ie(res, WLAN_EID_HT_CAP);
+               if (ie) {
+                       tmp = max_ht20_rate(snr);
+                       if (tmp > est)
+                               est = tmp;
+               }
+       }
+
+       if (capab == CAPAB_HT40 || capab == CAPAB_VHT) {
+               ie = wpa_scan_get_ie(res, WLAN_EID_HT_OPERATION);
+               if (ie && ie[1] >= 2 &&
+                   (ie[3] & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK)) {
+                       tmp = max_ht40_rate(snr);
+                       if (tmp > est)
+                               est = tmp;
+               }
+       }
+
+       if (capab == CAPAB_VHT) {
+               /* Use +1 to assume VHT is always faster than HT */
+               ie = wpa_scan_get_ie(res, WLAN_EID_VHT_CAP);
+               if (ie) {
+                       tmp = max_ht20_rate(snr) + 1;
+                       if (tmp > est)
+                               est = tmp;
+
+                       ie = wpa_scan_get_ie(res, WLAN_EID_HT_OPERATION);
+                       if (ie && ie[1] >= 2 &&
+                           (ie[3] &
+                            HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK)) {
+                               tmp = max_ht40_rate(snr) + 1;
+                               if (tmp > est)
+                                       est = tmp;
+                       }
+
+                       ie = wpa_scan_get_ie(res, WLAN_EID_VHT_OPERATION);
+                       if (ie && ie[1] >= 1 &&
+                           (ie[2] & VHT_OPMODE_CHANNEL_WIDTH_MASK)) {
+                               tmp = max_vht80_rate(snr) + 1;
+                               if (tmp > est)
+                                       est = tmp;
+                       }
+               }
+       }
+
+       /* TODO: channel utilization and AP load (e.g., from AP Beacon) */
+
+       res->est_throughput = est;
+}
+
+
 /**
  * wpa_supplicant_get_scan_results - Get scan results
  * @wpa_s: Pointer to wpa_supplicant data
@@ -1812,6 +2370,13 @@ wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,
        }
        filter_scan_res(wpa_s, scan_res);
 
+       for (i = 0; i < scan_res->num; i++) {
+               struct wpa_scan_res *scan_res_item = scan_res->res[i];
+
+               scan_snr(scan_res_item);
+               scan_est_throughput(wpa_s, scan_res_item);
+       }
+
 #ifdef CONFIG_WPS
        if (wpas_wps_searching(wpa_s)) {
                wpa_dbg(wpa_s, MSG_DEBUG, "WPS: Order scan results with WPS "
@@ -1820,10 +2385,22 @@ wpa_supplicant_get_scan_results(struct wpa_supplicant *wpa_s,
        }
 #endif /* CONFIG_WPS */
 
-       qsort(scan_res->res, scan_res->num, sizeof(struct wpa_scan_res *),
-             compar);
+       if (scan_res->res) {
+               qsort(scan_res->res, scan_res->num,
+                     sizeof(struct wpa_scan_res *), compar);
+       }
        dump_scan_res(scan_res);
 
+       if (wpa_s->ignore_post_flush_scan_res) {
+               /* FLUSH command aborted an ongoing scan and these are the
+                * results from the aborted scan. Do not process the results to
+                * maintain flushed state. */
+               wpa_dbg(wpa_s, MSG_DEBUG,
+                       "Do not update BSS table based on pending post-FLUSH scan results");
+               wpa_s->ignore_post_flush_scan_res = 0;
+               return scan_res;
+       }
+
        wpa_bss_update_start(wpa_s);
        for (i = 0; i < scan_res->num; i++)
                wpa_bss_update_scan_res(wpa_s, scan_res->res[i],
@@ -1880,6 +2457,9 @@ void scan_only_handler(struct wpa_supplicant *wpa_s,
                wpa_s->scan_work = NULL;
                radio_work_done(work);
        }
+
+       if (wpa_s->wpa_state == WPA_SCANNING)
+               wpa_supplicant_set_state(wpa_s, wpa_s->scan_prev_wpa_state);
 }
 
 
@@ -1902,11 +2482,10 @@ wpa_scan_clone_params(const struct wpa_driver_scan_params *src)
 
        for (i = 0; i < src->num_ssids; i++) {
                if (src->ssids[i].ssid) {
-                       n = os_malloc(src->ssids[i].ssid_len);
+                       n = os_memdup(src->ssids[i].ssid,
+                                     src->ssids[i].ssid_len);
                        if (n == NULL)
                                goto failed;
-                       os_memcpy(n, src->ssids[i].ssid,
-                                 src->ssids[i].ssid_len);
                        params->ssids[i].ssid = n;
                        params->ssids[i].ssid_len = src->ssids[i].ssid_len;
                }
@@ -1914,30 +2493,26 @@ wpa_scan_clone_params(const struct wpa_driver_scan_params *src)
        params->num_ssids = src->num_ssids;
 
        if (src->extra_ies) {
-               n = os_malloc(src->extra_ies_len);
+               n = os_memdup(src->extra_ies, src->extra_ies_len);
                if (n == NULL)
                        goto failed;
-               os_memcpy(n, src->extra_ies, src->extra_ies_len);
                params->extra_ies = n;
                params->extra_ies_len = src->extra_ies_len;
        }
 
        if (src->freqs) {
                int len = int_array_len(src->freqs);
-               params->freqs = os_malloc((len + 1) * sizeof(int));
+               params->freqs = os_memdup(src->freqs, (len + 1) * sizeof(int));
                if (params->freqs == NULL)
                        goto failed;
-               os_memcpy(params->freqs, src->freqs, (len + 1) * sizeof(int));
        }
 
        if (src->filter_ssids) {
-               params->filter_ssids = os_malloc(sizeof(*params->filter_ssids) *
+               params->filter_ssids = os_memdup(src->filter_ssids,
+                                                sizeof(*params->filter_ssids) *
                                                 src->num_filter_ssids);
                if (params->filter_ssids == NULL)
                        goto failed;
-               os_memcpy(params->filter_ssids, src->filter_ssids,
-                         sizeof(*params->filter_ssids) *
-                         src->num_filter_ssids);
                params->num_filter_ssids = src->num_filter_ssids;
        }
 
@@ -1945,6 +2520,20 @@ wpa_scan_clone_params(const struct wpa_driver_scan_params *src)
        params->p2p_probe = src->p2p_probe;
        params->only_new_results = src->only_new_results;
        params->low_priority = src->low_priority;
+       params->duration = src->duration;
+       params->duration_mandatory = src->duration_mandatory;
+       params->oce_scan = src->oce_scan;
+
+       if (src->sched_scan_plans_num > 0) {
+               params->sched_scan_plans =
+                       os_memdup(src->sched_scan_plans,
+                                 sizeof(*src->sched_scan_plans) *
+                                 src->sched_scan_plans_num);
+               if (!params->sched_scan_plans)
+                       goto failed;
+
+               params->sched_scan_plans_num = src->sched_scan_plans_num;
+       }
 
        if (src->mac_addr_rand) {
                params->mac_addr_rand = src->mac_addr_rand;
@@ -1963,6 +2552,20 @@ wpa_scan_clone_params(const struct wpa_driver_scan_params *src)
                        params->mac_addr_mask = mac_addr + ETH_ALEN;
                }
        }
+
+       if (src->bssid) {
+               u8 *bssid;
+
+               bssid = os_memdup(src->bssid, ETH_ALEN);
+               if (!bssid)
+                       goto failed;
+               params->bssid = bssid;
+       }
+
+       params->relative_rssi_set = src->relative_rssi_set;
+       params->relative_rssi = src->relative_rssi;
+       params->relative_adjust_band = src->relative_adjust_band;
+       params->relative_adjust_rssi = src->relative_adjust_rssi;
        return params;
 
 failed:
@@ -1983,6 +2586,7 @@ void wpa_scan_free_params(struct wpa_driver_scan_params *params)
        os_free((u8 *) params->extra_ies);
        os_free(params->freqs);
        os_free(params->filter_ssids);
+       os_free(params->sched_scan_plans);
 
        /*
         * Note: params->mac_addr_mask points to same memory allocation and
@@ -1990,25 +2594,36 @@ void wpa_scan_free_params(struct wpa_driver_scan_params *params)
         */
        os_free((u8 *) params->mac_addr);
 
+       os_free((u8 *) params->bssid);
+
        os_free(params);
 }
 
 
 int wpas_start_pno(struct wpa_supplicant *wpa_s)
 {
-       int ret, interval;
+       int ret, prio;
        size_t i, num_ssid, num_match_ssid;
        struct wpa_ssid *ssid;
        struct wpa_driver_scan_params params;
+       struct sched_scan_plan scan_plan;
+       unsigned int max_sched_scan_ssids;
 
        if (!wpa_s->sched_scan_supported)
                return -1;
 
+       if (wpa_s->max_sched_scan_ssids > WPAS_MAX_SCAN_SSIDS)
+               max_sched_scan_ssids = WPAS_MAX_SCAN_SSIDS;
+       else
+               max_sched_scan_ssids = wpa_s->max_sched_scan_ssids;
+       if (max_sched_scan_ssids < 1)
+               return -1;
+
        if (wpa_s->pno || wpa_s->pno_sched_pending)
                return 0;
 
        if ((wpa_s->wpa_state > WPA_SCANNING) &&
-           (wpa_s->wpa_state <= WPA_COMPLETED)) {
+           (wpa_s->wpa_state < WPA_COMPLETED)) {
                wpa_printf(MSG_ERROR, "PNO: In assoc process");
                return -EAGAIN;
        }
@@ -2024,6 +2639,13 @@ int wpas_start_pno(struct wpa_supplicant *wpa_s)
                }
        }
 
+       if (wpa_s->sched_scan_stop_req) {
+               wpa_printf(MSG_DEBUG,
+                          "Schedule PNO after previous sched scan has stopped");
+               wpa_s->pno_sched_pending = 1;
+               return 0;
+       }
+
        os_memset(&params, 0, sizeof(params));
 
        num_ssid = num_match_ssid = 0;
@@ -2047,10 +2669,10 @@ int wpas_start_pno(struct wpa_supplicant *wpa_s)
                num_ssid++;
        }
 
-       if (num_ssid > WPAS_MAX_SCAN_SSIDS) {
+       if (num_ssid > max_sched_scan_ssids) {
                wpa_printf(MSG_DEBUG, "PNO: Use only the first %u SSIDs from "
-                          "%u", WPAS_MAX_SCAN_SSIDS, (unsigned int) num_ssid);
-               num_ssid = WPAS_MAX_SCAN_SSIDS;
+                          "%u", max_sched_scan_ssids, (unsigned int) num_ssid);
+               num_ssid = max_sched_scan_ssids;
        }
 
        if (num_match_ssid > wpa_s->max_match_sets) {
@@ -2061,8 +2683,10 @@ int wpas_start_pno(struct wpa_supplicant *wpa_s)
                                        sizeof(struct wpa_driver_scan_filter));
        if (params.filter_ssids == NULL)
                return -1;
+
        i = 0;
-       ssid = wpa_s->conf->ssid;
+       prio = 0;
+       ssid = wpa_s->conf->pssid[prio];
        while (ssid) {
                if (!wpas_network_disabled(wpa_s, ssid)) {
                        if (ssid->scan_ssid && params.num_ssids < num_ssid) {
@@ -2080,21 +2704,41 @@ int wpas_start_pno(struct wpa_supplicant *wpa_s)
                        if (i == num_match_ssid)
                                break;
                }
-               ssid = ssid->next;
+               if (ssid->pnext)
+                       ssid = ssid->pnext;
+               else if (prio + 1 == wpa_s->conf->num_prio)
+                       break;
+               else
+                       ssid = wpa_s->conf->pssid[++prio];
        }
 
        if (wpa_s->conf->filter_rssi)
                params.filter_rssi = wpa_s->conf->filter_rssi;
 
-       interval = wpa_s->conf->sched_scan_interval ?
-               wpa_s->conf->sched_scan_interval : 10;
+       if (wpa_s->sched_scan_plans_num) {
+               params.sched_scan_plans = wpa_s->sched_scan_plans;
+               params.sched_scan_plans_num = wpa_s->sched_scan_plans_num;
+       } else {
+               /* Set one scan plan that will run infinitely */
+               if (wpa_s->conf->sched_scan_interval)
+                       scan_plan.interval = wpa_s->conf->sched_scan_interval;
+               else
+                       scan_plan.interval = 10;
+
+               scan_plan.iterations = 0;
+               params.sched_scan_plans = &scan_plan;
+               params.sched_scan_plans_num = 1;
+       }
+
+       params.sched_scan_start_delay = wpa_s->conf->sched_scan_start_delay;
 
        if (params.freqs == NULL && wpa_s->manual_sched_scan_freqs) {
                wpa_dbg(wpa_s, MSG_DEBUG, "Limit sched scan to specified channels");
                params.freqs = wpa_s->manual_sched_scan_freqs;
        }
 
-       if (wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_PNO) {
+       if ((wpa_s->mac_addr_rand_enable & MAC_ADDR_RAND_PNO) &&
+           wpa_s->wpa_state <= WPA_SCANNING) {
                params.mac_addr_rand = 1;
                if (wpa_s->mac_addr_pno) {
                        params.mac_addr = wpa_s->mac_addr_pno;
@@ -2102,7 +2746,9 @@ int wpas_start_pno(struct wpa_supplicant *wpa_s)
                }
        }
 
-       ret = wpa_supplicant_start_sched_scan(wpa_s, &params, interval);
+       wpa_scan_set_relative_rssi_params(wpa_s, &params);
+
+       ret = wpa_supplicant_start_sched_scan(wpa_s, &params);
        os_free(params.filter_ssids);
        if (ret == 0)
                wpa_s->pno = 1;
@@ -2120,6 +2766,7 @@ int wpas_stop_pno(struct wpa_supplicant *wpa_s)
                return 0;
 
        ret = wpa_supplicant_stop_sched_scan(wpa_s);
+       wpa_s->sched_scan_stop_req = 1;
 
        wpa_s->pno = 0;
        wpa_s->pno_sched_pending = 0;
@@ -2160,6 +2807,13 @@ int wpas_mac_addr_rand_scan_set(struct wpa_supplicant *wpa_s,
 {
        u8 *tmp = NULL;
 
+       if ((wpa_s->mac_addr_rand_supported & type) != type ) {
+               wpa_printf(MSG_INFO,
+                          "scan: MAC randomization type %u != supported=%u",
+                          type, wpa_s->mac_addr_rand_supported);
+               return -1;
+       }
+
        wpas_mac_addr_rand_scan_clear(wpa_s, type);
 
        if (addr) {
@@ -2187,3 +2841,162 @@ int wpas_mac_addr_rand_scan_set(struct wpa_supplicant *wpa_s,
        wpa_s->mac_addr_rand_enable |= type;
        return 0;
 }
+
+
+int wpas_abort_ongoing_scan(struct wpa_supplicant *wpa_s)
+{
+       struct wpa_radio_work *work;
+       struct wpa_radio *radio = wpa_s->radio;
+
+       dl_list_for_each(work, &radio->work, struct wpa_radio_work, list) {
+               if (work->wpa_s != wpa_s || !work->started ||
+                   (os_strcmp(work->type, "scan") != 0 &&
+                    os_strcmp(work->type, "p2p-scan") != 0))
+                       continue;
+               wpa_dbg(wpa_s, MSG_DEBUG, "Abort an ongoing scan");
+               return wpa_drv_abort_scan(wpa_s, wpa_s->curr_scan_cookie);
+       }
+
+       wpa_dbg(wpa_s, MSG_DEBUG, "No ongoing scan/p2p-scan found to abort");
+       return -1;
+}
+
+
+int wpas_sched_scan_plans_set(struct wpa_supplicant *wpa_s, const char *cmd)
+{
+       struct sched_scan_plan *scan_plans = NULL;
+       const char *token, *context = NULL;
+       unsigned int num = 0;
+
+       if (!cmd)
+               return -1;
+
+       if (!cmd[0]) {
+               wpa_printf(MSG_DEBUG, "Clear sched scan plans");
+               os_free(wpa_s->sched_scan_plans);
+               wpa_s->sched_scan_plans = NULL;
+               wpa_s->sched_scan_plans_num = 0;
+               return 0;
+       }
+
+       while ((token = cstr_token(cmd, " ", &context))) {
+               int ret;
+               struct sched_scan_plan *scan_plan, *n;
+
+               n = os_realloc_array(scan_plans, num + 1, sizeof(*scan_plans));
+               if (!n)
+                       goto fail;
+
+               scan_plans = n;
+               scan_plan = &scan_plans[num];
+               num++;
+
+               ret = sscanf(token, "%u:%u", &scan_plan->interval,
+                            &scan_plan->iterations);
+               if (ret <= 0 || ret > 2 || !scan_plan->interval) {
+                       wpa_printf(MSG_ERROR,
+                                  "Invalid sched scan plan input: %s", token);
+                       goto fail;
+               }
+
+               if (scan_plan->interval > wpa_s->max_sched_scan_plan_interval) {
+                       wpa_printf(MSG_WARNING,
+                                  "scan plan %u: Scan interval too long(%u), use the maximum allowed(%u)",
+                                  num, scan_plan->interval,
+                                  wpa_s->max_sched_scan_plan_interval);
+                       scan_plan->interval =
+                               wpa_s->max_sched_scan_plan_interval;
+               }
+
+               if (ret == 1) {
+                       scan_plan->iterations = 0;
+                       break;
+               }
+
+               if (!scan_plan->iterations) {
+                       wpa_printf(MSG_ERROR,
+                                  "scan plan %u: Number of iterations cannot be zero",
+                                  num);
+                       goto fail;
+               }
+
+               if (scan_plan->iterations >
+                   wpa_s->max_sched_scan_plan_iterations) {
+                       wpa_printf(MSG_WARNING,
+                                  "scan plan %u: Too many iterations(%u), use the maximum allowed(%u)",
+                                  num, scan_plan->iterations,
+                                  wpa_s->max_sched_scan_plan_iterations);
+                       scan_plan->iterations =
+                               wpa_s->max_sched_scan_plan_iterations;
+               }
+
+               wpa_printf(MSG_DEBUG,
+                          "scan plan %u: interval=%u iterations=%u",
+                          num, scan_plan->interval, scan_plan->iterations);
+       }
+
+       if (!scan_plans) {
+               wpa_printf(MSG_ERROR, "Invalid scan plans entry");
+               goto fail;
+       }
+
+       if (cstr_token(cmd, " ", &context) || scan_plans[num - 1].iterations) {
+               wpa_printf(MSG_ERROR,
+                          "All scan plans but the last must specify a number of iterations");
+               goto fail;
+       }
+
+       wpa_printf(MSG_DEBUG, "scan plan %u (last plan): interval=%u",
+                  num, scan_plans[num - 1].interval);
+
+       if (num > wpa_s->max_sched_scan_plans) {
+               wpa_printf(MSG_WARNING,
+                          "Too many scheduled scan plans (only %u supported)",
+                          wpa_s->max_sched_scan_plans);
+               wpa_printf(MSG_WARNING,
+                          "Use only the first %u scan plans, and the last one (in infinite loop)",
+                          wpa_s->max_sched_scan_plans - 1);
+               os_memcpy(&scan_plans[wpa_s->max_sched_scan_plans - 1],
+                         &scan_plans[num - 1], sizeof(*scan_plans));
+               num = wpa_s->max_sched_scan_plans;
+       }
+
+       os_free(wpa_s->sched_scan_plans);
+       wpa_s->sched_scan_plans = scan_plans;
+       wpa_s->sched_scan_plans_num = num;
+
+       return 0;
+
+fail:
+       os_free(scan_plans);
+       wpa_printf(MSG_ERROR, "invalid scan plans list");
+       return -1;
+}
+
+
+/**
+ * wpas_scan_reset_sched_scan - Reset sched_scan state
+ * @wpa_s: Pointer to wpa_supplicant data
+ *
+ * This function is used to cancel a running scheduled scan and to reset an
+ * internal scan state to continue with a regular scan on the following
+ * wpa_supplicant_req_scan() calls.
+ */
+void wpas_scan_reset_sched_scan(struct wpa_supplicant *wpa_s)
+{
+       wpa_s->normal_scans = 0;
+       if (wpa_s->sched_scanning) {
+               wpa_s->sched_scan_timed_out = 0;
+               wpa_s->prev_sched_ssid = NULL;
+               wpa_supplicant_cancel_sched_scan(wpa_s);
+       }
+}
+
+
+void wpas_scan_restart_sched_scan(struct wpa_supplicant *wpa_s)
+{
+       /* simulate timeout to restart the sched scan */
+       wpa_s->sched_scan_timed_out = 1;
+       wpa_s->prev_sched_ssid = NULL;
+       wpa_supplicant_cancel_sched_scan(wpa_s);
+}