initial import
[libicd-wpa] / icd.c
diff --git a/icd.c b/icd.c
new file mode 100644 (file)
index 0000000..4b07322
--- /dev/null
+++ b/icd.c
@@ -0,0 +1,531 @@
+/**
+  @file icd.c
+
+  Copyright (C) 2009 Javier S. Pedro
+
+  @author Javier S. Pedro <javispedro@javispedro.com>
+
+  This file is part of libicd-network-wpa.
+
+  This program is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by the
+  Free Software Foundation; either version 2 of the License, or (at your
+  option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along
+  with this program; if not, write to the Free Software Foundation, Inc.,
+  59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+
+#include <glib.h>
+
+#include <network_api.h>
+#include <osso-ic-dbus.h>
+
+#include "common.h"
+#include "dbus.h"
+#include "dbus-handler.h"
+#include "log.h"
+#include "supp.h"
+#include "networks.h"
+#include "icd.h"
+
+/**  time in seconds a found network is kept cached by icd2 */
+#define ICD_SEARCH_LIFETIME (5 * 60)
+/** time in seconds after which a new network scan is to be triggered */
+#define ICD_SEARCH_INTERVAL (2 * 60)
+
+/** time (in milliseconds) to wait after a wpa_supplicant disconnected event 
+ *   before calling it a day and shutting down the interface */
+#define ICD_DISCONNECTED_TIMEOUT (60 * 1000)
+
+/** icd flags to use */
+#define ICD_WPA_FLAGS (ICD_NW_ATTR_AUTOCONNECT | ICD_NW_ATTR_IAPNAME)
+
+
+/** equivalent to signal level 0 */
+#define RSSI_MIN (-85)
+/** equivalent to signal level max */
+#define RSSI_MAX (-30)
+/* guaranteed to be random, chosen by fair dice roll :) */
+
+
+static icd_nw_watch_pid_fn icd_watch_fn;
+static gpointer icd_watch_token;
+static icd_nw_close_fn icd_close_fn;
+static icd_nw_status_change_fn icd_status_change_fn;
+static icd_nw_renew_fn icd_renew_fn;
+
+static gchar * cur_network_id = NULL;
+
+/** @see ICD_DISCONNECTED_TIMEOUT */
+static guint connect_timeout = 0;
+
+static inline gint rssi_to_signal(gint rssi)
+{
+       gint signal = rssi - RSSI_MIN + ICD_NW_LEVEL_NONE;
+       signal *= ICD_NW_LEVEL_10;
+       signal /= (RSSI_MAX - RSSI_MIN);
+
+       if (signal < ICD_NW_LEVEL_NONE) return ICD_NW_LEVEL_NONE;
+       else if (signal > ICD_NW_LEVEL_10) return ICD_NW_LEVEL_10;
+       else return signal;     
+}
+
+static gboolean disconnected_timeout_cb(gpointer data)
+{
+       // If connect_timeout is 0, we have been disabled.
+       if (!connect_timeout) return FALSE; 
+       
+       // Disconnected for too long
+       DLOG_DEBUG("Disconnected for way too long");
+       icd_close(ICD_NW_ERROR, ICD_DBUS_ERROR_NETWORK_ERROR);
+       connect_timeout = 0;
+       return FALSE;
+}
+
+/** Supplicant callback while fully connected */
+static void icd_supp_cb(enum supp_status status, const char * error_str,
+       gpointer user_data)
+{
+       DLOG_DEBUG("%s: %d", __func__, status);
+       
+       switch (status) {
+       case SUPP_STATUS_ERROR:
+               DLOG_WARN_L("Unexpected supplicant error");
+               
+               // Fall through
+       case SUPP_STATUS_KILLED:
+               // Close connection
+               icd_close(ICD_NW_ERROR, ICD_DBUS_ERROR_SYSTEM_ERROR);
+               
+               break;
+       case SUPP_STATUS_DISCONNECTED:
+               if (!connect_timeout) {
+                       DLOG_DEBUG("Disconnected, starting timeout");
+                       connect_timeout = g_timeout_add(
+                               ICD_DISCONNECTED_TIMEOUT,
+                               disconnected_timeout_cb,
+                               NULL
+                               );
+               }
+               break;
+       case SUPP_STATUS_CONNECTED:
+               if (connect_timeout) {
+                       DLOG_DEBUG("Connected, removing timeout");
+                       g_source_remove(connect_timeout);
+                       connect_timeout = 0;
+               }
+               break;
+       }
+       
+       // TODO Disable PSM while roaming (dis/asociating)?
+}
+
+struct pre_down_data {
+       icd_nw_link_pre_down_cb_fn link_pre_down_cb;
+       gpointer link_pre_down_cb_token;
+};
+
+/** Supplicant callback in link_pre_down mode */
+static void icd_down_supp_cb(enum supp_status status, const char * error_str,
+       gpointer user_data)
+{
+       struct pre_down_data *data = (struct pre_down_data *) user_data;
+       DLOG_DEBUG("%s: %d", __func__, status);
+       
+       if (status == SUPP_STATUS_KILLED) {
+               // Everything was fine
+               supp_set_callback(NULL, NULL);
+               data->link_pre_down_cb(ICD_NW_SUCCESS,
+                       data->link_pre_down_cb_token);
+       }
+}
+
+static void
+icd_pre_down (const gchar *network_type,
+                           const guint network_attrs,
+                           const gchar *network_id,
+                           const gchar *interface_name,
+                           icd_nw_link_pre_down_cb_fn link_pre_down_cb,
+                           const gpointer link_pre_down_cb_token,
+                           gpointer *private)
+{
+       DLOG_DEBUG(__func__);
+       
+       
+       struct pre_down_data *data = g_new(struct pre_down_data, 1);
+       data->link_pre_down_cb = link_pre_down_cb;
+       data->link_pre_down_cb_token = link_pre_down_cb_token;
+       
+       if (supp_is_active()) {
+               supp_set_callback(icd_down_supp_cb, data);
+       
+               // Kill the supplicant, 
+               supp_disable();
+               //  but wait for the "child exit" event.
+       } else {
+               // Supplicant is already dead, no need to wait.
+               supp_set_callback(NULL, NULL);
+               supp_disable(); // Clears status info
+       
+               link_pre_down_cb(ICD_NW_SUCCESS, link_pre_down_cb_token);
+       }
+}
+
+struct post_up_data {
+       icd_nw_link_post_up_cb_fn link_post_up_cb;
+       gpointer link_post_up_cb_token;
+};
+
+/** Supplicant callback in link_post_up status */
+static void icd_up_supp_cb(enum supp_status status, const char * error_str,
+       gpointer user_data)
+{
+       struct post_up_data *data = (struct post_up_data *) user_data;
+       DLOG_DEBUG("%s: %d", __func__, status);
+       
+       switch (status) {
+               case SUPP_STATUS_CONNECTED:
+                       data->link_post_up_cb(ICD_NW_SUCCESS_NEXT_LAYER,
+                       NULL,
+                       data->link_post_up_cb_token, NULL);
+                       
+                       supp_set_callback(icd_supp_cb, NULL);
+                       g_free(data);
+                       break;
+               case SUPP_STATUS_DISCONNECTED:
+                       error_str = ICD_DBUS_ERROR_WLAN_AUTH_FAILED;
+                       
+                       // Fall through
+               case SUPP_STATUS_ERROR:
+               
+                       // An error happened
+                       //  kill the supplicant before everything crashes.
+                       supp_set_callback(NULL, NULL);
+                       supp_disable();
+                       
+                       // Fall through         
+               case SUPP_STATUS_KILLED:        
+                       data->link_post_up_cb(ICD_NW_ERROR,
+                       error_str,
+                       data->link_post_up_cb_token, NULL);
+                       
+                       g_free(data);
+                       break;
+               
+       }
+}
+
+static void icd_post_up(const gchar *network_type,
+                          const guint network_attrs,
+                          const gchar *network_id,
+                          const gchar *interface_name,
+                          icd_nw_link_post_up_cb_fn link_post_up_cb,
+                          const gpointer link_post_up_cb_token,
+                          gpointer *private)
+{
+       DLOG_DEBUG(__func__);
+       
+       struct post_up_data *data = g_new(struct post_up_data, 1);
+       data->link_post_up_cb = link_post_up_cb;
+       data->link_post_up_cb_token = link_post_up_cb_token;
+       
+       supp_set_callback(icd_up_supp_cb, data);
+       
+       if (supp_enable() != 0)
+       {
+               icd_up_supp_cb(-1, ICD_DBUS_ERROR_SYSTEM_ERROR, data);
+       }
+       
+       supp_set_interface(interface_name);
+       supp_set_network_id(network_id);
+}
+
+static void icd_link_down(const gchar *network_type,
+                       const guint network_attrs,
+                       const gchar *network_id,
+                       const gchar *interface_name,
+                       icd_nw_link_down_cb_fn link_down_cb,
+                       const gpointer link_down_cb_token,
+                       gpointer *private)
+{
+       DLOG_DEBUG(__func__);
+       
+       networks_disconnect(network_id);
+       
+       g_free(cur_network_id);
+       cur_network_id = NULL;
+       
+       // TODO: Maybe wait for wlancond->disconnect callback?
+       
+       link_down_cb(ICD_NW_SUCCESS, link_down_cb_token);
+}
+
+struct link_up_data {
+       icd_nw_link_up_cb_fn link_up_cb;
+       gpointer link_up_cb_token;
+};
+
+static void icd_link_up_done(int status, const char *error, gpointer user_data)
+{
+       DLOG_DEBUG("%s: %d", __func__, status);
+       
+       struct link_up_data *data = (struct link_up_data *) user_data;
+       
+       if (status) {
+               g_free(cur_network_id);
+               cur_network_id = NULL;
+               data->link_up_cb(ICD_NW_ERROR,
+                       error,
+                       WPA_IFACE, data->link_up_cb_token,
+                       NULL);
+                       
+       } else {
+               data->link_up_cb(ICD_NW_SUCCESS_NEXT_LAYER,
+                       NULL,
+                       WPA_IFACE, data->link_up_cb_token,
+                       NULL);
+       }
+       
+       g_free(data);
+}
+
+static void icd_link_up(const gchar *network_type,
+                     const guint network_attrs,
+                     const gchar *network_id,
+                     icd_nw_link_up_cb_fn link_up_cb,
+                     const gpointer link_up_cb_token,
+                     gpointer *private)
+{
+       DLOG_DEBUG("%s: %s", __func__, network_id);
+       
+       struct link_up_data *data = g_new(struct link_up_data, 1);
+       data->link_up_cb = link_up_cb;
+       data->link_up_cb_token = link_up_cb_token;
+       
+       if (cur_network_id) {
+               DLOG_WARN_L("Double link up");
+               
+               data->link_up_cb(ICD_NW_TOO_MANY_CONNECTIONS,
+                       NULL,
+                       WPA_IFACE, data->link_up_cb_token,
+                       NULL);
+               
+               return;
+       }
+       cur_network_id = g_strdup(network_id);
+       
+       networks_connect(network_id, icd_link_up_done, data);
+}
+
+struct stats_data {
+       icd_nw_link_stats_cb_fn cb;
+       gpointer token;
+};
+
+static void icd_link_stats_done
+       (int status, const char * strdata, int rssi, gpointer user_data)
+{
+       DLOG_DEBUG("%s: %d", __func__, status);
+       
+       struct stats_data * data = (struct stats_data *) user_data;
+       
+       gint signal = rssi_to_signal(rssi);
+       
+       DLOG_DEBUG("status: rssi=%d, signal=%d", rssi, signal);
+       
+       data->cb(data->token,
+               WPA_NETWORK_TYPE,
+               ICD_WPA_FLAGS,
+               cur_network_id,
+               /*time_active*/ 0,
+               /*signal*/      signal,
+               /*station_id*/  NULL,
+               /*dB*/          rssi,
+               /*tx*/0,
+               /*rx*/0);
+               
+       // IPv[46] module will fill time_active, rx & tx values.
+       // TODO station_id
+       
+       g_free(data);
+}
+
+static void icd_link_stats(const gchar *network_type,
+                                     const guint network_attrs,
+                                     const gchar *network_id,
+                                     gpointer *private,
+                                     icd_nw_link_stats_cb_fn cb,
+                                     const gpointer link_stats_cb_token)             
+{
+       DLOG_DEBUG("%s", __func__);
+       
+       struct stats_data *data = g_new(struct stats_data, 1);
+       data->cb = cb;
+       data->token = link_stats_cb_token;
+       
+       networks_status(icd_link_stats_done, data);
+}
+
+struct search_data {
+       icd_nw_search_cb_fn search_cb;
+       gpointer search_cb_token;
+};
+
+static void icd_send_search_result(int status, const char * id,
+       const char * ssid, const char * ap, int rssi, gpointer user_data)
+{
+       DLOG_DEBUG("%s: %d", __func__, status);
+       
+       struct search_data * data = (struct search_data *) user_data;
+       gint signal;
+       
+       switch (status) {
+       case SEARCH_CONTINUE:
+               signal = rssi_to_signal(rssi);
+               
+               data->search_cb(ICD_NW_SEARCH_CONTINUE,
+                               (gchar *) ssid,
+                               WPA_NETWORK_TYPE,
+                               ICD_WPA_FLAGS,
+                               (gchar *) id,
+                               signal,
+                               /*ap*/"ap",
+                               signal,
+                               data->search_cb_token);
+               break;
+       case SEARCH_FINISHED:
+               data->search_cb(ICD_NW_SEARCH_COMPLETE,
+                               NULL,
+                               WPA_NETWORK_TYPE,
+                               0,
+                               NULL,
+                               ICD_NW_LEVEL_NONE,
+                               NULL,
+                               0,
+                               data->search_cb_token);
+               // Fall through
+       case SEARCH_STOPPED:
+               g_free(user_data);
+               break;
+       }
+}
+
+static void icd_start_search(const gchar *network_type,
+                          guint search_scope,
+                          icd_nw_search_cb_fn search_cb,
+                          const gpointer search_cb_token,
+                          gpointer *private)
+{
+       DLOG_DEBUG(__func__);
+       
+       struct search_data *data = g_new(struct search_data, 1);
+       data->search_cb = search_cb;
+       data->search_cb_token = search_cb_token;
+       
+       networks_search_start(icd_send_search_result, data);
+}
+
+static void icd_stop_search(gpointer *private)
+{
+       DLOG_DEBUG(__func__);
+       
+       // Never seen this called.
+       
+       networks_search_stop();
+}
+
+static void icd_child_exit(const pid_t pid,
+                        const gint exit_value,
+                        gpointer *private)
+{
+       DLOG_DEBUG(__func__);
+       
+       /*// Supplicant crashed/exited!
+       if (killed_supp_cb) {
+               // We have killed it, notify auth layer is now down.
+
+               g_free(killed_supp_cb);
+               killed_supp_cb = 0;
+       } else {
+               DLOG_WARN("Supplicant exited, but we didn't expect it");
+               icd_close(ICD_NW_ERROR, ICD_DBUS_ERROR_SYSTEM_ERROR);
+       }*/
+       supp_handle_killed();
+}
+
+static void icd_network_destruct(gpointer *private)
+{
+       DLOG_DEBUG(__func__);
+       
+       // Fortunately, icd kills the supplicant for us.
+       
+       close_dbus_connection();
+       networks_free();
+}
+
+gboolean icd_nw_init (
+       struct icd_nw_api *network_api,
+       icd_nw_watch_pid_fn watch_fn,
+       gpointer watch_fn_token,
+       icd_nw_close_fn close_fn,
+       icd_nw_status_change_fn status_change_fn,
+       icd_nw_renew_fn renew_fn )
+{
+       network_api->version = ICD_NW_MODULE_VERSION;
+       network_api->private = NULL;
+       
+       network_api->link_pre_down = icd_pre_down;
+       network_api->link_post_up = icd_post_up;
+       
+       network_api->link_down = icd_link_down;
+       network_api->link_up = icd_link_up;
+       network_api->link_stats = icd_link_stats;
+       
+       network_api->search_lifetime = ICD_SEARCH_LIFETIME;
+       network_api->search_interval = ICD_SEARCH_INTERVAL;
+       
+       network_api->start_search = icd_start_search;
+       network_api->stop_search = icd_stop_search;
+       
+       network_api->child_exit = icd_child_exit;
+       
+       network_api->network_destruct = icd_network_destruct;
+       
+       icd_watch_fn = watch_fn;
+       icd_watch_token = watch_fn_token;
+       icd_close_fn = close_fn;
+       icd_status_change_fn = status_change_fn;
+       icd_renew_fn = renew_fn;
+       
+       if (networks_initialize() != 0) {
+               DLOG_ERR_L("Network list failed!");
+               return FALSE;
+       }
+       
+       if (setup_dbus_connection(NULL, init_dbus_handlers) != 0) {
+               DLOG_ERR_L("D-BUS connection setup failed!");
+               return FALSE;
+       }
+       
+       return TRUE;
+}
+
+void icd_watch_pid(const pid_t pid)
+{
+       icd_watch_fn(pid, icd_watch_token);
+}
+
+void icd_close(enum icd_nw_status status,
+               const gchar *err_str)
+{
+       icd_close_fn(status, err_str, WPA_NETWORK_TYPE, ICD_WPA_FLAGS, cur_network_id);
+}
+