diff --git a/src/p2p.c b/src/p2p.c index 70aa2f02..e758511f 100644 --- a/src/p2p.c +++ b/src/p2p.c @@ -81,6 +81,12 @@ struct p2p_device { struct l_queue *discovery_users; struct l_queue *peer_list; + struct p2p_peer *conn_peer; + uint16_t conn_config_method; + char *conn_pin; + uint8_t conn_addr[6]; + uint16_t conn_password_id; + bool enabled : 1; bool have_roc_cookie : 1; }; @@ -186,10 +192,14 @@ static void p2p_peer_put(void *user_data) { struct p2p_peer *peer = user_data; + /* Removes both interfaces, no need to call wsc_dbus_remove_interface */ l_dbus_unregister_object(dbus_get_bus(), p2p_peer_get_path(peer)); p2p_peer_free(peer); } +static void p2p_device_discovery_start(struct p2p_device *dev); +static void p2p_device_discovery_stop(struct p2p_device *dev); + /* TODO: convert to iovecs */ static uint8_t *p2p_build_scan_ies(struct p2p_device *dev, uint8_t *buf, size_t buf_len, size_t *out_len) @@ -256,6 +266,61 @@ static uint8_t *p2p_build_scan_ies(struct p2p_device *dev, uint8_t *buf, return buf; } +static void p2p_connection_reset(struct p2p_device *dev) +{ + struct p2p_peer *peer = dev->conn_peer; + + if (!peer) + return; + + /* + * conn_peer is currently not refcounted and we make sure it's always + * on the dev->peer_list so we can just drop our reference. Since we + * may not have been scanning for a while, don't drop the peer object + * now just because it's not been seen in scan results recently, its + * age will be checked on the next scan. + */ + dev->conn_peer = NULL; + dev->connections_left++; + + if (dev->conn_pin) { + explicit_bzero(dev->conn_pin, strlen(dev->conn_pin)); + l_free(dev->conn_pin); + dev->conn_pin = NULL; + } + + l_dbus_property_changed(dbus_get_bus(), p2p_device_get_path(dev), + IWD_P2P_INTERFACE, "AvailableConnections"); + + if (!dev->enabled || (dev->enabled && dev->start_stop_cmd_id)) { + /* + * The device has been disabled in the mean time, all peers + * have been removed except this one. Now it's safe to + * drop this peer from the scan results too. + */ + l_queue_destroy(dev->peer_list, p2p_peer_put); + dev->peer_list = NULL; + } + + if (dev->enabled && !dev->start_stop_cmd_id && + !l_queue_isempty(dev->discovery_users)) + p2p_device_discovery_start(dev); +} + +static void p2p_connect_failed(struct p2p_device *dev) +{ + struct p2p_peer *peer = dev->conn_peer; + + if (!peer) + return; + + if (peer->wsc.pending_connect) + dbus_pending_reply(&peer->wsc.pending_connect, + dbus_error_failed(peer->wsc.pending_connect)); + + p2p_connection_reset(dev); +} + static void p2p_scan_destroy(void *user_data) { struct p2p_device *dev = user_data; @@ -263,6 +328,165 @@ static void p2p_scan_destroy(void *user_data) dev->scan_id = 0; } +static void p2p_start_go_negotiation(struct p2p_device *dev) +{ + /* TODO: start GO Negotiation */ +} + +static void p2p_start_provision_discovery(struct p2p_device *dev) +{ + /* TODO: start Provision Discovery */ +} + +static bool p2p_peer_get_info(struct p2p_peer *peer, + uint16_t *wsc_config_methods, + struct p2p_capability_attr **capability) +{ + struct wsc_probe_request wsc_info; + + switch (peer->bss->source_frame) { + case SCAN_BSS_PROBE_RESP: + if (!peer->bss->p2p_probe_resp_info) + return false; + + if (wsc_config_methods) + *wsc_config_methods = peer->bss->p2p_probe_resp_info-> + device_info.wsc_config_methods; + + *capability = &peer->bss->p2p_probe_resp_info->capability; + return true; + case SCAN_BSS_PROBE_REQ: + if (!peer->bss->p2p_probe_req_info || !peer->bss->wsc) + return false; + + if (wsc_parse_probe_request(peer->bss->wsc, peer->bss->wsc_size, + &wsc_info) < 0) + return false; + + if (wsc_config_methods) + *wsc_config_methods = wsc_info.config_methods; + + *capability = &peer->bss->p2p_probe_req_info->capability; + return true; + case SCAN_BSS_BEACON: + if (!peer->bss->p2p_beacon_info || !peer->bss->wsc) + return false; + + if (wsc_parse_probe_request(peer->bss->wsc, peer->bss->wsc_size, + &wsc_info) < 0) + return false; + + if (wsc_config_methods) + *wsc_config_methods = wsc_info.config_methods; + + *capability = &peer->bss->p2p_beacon_info->capability; + break; + } + + return false; +} + +static void p2p_peer_connect(struct p2p_peer *peer, const char *pin) +{ + struct p2p_device *dev = peer->dev; + uint16_t wsc_config_methods; + struct p2p_capability_attr *capability; + struct l_dbus_message *message = peer->wsc.pending_connect; + struct l_dbus_message *reply; + + if (dev->conn_peer) { + reply = dbus_error_busy(message); + goto send_error; + } + + /* + * Step 1, check if the device indicates it supports our WSC method + * and check other flags to make sure a connection is possible. + */ + if (!p2p_peer_get_info(peer, &wsc_config_methods, &capability)) { + reply = dbus_error_failed(message); + goto send_error; + } + + dev->conn_config_method = pin ? WSC_CONFIGURATION_METHOD_KEYPAD : + WSC_CONFIGURATION_METHOD_PUSH_BUTTON; + dev->conn_password_id = pin ? + (strlen(pin) == 4 || wsc_pin_is_checksum_valid(pin) ? + WSC_DEVICE_PASSWORD_ID_DEFAULT : + WSC_DEVICE_PASSWORD_ID_USER_SPECIFIED) : + WSC_DEVICE_PASSWORD_ID_PUSH_BUTTON; + + if (!(wsc_config_methods & dev->conn_config_method)) { + reply = dbus_error_not_supported(message); + goto send_error; + } + + if (capability->device_caps & P2P_DEVICE_CAP_DEVICE_LIMIT) { + reply = dbus_error_not_supported(message); + goto send_error; + } + + if (capability->group_caps & P2P_GROUP_CAP_GROUP_LIMIT) { + reply = dbus_error_not_supported(message); + goto send_error; + } + + if (capability->group_caps & P2P_GROUP_CAP_GROUP_FORMATION) { + reply = dbus_error_busy(message); + goto send_error; + } + + p2p_device_discovery_stop(dev); + + /* Generate the interface address for our P2P-Client connection */ + wiphy_generate_random_address(dev->wiphy, dev->conn_addr); + + dev->conn_peer = peer; /* No ref counting so just set the pointer */ + dev->conn_pin = l_strdup(pin); + dev->connections_left--; + l_dbus_property_changed(dbus_get_bus(), p2p_device_get_path(dev), + IWD_P2P_INTERFACE, "AvailableConnections"); + + /* + * Step 2, if peer is already a GO then send the Provision Discovery + * before doing WSC. If it's not then do Provision Discovery + * optionally as seems to be required by some implementations, and + * start GO negotiation following that. + * TODO: Add a AlwaysUsePD config setting. + */ + if (dev->conn_peer->group) + p2p_start_provision_discovery(dev); + else + p2p_start_go_negotiation(dev); + + return; + +send_error: + dbus_pending_reply(&peer->wsc.pending_connect, reply); +} + +static void p2p_peer_disconnect(struct p2p_peer *peer) +{ + struct p2p_device *dev = peer->dev; + struct l_dbus_message *message = peer->wsc.pending_cancel; + struct l_dbus_message *reply; + + if (dev->conn_peer != peer) { + reply = dbus_error_not_connected(message); + goto send_reply; + } + + if (peer->wsc.pending_connect) + dbus_pending_reply(&peer->wsc.pending_connect, + dbus_error_aborted(peer->wsc.pending_connect)); + + p2p_connection_reset(dev); + reply = l_dbus_message_new_method_return(message); + +send_reply: + dbus_pending_reply(&peer->wsc.pending_cancel, reply); +} + #define SCAN_INTERVAL_MAX 3 #define SCAN_INTERVAL_STEP 1 #define CHANS_PER_SCAN_INITIAL 2 @@ -413,6 +637,29 @@ static void p2p_device_roc_start(struct p2p_device *dev) (int) dev->listen_channel, (int) duration); } +static const char *p2p_peer_wsc_get_path(struct wsc_dbus *wsc) +{ + return p2p_peer_get_path(l_container_of(wsc, struct p2p_peer, wsc)); +} + +static void p2p_peer_wsc_connect(struct wsc_dbus *wsc, const char *pin) +{ + p2p_peer_connect(l_container_of(wsc, struct p2p_peer, wsc), pin); +} + +static void p2p_peer_wsc_cancel(struct wsc_dbus *wsc) +{ + p2p_peer_disconnect(l_container_of(wsc, struct p2p_peer, wsc)); +} + +static void p2p_peer_wsc_remove(struct wsc_dbus *wsc) +{ + /* + * The WSC removal is triggered in p2p_peer_put so we call + * p2p_peer_free directly from there too. + */ +} + static bool p2p_device_peer_add(struct p2p_device *dev, struct p2p_peer *peer) { if (!strlen(peer->name) || !l_utf8_validate( @@ -441,6 +688,17 @@ static bool p2p_device_peer_add(struct p2p_device *dev, struct p2p_peer *peer) return false; } + peer->wsc.get_path = p2p_peer_wsc_get_path; + peer->wsc.connect = p2p_peer_wsc_connect; + peer->wsc.cancel = p2p_peer_wsc_cancel; + peer->wsc.remove = p2p_peer_wsc_remove; + + if (!wsc_dbus_add_interface(&peer->wsc)) { + l_dbus_unregister_object(dbus_get_bus(), + p2p_peer_get_path(peer)); + return false; + } + l_queue_push_tail(dev->peer_list, peer); return true; @@ -448,6 +706,7 @@ static bool p2p_device_peer_add(struct p2p_device *dev, struct p2p_peer *peer) struct p2p_peer_move_data { struct l_queue *new_list; + struct p2p_peer *conn_peer; uint64_t now; }; @@ -456,7 +715,8 @@ static bool p2p_peer_move_recent(void *data, void *user_data) struct p2p_peer *peer = data; struct p2p_peer_move_data *move_data = user_data; - if (move_data->now > peer->bss->time_stamp + 30 * L_USEC_PER_SEC) + if (move_data->now > peer->bss->time_stamp + 30 * L_USEC_PER_SEC && + peer != move_data->conn_peer) return false; /* Old, keep on the list */ /* Recently seen or currently connected, move to the new list */ @@ -543,6 +803,7 @@ static bool p2p_scan_notify(int err, struct l_queue *bss_list, * dev->peer_list and unref only the remaining peers. */ move_data.new_list = dev->peer_list; + move_data.conn_peer = dev->conn_peer; move_data.now = l_time_now(); l_queue_foreach_remove(old_peer_list, p2p_peer_move_recent, &move_data); l_queue_destroy(old_peer_list, p2p_peer_put); @@ -872,6 +1133,9 @@ static void p2p_device_start_stop(struct p2p_device *dev, * Stopping the P2P device, drop all peers as we can't start * new connections from now on. */ + if (dev->conn_peer) + p2p_connect_failed(dev); + l_queue_destroy(dev->peer_list, p2p_peer_put); dev->peer_list = NULL; } @@ -1108,6 +1372,7 @@ static void p2p_device_free(void *user_data) } p2p_device_discovery_stop(dev); + p2p_connection_reset(dev); l_dbus_unregister_object(dbus_get_bus(), p2p_device_get_path(dev)); l_queue_destroy(dev->peer_list, p2p_peer_put); l_queue_destroy(dev->discovery_users, p2p_discovery_user_free); @@ -1303,7 +1568,7 @@ static struct l_dbus_message *p2p_device_request_discovery(struct l_dbus *dbus, p2p_device_discovery_destroy); l_queue_push_tail(dev->discovery_users, user); - if (first_user && dev->enabled) + if (first_user && !dev->conn_peer && dev->enabled) p2p_device_discovery_start(dev); return l_dbus_message_new_method_return(message); @@ -1413,6 +1678,25 @@ static bool p2p_peer_get_connected(struct l_dbus *dbus, return true; } +static struct l_dbus_message *p2p_peer_dbus_disconnect(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct p2p_peer *peer = user_data; + + if (!l_dbus_message_get_arguments(message, "")) + return dbus_error_invalid_args(message); + + /* + * Save the message for both WSC.Cancel and Peer.Disconnect the + * same way. + */ + peer->wsc.pending_cancel = l_dbus_message_ref(message); + + p2p_peer_disconnect(peer); + return NULL; +} + static void p2p_peer_interface_setup(struct l_dbus_interface *interface) { l_dbus_interface_property(interface, "Name", 0, "s", @@ -1423,6 +1707,8 @@ static void p2p_peer_interface_setup(struct l_dbus_interface *interface) p2p_peer_get_subcategory, NULL); l_dbus_interface_property(interface, "Connected", 0, "b", p2p_peer_get_connected, NULL); + l_dbus_interface_method(interface, "Disconnect", 0, + p2p_peer_dbus_disconnect, "", ""); } static int p2p_init(void)