P2P NFC: Optimize join-a-group operation based on NFC information
[mech_eap.git] / src / p2p / p2p.c
index 2a13736..64dc8bd 100644 (file)
@@ -41,7 +41,9 @@ static void p2p_scan_timeout(void *eloop_ctx, void *timeout_ctx);
  * P2P_PEER_EXPIRATION_AGE - Number of seconds after which inactive peer
  * entries will be removed
  */
-#define P2P_PEER_EXPIRATION_AGE 300
+#ifndef P2P_PEER_EXPIRATION_AGE
+#define P2P_PEER_EXPIRATION_AGE 60
+#endif /* P2P_PEER_EXPIRATION_AGE */
 
 #define P2P_PEER_EXPIRATION_INTERVAL (P2P_PEER_EXPIRATION_AGE / 2)
 
@@ -56,6 +58,15 @@ static void p2p_expire_peers(struct p2p_data *p2p)
                if (dev->last_seen.sec + P2P_PEER_EXPIRATION_AGE >= now.sec)
                        continue;
 
+               if (dev == p2p->go_neg_peer) {
+                       /*
+                        * GO Negotiation is in progress with the peer, so
+                        * don't expire the peer entry until GO Negotiation
+                        * fails or times out.
+                        */
+                       continue;
+               }
+
                if (p2p->cfg->go_connected &&
                    p2p->cfg->go_connected(p2p->cfg->cb_ctx,
                                           dev->info.p2p_device_addr)) {
@@ -127,10 +138,6 @@ static const char * p2p_state_txt(int state)
                return "INVITE";
        case P2P_INVITE_LISTEN:
                return "INVITE_LISTEN";
-       case P2P_SEARCH_WHEN_READY:
-               return "SEARCH_WHEN_READY";
-       case P2P_CONTINUE_SEARCH_WHEN_READY:
-               return "CONTINUE_SEARCH_WHEN_READY";
        default:
                return "?";
        }
@@ -204,6 +211,7 @@ void p2p_go_neg_failed(struct p2p_data *p2p, struct p2p_device *peer,
        if (p2p->go_neg_peer) {
                p2p->go_neg_peer->flags &= ~P2P_DEV_PEER_WAITING_RESPONSE;
                p2p->go_neg_peer->wps_method = WPS_NOT_READY;
+               p2p->go_neg_peer->oob_pw_id = 0;
        }
        p2p->go_neg_peer = NULL;
 
@@ -884,9 +892,6 @@ static void p2p_search(struct p2p_data *p2p)
        if (res < 0) {
                p2p_dbg(p2p, "Scan request failed");
                p2p_continue_find(p2p);
-       } else if (res == 1) {
-               p2p_dbg(p2p, "Could not start p2p_scan at this point - will try again after previous scan completes");
-               p2p_set_state(p2p, P2P_CONTINUE_SEARCH_WHEN_READY);
        } else {
                p2p_dbg(p2p, "Running p2p_scan");
                p2p->p2p_scan_running = 1;
@@ -1041,11 +1046,9 @@ int p2p_find(struct p2p_data *p2p, unsigned int timeout,
                eloop_cancel_timeout(p2p_scan_timeout, p2p, NULL);
                eloop_register_timeout(P2P_SCAN_TIMEOUT, 0, p2p_scan_timeout,
                                       p2p, NULL);
-       } else if (res == 1) {
-               p2p_dbg(p2p, "Could not start p2p_scan at this point - will try again after previous scan completes");
-               res = 0;
-               p2p_set_state(p2p, P2P_SEARCH_WHEN_READY);
-               eloop_cancel_timeout(p2p_find_timeout, p2p, NULL);
+       } else if (p2p->p2p_scan_running) {
+               p2p_dbg(p2p, "Failed to start p2p_scan - another p2p_scan was already running");
+               /* wait for the previous p2p_scan to complete */
        } else {
                p2p_dbg(p2p, "Failed to start p2p_scan");
                p2p_set_state(p2p, P2P_IDLE);
@@ -1056,34 +1059,12 @@ int p2p_find(struct p2p_data *p2p, unsigned int timeout,
 }
 
 
-int p2p_other_scan_completed(struct p2p_data *p2p)
-{
-       if (p2p->state == P2P_CONTINUE_SEARCH_WHEN_READY) {
-               p2p_set_state(p2p, P2P_SEARCH);
-               p2p_search(p2p);
-               return 1;
-       }
-       if (p2p->state != P2P_SEARCH_WHEN_READY)
-               return 0;
-       p2p_dbg(p2p, "Starting pending P2P find now that previous scan was completed");
-       if (p2p_find(p2p, p2p->last_p2p_find_timeout, p2p->find_type,
-                    p2p->num_req_dev_types, p2p->req_dev_types,
-                    p2p->find_dev_id, p2p->search_delay) < 0) {
-               p2p->cfg->find_stopped(p2p->cfg->cb_ctx);
-               return 0;
-       }
-       return 1;
-}
-
-
 void p2p_stop_find_for_freq(struct p2p_data *p2p, int freq)
 {
        p2p_dbg(p2p, "Stopping find");
        eloop_cancel_timeout(p2p_find_timeout, p2p, NULL);
        p2p_clear_timeout(p2p);
-       if (p2p->state == P2P_SEARCH ||
-           p2p->state == P2P_CONTINUE_SEARCH_WHEN_READY ||
-           p2p->state == P2P_SEARCH_WHEN_READY)
+       if (p2p->state == P2P_SEARCH)
                p2p->cfg->find_stopped(p2p->cfg->cb_ctx);
        p2p_set_state(p2p, P2P_IDLE);
        p2p_free_req_dev_types(p2p);
@@ -1312,15 +1293,16 @@ int p2p_connect(struct p2p_data *p2p, const u8 *peer_addr,
                int go_intent, const u8 *own_interface_addr,
                unsigned int force_freq, int persistent_group,
                const u8 *force_ssid, size_t force_ssid_len,
-               int pd_before_go_neg, unsigned int pref_freq)
+               int pd_before_go_neg, unsigned int pref_freq, u16 oob_pw_id)
 {
        struct p2p_device *dev;
 
        p2p_dbg(p2p, "Request to start group negotiation - peer=" MACSTR
                "  GO Intent=%d  Intended Interface Address=" MACSTR
-               " wps_method=%d persistent_group=%d pd_before_go_neg=%d",
+               " wps_method=%d persistent_group=%d pd_before_go_neg=%d "
+               "oob_pw_id=%u",
                MAC2STR(peer_addr), go_intent, MAC2STR(own_interface_addr),
-               wps_method, persistent_group, pd_before_go_neg);
+               wps_method, persistent_group, pd_before_go_neg, oob_pw_id);
 
        dev = p2p_get_device(p2p, peer_addr);
        if (dev == NULL || (dev->flags & P2P_DEV_PROBE_REQ_ONLY)) {
@@ -1404,6 +1386,7 @@ int p2p_connect(struct p2p_data *p2p, const u8 *peer_addr,
        }
 
        dev->wps_method = wps_method;
+       dev->oob_pw_id = oob_pw_id;
        dev->status = P2P_SC_SUCCESS;
 
        if (p2p->p2p_scan_running) {
@@ -1423,15 +1406,15 @@ int p2p_authorize(struct p2p_data *p2p, const u8 *peer_addr,
                  int go_intent, const u8 *own_interface_addr,
                  unsigned int force_freq, int persistent_group,
                  const u8 *force_ssid, size_t force_ssid_len,
-                 unsigned int pref_freq)
+                 unsigned int pref_freq, u16 oob_pw_id)
 {
        struct p2p_device *dev;
 
        p2p_dbg(p2p, "Request to authorize group negotiation - peer=" MACSTR
                "  GO Intent=%d  Intended Interface Address=" MACSTR
-               " wps_method=%d  persistent_group=%d",
+               " wps_method=%d  persistent_group=%d oob_pw_id=%u",
                MAC2STR(peer_addr), go_intent, MAC2STR(own_interface_addr),
-               wps_method, persistent_group);
+               wps_method, persistent_group, oob_pw_id);
 
        dev = p2p_get_device(p2p, peer_addr);
        if (dev == NULL) {
@@ -1462,6 +1445,7 @@ int p2p_authorize(struct p2p_data *p2p, const u8 *peer_addr,
        os_memcpy(p2p->intended_addr, own_interface_addr, ETH_ALEN);
 
        dev->wps_method = wps_method;
+       dev->oob_pw_id = oob_pw_id;
        dev->status = P2P_SC_SUCCESS;
 
        return 0;
@@ -1613,6 +1597,7 @@ void p2p_go_complete(struct p2p_data *p2p, struct p2p_device *peer)
        p2p->ssid_set = 0;
        peer->go_neg_req_sent = 0;
        peer->wps_method = WPS_NOT_READY;
+       peer->oob_pw_id = 0;
 
        p2p_set_state(p2p, P2P_PROVISIONING);
        p2p->cfg->go_neg_completed(p2p->cfg->cb_ctx, &res);
@@ -2479,6 +2464,7 @@ int p2p_unauthorize(struct p2p_data *p2p, const u8 *addr)
                p2p->go_neg_peer = NULL;
 
        dev->wps_method = WPS_NOT_READY;
+       dev->oob_pw_id = 0;
        dev->flags &= ~P2P_DEV_WAIT_GO_NEG_RESPONSE;
        dev->flags &= ~P2P_DEV_WAIT_GO_NEG_CONFIRM;
 
@@ -3142,7 +3128,12 @@ static void p2p_timeout_connect(struct p2p_data *p2p)
                p2p_connect_send(p2p, p2p->go_neg_peer);
                return;
        }
-
+       if (p2p->go_neg_peer && p2p->go_neg_peer->oob_go_neg_freq > 0) {
+               p2p_dbg(p2p, "Skip connect-listen since GO Neg channel known (OOB)");
+               p2p_set_state(p2p, P2P_CONNECT_LISTEN);
+               p2p_set_timeout(p2p, 0, 30000);
+               return;
+       }
        p2p_set_state(p2p, P2P_CONNECT_LISTEN);
        p2p_listen_in_find(p2p, 0);
 }
@@ -3171,13 +3162,13 @@ static void p2p_timeout_connect_listen(struct p2p_data *p2p)
 
 static void p2p_timeout_wait_peer_connect(struct p2p_data *p2p)
 {
-       /*
-        * TODO: could remain constantly in Listen state for some time if there
-        * are no other concurrent uses for the radio. For now, go to listen
-        * state once per second to give other uses a chance to use the radio.
-        */
        p2p_set_state(p2p, P2P_WAIT_PEER_IDLE);
-       p2p_set_timeout(p2p, 0, 500000);
+
+       if (p2p->cfg->is_concurrent_session_active &&
+           p2p->cfg->is_concurrent_session_active(p2p->cfg->cb_ctx))
+               p2p_set_timeout(p2p, 0, 500000);
+       else
+               p2p_set_timeout(p2p, 0, 200000);
 }
 
 
@@ -3369,10 +3360,6 @@ static void p2p_state_timeout(void *eloop_ctx, void *timeout_ctx)
        case P2P_INVITE_LISTEN:
                p2p_timeout_invite_listen(p2p);
                break;
-       case P2P_SEARCH_WHEN_READY:
-               break;
-       case P2P_CONTINUE_SEARCH_WHEN_READY:
-               break;
        }
 }
 
@@ -3405,6 +3392,8 @@ const char * p2p_wps_method_text(enum p2p_wps_method method)
                return "Keypad";
        case WPS_PBC:
                return "PBC";
+       case WPS_NFC:
+               return "NFC";
        }
 
        return "??";
@@ -4183,8 +4172,7 @@ int p2p_in_progress(struct p2p_data *p2p)
 {
        if (p2p == NULL)
                return 0;
-       if (p2p->state == P2P_SEARCH || p2p->state == P2P_SEARCH_WHEN_READY ||
-           p2p->state == P2P_CONTINUE_SEARCH_WHEN_READY)
+       if (p2p->state == P2P_SEARCH)
                return 2;
        return p2p->state != P2P_IDLE && p2p->state != P2P_PROVISIONING;
 }
@@ -4200,13 +4188,6 @@ void p2p_set_config_timeout(struct p2p_data *p2p, u8 go_timeout,
 }
 
 
-void p2p_increase_search_delay(struct p2p_data *p2p, unsigned int delay)
-{
-       if (p2p && p2p->search_delay < delay)
-               p2p->search_delay = delay;
-}
-
-
 #ifdef CONFIG_WIFI_DISPLAY
 
 static void p2p_update_wfd_ie_groups(struct p2p_data *p2p)
@@ -4394,3 +4375,199 @@ void p2p_err(struct p2p_data *p2p, const char *fmt, ...)
        va_end(ap);
        p2p->cfg->debug_print(p2p->cfg->cb_ctx, MSG_ERROR, buf);
 }
+
+
+#ifdef CONFIG_WPS_NFC
+
+static struct wpabuf * p2p_build_nfc_handover(struct p2p_data *p2p,
+                                             int client_freq)
+{
+       struct wpabuf *buf;
+       u8 op_class, channel;
+       enum p2p_role_indication role = P2P_DEVICE_NOT_IN_GROUP;
+
+       buf = wpabuf_alloc(1000);
+       if (buf == NULL)
+               return NULL;
+
+       op_class = p2p->cfg->reg_class;
+       channel = p2p->cfg->channel;
+
+       p2p_buf_add_capability(buf, p2p->dev_capab &
+                              ~P2P_DEV_CAPAB_CLIENT_DISCOVERABILITY, 0);
+       p2p_buf_add_device_info(buf, p2p, NULL);
+
+       if (p2p->num_groups > 0) {
+               role = P2P_GO_IN_A_GROUP;
+               p2p_freq_to_channel(p2p_group_get_freq(p2p->groups[0]),
+                                   &op_class, &channel);
+       } else if (client_freq > 0) {
+               role = P2P_CLIENT_IN_A_GROUP;
+               p2p_freq_to_channel(client_freq, &op_class, &channel);
+       }
+
+       p2p_buf_add_oob_go_neg_channel(buf, p2p->cfg->country, op_class,
+                                      channel, role);
+
+       if (p2p->num_groups > 0) {
+               /* Limit number of clients to avoid very long message */
+               p2p_buf_add_group_info(p2p->groups[0], buf, 5);
+               p2p_group_buf_add_id(p2p->groups[0], buf);
+       }
+
+       return buf;
+}
+
+
+struct wpabuf * p2p_build_nfc_handover_req(struct p2p_data *p2p,
+                                          int client_freq)
+{
+       return p2p_build_nfc_handover(p2p, client_freq);
+}
+
+
+struct wpabuf * p2p_build_nfc_handover_sel(struct p2p_data *p2p,
+                                          int client_freq)
+{
+       return p2p_build_nfc_handover(p2p, client_freq);
+}
+
+
+int p2p_process_nfc_connection_handover(struct p2p_data *p2p,
+                                       struct p2p_nfc_params *params)
+{
+       struct p2p_message msg;
+       struct p2p_device *dev;
+       const u8 *p2p_dev_addr;
+       int freq;
+       enum p2p_role_indication role;
+
+       params->next_step = NO_ACTION;
+
+       if (p2p_parse_ies_separate(params->wsc_attr, params->wsc_len,
+                                  params->p2p_attr, params->p2p_len, &msg)) {
+               p2p_dbg(p2p, "Failed to parse WSC/P2P attributes from NFC");
+               p2p_parse_free(&msg);
+               return -1;
+       }
+
+       if (msg.p2p_device_addr)
+               p2p_dev_addr = msg.p2p_device_addr;
+       else if (msg.device_id)
+               p2p_dev_addr = msg.device_id;
+       else {
+               p2p_dbg(p2p, "Ignore scan data without P2P Device Info or P2P Device Id");
+               p2p_parse_free(&msg);
+               return -1;
+       }
+
+       if (msg.oob_dev_password) {
+               os_memcpy(params->oob_dev_pw, msg.oob_dev_password,
+                         msg.oob_dev_password_len);
+               params->oob_dev_pw_len = msg.oob_dev_password_len;
+       }
+
+       dev = p2p_create_device(p2p, p2p_dev_addr);
+       if (dev == NULL) {
+               p2p_parse_free(&msg);
+               return -1;
+       }
+
+       params->peer = &dev->info;
+
+       os_get_reltime(&dev->last_seen);
+       dev->flags &= ~(P2P_DEV_PROBE_REQ_ONLY | P2P_DEV_GROUP_CLIENT_ONLY);
+       p2p_copy_wps_info(p2p, dev, 0, &msg);
+
+       if (!msg.oob_go_neg_channel) {
+               p2p_dbg(p2p, "OOB GO Negotiation Channel attribute not included");
+               return -1;
+       }
+
+       if (msg.oob_go_neg_channel[3] == 0 &&
+           msg.oob_go_neg_channel[4] == 0)
+               freq = 0;
+       else
+               freq = p2p_channel_to_freq(msg.oob_go_neg_channel[3],
+                                          msg.oob_go_neg_channel[4]);
+       if (freq < 0) {
+               p2p_dbg(p2p, "Unknown peer OOB GO Neg channel");
+               return -1;
+       }
+       role = msg.oob_go_neg_channel[5];
+
+       if (role == P2P_GO_IN_A_GROUP) {
+               p2p_dbg(p2p, "Peer OOB GO operating channel: %u MHz", freq);
+               params->go_freq = freq;
+       } else
+               p2p_dbg(p2p, "Peer OOB GO Neg channel: %u MHz", freq);
+       dev->oob_go_neg_freq = freq;
+
+       if (!params->sel && role != P2P_GO_IN_A_GROUP) {
+               freq = p2p_channel_to_freq(p2p->cfg->reg_class,
+                                          p2p->cfg->channel);
+               if (freq < 0) {
+                       p2p_dbg(p2p, "Own listen channel not known");
+                       return -1;
+               }
+               p2p_dbg(p2p, "Use own Listen channel as OOB GO Neg channel: %u MHz", freq);
+               dev->oob_go_neg_freq = freq;
+       }
+
+       if (msg.group_id) {
+               os_memcpy(params->go_dev_addr, msg.group_id, ETH_ALEN);
+               params->go_ssid_len = msg.group_id_len - ETH_ALEN;
+               os_memcpy(params->go_ssid, msg.group_id + ETH_ALEN,
+                         params->go_ssid_len);
+       }
+
+       p2p_parse_free(&msg);
+
+       if (dev->flags & P2P_DEV_USER_REJECTED) {
+               p2p_dbg(p2p, "Do not report rejected device");
+               return 0;
+       }
+
+       if (!(dev->flags & P2P_DEV_REPORTED)) {
+               p2p->cfg->dev_found(p2p->cfg->cb_ctx, p2p_dev_addr, &dev->info,
+                                   !(dev->flags & P2P_DEV_REPORTED_ONCE));
+               dev->flags |= P2P_DEV_REPORTED | P2P_DEV_REPORTED_ONCE;
+       }
+
+       if (role == P2P_GO_IN_A_GROUP && p2p->num_groups > 0)
+               params->next_step = BOTH_GO;
+       else if (role == P2P_GO_IN_A_GROUP)
+               params->next_step = JOIN_GROUP;
+       else if (role == P2P_CLIENT_IN_A_GROUP) {
+               dev->flags |= P2P_DEV_GROUP_CLIENT_ONLY;
+               params->next_step = PEER_CLIENT;
+       } else if (p2p->num_groups > 0)
+               params->next_step = AUTH_JOIN;
+       else if (params->sel)
+               params->next_step = INIT_GO_NEG;
+       else
+               params->next_step = RESP_GO_NEG;
+
+       return 0;
+}
+
+
+void p2p_set_authorized_oob_dev_pw_id(struct p2p_data *p2p, u16 dev_pw_id,
+                                     int go_intent,
+                                     const u8 *own_interface_addr)
+{
+
+       p2p->authorized_oob_dev_pw_id = dev_pw_id;
+       if (dev_pw_id == 0) {
+               p2p_dbg(p2p, "NFC OOB Password unauthorized for static handover");
+               return;
+       }
+
+       p2p_dbg(p2p, "NFC OOB Password (id=%u) authorized for static handover",
+               dev_pw_id);
+
+       p2p->go_intent = go_intent;
+       os_memcpy(p2p->intended_addr, own_interface_addr, ETH_ALEN);
+}
+
+#endif /* CONFIG_WPS_NFC */