--- /dev/null
+/**
+ @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);
+}
+