X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=src%2Fsession.c;h=a5db7ad9f842381e1e58c75a96a516c74657b27f;hb=46d7e52dcabccff5e010a6aa7232fbbfea1abecb;hp=b57ef71fd50213dab8e76b69a1cf3625a9f92017;hpb=6b2b61359a7e84c06ea6f2dba38be02489e7e26b;p=framework%2Fconnectivity%2Fconnman.git diff --git a/src/session.c b/src/session.c index b57ef71..a5db7ad 100644 --- a/src/session.c +++ b/src/session.c @@ -2,7 +2,7 @@ * * Connection Manager * - * Copyright (C) 2007-2010 Intel Corporation. All rights reserved. + * Copyright (C) 2007-2012 Intel Corporation. All rights reserved. * Copyright (C) 2011 BWM CarIT GmbH. All rights reserved. * * This program is free software; you can redistribute it and/or modify @@ -24,6 +24,8 @@ #include #endif +#include + #include #include "connman.h" @@ -31,7 +33,7 @@ static DBusConnection *connection; static GHashTable *session_hash; static connman_bool_t sessionmode; -static struct connman_session *ecall_session; +static struct session_info *ecall_info; enum connman_session_trigger { CONNMAN_SESSION_TRIGGER_UNKNOWN = 0, @@ -46,8 +48,21 @@ enum connman_session_trigger { enum connman_session_reason { CONNMAN_SESSION_REASON_UNKNOWN = 0, CONNMAN_SESSION_REASON_CONNECT = 1, - CONNMAN_SESSION_REASON_FREE_RIDE = 2, - CONNMAN_SESSION_REASON_PERIODIC = 3, + CONNMAN_SESSION_REASON_DISCONNECT = 2, + CONNMAN_SESSION_REASON_FREE_RIDE = 3, + CONNMAN_SESSION_REASON_PERIODIC = 4, +}; + +enum connman_session_state { + CONNMAN_SESSION_STATE_DISCONNECTED = 0, + CONNMAN_SESSION_STATE_CONNECTED = 1, + CONNMAN_SESSION_STATE_ONLINE = 2, +}; + +enum connman_session_type { + CONNMAN_SESSION_TYPE_ANY = 0, + CONNMAN_SESSION_TYPE_LOCAL = 1, + CONNMAN_SESSION_TYPE_INTERNET = 2, }; enum connman_session_roaming_policy { @@ -65,12 +80,14 @@ struct service_entry { enum connman_service_state state; const char *name; struct connman_service *service; - const char *ifname; + char *ifname; const char *bearer; + GSList *pending_timeouts; }; struct session_info { - connman_bool_t online; + enum connman_session_state state; + enum connman_session_type type; connman_bool_t priority; GSList *allowed_bearers; connman_bool_t avoid_handover; @@ -82,6 +99,7 @@ struct session_info { unsigned int marker; struct service_entry *entry; + enum connman_session_reason reason; }; struct connman_session { @@ -91,7 +109,6 @@ struct connman_session { guint notify_watch; connman_bool_t append_all; - connman_bool_t info_dirty; struct session_info *info; struct session_info *info_last; @@ -131,9 +148,11 @@ static const char *reason2string(enum connman_session_reason reason) { switch (reason) { case CONNMAN_SESSION_REASON_UNKNOWN: - break; + return "unknown"; case CONNMAN_SESSION_REASON_CONNECT: return "connect"; + case CONNMAN_SESSION_REASON_DISCONNECT: + return "disconnect"; case CONNMAN_SESSION_REASON_FREE_RIDE: return "free-ride"; case CONNMAN_SESSION_REASON_PERIODIC: @@ -143,6 +162,44 @@ static const char *reason2string(enum connman_session_reason reason) return NULL; } +static const char *state2string(enum connman_session_state state) +{ + switch (state) { + case CONNMAN_SESSION_STATE_DISCONNECTED: + return "disconnected"; + case CONNMAN_SESSION_STATE_CONNECTED: + return "connected"; + case CONNMAN_SESSION_STATE_ONLINE: + return "online"; + } + + return NULL; +} + +static const char *type2string(enum connman_session_type type) +{ + switch (type) { + case CONNMAN_SESSION_TYPE_ANY: + return ""; + case CONNMAN_SESSION_TYPE_LOCAL: + return "local"; + case CONNMAN_SESSION_TYPE_INTERNET: + return "internet"; + } + + return NULL; +} + +static enum connman_session_type string2type(const char *type) +{ + if (g_strcmp0(type, "local") == 0) + return CONNMAN_SESSION_TYPE_LOCAL; + else if (g_strcmp0(type, "internet") == 0) + return CONNMAN_SESSION_TYPE_INTERNET; + + return CONNMAN_SESSION_TYPE_ANY; +} + static const char *roamingpolicy2string(enum connman_session_roaming_policy policy) { switch (policy) { @@ -192,8 +249,10 @@ static enum connman_service_type bearer2service(const char *bearer) return CONNMAN_SERVICE_TYPE_WIMAX; else if (g_strcmp0(bearer, "bluetooth") == 0) return CONNMAN_SERVICE_TYPE_BLUETOOTH; - else if (g_strcmp0(bearer, "3g") == 0) + else if (g_strcmp0(bearer, "cellular") == 0) return CONNMAN_SERVICE_TYPE_CELLULAR; + else if (g_strcmp0(bearer, "vpn") == 0) + return CONNMAN_SERVICE_TYPE_VPN; else return CONNMAN_SERVICE_TYPE_UNKNOWN; } @@ -210,11 +269,12 @@ static char *service2bearer(enum connman_service_type type) case CONNMAN_SERVICE_TYPE_BLUETOOTH: return "bluetooth"; case CONNMAN_SERVICE_TYPE_CELLULAR: - return "3g"; + return "cellular"; + case CONNMAN_SERVICE_TYPE_VPN: + return "vpn"; case CONNMAN_SERVICE_TYPE_UNKNOWN: case CONNMAN_SERVICE_TYPE_SYSTEM: case CONNMAN_SERVICE_TYPE_GPS: - case CONNMAN_SERVICE_TYPE_VPN: case CONNMAN_SERVICE_TYPE_GADGET: return ""; } @@ -312,6 +372,11 @@ static void append_ipconfig_ipv4(DBusMessageIter *iter, void *user_data) if (service == NULL) return; + if (__connman_service_is_connected_state(service, + CONNMAN_IPCONFIG_TYPE_IPV4) == FALSE) { + return; + } + ipconfig_ipv4 = __connman_service_get_ip4config(service); if (ipconfig_ipv4 == NULL) return; @@ -327,6 +392,11 @@ static void append_ipconfig_ipv6(DBusMessageIter *iter, void *user_data) if (service == NULL) return; + if (__connman_service_is_connected_state(service, + CONNMAN_IPCONFIG_TYPE_IPV6) == FALSE) { + return; + } + ipconfig_ipv4 = __connman_service_get_ip4config(service); ipconfig_ipv6 = __connman_service_get_ip6config(service); if (ipconfig_ipv6 == NULL) @@ -345,11 +415,13 @@ static void append_notify(DBusMessageIter *dict, const char *name, *ifname, *bearer; if (session->append_all == TRUE || - info->online != info_last->online) { - connman_dbus_dict_append_basic(dict, "Online", - DBUS_TYPE_BOOLEAN, - &info->online); - info_last->online = info->online; + info->state != info_last->state) { + const char *state = state2string(info->state); + + connman_dbus_dict_append_basic(dict, "State", + DBUS_TYPE_STRING, + &state); + info_last->state = info->state; } if (session->append_all == TRUE || @@ -389,6 +461,14 @@ static void append_notify(DBusMessageIter *dict, info_last->entry = info->entry; } + if (session->append_all == TRUE || info->type != info_last->type) { + const char *type = type2string(info->type); + + connman_dbus_dict_append_basic(dict, "ConnectionType", + DBUS_TYPE_STRING, + &type); + info_last->type = info->type; + } if (session->append_all == TRUE || info->priority != info_last->priority) { @@ -465,7 +545,58 @@ static void append_notify(DBusMessageIter *dict, } session->append_all = FALSE; - session->info_dirty = FALSE; +} + +static connman_bool_t is_type_matching_state(enum connman_session_state *state, + enum connman_session_type type) +{ + switch (type) { + case CONNMAN_SESSION_TYPE_ANY: + return TRUE; + case CONNMAN_SESSION_TYPE_LOCAL: + if (*state >= CONNMAN_SESSION_STATE_CONNECTED) { + *state = CONNMAN_SESSION_STATE_CONNECTED; + return TRUE; + } + + break; + case CONNMAN_SESSION_TYPE_INTERNET: + if (*state == CONNMAN_SESSION_STATE_ONLINE) + return TRUE; + break; + } + + return FALSE; +} + +static connman_bool_t compute_notifiable_changes(struct connman_session *session) +{ + struct session_info *info_last = session->info_last; + struct session_info *info = session->info; + + if (session->append_all == TRUE) + return TRUE; + + if (info->state != info_last->state) + return TRUE; + + if (info->entry != info_last->entry && + info->state >= CONNMAN_SESSION_STATE_CONNECTED) + return TRUE; + + if (info->periodic_connect != info_last->periodic_connect || + info->allowed_bearers != info_last->allowed_bearers || + info->avoid_handover != info_last->avoid_handover || + info->stay_connected != info_last->stay_connected || + info->roaming_policy != info_last->roaming_policy || + info->idle_timeout != info_last->idle_timeout || + info->priority != info_last->priority || + info->marker != info_last->marker || + info->ecall != info_last->ecall || + info->type != info_last->type) + return TRUE; + + return FALSE; } static gboolean session_notify(gpointer user_data) @@ -474,8 +605,8 @@ static gboolean session_notify(gpointer user_data) DBusMessage *msg; DBusMessageIter array, dict; - if (session->info_dirty == FALSE) - return 0; + if (compute_notifiable_changes(session) == FALSE) + return FALSE; DBG("session %p owner %s notify_path %s", session, session->owner, session->notify_path); @@ -495,8 +626,6 @@ static gboolean session_notify(gpointer user_data) g_dbus_send_message(connection, msg); - session->info_dirty = FALSE; - return FALSE; } @@ -559,7 +688,7 @@ static int service_type_weight(enum connman_service_type type) * 1. Ethernet * 2. Bluetooth * 3. WiFi/WiMAX - * 4. GSM/UTMS/3G + * 4. Cellular */ switch (type) { @@ -635,11 +764,12 @@ static gint sort_allowed_bearers(struct connman_service *service_a, static gint sort_services(gconstpointer a, gconstpointer b, gpointer user_data) { - struct connman_service *service_a = (void *)a; - struct connman_service *service_b = (void *)b; + struct service_entry *entry_a = (void *)a; + struct service_entry *entry_b = (void *)b; struct connman_session *session = user_data; - return sort_allowed_bearers(service_a, service_b, session); + return sort_allowed_bearers(entry_a->service, entry_b->service, + session); } static void cleanup_session(gpointer user_data) @@ -669,78 +799,57 @@ static void cleanup_session(gpointer user_data) g_free(session); } -static void release_session(gpointer key, gpointer value, gpointer user_data) +static enum connman_session_state service_to_session_state(enum connman_service_state state) { - struct connman_session *session = value; - DBusMessage *message; - - DBG("owner %s path %s", session->owner, session->notify_path); - - if (session->notify_watch > 0) - g_dbus_remove_watch(connection, session->notify_watch); - - g_dbus_unregister_interface(connection, session->session_path, - CONNMAN_SESSION_INTERFACE); - - message = dbus_message_new_method_call(session->owner, - session->notify_path, - CONNMAN_NOTIFICATION_INTERFACE, - "Release"); - if (message == NULL) - return; - - dbus_message_set_no_reply(message, TRUE); - - g_dbus_send_message(connection, message); -} - -static int session_disconnect(struct connman_session *session) -{ - DBG("session %p, %s", session, session->owner); - - if (session->notify_watch > 0) - g_dbus_remove_watch(connection, session->notify_watch); - - g_dbus_unregister_interface(connection, session->session_path, - CONNMAN_SESSION_INTERFACE); - - g_hash_table_remove(session_hash, session->session_path); - - return 0; -} - -static void owner_disconnect(DBusConnection *conn, void *user_data) -{ - struct connman_session *session = user_data; - - DBG("session %p, %s died", session, session->owner); + switch (state) { + case CONNMAN_SERVICE_STATE_UNKNOWN: + case CONNMAN_SERVICE_STATE_IDLE: + case CONNMAN_SERVICE_STATE_ASSOCIATION: + case CONNMAN_SERVICE_STATE_CONFIGURATION: + case CONNMAN_SERVICE_STATE_DISCONNECT: + case CONNMAN_SERVICE_STATE_FAILURE: + break; + case CONNMAN_SERVICE_STATE_READY: + return CONNMAN_SESSION_STATE_CONNECTED; + case CONNMAN_SERVICE_STATE_ONLINE: + return CONNMAN_SESSION_STATE_ONLINE; + } - session_disconnect(session); + return CONNMAN_SESSION_STATE_DISCONNECTED; } -static DBusMessage *destroy_session(DBusConnection *conn, - DBusMessage *msg, void *user_data) +static connman_bool_t is_connected(enum connman_service_state state) { - struct connman_session *session = user_data; - - DBG("session %p", session); + switch (state) { + case CONNMAN_SERVICE_STATE_UNKNOWN: + case CONNMAN_SERVICE_STATE_IDLE: + case CONNMAN_SERVICE_STATE_ASSOCIATION: + case CONNMAN_SERVICE_STATE_CONFIGURATION: + case CONNMAN_SERVICE_STATE_DISCONNECT: + case CONNMAN_SERVICE_STATE_FAILURE: + break; + case CONNMAN_SERVICE_STATE_READY: + case CONNMAN_SERVICE_STATE_ONLINE: + return TRUE; + } - return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + return FALSE; } -static connman_bool_t is_connected(enum connman_service_state state) +static connman_bool_t is_connecting(enum connman_service_state state) { switch (state) { case CONNMAN_SERVICE_STATE_UNKNOWN: case CONNMAN_SERVICE_STATE_IDLE: + break; case CONNMAN_SERVICE_STATE_ASSOCIATION: case CONNMAN_SERVICE_STATE_CONFIGURATION: + return TRUE; case CONNMAN_SERVICE_STATE_DISCONNECT: case CONNMAN_SERVICE_STATE_FAILURE: - break; case CONNMAN_SERVICE_STATE_READY: case CONNMAN_SERVICE_STATE_ONLINE: - return TRUE; + break; } return FALSE; @@ -751,6 +860,7 @@ static connman_bool_t explicit_connect(enum connman_session_reason reason) switch (reason) { case CONNMAN_SESSION_REASON_UNKNOWN: case CONNMAN_SESSION_REASON_FREE_RIDE: + case CONNMAN_SESSION_REASON_DISCONNECT: break; case CONNMAN_SESSION_REASON_CONNECT: case CONNMAN_SESSION_REASON_PERIODIC: @@ -760,37 +870,204 @@ static connman_bool_t explicit_connect(enum connman_session_reason reason) return FALSE; } -static void test_and_disconnect(struct connman_session *session) +static connman_bool_t explicit_disconnect(struct session_info *info) { - struct session_info *info = session->info; - if (info->entry == NULL) - return; + return FALSE; - DBG("session %p reason %s service %p state %d", - session, reason2string(info->entry->reason), + DBG("reason %s service %p state %d", + reason2string(info->entry->reason), info->entry->service, info->entry->state); + if (info->entry->reason == CONNMAN_SESSION_REASON_UNKNOWN) + return FALSE; + if (explicit_connect(info->entry->reason) == FALSE) - goto out; + return FALSE; + + if (__connman_service_session_dec(info->entry->service) == FALSE) + return FALSE; + + if (ecall_info != NULL && ecall_info != info) + return FALSE; + + return TRUE; +} + +struct pending_data { + unsigned int timeout; + struct service_entry *entry; + gboolean (*cb)(gpointer); +}; + +static void pending_timeout_free(gpointer data, gpointer user_data) +{ + struct pending_data *pending = data; + + DBG("pending %p timeout %d", pending, pending->timeout); + g_source_remove(pending->timeout); + g_free(pending); +} + +static void pending_timeout_remove_all(struct service_entry *entry) +{ + DBG(""); + + g_slist_foreach(entry->pending_timeouts, pending_timeout_free, NULL); + g_slist_free(entry->pending_timeouts); + entry->pending_timeouts = NULL; +} + +static gboolean pending_timeout_cb(gpointer data) +{ + struct pending_data *pending = data; + struct service_entry *entry = pending->entry; + gboolean ret; + + DBG("pending %p timeout %d", pending, pending->timeout); + + ret = pending->cb(pending->entry); + if (ret == FALSE) { + entry->pending_timeouts = + g_slist_remove(entry->pending_timeouts, + pending); + g_free(pending); + } + return ret; +} + +static connman_bool_t pending_timeout_add(unsigned int seconds, + gboolean (*cb)(gpointer), + struct service_entry *entry) +{ + struct pending_data *pending = g_try_new0(struct pending_data, 1); + + if (pending == NULL || cb == NULL || entry == NULL) { + g_free(pending); + return FALSE; + } + + pending->cb = cb; + pending->entry = entry; + pending->timeout = g_timeout_add_seconds(seconds, pending_timeout_cb, + pending); + entry->pending_timeouts = g_slist_prepend(entry->pending_timeouts, + pending); - if (__connman_service_session_dec(info->entry->service) == TRUE) - goto out; + DBG("pending %p entry %p timeout id %d", pending, entry, + pending->timeout); - if (ecall_session != NULL && ecall_session != session) - goto out; + return TRUE; +} - __connman_service_disconnect(info->entry->service); +static gboolean call_disconnect(gpointer user_data) +{ + struct service_entry *entry = user_data; + struct connman_service *service = entry->service; /* * TODO: We should mark this entry as pending work. In case * disconnect fails we just unassign this session from the * service and can't do anything later on it */ + DBG("disconnect service %p", service); + __connman_service_disconnect(service); + + return FALSE; +} + +static gboolean call_connect(gpointer user_data) +{ + struct service_entry *entry = user_data; + struct connman_service *service = entry->service; + + DBG("connect service %p", service); + __connman_service_connect(service); + + return FALSE; +} + +static void deselect_service(struct session_info *info) +{ + struct service_entry *entry; + connman_bool_t disconnect, connected; + + DBG(""); + + if (info->entry == NULL) + return; + + disconnect = explicit_disconnect(info); + + connected = is_connecting(info->entry->state) == TRUE || + is_connected(info->entry->state) == TRUE; + + info->state = CONNMAN_SESSION_STATE_DISCONNECTED; + info->entry->reason = CONNMAN_SESSION_REASON_UNKNOWN; -out: - info->online = FALSE; + entry = info->entry; info->entry = NULL; + + DBG("disconnect %d connected %d", disconnect, connected); + + if (disconnect == TRUE && connected == TRUE) + pending_timeout_add(0, call_disconnect, entry); +} + +static void deselect_and_disconnect(struct connman_session *session, + enum connman_session_reason reason) +{ + struct session_info *info = session->info; + + deselect_service(info); + + info->reason = reason; +} + +static void select_connected_service(struct session_info *info, + struct service_entry *entry) +{ + enum connman_session_state state; + + state = service_to_session_state(entry->state); + if (is_type_matching_state(&state, info->type) == FALSE) + return; + + info->state = state; + + info->entry = entry; + info->entry->reason = info->reason; + + if (explicit_connect(info->reason) == FALSE) + return; + + __connman_service_session_inc(info->entry->service); +} + +static void select_offline_service(struct session_info *info, + struct service_entry *entry) +{ + if (explicit_connect(info->reason) == FALSE) + return; + + info->state = service_to_session_state(entry->state); + + info->entry = entry; + info->entry->reason = info->reason; + + __connman_service_session_inc(info->entry->service); + pending_timeout_add(0, call_connect, entry); +} + +static void select_service(struct session_info *info, + struct service_entry *entry) +{ + DBG("service %p", entry->service); + + if (is_connected(entry->state) == TRUE) + select_connected_service(info, entry); + else + select_offline_service(info, entry); } static void select_and_connect(struct connman_session *session, @@ -799,10 +1076,11 @@ static void select_and_connect(struct connman_session *session, struct session_info *info = session->info; struct service_entry *entry = NULL; GSequenceIter *iter; - connman_bool_t do_connect = FALSE; DBG("session %p reason %s", session, reason2string(reason)); + info->reason = reason; + iter = g_sequence_get_begin_iter(session->service_list); while (g_sequence_iter_is_end(iter) == FALSE) { @@ -813,39 +1091,88 @@ static void select_and_connect(struct connman_session *session, case CONNMAN_SERVICE_STATE_CONFIGURATION: case CONNMAN_SERVICE_STATE_READY: case CONNMAN_SERVICE_STATE_ONLINE: - /* connecting or connected */ - break; case CONNMAN_SERVICE_STATE_IDLE: case CONNMAN_SERVICE_STATE_DISCONNECT: - if (explicit_connect(reason) == TRUE) - do_connect = TRUE; - break; + select_service(info, entry); + return; case CONNMAN_SERVICE_STATE_UNKNOWN: case CONNMAN_SERVICE_STATE_FAILURE: - entry = NULL; break; } - if (entry != NULL) - break; - iter = g_sequence_iter_next(iter); } +} - if (info->entry != NULL && info->entry != entry) - test_and_disconnect(session); +static struct service_entry *create_service_entry(struct connman_service *service, + const char *name, + enum connman_service_state state) +{ + struct service_entry *entry; + enum connman_service_type type; + int idx; - if (entry != NULL) { - info->entry = entry; - info->entry->reason = reason; + entry = g_try_new0(struct service_entry, 1); + if (entry == NULL) + return entry; - if (explicit_connect(reason) == TRUE) - __connman_service_session_inc(info->entry->service); + entry->reason = CONNMAN_SESSION_REASON_UNKNOWN; + entry->state = state; + if (name != NULL) + entry->name = name; + else + entry->name = ""; + entry->service = service; + + idx = __connman_service_get_index(entry->service); + entry->ifname = connman_inet_ifname(idx); + if (entry->ifname == NULL) + entry->ifname = g_strdup(""); + + type = connman_service_get_type(entry->service); + entry->bearer = service2bearer(type); + + return entry; +} + +static void destroy_service_entry(gpointer data) +{ + struct service_entry *entry = data; + + pending_timeout_remove_all(entry); + g_free(entry->ifname); + + g_free(entry); +} + +static void populate_service_list(struct connman_session *session) +{ + struct service_entry *entry; + GSequenceIter *iter; + + session->service_hash = + g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, NULL); + session->service_list = __connman_service_get_list(session, + service_match, + create_service_entry, + destroy_service_entry); + + g_sequence_sort(session->service_list, sort_services, session); + + iter = g_sequence_get_begin_iter(session->service_list); + + while (g_sequence_iter_is_end(iter) == FALSE) { + entry = g_sequence_get(iter); + + DBG("service %p type %s name %s", entry->service, + service2bearer(connman_service_get_type(entry->service)), + entry->name); + + g_hash_table_replace(session->service_hash, + entry->service, iter); - if (do_connect == TRUE) - __connman_service_connect(info->entry->service); - else - info->online = is_connected(entry->state); + iter = g_sequence_iter_next(iter); } } @@ -854,55 +1181,98 @@ static void session_changed(struct connman_session *session, { struct session_info *info = session->info; struct session_info *info_last = session->info_last; - GSequenceIter *iter; + GSequenceIter *service_iter = NULL, *service_iter_last = NULL; + GSequence *service_list_last; + GHashTable *service_hash_last; /* * TODO: This only a placeholder for the 'real' algorithm to * play a bit around. So we are going to improve it step by step. */ - DBG("session %p trigger %s", session, trigger2string(trigger)); + DBG("session %p trigger %s reason %s", session, trigger2string(trigger), + reason2string(info->reason)); + + if (info->entry != NULL) { + enum connman_session_state state; + + state = service_to_session_state(info->entry->state); + + if (is_type_matching_state(&state, info->type) == TRUE) + info->state = state; + } switch (trigger) { case CONNMAN_SESSION_TRIGGER_UNKNOWN: DBG("ignore session changed event"); return; case CONNMAN_SESSION_TRIGGER_SETTING: - if (info->entry != NULL) { - iter = g_hash_table_lookup(session->service_hash, + if (info->allowed_bearers != info_last->allowed_bearers) { + + service_hash_last = session->service_hash; + service_list_last = session->service_list; + + populate_service_list(session); + + if (info->entry != NULL) { + service_iter_last = g_hash_table_lookup( + service_hash_last, info->entry->service); - if (iter == NULL) { + service_iter = g_hash_table_lookup( + session->service_hash, + info->entry->service); + } + + if (service_iter == NULL && service_iter_last != NULL) { /* - * This service is not part of this - * session anymore. + * The currently selected service is + * not part of this session anymore. */ - test_and_disconnect(session); + deselect_and_disconnect(session, info->reason); } + + g_hash_table_remove_all(service_hash_last); + g_sequence_free(service_list_last); + } + + if (info->type != info_last->type) { + if (info->state >= CONNMAN_SESSION_STATE_CONNECTED && + is_type_matching_state(&info->state, + info->type) == FALSE) + deselect_and_disconnect(session, info->reason); } - if (info->online == FALSE) { + if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED) { select_and_connect(session, CONNMAN_SESSION_REASON_FREE_RIDE); } break; case CONNMAN_SESSION_TRIGGER_CONNECT: - if (info->online == TRUE) { + if (info->state >= CONNMAN_SESSION_STATE_CONNECTED) { + if (info->entry->reason == CONNMAN_SESSION_REASON_CONNECT) + break; info->entry->reason = CONNMAN_SESSION_REASON_CONNECT; __connman_service_session_inc(info->entry->service); break; } + if (info->entry != NULL && + is_connecting(info->entry->state) == TRUE) { + break; + } + select_and_connect(session, CONNMAN_SESSION_REASON_CONNECT); break; case CONNMAN_SESSION_TRIGGER_DISCONNECT: - test_and_disconnect(session); + deselect_and_disconnect(session, + CONNMAN_SESSION_REASON_DISCONNECT); break; case CONNMAN_SESSION_TRIGGER_PERIODIC: - if (info->online == TRUE) { + if (info->state >= CONNMAN_SESSION_STATE_CONNECTED) { info->entry->reason = CONNMAN_SESSION_REASON_PERIODIC; __connman_service_session_inc(info->entry->service); break; @@ -913,31 +1283,30 @@ static void session_changed(struct connman_session *session, break; case CONNMAN_SESSION_TRIGGER_SERVICE: - if (info->online == TRUE) + if (info->entry != NULL && + (is_connecting(info->entry->state) == TRUE || + is_connected(info->entry->state) == TRUE)) { break; + } - if (info->stay_connected == TRUE) { - DBG("StayConnected"); - select_and_connect(session, - CONNMAN_SESSION_REASON_CONNECT); + deselect_and_disconnect(session, info->reason); - break; + if (info->reason == CONNMAN_SESSION_REASON_FREE_RIDE || + info->stay_connected == TRUE) { + select_and_connect(session, info->reason); } - select_and_connect(session, - CONNMAN_SESSION_REASON_FREE_RIDE); - break; case CONNMAN_SESSION_TRIGGER_ECALL: - if (info->online == FALSE && info->entry->service != NULL) - test_and_disconnect(session); + if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED && + info->entry != NULL && + info->entry->service != NULL) { + deselect_and_disconnect(session, info->reason); + } break; } - if (info->entry != info_last->entry) - session->info_dirty = TRUE; - session_notify(session); } @@ -945,10 +1314,11 @@ static DBusMessage *connect_session(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct connman_session *session = user_data; + struct session_info *info = session->info; DBG("session %p", session); - if (ecall_session != NULL && ecall_session != session) + if (ecall_info != NULL && ecall_info != info) return __connman_error_failed(msg, EBUSY); session_changed(session, CONNMAN_SESSION_TRIGGER_CONNECT); @@ -960,10 +1330,11 @@ static DBusMessage *disconnect_session(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct connman_session *session = user_data; + struct session_info *info = session->info; DBG("session %p", session); - if (ecall_session != NULL && ecall_session != session) + if (ecall_info != NULL && ecall_info != info) return __connman_error_failed(msg, EBUSY); session_changed(session, CONNMAN_SESSION_TRIGGER_DISCONNECT); @@ -971,76 +1342,6 @@ static DBusMessage *disconnect_session(DBusConnection *conn, return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } -static struct service_entry *create_service_entry(struct connman_service *service, - const char *name, - enum connman_service_state state) -{ - struct service_entry *entry; - enum connman_service_type type; - int idx; - - entry = g_try_new0(struct service_entry, 1); - if (entry == NULL) - return entry; - - entry->reason = CONNMAN_SESSION_REASON_UNKNOWN; - entry->state = state; - entry->name = name; - entry->service = service; - - idx = __connman_service_get_index(entry->service); - entry->ifname = connman_inet_ifname(idx); - if (entry->ifname == NULL) - entry->ifname = ""; - - type = connman_service_get_type(entry->service); - entry->bearer = service2bearer(type); - - return entry; -} - -static void destroy_service_entry(gpointer data) -{ - struct service_entry *entry = data; - - g_free(entry); -} - -static void update_allowed_bearers(struct connman_session *session) -{ - struct service_entry *entry; - GSequenceIter *iter; - - if (session->service_list != NULL) { - g_hash_table_remove_all(session->service_hash); - g_sequence_free(session->service_list); - } - - session->service_list = __connman_service_get_list(session, - service_match, - create_service_entry, - destroy_service_entry); - - g_sequence_sort(session->service_list, sort_services, session); - - iter = g_sequence_get_begin_iter(session->service_list); - - while (g_sequence_iter_is_end(iter) == FALSE) { - entry = g_sequence_get(iter); - - DBG("service %p type %s name %s", entry->service, - service2bearer(connman_service_get_type(entry->service)), - entry->name); - - g_hash_table_replace(session->service_hash, - entry->service, iter); - - iter = g_sequence_iter_next(iter); - } - - session->info_dirty = TRUE; -} - static void update_ecall_sessions(struct connman_session *session) { struct session_info *info = session->info; @@ -1057,7 +1358,6 @@ static void update_ecall_sessions(struct connman_session *session) continue; session_iter->info->ecall = info->ecall; - session_iter->info_dirty = TRUE; session_changed(session_iter, CONNMAN_SESSION_TRIGGER_ECALL); } @@ -1068,26 +1368,25 @@ static void update_ecall(struct connman_session *session) struct session_info *info = session->info; struct session_info *info_last = session->info_last; - DBG("session %p ecall_session %p ecall %d -> %d", session, - ecall_session, info_last->ecall, info->ecall); + DBG("session %p ecall_info %p ecall %d -> %d", session, + ecall_info, info_last->ecall, info->ecall); - if (ecall_session == NULL) { + if (ecall_info == NULL) { if (!(info_last->ecall == FALSE && info->ecall == TRUE)) goto err; - ecall_session = session; - } else if (ecall_session == session) { + ecall_info = info; + } else if (ecall_info == info) { if (!(info_last->ecall == TRUE && info->ecall == FALSE)) goto err; - ecall_session = NULL; + ecall_info = NULL; } else { goto err; } update_ecall_sessions(session); - session->info_dirty = TRUE; return; err: @@ -1100,9 +1399,9 @@ static DBusMessage *change_session(DBusConnection *conn, { struct connman_session *session = user_data; struct session_info *info = session->info; - struct session_info *info_last = session->info_last; DBusMessageIter iter, value; const char *name; + const char *val; GSList *allowed_bearers; DBG("session %p", session); @@ -1130,8 +1429,6 @@ static DBusMessage *change_session(DBusConnection *conn, } info->allowed_bearers = allowed_bearers; - - update_allowed_bearers(session); } else { goto err; } @@ -1140,21 +1437,12 @@ static DBusMessage *change_session(DBusConnection *conn, if (g_str_equal(name, "Priority") == TRUE) { dbus_message_iter_get_basic(&value, &info->priority); - - if (info_last->priority != info->priority) - session->info_dirty = TRUE; } else if (g_str_equal(name, "AvoidHandover") == TRUE) { dbus_message_iter_get_basic(&value, &info->avoid_handover); - - if (info_last->avoid_handover != info->avoid_handover) - session->info_dirty = TRUE; } else if (g_str_equal(name, "StayConnected") == TRUE) { dbus_message_iter_get_basic(&value, &info->stay_connected); - - if (info_last->stay_connected != info->stay_connected) - session->info_dirty = TRUE; } else if (g_str_equal(name, "EmergencyCall") == TRUE) { dbus_message_iter_get_basic(&value, &info->ecall); @@ -1168,28 +1456,21 @@ static DBusMessage *change_session(DBusConnection *conn, if (g_str_equal(name, "PeriodicConnect") == TRUE) { dbus_message_iter_get_basic(&value, &info->periodic_connect); - - if (info_last->periodic_connect != info->periodic_connect) - session->info_dirty = TRUE; } else if (g_str_equal(name, "IdleTimeout") == TRUE) { dbus_message_iter_get_basic(&value, &info->idle_timeout); - - if (info_last->idle_timeout != info->idle_timeout) - session->info_dirty = TRUE; } else { goto err; } break; case DBUS_TYPE_STRING: - if (g_str_equal(name, "RoamingPolicy") == TRUE) { - const char *val; + if (g_str_equal(name, "ConnectionType") == TRUE) { + dbus_message_iter_get_basic(&value, &val); + info->type = string2type(val); + } else if (g_str_equal(name, "RoamingPolicy") == TRUE) { dbus_message_iter_get_basic(&value, &val); info->roaming_policy = string2roamingpolicy(val); - - if (info_last->roaming_policy != info->roaming_policy) - session->info_dirty = TRUE; } else { goto err; } @@ -1198,8 +1479,7 @@ static DBusMessage *change_session(DBusConnection *conn, goto err; } - if (session->info_dirty == TRUE) - session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING); + session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); @@ -1207,11 +1487,82 @@ err: return __connman_error_invalid_arguments(msg); } -static GDBusMethodTable session_methods[] = { - { "Destroy", "", "", destroy_session }, - { "Connect", "", "", connect_session }, - { "Disconnect", "", "", disconnect_session }, - { "Change", "sv", "", change_session }, +static void release_session(gpointer key, gpointer value, gpointer user_data) +{ + struct connman_session *session = value; + DBusMessage *message; + + DBG("owner %s path %s", session->owner, session->notify_path); + + if (session->notify_watch > 0) + g_dbus_remove_watch(connection, session->notify_watch); + + g_dbus_unregister_interface(connection, session->session_path, + CONNMAN_SESSION_INTERFACE); + + message = dbus_message_new_method_call(session->owner, + session->notify_path, + CONNMAN_NOTIFICATION_INTERFACE, + "Release"); + if (message == NULL) + return; + + dbus_message_set_no_reply(message, TRUE); + + g_dbus_send_message(connection, message); +} + +static int session_disconnect(struct connman_session *session) +{ + DBG("session %p, %s", session, session->owner); + + if (session->notify_watch > 0) + g_dbus_remove_watch(connection, session->notify_watch); + + g_dbus_unregister_interface(connection, session->session_path, + CONNMAN_SESSION_INTERFACE); + + deselect_and_disconnect(session, + CONNMAN_SESSION_REASON_DISCONNECT); + + g_hash_table_remove(session_hash, session->session_path); + + return 0; +} + +static void owner_disconnect(DBusConnection *conn, void *user_data) +{ + struct connman_session *session = user_data; + + DBG("session %p, %s died", session, session->owner); + + session_disconnect(session); +} + +static DBusMessage *destroy_session(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct connman_session *session = user_data; + struct session_info *info = session->info; + + DBG("session %p", session); + + if (ecall_info != NULL && ecall_info != info) + return __connman_error_failed(msg, EBUSY); + + session_disconnect(session); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static const GDBusMethodTable session_methods[] = { + { GDBUS_METHOD("Destroy", NULL, NULL, destroy_session) }, + { GDBUS_METHOD("Connect", NULL, NULL, connect_session) }, + { GDBUS_METHOD("Disconnect", NULL, NULL, + disconnect_session ) }, + { GDBUS_METHOD("Change", + GDBUS_ARGS({ "name", "s" }, { "value", "v" }), + NULL, change_session) }, { }, }; @@ -1223,6 +1574,7 @@ int __connman_session_create(DBusMessage *msg) struct connman_session *session = NULL; struct session_info *info, *info_last; + enum connman_session_type type = CONNMAN_SESSION_TYPE_ANY; connman_bool_t priority = FALSE, avoid_handover = FALSE; connman_bool_t stay_connected = FALSE, ecall = FALSE; enum connman_session_roaming_policy roaming_policy = @@ -1237,7 +1589,7 @@ int __connman_session_create(DBusMessage *msg) DBG("owner %s", owner); - if (ecall_session != NULL) { + if (ecall_info != NULL) { /* * If there is an emergency call already going on, * ignore session creation attempt @@ -1297,7 +1649,10 @@ int __connman_session_create(DBusMessage *msg) } break; case DBUS_TYPE_STRING: - if (g_str_equal(key, "RoamingPolicy") == TRUE) { + if (g_str_equal(key, "ConnectionType") == TRUE) { + dbus_message_iter_get_basic(&value, &val); + type = string2type(val); + } else if (g_str_equal(key, "RoamingPolicy") == TRUE) { dbus_message_iter_get_basic(&value, &val); roaming_policy = string2roamingpolicy(val); } else { @@ -1323,6 +1678,7 @@ int __connman_session_create(DBusMessage *msg) session = g_hash_table_lookup(session_hash, session_path); if (session != NULL) { + session = NULL; err = -EEXIST; goto err; } @@ -1355,10 +1711,8 @@ int __connman_session_create(DBusMessage *msg) g_dbus_add_disconnect_watch(connection, session->owner, owner_disconnect, session, NULL); - session->service_hash = g_hash_table_new_full(g_str_hash, g_str_equal, - NULL, NULL); - - info->online = FALSE; + info->state = CONNMAN_SESSION_STATE_DISCONNECTED; + info->type = type; info->priority = priority; info->avoid_handover = avoid_handover; info->stay_connected = stay_connected; @@ -1402,13 +1756,13 @@ int __connman_session_create(DBusMessage *msg) DBUS_TYPE_INVALID); - update_allowed_bearers(session); + populate_service_list(session); if (info->ecall == TRUE) { - ecall_session = session; + ecall_info = info; update_ecall_sessions(session); } - info_last->online = info->online; + info_last->state = info->state; info_last->priority = info->priority; info_last->avoid_handover = info->avoid_handover; info_last->stay_connected = info->stay_connected; @@ -1420,7 +1774,6 @@ int __connman_session_create(DBusMessage *msg) info_last->marker = info->marker; info_last->allowed_bearers = info->allowed_bearers; - session->info_dirty = TRUE; session->append_all = TRUE; session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING); @@ -1481,21 +1834,26 @@ void __connman_session_set_mode(connman_bool_t enable) { DBG("enable %d", enable); - if (sessionmode == enable) - return; + if (sessionmode != enable) { + sessionmode = enable; - sessionmode = enable; + connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH, + CONNMAN_MANAGER_INTERFACE, "SessionMode", + DBUS_TYPE_BOOLEAN, &sessionmode); + } if (sessionmode == TRUE) __connman_service_disconnect_all(); } -static void service_add(struct connman_service *service) +static void service_add(struct connman_service *service, + const char *name) { GHashTableIter iter; GSequenceIter *iter_service_list; gpointer key, value; struct connman_session *session; + struct service_entry *entry; DBG("service %p", service); @@ -1507,9 +1865,14 @@ static void service_add(struct connman_service *service) if (service_match(session, service) == FALSE) continue; + entry = create_service_entry(service, name, + CONNMAN_SERVICE_STATE_IDLE); + if (entry == NULL) + continue; + iter_service_list = g_sequence_insert_sorted(session->service_list, - service, sort_services, + entry, sort_services, session); g_hash_table_replace(session->service_hash, service, @@ -1542,7 +1905,8 @@ static void service_remove(struct connman_service *service) g_sequence_remove(iter); - info->entry = NULL; + if (info->entry != NULL && info->entry->service == service) + info->entry = NULL; session_changed(session, CONNMAN_SESSION_TRIGGER_SERVICE); } } @@ -1552,23 +1916,21 @@ static void service_state_changed(struct connman_service *service, { GHashTableIter iter; gpointer key, value; - struct connman_session *session; - struct session_info *info, *info_last; DBG("service %p state %d", service, state); g_hash_table_iter_init(&iter, session_hash); while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) { - session = value; - info = session->info; - info_last = session->info_last; + struct connman_session *session = value; + GSequenceIter *service_iter; - if (info->entry != NULL && info->entry->service == service) { - info->entry->state = state; - info->online = is_connected(info->entry->state); - if (info_last->online != info->online) - session->info_dirty = TRUE; + service_iter = g_hash_table_lookup(session->service_hash, service); + if (service_iter != NULL) { + struct service_entry *entry; + + entry = g_sequence_get(service_iter); + entry->state = state; } session_changed(session, @@ -1595,6 +1957,9 @@ static void ipconfig_changed(struct connman_service *service, session = value; info = session->info; + if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED) + continue; + if (info->entry != NULL && info->entry->service == service) { if (type == CONNMAN_IPCONFIG_TYPE_IPV4) ipconfig_ipv4_changed(session); @@ -1646,6 +2011,7 @@ void __connman_session_cleanup(void) g_hash_table_foreach(session_hash, release_session, NULL); g_hash_table_destroy(session_hash); + session_hash = NULL; dbus_connection_unref(connection); }