Imported Upstream version 1.26
[platform/upstream/connman.git] / plugins / wifi.c
index ef4dd95..5f2ebf1 100644 (file)
@@ -54,6 +54,8 @@
 #include <connman/storage.h>
 #include <include/setting.h>
 #include <connman/provision.h>
+#include <connman/utsname.h>
+#include <connman/machine.h>
 
 #include <gsupplicant/gsupplicant.h>
 
@@ -65,6 +67,9 @@
 #define AUTOSCAN_DEFAULT "exponential:3:300"
 
 #define P2P_FIND_TIMEOUT 30
+#define P2P_CONNECTION_TIMEOUT 100
+#define P2P_LISTEN_PERIOD 500
+#define P2P_LISTEN_INTERVAL 2000
 
 static struct connman_technology *wifi_technology = NULL;
 static struct connman_technology *p2p_technology = NULL;
@@ -117,10 +122,20 @@ struct wifi_data {
 
        GSupplicantScanParams *scan_params;
        unsigned int p2p_find_timeout;
+       unsigned int p2p_connection_timeout;
+       struct connman_peer *pending_peer;
+       GSupplicantPeer *peer;
+       bool p2p_connecting;
+       bool p2p_device;
+       int servicing;
 };
 
 static GList *iface_list = NULL;
 
+static GList *pending_wifi_device = NULL;
+static GList *p2p_iface_list = NULL;
+bool wfd_service_registered = false;
+
 static void start_autoscan(struct connman_device *device);
 
 static int p2p_tech_probe(struct connman_technology *technology)
@@ -142,6 +157,527 @@ static struct connman_technology_driver p2p_tech_driver = {
        .remove         = p2p_tech_remove,
 };
 
+static bool is_p2p_connecting(void)
+{
+       GList *list;
+
+       for (list = iface_list; list; list = list->next) {
+               struct wifi_data *wifi = list->data;
+
+               if (wifi->p2p_connecting)
+                       return true;
+       }
+
+       return false;
+}
+
+static void add_pending_wifi_device(struct wifi_data *wifi)
+{
+       if (g_list_find(pending_wifi_device, wifi))
+               return;
+
+       pending_wifi_device = g_list_append(pending_wifi_device, wifi);
+}
+
+static struct wifi_data *get_pending_wifi_data(const char *ifname)
+{
+       GList *list;
+
+       for (list = pending_wifi_device; list; list = list->next) {
+               struct wifi_data *wifi;
+               const char *dev_name;
+
+               wifi = list->data;
+               if (!wifi || !wifi->device)
+                       continue;
+
+               dev_name = connman_device_get_string(wifi->device, "Interface");
+               if (!g_strcmp0(ifname, dev_name)) {
+                       pending_wifi_device = g_list_delete_link(
+                                               pending_wifi_device, list);
+                       return wifi;
+               }
+       }
+
+       return NULL;
+}
+
+static void remove_pending_wifi_device(struct wifi_data *wifi)
+{
+       GList *link;
+
+       link = g_list_find(pending_wifi_device, wifi);
+
+       if (!link)
+               return;
+
+       pending_wifi_device = g_list_delete_link(pending_wifi_device, link);
+}
+
+static void peer_cancel_timeout(struct wifi_data *wifi)
+{
+       if (wifi->p2p_connection_timeout > 0)
+               g_source_remove(wifi->p2p_connection_timeout);
+
+       wifi->p2p_connection_timeout = 0;
+       wifi->p2p_connecting = false;
+
+       if (wifi->pending_peer) {
+               connman_peer_unref(wifi->pending_peer);
+               wifi->pending_peer = NULL;
+       }
+
+       wifi->peer = NULL;
+}
+
+static gboolean peer_connect_timeout(gpointer data)
+{
+       struct wifi_data *wifi = data;
+
+       DBG("");
+
+       if (wifi->p2p_connecting) {
+               enum connman_peer_state state = CONNMAN_PEER_STATE_FAILURE;
+
+               if (g_supplicant_peer_has_requested_connection(wifi->peer))
+                       state = CONNMAN_PEER_STATE_IDLE;
+
+               connman_peer_set_state(wifi->pending_peer, state);
+       }
+
+       peer_cancel_timeout(wifi);
+
+       return FALSE;
+}
+
+static void peer_connect_callback(int result, GSupplicantInterface *interface,
+                                                       void *user_data)
+{
+       struct wifi_data *wifi = user_data;
+       struct connman_peer *peer = wifi->pending_peer;
+
+       DBG("peer %p - %d", peer, result);
+
+       if (!peer)
+               return;
+
+       if (result < 0) {
+               peer_connect_timeout(wifi);
+               return;
+       }
+
+       connman_peer_set_state(peer, CONNMAN_PEER_STATE_ASSOCIATION);
+
+       wifi->p2p_connection_timeout = g_timeout_add_seconds(
+                                               P2P_CONNECTION_TIMEOUT,
+                                               peer_connect_timeout, wifi);
+}
+
+static int peer_connect(struct connman_peer *peer,
+                       enum connman_peer_wps_method wps_method,
+                       const char *wps_pin)
+{
+       struct connman_device *device = connman_peer_get_device(peer);
+       GSupplicantPeerParams *peer_params;
+       GSupplicantPeer *gs_peer;
+       struct wifi_data *wifi;
+       bool pbc, pin;
+       int ret;
+
+       DBG("peer %p", peer);
+
+       if (!device)
+               return -ENODEV;
+
+       wifi = connman_device_get_data(device);
+       if (!wifi)
+               return -ENODEV;
+
+       if (wifi->p2p_connecting)
+               return -EBUSY;
+
+       wifi->peer = NULL;
+
+       gs_peer = g_supplicant_interface_peer_lookup(wifi->interface,
+                                       connman_peer_get_identifier(peer));
+       if (!gs_peer)
+               return -EINVAL;
+
+       pbc = g_supplicant_peer_is_wps_pbc(gs_peer);
+       pin = g_supplicant_peer_is_wps_pin(gs_peer);
+
+       switch (wps_method) {
+       case CONNMAN_PEER_WPS_UNKNOWN:
+               if ((pbc && pin) || pin)
+                       return -ENOKEY;
+               break;
+       case CONNMAN_PEER_WPS_PBC:
+               if (!pbc)
+                       return -EINVAL;
+               wps_pin = NULL;
+               break;
+       case CONNMAN_PEER_WPS_PIN:
+               if (!pin || !wps_pin)
+                       return -EINVAL;
+               break;
+       }
+
+       peer_params = g_try_malloc0(sizeof(GSupplicantPeerParams));
+       if (!peer_params)
+               return -ENOMEM;
+
+       peer_params->path = g_strdup(g_supplicant_peer_get_path(gs_peer));
+       if (wps_pin)
+               peer_params->wps_pin = g_strdup(wps_pin);
+
+       peer_params->master = connman_peer_service_is_master();
+
+       ret = g_supplicant_interface_p2p_connect(wifi->interface, peer_params,
+                                               peer_connect_callback, wifi);
+       if (ret == -EINPROGRESS) {
+               wifi->pending_peer = connman_peer_ref(peer);
+               wifi->peer = gs_peer;
+               wifi->p2p_connecting = true;
+       } else if (ret < 0)
+               g_free(peer_params);
+
+       return ret;
+}
+
+static int peer_disconnect(struct connman_peer *peer)
+{
+       struct connman_device *device = connman_peer_get_device(peer);
+       GSupplicantPeerParams peer_params = {};
+       GSupplicantPeer *gs_peer;
+       struct wifi_data *wifi;
+       int ret;
+
+       DBG("peer %p", peer);
+
+       if (!device)
+               return -ENODEV;
+
+       wifi = connman_device_get_data(device);
+       if (!wifi)
+               return -ENODEV;
+
+       gs_peer = g_supplicant_interface_peer_lookup(wifi->interface,
+                                       connman_peer_get_identifier(peer));
+       if (!gs_peer)
+               return -EINVAL;
+
+       peer_params.path = g_strdup(g_supplicant_peer_get_path(gs_peer));
+
+       ret = g_supplicant_interface_p2p_disconnect(wifi->interface,
+                                                       &peer_params);
+       g_free(peer_params.path);
+
+       if (ret == -EINPROGRESS)
+               peer_cancel_timeout(wifi);
+
+       return ret;
+}
+
+struct peer_service_registration {
+       peer_service_registration_cb_t callback;
+       void *user_data;
+};
+
+static bool is_service_wfd(const unsigned char *specs, int length)
+{
+       if (length < 9 || specs[0] != 0 || specs[1] != 0 || specs[2] != 6)
+               return false;
+
+       return true;
+}
+
+static void apply_p2p_listen_on_iface(gpointer data, gpointer user_data)
+{
+       struct wifi_data *wifi = data;
+
+       if (!wifi->interface ||
+                       !g_supplicant_interface_has_p2p(wifi->interface))
+               return;
+
+       if (!wifi->servicing) {
+               g_supplicant_interface_p2p_listen(wifi->interface,
+                               P2P_LISTEN_PERIOD, P2P_LISTEN_INTERVAL);
+       }
+
+       wifi->servicing++;
+}
+
+static void register_wfd_service_cb(int result,
+                               GSupplicantInterface *iface, void *user_data)
+{
+       struct peer_service_registration *reg_data = user_data;
+
+       DBG("");
+
+       if (result == 0)
+               g_list_foreach(iface_list, apply_p2p_listen_on_iface, NULL);
+
+       if (reg_data && reg_data->callback) {
+               reg_data->callback(result, reg_data->user_data);
+               g_free(reg_data);
+       }
+}
+
+static GSupplicantP2PServiceParams *fill_in_peer_service_params(
+                               const unsigned char *spec,
+                               int spec_length, const unsigned char *query,
+                               int query_length, int version)
+{
+       GSupplicantP2PServiceParams *params;
+
+       params = g_try_malloc0(sizeof(GSupplicantP2PServiceParams));
+       if (!params)
+               return NULL;
+
+       if (version > 0) {
+               params->version = version;
+               params->service = g_memdup(spec, spec_length);
+       } else if (query_length > 0 && spec_length > 0) {
+               params->query = g_memdup(query, query_length);
+               params->query_length = query_length;
+
+               params->response = g_memdup(spec, spec_length);
+               params->response_length = spec_length;
+       } else {
+               params->wfd_ies = g_memdup(spec, spec_length);
+               params->wfd_ies_length = spec_length;
+       }
+
+       return params;
+}
+
+static void free_peer_service_params(GSupplicantP2PServiceParams *params)
+{
+       if (!params)
+               return;
+
+       g_free(params->service);
+       g_free(params->query);
+       g_free(params->response);
+       g_free(params->wfd_ies);
+
+       g_free(params);
+}
+
+static int peer_register_wfd_service(const unsigned char *specification,
+                               int specification_length,
+                               peer_service_registration_cb_t callback,
+                               void *user_data)
+{
+       struct peer_service_registration *reg_data = NULL;
+       static GSupplicantP2PServiceParams *params;
+       int ret;
+
+       DBG("");
+
+       if (wfd_service_registered)
+               return -EBUSY;
+
+       params = fill_in_peer_service_params(specification,
+                                       specification_length, NULL, 0, 0);
+       if (!params)
+               return -ENOMEM;
+
+       reg_data = g_try_malloc0(sizeof(*reg_data));
+       if (!reg_data) {
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       reg_data->callback = callback;
+       reg_data->user_data = user_data;
+
+       ret = g_supplicant_set_widi_ies(params,
+                                       register_wfd_service_cb, reg_data);
+       if (ret < 0 && ret != -EINPROGRESS)
+               goto error;
+
+       wfd_service_registered = true;
+
+       return ret;
+error:
+       free_peer_service_params(params);
+       g_free(reg_data);
+
+       return ret;
+}
+
+static void register_peer_service_cb(int result,
+                               GSupplicantInterface *iface, void *user_data)
+{
+       struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
+       struct peer_service_registration *reg_data = user_data;
+
+       DBG("");
+
+       if (result == 0)
+               apply_p2p_listen_on_iface(wifi, NULL);
+
+       if (reg_data->callback)
+               reg_data->callback(result, reg_data->user_data);
+
+       g_free(reg_data);
+}
+
+static int peer_register_service(const unsigned char *specification,
+                               int specification_length,
+                               const unsigned char *query,
+                               int query_length, int version,
+                               peer_service_registration_cb_t callback,
+                               void *user_data)
+{
+       struct peer_service_registration *reg_data;
+       GSupplicantP2PServiceParams *params;
+       bool found = false;
+       int ret, ret_f;
+       GList *list;
+
+       DBG("");
+
+       if (specification && !version && !query &&
+                       is_service_wfd(specification, specification_length)) {
+               return peer_register_wfd_service(specification,
+                               specification_length, callback, user_data);
+       }
+
+       reg_data = g_try_malloc0(sizeof(*reg_data));
+       if (!reg_data)
+               return -ENOMEM;
+
+       reg_data->callback = callback;
+       reg_data->user_data = user_data;
+
+       ret_f = -EOPNOTSUPP;
+
+       for (list = iface_list; list; list = list->next) {
+               struct wifi_data *wifi = list->data;
+               GSupplicantInterface *iface = wifi->interface;
+
+               if (!g_supplicant_interface_has_p2p(iface))
+                       continue;
+
+               params = fill_in_peer_service_params(specification,
+                                               specification_length, query,
+                                               query_length, version);
+               if (!params) {
+                       ret = -ENOMEM;
+                       continue;
+               }
+
+               if (!found) {
+                       ret_f = g_supplicant_interface_p2p_add_service(iface,
+                               register_peer_service_cb, params, reg_data);
+                       if (ret_f == 0 || ret_f == -EINPROGRESS)
+                               found = true;
+                       ret = ret_f;
+               } else
+                       ret = g_supplicant_interface_p2p_add_service(iface,
+                               register_peer_service_cb, params, NULL);
+               if (ret != 0 && ret != -EINPROGRESS)
+                       free_peer_service_params(params);
+       }
+
+       if (ret_f != 0 && ret_f != -EINPROGRESS)
+               g_free(reg_data);
+
+       return ret_f;
+}
+
+static int peer_unregister_wfd_service(void)
+{
+       GSupplicantP2PServiceParams *params;
+       GList *list;
+
+       if (!wfd_service_registered)
+               return -EALREADY;
+
+       params = fill_in_peer_service_params(NULL, 0, NULL, 0, 0);
+       if (!params)
+               return -ENOMEM;
+
+       wfd_service_registered = false;
+
+       g_supplicant_set_widi_ies(params, NULL, NULL);
+
+       for (list = iface_list; list; list = list->next) {
+               struct wifi_data *wifi = list->data;
+
+               if (!g_supplicant_interface_has_p2p(wifi->interface))
+                       continue;
+
+               wifi->servicing--;
+               if (!wifi->servicing || wifi->servicing < 0) {
+                       g_supplicant_interface_p2p_listen(wifi->interface,
+                                                                       0, 0);
+                       wifi->servicing = 0;
+               }
+       }
+
+       return 0;
+}
+
+static int peer_unregister_service(const unsigned char *specification,
+                                               int specification_length,
+                                               const unsigned char *query,
+                                               int query_length, int version)
+{
+       GSupplicantP2PServiceParams *params;
+       bool wfd = false;
+       GList *list;
+       int ret;
+
+       if (specification && !version && !query &&
+                       is_service_wfd(specification, specification_length)) {
+               ret = peer_unregister_wfd_service();
+               if (ret != 0 && ret != -EINPROGRESS)
+                       return ret;
+               wfd = true;
+       }
+
+       for (list = iface_list; list; list = list->next) {
+               struct wifi_data *wifi = list->data;
+               GSupplicantInterface *iface = wifi->interface;
+
+               if (wfd)
+                       goto stop_listening;
+
+               if (!g_supplicant_interface_has_p2p(iface))
+                       continue;
+
+               params = fill_in_peer_service_params(specification,
+                                               specification_length, query,
+                                               query_length, version);
+               if (!params) {
+                       ret = -ENOMEM;
+                       continue;
+               }
+
+               ret = g_supplicant_interface_p2p_del_service(iface, params);
+               if (ret != 0 && ret != -EINPROGRESS)
+                       free_peer_service_params(params);
+stop_listening:
+               wifi->servicing--;
+               if (!wifi->servicing || wifi->servicing < 0) {
+                       g_supplicant_interface_p2p_listen(iface, 0, 0);
+                       wifi->servicing = 0;
+               }
+       }
+
+       return 0;
+}
+
+static struct connman_peer_driver peer_driver = {
+       .connect    = peer_connect,
+       .disconnect = peer_disconnect,
+       .register_service = peer_register_service,
+       .unregister_service = peer_unregister_service,
+};
+
 static void handle_tethering(struct wifi_data *wifi)
 {
        if (!wifi->tethering)
@@ -210,8 +746,10 @@ static int wifi_probe(struct connman_device *device)
 
        wifi->watch = connman_rtnl_add_newlink_watch(wifi->index,
                                                        wifi_newlink, device);
-
-       iface_list = g_list_append(iface_list, wifi);
+       if (is_p2p_connecting())
+               add_pending_wifi_device(wifi);
+       else
+               iface_list = g_list_append(iface_list, wifi);
 
        return 0;
 }
@@ -280,8 +818,10 @@ static void check_p2p_technology(void)
                        p2p_exists = true;
        }
 
-       if (!p2p_exists)
+       if (!p2p_exists) {
                connman_technology_driver_unregister(&p2p_tech_driver);
+               connman_peer_driver_unregister(&peer_driver);
+       }
 }
 
 static void wifi_remove(struct connman_device *device)
@@ -295,15 +835,23 @@ static void wifi_remove(struct connman_device *device)
 
        stop_autoscan(device);
 
-       iface_list = g_list_remove(iface_list, wifi);
+       if (wifi->p2p_device)
+               p2p_iface_list = g_list_remove(p2p_iface_list, wifi);
+       else
+               iface_list = g_list_remove(iface_list, wifi);
 
        check_p2p_technology();
 
+       remove_pending_wifi_device(wifi);
+
        if (wifi->p2p_find_timeout) {
                g_source_remove(wifi->p2p_find_timeout);
                connman_device_unref(wifi->device);
        }
 
+       if (wifi->p2p_connection_timeout)
+               g_source_remove(wifi->p2p_connection_timeout);
+
        remove_networks(device, wifi);
 
        connman_device_set_powered(device, false);
@@ -313,6 +861,8 @@ static void wifi_remove(struct connman_device *device)
 
        g_supplicant_interface_set_data(wifi->interface, NULL);
 
+       g_supplicant_interface_cancel(wifi->interface);
+
        if (wifi->scan_params)
                g_supplicant_free_scan_params(wifi->scan_params);
 
@@ -787,6 +1337,9 @@ static void start_autoscan(struct connman_device *device)
        if (!wifi)
                return;
 
+       if (wifi->p2p_device)
+               return;
+
        autoscan = wifi->autoscan;
        if (!autoscan)
                return;
@@ -871,6 +1424,9 @@ static void finalize_interface_creation(struct wifi_data *wifi)
        if (!connman_setting_get_bool("BackgroundScanning"))
                return;
 
+       if (wifi->p2p_device)
+               return;
+
        /* Setting up automatic scanning */
        if (g_supplicant_interface_autoscan(interface, AUTOSCAN_DEFAULT,
                                interface_autoscan_callback, wifi) < 0) {
@@ -915,6 +1471,9 @@ static int wifi_enable(struct connman_device *device)
        if (!wifi || index < 0)
                return -ENODEV;
 
+       if (is_p2p_connecting())
+               return -EINPROGRESS;
+
        interface = connman_inet_ifname(index);
        ret = g_supplicant_interface_create(interface, driver, NULL,
                                                interface_create_callback,
@@ -948,6 +1507,7 @@ static int wifi_disable(struct connman_device *device)
        if (wifi->p2p_find_timeout) {
                g_source_remove(wifi->p2p_find_timeout);
                wifi->p2p_find_timeout = 0;
+               connman_device_set_scanning(device, CONNMAN_SERVICE_TYPE_P2P, false);
                connman_device_unref(wifi->device);
        }
 
@@ -1153,7 +1713,7 @@ error:
 
 static int p2p_find(struct connman_device *device)
 {
-       struct wifi_data *wifi = connman_device_get_data(device);
+       struct wifi_data *wifi;
        int ret;
 
        DBG("");
@@ -1161,6 +1721,11 @@ static int p2p_find(struct connman_device *device)
        if (!p2p_technology)
                return -ENOTSUP;
 
+       wifi = connman_device_get_data(device);
+
+       if (g_supplicant_interface_is_p2p_finding(wifi->interface))
+               return -EALREADY;
+
        reset_autoscan(device);
        connman_device_ref(device);
 
@@ -1199,6 +1764,9 @@ static int wifi_scan(enum connman_service_type type,
        if (!wifi)
                return -ENODEV;
 
+       if (wifi->p2p_device)
+               return 0;
+
        if (type == CONNMAN_SERVICE_TYPE_P2P)
                return p2p_find(device);
 
@@ -1588,14 +2156,15 @@ static void interface_added(GSupplicantInterface *interface)
        struct wifi_data *wifi;
 
        wifi = g_supplicant_interface_get_data(interface);
+       if (!wifi) {
+               wifi = get_pending_wifi_data(ifname);
+               if (!wifi)
+                       return;
 
-       /*
-        * We can get here with a NULL wifi pointer when
-        * the interface added signal is sent before the
-        * interface creation callback is called.
-        */
-       if (!wifi)
-               return;
+               g_supplicant_interface_set_data(interface, wifi);
+               p2p_iface_list = g_list_append(p2p_iface_list, wifi);
+               wifi->p2p_device = true;
+       }
 
        DBG("ifname %s driver %s wifi %p tethering %d",
                        ifname, driver, wifi, wifi->tethering);
@@ -1606,9 +2175,6 @@ static void interface_added(GSupplicantInterface *interface)
        }
 
        connman_device_set_powered(wifi->device, true);
-
-       if (wifi->tethering)
-               return;
 }
 
 static bool is_idle(struct wifi_data *wifi)
@@ -1874,6 +2440,9 @@ static void interface_removed(GSupplicantInterface *interface)
 
        wifi = g_supplicant_interface_get_data(interface);
 
+       if (wifi)
+               wifi->interface = NULL;
+
        if (wifi && wifi->tethering)
                return;
 
@@ -1882,21 +2451,59 @@ static void interface_removed(GSupplicantInterface *interface)
                return;
        }
 
-       wifi->interface = NULL;
        connman_device_set_powered(wifi->device, false);
 
        check_p2p_technology();
 }
 
+static void set_device_type(const char *type, char dev_type[17])
+{
+       const char *oui = "0050F204";
+       const char *category = "0100";
+       const char *sub_category = "0000";
+
+       if (!g_strcmp0(type, "handset")) {
+               category = "0A00";
+               sub_category = "0500";
+       } else if (!g_strcmp0(type, "vm") || !g_strcmp0(type, "container"))
+               sub_category = "0100";
+       else if (!g_strcmp0(type, "server"))
+               sub_category = "0200";
+       else if (!g_strcmp0(type, "laptop"))
+               sub_category = "0500";
+       else if (!g_strcmp0(type, "desktop"))
+               sub_category = "0600";
+       else if (!g_strcmp0(type, "tablet"))
+               sub_category = "0900";
+       else if (!g_strcmp0(type, "watch"))
+               category = "FF00";
+
+       snprintf(dev_type, 17, "%s%s%s", category, oui, sub_category);
+}
+
 static void p2p_support(GSupplicantInterface *interface)
 {
+       char dev_type[17] = {};
+       const char *hostname;
+
        DBG("");
 
        if (!g_supplicant_interface_has_p2p(interface))
                return;
 
-       if (connman_technology_driver_register(&p2p_tech_driver) == 0)
+       if (connman_technology_driver_register(&p2p_tech_driver) < 0) {
                DBG("Could not register P2P technology driver");
+               return;
+       }
+
+       hostname = connman_utsname_get_hostname();
+       if (!hostname)
+               hostname = "ConnMan";
+
+       set_device_type(connman_machine_get_type(), dev_type);
+       g_supplicant_interface_set_p2p_device_config(interface,
+                                                       hostname, dev_type);
+       connman_peer_driver_register(&peer_driver);
 }
 
 static void scan_started(GSupplicantInterface *interface)
@@ -2072,38 +2679,171 @@ static void network_changed(GSupplicantNetwork *network, const char *property)
        }
 }
 
+static void apply_peer_services(GSupplicantPeer *peer,
+                               struct connman_peer *connman_peer)
+{
+       const unsigned char *data;
+       int length;
+
+       DBG("");
+
+       connman_peer_reset_services(connman_peer);
+
+       data = g_supplicant_peer_get_widi_ies(peer, &length);
+       if (data) {
+               connman_peer_add_service(connman_peer,
+                       CONNMAN_PEER_SERVICE_WIFI_DISPLAY, data, length);
+       }
+}
+
 static void peer_found(GSupplicantPeer *peer)
 {
+       GSupplicantInterface *iface = g_supplicant_peer_get_interface(peer);
+       struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
        struct connman_peer *connman_peer;
        const char *identifier, *name;
+       int ret;
 
        identifier = g_supplicant_peer_get_identifier(peer);
        name = g_supplicant_peer_get_name(peer);
 
        DBG("ident: %s", identifier);
 
-       connman_peer = connman_peer_get(identifier);
+       connman_peer = connman_peer_get(wifi->device, identifier);
        if (connman_peer)
                return;
 
        connman_peer = connman_peer_create(identifier);
        connman_peer_set_name(connman_peer, name);
+       connman_peer_set_device(connman_peer, wifi->device);
+       apply_peer_services(peer, connman_peer);
 
-       connman_peer_register(connman_peer);
+       ret = connman_peer_register(connman_peer);
+       if (ret < 0 && ret != -EALREADY)
+               connman_peer_unref(connman_peer);
 }
 
 static void peer_lost(GSupplicantPeer *peer)
 {
+       GSupplicantInterface *iface = g_supplicant_peer_get_interface(peer);
+       struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
        struct connman_peer *connman_peer;
        const char *identifier;
 
+       if (!wifi)
+               return;
+
        identifier = g_supplicant_peer_get_identifier(peer);
 
        DBG("ident: %s", identifier);
 
-       connman_peer = connman_peer_get(identifier);
-       if (connman_peer)
+       connman_peer = connman_peer_get(wifi->device, identifier);
+       if (connman_peer) {
+               if (wifi->p2p_connecting &&
+                               wifi->pending_peer == connman_peer) {
+                       peer_connect_timeout(wifi);
+               }
                connman_peer_unregister(connman_peer);
+               connman_peer_unref(connman_peer);
+       }
+}
+
+static void peer_changed(GSupplicantPeer *peer, GSupplicantPeerState state)
+{
+       GSupplicantInterface *iface = g_supplicant_peer_get_interface(peer);
+       struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
+       enum connman_peer_state p_state = CONNMAN_PEER_STATE_UNKNOWN;
+       struct connman_peer *connman_peer;
+       const char *identifier;
+
+       identifier = g_supplicant_peer_get_identifier(peer);
+
+       DBG("ident: %s", identifier);
+
+       connman_peer = connman_peer_get(wifi->device, identifier);
+       if (!connman_peer)
+               return;
+
+       switch (state) {
+       case G_SUPPLICANT_PEER_SERVICES_CHANGED:
+               apply_peer_services(peer, connman_peer);
+               connman_peer_services_changed(connman_peer);
+               return;
+       case G_SUPPLICANT_PEER_GROUP_CHANGED:
+               if (!g_supplicant_peer_is_in_a_group(peer))
+                       p_state = CONNMAN_PEER_STATE_IDLE;
+               else
+                       p_state = CONNMAN_PEER_STATE_CONFIGURATION;
+               break;
+       case G_SUPPLICANT_PEER_GROUP_STARTED:
+               break;
+       case G_SUPPLICANT_PEER_GROUP_FINISHED:
+               p_state = CONNMAN_PEER_STATE_IDLE;
+               break;
+       case G_SUPPLICANT_PEER_GROUP_JOINED:
+               if (!g_supplicant_peer_is_in_a_group(peer))
+                       break;
+               p_state = CONNMAN_PEER_STATE_READY;
+               break;
+       case G_SUPPLICANT_PEER_GROUP_DISCONNECTED:
+               p_state = CONNMAN_PEER_STATE_IDLE;
+               break;
+       case G_SUPPLICANT_PEER_GROUP_FAILED:
+               if (g_supplicant_peer_has_requested_connection(peer))
+                       p_state = CONNMAN_PEER_STATE_IDLE;
+               else
+                       p_state = CONNMAN_PEER_STATE_FAILURE;
+               break;
+       }
+
+       if (p_state == CONNMAN_PEER_STATE_CONFIGURATION ||
+                                       p_state == CONNMAN_PEER_STATE_FAILURE) {
+               if (wifi->p2p_connecting
+                               && connman_peer == wifi->pending_peer)
+                       peer_cancel_timeout(wifi);
+               else
+                       p_state = CONNMAN_PEER_STATE_UNKNOWN;
+       }
+
+       if (p_state == CONNMAN_PEER_STATE_UNKNOWN)
+               return;
+
+       if (p_state == CONNMAN_PEER_STATE_CONFIGURATION) {
+               GSupplicantInterface *g_iface;
+               struct wifi_data *g_wifi;
+
+               g_iface = g_supplicant_peer_get_group_interface(peer);
+               if (!g_iface)
+                       return;
+
+               g_wifi = g_supplicant_interface_get_data(g_iface);
+               if (!g_wifi)
+                       return;
+
+               connman_peer_set_as_master(connman_peer,
+                                       !g_supplicant_peer_is_client(peer));
+               connman_peer_set_sub_device(connman_peer, g_wifi->device);
+       }
+
+       connman_peer_set_state(connman_peer, p_state);
+}
+
+static void peer_request(GSupplicantPeer *peer)
+{
+       GSupplicantInterface *iface = g_supplicant_peer_get_interface(peer);
+       struct wifi_data *wifi = g_supplicant_interface_get_data(iface);
+       struct connman_peer *connman_peer;
+       const char *identifier;
+
+       identifier = g_supplicant_peer_get_identifier(peer);
+
+       DBG("ident: %s", identifier);
+
+       connman_peer = connman_peer_get(wifi->device, identifier);
+       if (!connman_peer)
+               return;
+
+       connman_peer_request_connection(connman_peer);
 }
 
 static void debug(const char *str)
@@ -2126,6 +2866,8 @@ static const GSupplicantCallbacks callbacks = {
        .network_changed        = network_changed,
        .peer_found             = peer_found,
        .peer_lost              = peer_lost,
+       .peer_changed           = peer_changed,
+       .peer_request           = peer_request,
        .debug                  = debug,
 };