session: changing online property to a state property
[platform/upstream/connman.git] / src / session.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
6  *  Copyright (C) 2011  BWM CarIT GmbH. All rights reserved.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License version 2 as
10  *  published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <errno.h>
28
29 #include <gdbus.h>
30
31 #include "connman.h"
32
33 static DBusConnection *connection;
34 static GHashTable *session_hash;
35 static connman_bool_t sessionmode;
36 static struct session_info *ecall_info;
37
38 enum connman_session_trigger {
39         CONNMAN_SESSION_TRIGGER_UNKNOWN         = 0,
40         CONNMAN_SESSION_TRIGGER_SETTING         = 1,
41         CONNMAN_SESSION_TRIGGER_CONNECT         = 2,
42         CONNMAN_SESSION_TRIGGER_DISCONNECT      = 3,
43         CONNMAN_SESSION_TRIGGER_PERIODIC        = 4,
44         CONNMAN_SESSION_TRIGGER_SERVICE         = 5,
45         CONNMAN_SESSION_TRIGGER_ECALL           = 6,
46 };
47
48 enum connman_session_reason {
49         CONNMAN_SESSION_REASON_UNKNOWN          = 0,
50         CONNMAN_SESSION_REASON_CONNECT          = 1,
51         CONNMAN_SESSION_REASON_DISCONNECT       = 2,
52         CONNMAN_SESSION_REASON_FREE_RIDE        = 3,
53         CONNMAN_SESSION_REASON_PERIODIC         = 4,
54 };
55
56 enum connman_session_state {
57         CONNMAN_SESSION_STATE_DISCONNECTED   = 0,
58         CONNMAN_SESSION_STATE_CONNECTED      = 1,
59         CONNMAN_SESSION_STATE_ONLINE         = 2,
60 };
61
62 enum connman_session_roaming_policy {
63         CONNMAN_SESSION_ROAMING_POLICY_UNKNOWN          = 0,
64         CONNMAN_SESSION_ROAMING_POLICY_DEFAULT          = 1,
65         CONNMAN_SESSION_ROAMING_POLICY_ALWAYS           = 2,
66         CONNMAN_SESSION_ROAMING_POLICY_FORBIDDEN        = 3,
67         CONNMAN_SESSION_ROAMING_POLICY_NATIONAL         = 4,
68         CONNMAN_SESSION_ROAMING_POLICY_INTERNATIONAL    = 5,
69 };
70
71 struct service_entry {
72         /* track why this service was selected */
73         enum connman_session_reason reason;
74         enum connman_service_state state;
75         const char *name;
76         struct connman_service *service;
77         char *ifname;
78         const char *bearer;
79         GSList *pending_timeouts;
80 };
81
82 struct session_info {
83         enum connman_session_state state;
84         connman_bool_t priority;
85         GSList *allowed_bearers;
86         connman_bool_t avoid_handover;
87         connman_bool_t stay_connected;
88         unsigned int periodic_connect;
89         unsigned int idle_timeout;
90         connman_bool_t ecall;
91         enum connman_session_roaming_policy roaming_policy;
92         unsigned int marker;
93
94         struct service_entry *entry;
95         enum connman_session_reason reason;
96 };
97
98 struct connman_session {
99         char *owner;
100         char *session_path;
101         char *notify_path;
102         guint notify_watch;
103
104         connman_bool_t append_all;
105         connman_bool_t info_dirty;
106         struct session_info *info;
107         struct session_info *info_last;
108
109         GSequence *service_list;
110         GHashTable *service_hash;
111 };
112
113 struct bearer_info {
114         char *name;
115         connman_bool_t match_all;
116         enum connman_service_type service_type;
117 };
118
119 static const char *trigger2string(enum connman_session_trigger trigger)
120 {
121         switch (trigger) {
122         case CONNMAN_SESSION_TRIGGER_UNKNOWN:
123                 break;
124         case CONNMAN_SESSION_TRIGGER_SETTING:
125                 return "setting";
126         case CONNMAN_SESSION_TRIGGER_CONNECT:
127                 return "connect";
128         case CONNMAN_SESSION_TRIGGER_DISCONNECT:
129                 return "disconnect";
130         case CONNMAN_SESSION_TRIGGER_PERIODIC:
131                 return "periodic";
132         case CONNMAN_SESSION_TRIGGER_SERVICE:
133                 return "service";
134         case CONNMAN_SESSION_TRIGGER_ECALL:
135                 return "ecall";
136         }
137
138         return NULL;
139 }
140
141 static const char *reason2string(enum connman_session_reason reason)
142 {
143         switch (reason) {
144         case CONNMAN_SESSION_REASON_UNKNOWN:
145                 return "unknown";
146         case CONNMAN_SESSION_REASON_CONNECT:
147                 return "connect";
148         case CONNMAN_SESSION_REASON_DISCONNECT:
149                 return "disconnect";
150         case CONNMAN_SESSION_REASON_FREE_RIDE:
151                 return "free-ride";
152         case CONNMAN_SESSION_REASON_PERIODIC:
153                 return "periodic";
154         }
155
156         return NULL;
157 }
158
159 static const char *state2string(enum connman_session_state state)
160 {
161         switch (state) {
162         case CONNMAN_SESSION_STATE_DISCONNECTED:
163                 return "disconnected";
164         case CONNMAN_SESSION_STATE_CONNECTED:
165                 return "connected";
166         case CONNMAN_SESSION_STATE_ONLINE:
167                 return "online";
168         }
169
170         return NULL;
171 }
172
173 static const char *roamingpolicy2string(enum connman_session_roaming_policy policy)
174 {
175         switch (policy) {
176         case CONNMAN_SESSION_ROAMING_POLICY_UNKNOWN:
177                 return "unknown";
178         case CONNMAN_SESSION_ROAMING_POLICY_DEFAULT:
179                 return "default";
180         case CONNMAN_SESSION_ROAMING_POLICY_ALWAYS:
181                 return "always";
182         case CONNMAN_SESSION_ROAMING_POLICY_FORBIDDEN:
183                 return "forbidden";
184         case CONNMAN_SESSION_ROAMING_POLICY_NATIONAL:
185                 return "national";
186         case CONNMAN_SESSION_ROAMING_POLICY_INTERNATIONAL:
187                 return "international";
188         }
189
190         return NULL;
191 }
192
193 static enum connman_session_roaming_policy string2roamingpolicy(const char *policy)
194 {
195         if (g_strcmp0(policy, "default") == 0)
196                 return CONNMAN_SESSION_ROAMING_POLICY_DEFAULT;
197         else if (g_strcmp0(policy, "always") == 0)
198                 return CONNMAN_SESSION_ROAMING_POLICY_ALWAYS;
199         else if (g_strcmp0(policy, "forbidden") == 0)
200                 return CONNMAN_SESSION_ROAMING_POLICY_FORBIDDEN;
201         else if (g_strcmp0(policy, "national") == 0)
202                 return CONNMAN_SESSION_ROAMING_POLICY_NATIONAL;
203         else if (g_strcmp0(policy, "international") == 0)
204                 return CONNMAN_SESSION_ROAMING_POLICY_INTERNATIONAL;
205         else
206                 return CONNMAN_SESSION_ROAMING_POLICY_UNKNOWN;
207 }
208
209 static enum connman_service_type bearer2service(const char *bearer)
210 {
211         if (bearer == NULL)
212                 return CONNMAN_SERVICE_TYPE_UNKNOWN;
213
214         if (g_strcmp0(bearer, "ethernet") == 0)
215                 return CONNMAN_SERVICE_TYPE_ETHERNET;
216         else if (g_strcmp0(bearer, "wifi") == 0)
217                 return CONNMAN_SERVICE_TYPE_WIFI;
218         else if (g_strcmp0(bearer, "wimax") == 0)
219                 return CONNMAN_SERVICE_TYPE_WIMAX;
220         else if (g_strcmp0(bearer, "bluetooth") == 0)
221                 return CONNMAN_SERVICE_TYPE_BLUETOOTH;
222         else if (g_strcmp0(bearer, "cellular") == 0)
223                 return CONNMAN_SERVICE_TYPE_CELLULAR;
224         else if (g_strcmp0(bearer, "vpn") == 0)
225                 return CONNMAN_SERVICE_TYPE_VPN;
226         else
227                 return CONNMAN_SERVICE_TYPE_UNKNOWN;
228 }
229
230 static char *service2bearer(enum connman_service_type type)
231 {
232         switch (type) {
233         case CONNMAN_SERVICE_TYPE_ETHERNET:
234                 return "ethernet";
235         case CONNMAN_SERVICE_TYPE_WIFI:
236                 return "wifi";
237         case CONNMAN_SERVICE_TYPE_WIMAX:
238                 return "wimax";
239         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
240                 return "bluetooth";
241         case CONNMAN_SERVICE_TYPE_CELLULAR:
242                 return "cellular";
243         case CONNMAN_SERVICE_TYPE_VPN:
244                 return "vpn";
245         case CONNMAN_SERVICE_TYPE_UNKNOWN:
246         case CONNMAN_SERVICE_TYPE_SYSTEM:
247         case CONNMAN_SERVICE_TYPE_GPS:
248         case CONNMAN_SERVICE_TYPE_GADGET:
249                 return "";
250         }
251
252         return "";
253 }
254
255 static void cleanup_bearer_info(gpointer data, gpointer user_data)
256 {
257         struct bearer_info *info = data;
258
259         g_free(info->name);
260         g_free(info);
261 }
262
263 static GSList *session_parse_allowed_bearers(DBusMessageIter *iter)
264 {
265         struct bearer_info *info;
266         DBusMessageIter array;
267         GSList *list = NULL;
268
269         dbus_message_iter_recurse(iter, &array);
270
271         while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
272                 char *bearer = NULL;
273
274                 dbus_message_iter_get_basic(&array, &bearer);
275
276                 info = g_try_new0(struct bearer_info, 1);
277                 if (info == NULL) {
278                         g_slist_foreach(list, cleanup_bearer_info, NULL);
279                         g_slist_free(list);
280
281                         return NULL;
282                 }
283
284                 info->name = g_strdup(bearer);
285                 info->service_type = bearer2service(info->name);
286
287                 if (info->service_type == CONNMAN_SERVICE_TYPE_UNKNOWN &&
288                                 g_strcmp0(info->name, "*") == 0) {
289                         info->match_all = TRUE;
290                 } else {
291                         info->match_all = FALSE;
292                 }
293
294                 list = g_slist_append(list, info);
295
296                 dbus_message_iter_next(&array);
297         }
298
299         return list;
300 }
301
302 static GSList *session_allowed_bearers_any(void)
303 {
304         struct bearer_info *info;
305         GSList *list = NULL;
306
307         info = g_try_new0(struct bearer_info, 1);
308         if (info == NULL) {
309                 g_slist_free(list);
310
311                 return NULL;
312         }
313
314         info->name = g_strdup("");
315         info->match_all = TRUE;
316         info->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
317
318         list = g_slist_append(list, info);
319
320         return list;
321 }
322
323 static void append_allowed_bearers(DBusMessageIter *iter, void *user_data)
324 {
325         struct session_info *info = user_data;
326         GSList *list;
327
328         for (list = info->allowed_bearers;
329                         list != NULL; list = list->next) {
330                 struct bearer_info *info = list->data;
331
332                 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
333                                                 &info->name);
334         }
335 }
336
337 static void append_ipconfig_ipv4(DBusMessageIter *iter, void *user_data)
338 {
339         struct connman_service *service = user_data;
340         struct connman_ipconfig *ipconfig_ipv4;
341
342         if (service == NULL)
343                 return;
344
345         ipconfig_ipv4 = __connman_service_get_ip4config(service);
346         if (ipconfig_ipv4 == NULL)
347                 return;
348
349         __connman_ipconfig_append_ipv4(ipconfig_ipv4, iter);
350 }
351
352 static void append_ipconfig_ipv6(DBusMessageIter *iter, void *user_data)
353 {
354         struct connman_service *service = user_data;
355         struct connman_ipconfig *ipconfig_ipv4, *ipconfig_ipv6;
356
357         if (service == NULL)
358                 return;
359
360         ipconfig_ipv4 = __connman_service_get_ip4config(service);
361         ipconfig_ipv6 = __connman_service_get_ip6config(service);
362         if (ipconfig_ipv6 == NULL)
363                 return;
364
365         __connman_ipconfig_append_ipv6(ipconfig_ipv6, iter, ipconfig_ipv4);
366 }
367
368 static void append_notify(DBusMessageIter *dict,
369                                         struct connman_session *session)
370 {
371         struct session_info *info = session->info;
372         struct session_info *info_last = session->info_last;
373         const char *policy;
374         struct connman_service *service;
375         const char *name, *ifname, *bearer;
376
377         if (session->append_all == TRUE ||
378                         info->state != info_last->state) {
379                 const char *state = state2string(info->state);
380
381                 connman_dbus_dict_append_basic(dict, "State",
382                                                 DBUS_TYPE_STRING,
383                                                 &state);
384                 info_last->state = info->state;
385         }
386
387         if (session->append_all == TRUE ||
388                         info->entry != info_last->entry) {
389                 if (info->entry == NULL) {
390                         name = "";
391                         ifname = "";
392                         service = NULL;
393                         bearer = "";
394                 } else {
395                         name = info->entry->name;
396                         ifname = info->entry->ifname;
397                         service = info->entry->service;
398                         bearer = info->entry->bearer;
399                 }
400
401                 connman_dbus_dict_append_basic(dict, "Name",
402                                                 DBUS_TYPE_STRING,
403                                                 &name);
404
405                 connman_dbus_dict_append_dict(dict, "IPv4",
406                                                 append_ipconfig_ipv4,
407                                                 service);
408
409                 connman_dbus_dict_append_dict(dict, "IPv6",
410                                                 append_ipconfig_ipv6,
411                                                 service);
412
413                 connman_dbus_dict_append_basic(dict, "Interface",
414                                                 DBUS_TYPE_STRING,
415                                                 &ifname);
416
417                 connman_dbus_dict_append_basic(dict, "Bearer",
418                                                 DBUS_TYPE_STRING,
419                                                 &bearer);
420
421                 info_last->entry = info->entry;
422         }
423
424
425         if (session->append_all == TRUE ||
426                         info->priority != info_last->priority) {
427                 connman_dbus_dict_append_basic(dict, "Priority",
428                                                 DBUS_TYPE_BOOLEAN,
429                                                 &info->priority);
430                 info_last->priority = info->priority;
431         }
432
433         if (session->append_all == TRUE ||
434                         info->allowed_bearers != info_last->allowed_bearers) {
435                 connman_dbus_dict_append_array(dict, "AllowedBearers",
436                                                 DBUS_TYPE_STRING,
437                                                 append_allowed_bearers,
438                                                 info);
439                 info_last->allowed_bearers = info->allowed_bearers;
440         }
441
442         if (session->append_all == TRUE ||
443                         info->avoid_handover != info_last->avoid_handover) {
444                 connman_dbus_dict_append_basic(dict, "AvoidHandover",
445                                                 DBUS_TYPE_BOOLEAN,
446                                                 &info->avoid_handover);
447                 info_last->avoid_handover = info->avoid_handover;
448         }
449
450         if (session->append_all == TRUE ||
451                         info->stay_connected != info_last->stay_connected) {
452                 connman_dbus_dict_append_basic(dict, "StayConnected",
453                                                 DBUS_TYPE_BOOLEAN,
454                                                 &info->stay_connected);
455                 info_last->stay_connected = info->stay_connected;
456         }
457
458         if (session->append_all == TRUE ||
459                         info->periodic_connect != info_last->periodic_connect) {
460                 connman_dbus_dict_append_basic(dict, "PeriodicConnect",
461                                                 DBUS_TYPE_UINT32,
462                                                 &info->periodic_connect);
463                 info_last->periodic_connect = info->periodic_connect;
464         }
465
466         if (session->append_all == TRUE ||
467                         info->idle_timeout != info_last->idle_timeout) {
468                 connman_dbus_dict_append_basic(dict, "IdleTimeout",
469                                                 DBUS_TYPE_UINT32,
470                                                 &info->idle_timeout);
471                 info_last->idle_timeout = info->idle_timeout;
472         }
473
474         if (session->append_all == TRUE ||
475                         info->ecall != info_last->ecall) {
476                 connman_dbus_dict_append_basic(dict, "EmergencyCall",
477                                                 DBUS_TYPE_BOOLEAN,
478                                                 &info->ecall);
479                 info_last->ecall = info->ecall;
480         }
481
482         if (session->append_all == TRUE ||
483                         info->roaming_policy != info_last->roaming_policy) {
484                 policy = roamingpolicy2string(info->roaming_policy);
485                 connman_dbus_dict_append_basic(dict, "RoamingPolicy",
486                                                 DBUS_TYPE_STRING,
487                                                 &policy);
488                 info_last->roaming_policy = info->roaming_policy;
489         }
490
491         if (session->append_all == TRUE ||
492                         info->marker != info_last->marker) {
493                 connman_dbus_dict_append_basic(dict, "SessionMarker",
494                                                 DBUS_TYPE_UINT32,
495                                                 &info->marker);
496                 info_last->marker = info->marker;
497         }
498
499         session->append_all = FALSE;
500         session->info_dirty = FALSE;
501 }
502
503 static gboolean session_notify(gpointer user_data)
504 {
505         struct connman_session *session = user_data;
506         DBusMessage *msg;
507         DBusMessageIter array, dict;
508
509         if (session->info_dirty == FALSE)
510                 return 0;
511
512         DBG("session %p owner %s notify_path %s", session,
513                 session->owner, session->notify_path);
514
515         msg = dbus_message_new_method_call(session->owner, session->notify_path,
516                                                 CONNMAN_NOTIFICATION_INTERFACE,
517                                                 "Update");
518         if (msg == NULL)
519                 return FALSE;
520
521         dbus_message_iter_init_append(msg, &array);
522         connman_dbus_dict_open(&array, &dict);
523
524         append_notify(&dict, session);
525
526         connman_dbus_dict_close(&array, &dict);
527
528         g_dbus_send_message(connection, msg);
529
530         return FALSE;
531 }
532
533 static void ipconfig_ipv4_changed(struct connman_session *session)
534 {
535         struct session_info *info = session->info;
536
537         connman_dbus_setting_changed_dict(session->owner, session->notify_path,
538                                                 "IPv4", append_ipconfig_ipv4,
539                                                 info->entry->service);
540 }
541
542 static void ipconfig_ipv6_changed(struct connman_session *session)
543 {
544         struct session_info *info = session->info;
545
546         connman_dbus_setting_changed_dict(session->owner, session->notify_path,
547                                                 "IPv6", append_ipconfig_ipv6,
548                                                 info->entry->service);
549 }
550
551 static connman_bool_t service_type_match(struct connman_session *session,
552                                         struct connman_service *service)
553 {
554         struct session_info *info = session->info;
555         GSList *list;
556
557         for (list = info->allowed_bearers;
558                         list != NULL; list = list->next) {
559                 struct bearer_info *info = list->data;
560                 enum connman_service_type service_type;
561
562                 if (info->match_all == TRUE)
563                         return TRUE;
564
565                 service_type = connman_service_get_type(service);
566                 if (info->service_type == service_type)
567                         return TRUE;
568         }
569
570         return FALSE;
571 }
572
573 static connman_bool_t service_match(struct connman_session *session,
574                                         struct connman_service *service)
575 {
576         if (service_type_match(session, service) == FALSE)
577                 return FALSE;
578
579         return TRUE;
580 }
581
582 static int service_type_weight(enum connman_service_type type)
583 {
584         /*
585          * The session doesn't care which service
586          * to use. Nevertheless we have to sort them
587          * according their type. The ordering is
588          *
589          * 1. Ethernet
590          * 2. Bluetooth
591          * 3. WiFi/WiMAX
592          * 4. Cellular
593          */
594
595         switch (type) {
596         case CONNMAN_SERVICE_TYPE_ETHERNET:
597                 return 4;
598         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
599                 return 3;
600         case CONNMAN_SERVICE_TYPE_WIFI:
601         case CONNMAN_SERVICE_TYPE_WIMAX:
602                 return 2;
603         case CONNMAN_SERVICE_TYPE_CELLULAR:
604                 return 1;
605         case CONNMAN_SERVICE_TYPE_UNKNOWN:
606         case CONNMAN_SERVICE_TYPE_SYSTEM:
607         case CONNMAN_SERVICE_TYPE_GPS:
608         case CONNMAN_SERVICE_TYPE_VPN:
609         case CONNMAN_SERVICE_TYPE_GADGET:
610                 break;
611         }
612
613         return 0;
614 }
615
616 static gint sort_allowed_bearers(struct connman_service *service_a,
617                                         struct connman_service *service_b,
618                                         struct connman_session *session)
619 {
620         struct session_info *info = session->info;
621         GSList *list;
622         enum connman_service_type type_a, type_b;
623         int weight_a, weight_b;
624
625         type_a = connman_service_get_type(service_a);
626         type_b = connman_service_get_type(service_b);
627
628         for (list = info->allowed_bearers;
629                         list != NULL; list = list->next) {
630                 struct bearer_info *info = list->data;
631
632                 if (info->match_all == TRUE) {
633                         if (type_a != type_b) {
634                                 weight_a = service_type_weight(type_a);
635                                 weight_b = service_type_weight(type_b);
636
637                                 if (weight_a > weight_b)
638                                         return -1;
639
640                                 if (weight_a < weight_b)
641                                         return 1;
642
643                                 return 0;
644                         }
645                 }
646
647                 if (type_a == info->service_type &&
648                                 type_b == info->service_type) {
649                         return 0;
650                 }
651
652                 if (type_a == info->service_type &&
653                                 type_b != info->service_type) {
654                         return -1;
655                 }
656
657                 if (type_a != info->service_type &&
658                                 type_b == info->service_type) {
659                         return 1;
660                 }
661         }
662
663         return 0;
664 }
665
666 static gint sort_services(gconstpointer a, gconstpointer b, gpointer user_data)
667 {
668         struct service_entry *entry_a = (void *)a;
669         struct service_entry *entry_b = (void *)b;
670         struct connman_session *session = user_data;
671
672         return sort_allowed_bearers(entry_a->service, entry_b->service,
673                                 session);
674 }
675
676 static void cleanup_session(gpointer user_data)
677 {
678         struct connman_session *session = user_data;
679         struct session_info *info = session->info;
680
681         DBG("remove %s", session->session_path);
682
683         g_hash_table_destroy(session->service_hash);
684         g_sequence_free(session->service_list);
685
686         if (info->entry != NULL &&
687                         info->entry->reason == CONNMAN_SESSION_REASON_CONNECT) {
688                 __connman_service_disconnect(info->entry->service);
689         }
690
691         g_slist_foreach(info->allowed_bearers, cleanup_bearer_info, NULL);
692         g_slist_free(info->allowed_bearers);
693
694         g_free(session->owner);
695         g_free(session->session_path);
696         g_free(session->notify_path);
697         g_free(session->info);
698         g_free(session->info_last);
699
700         g_free(session);
701 }
702
703 static enum connman_session_state service_to_session_state(enum connman_service_state state)
704 {
705         switch (state) {
706         case CONNMAN_SERVICE_STATE_UNKNOWN:
707         case CONNMAN_SERVICE_STATE_IDLE:
708         case CONNMAN_SERVICE_STATE_ASSOCIATION:
709         case CONNMAN_SERVICE_STATE_CONFIGURATION:
710         case CONNMAN_SERVICE_STATE_DISCONNECT:
711         case CONNMAN_SERVICE_STATE_FAILURE:
712                 break;
713         case CONNMAN_SERVICE_STATE_READY:
714                 return CONNMAN_SESSION_STATE_CONNECTED;
715         case CONNMAN_SERVICE_STATE_ONLINE:
716                 return CONNMAN_SESSION_STATE_ONLINE;
717         }
718
719         return CONNMAN_SESSION_STATE_DISCONNECTED;
720 }
721
722 static connman_bool_t is_connected(enum connman_service_state state)
723 {
724         switch (state) {
725         case CONNMAN_SERVICE_STATE_UNKNOWN:
726         case CONNMAN_SERVICE_STATE_IDLE:
727         case CONNMAN_SERVICE_STATE_ASSOCIATION:
728         case CONNMAN_SERVICE_STATE_CONFIGURATION:
729         case CONNMAN_SERVICE_STATE_DISCONNECT:
730         case CONNMAN_SERVICE_STATE_FAILURE:
731                 break;
732         case CONNMAN_SERVICE_STATE_READY:
733         case CONNMAN_SERVICE_STATE_ONLINE:
734                 return TRUE;
735         }
736
737         return FALSE;
738 }
739
740 static connman_bool_t is_connecting(enum connman_service_state state)
741 {
742         switch (state) {
743         case CONNMAN_SERVICE_STATE_UNKNOWN:
744         case CONNMAN_SERVICE_STATE_IDLE:
745                 break;
746         case CONNMAN_SERVICE_STATE_ASSOCIATION:
747         case CONNMAN_SERVICE_STATE_CONFIGURATION:
748                 return TRUE;
749         case CONNMAN_SERVICE_STATE_DISCONNECT:
750         case CONNMAN_SERVICE_STATE_FAILURE:
751         case CONNMAN_SERVICE_STATE_READY:
752         case CONNMAN_SERVICE_STATE_ONLINE:
753                 break;
754         }
755
756         return FALSE;
757 }
758
759 static connman_bool_t explicit_connect(enum connman_session_reason reason)
760 {
761         switch (reason) {
762         case CONNMAN_SESSION_REASON_UNKNOWN:
763         case CONNMAN_SESSION_REASON_FREE_RIDE:
764         case CONNMAN_SESSION_REASON_DISCONNECT:
765                 break;
766         case CONNMAN_SESSION_REASON_CONNECT:
767         case CONNMAN_SESSION_REASON_PERIODIC:
768                 return TRUE;
769         }
770
771         return FALSE;
772 }
773
774 static connman_bool_t explicit_disconnect(struct session_info *info)
775 {
776         if (info->entry == NULL)
777                 return FALSE;
778
779         DBG("reason %s service %p state %d",
780                 reason2string(info->entry->reason),
781                 info->entry->service, info->entry->state);
782
783         if (info->entry->reason == CONNMAN_SESSION_REASON_UNKNOWN)
784                 return FALSE;
785
786         if (explicit_connect(info->entry->reason) == FALSE)
787                 return FALSE;
788
789         if (__connman_service_session_dec(info->entry->service) == FALSE)
790                 return FALSE;
791
792         if (ecall_info != NULL && ecall_info != info)
793                 return FALSE;
794
795         return TRUE;
796 }
797
798 struct pending_data {
799         unsigned int timeout;
800         struct service_entry *entry;
801         gboolean (*cb)(gpointer);
802 };
803
804 static void pending_timeout_free(gpointer data, gpointer user_data)
805 {
806         struct pending_data *pending = data;
807
808         DBG("pending %p timeout %d", pending, pending->timeout);
809         g_source_remove(pending->timeout);
810         g_free(pending);
811 }
812
813 static void pending_timeout_remove_all(struct service_entry *entry)
814 {
815         DBG("");
816
817         g_slist_foreach(entry->pending_timeouts, pending_timeout_free, NULL);
818         g_slist_free(entry->pending_timeouts);
819         entry->pending_timeouts = NULL;
820 }
821
822 static gboolean pending_timeout_cb(gpointer data)
823 {
824         struct pending_data *pending = data;
825         struct service_entry *entry = pending->entry;
826         gboolean ret;
827
828         DBG("pending %p timeout %d", pending, pending->timeout);
829
830         ret = pending->cb(pending->entry);
831         if (ret == FALSE) {
832                 entry->pending_timeouts =
833                         g_slist_remove(entry->pending_timeouts,
834                                         pending);
835                 g_free(pending);
836         }
837         return ret;
838 }
839
840 static connman_bool_t pending_timeout_add(unsigned int seconds,
841                                         gboolean (*cb)(gpointer),
842                                         struct service_entry *entry)
843 {
844         struct pending_data *pending = g_try_new0(struct pending_data, 1);
845
846         if (pending == NULL || cb == NULL || entry == NULL) {
847                 g_free(pending);
848                 return FALSE;
849         }
850
851         pending->cb = cb;
852         pending->entry = entry;
853         pending->timeout = g_timeout_add_seconds(seconds, pending_timeout_cb,
854                                                 pending);
855         entry->pending_timeouts = g_slist_prepend(entry->pending_timeouts,
856                                                 pending);
857
858         DBG("pending %p entry %p timeout id %d", pending, entry,
859                 pending->timeout);
860
861         return TRUE;
862 }
863
864 static gboolean call_disconnect(gpointer user_data)
865 {
866         struct service_entry *entry = user_data;
867         struct connman_service *service = entry->service;
868
869         /*
870          * TODO: We should mark this entry as pending work. In case
871          * disconnect fails we just unassign this session from the
872          * service and can't do anything later on it
873          */
874         DBG("disconnect service %p", service);
875         __connman_service_disconnect(service);
876
877         return FALSE;
878 }
879
880 static gboolean call_connect(gpointer user_data)
881 {
882         struct service_entry *entry = user_data;
883         struct connman_service *service = entry->service;
884
885         DBG("connect service %p", service);
886         __connman_service_connect(service);
887
888         return FALSE;
889 }
890
891 static connman_bool_t deselect_service(struct session_info *info)
892 {
893         struct service_entry *entry;
894         connman_bool_t disconnect, connected;
895
896         DBG("");
897
898         if (info->entry == NULL)
899                 return FALSE;
900
901         disconnect = explicit_disconnect(info);
902
903         connected = is_connecting(info->entry->state) == TRUE ||
904                         is_connected(info->entry->state) == TRUE;
905
906         info->state = CONNMAN_SESSION_STATE_DISCONNECTED;
907         info->entry->reason = CONNMAN_SESSION_REASON_UNKNOWN;
908
909         entry = info->entry;
910         info->entry = NULL;
911
912         DBG("disconnect %d connected %d", disconnect, connected);
913
914         if (disconnect == TRUE && connected == TRUE)
915                 pending_timeout_add(0, call_disconnect, entry);
916
917         return TRUE;
918 }
919
920 static void deselect_and_disconnect(struct connman_session *session,
921                                         enum connman_session_reason reason)
922 {
923         struct session_info *info = session->info;
924
925         session->info_dirty |= deselect_service(info);
926
927         info->reason = reason;
928 }
929
930 static connman_bool_t select_connected_service(struct session_info *info,
931                                         struct service_entry *entry)
932 {
933         info->state = service_to_session_state(entry->state);
934
935         info->entry = entry;
936         info->entry->reason = info->reason;
937
938         if (explicit_connect(info->reason) == FALSE)
939                 return TRUE;
940
941         __connman_service_session_inc(info->entry->service);
942
943         return TRUE;
944 }
945
946 static connman_bool_t select_offline_service(struct session_info *info,
947                                         struct service_entry *entry)
948 {
949         if (explicit_connect(info->reason) == FALSE)
950                 return FALSE;
951
952         info->state = service_to_session_state(entry->state);
953
954         info->entry = entry;
955         info->entry->reason = info->reason;
956
957         __connman_service_session_inc(info->entry->service);
958         pending_timeout_add(0, call_connect, entry);
959
960         return TRUE;
961 }
962
963 static connman_bool_t select_service(struct session_info *info,
964                                 struct service_entry *entry)
965 {
966         DBG("service %p", entry->service);
967
968         if (is_connected(entry->state) == TRUE)
969                 return select_connected_service(info, entry);
970         else
971                 return select_offline_service(info, entry);
972 }
973
974 static void select_and_connect(struct connman_session *session,
975                                 enum connman_session_reason reason)
976 {
977         struct session_info *info = session->info;
978         struct service_entry *entry = NULL;
979         GSequenceIter *iter;
980
981         DBG("session %p reason %s", session, reason2string(reason));
982
983         info->reason = reason;
984
985         iter = g_sequence_get_begin_iter(session->service_list);
986
987         while (g_sequence_iter_is_end(iter) == FALSE) {
988                 entry = g_sequence_get(iter);
989
990                 switch (entry->state) {
991                 case CONNMAN_SERVICE_STATE_ASSOCIATION:
992                 case CONNMAN_SERVICE_STATE_CONFIGURATION:
993                 case CONNMAN_SERVICE_STATE_READY:
994                 case CONNMAN_SERVICE_STATE_ONLINE:
995                 case CONNMAN_SERVICE_STATE_IDLE:
996                 case CONNMAN_SERVICE_STATE_DISCONNECT:
997                         session->info_dirty |=
998                                 select_service(info, entry);
999                         return;
1000                 case CONNMAN_SERVICE_STATE_UNKNOWN:
1001                 case CONNMAN_SERVICE_STATE_FAILURE:
1002                         break;
1003                 }
1004
1005                 iter = g_sequence_iter_next(iter);
1006         }
1007 }
1008
1009 static struct service_entry *create_service_entry(struct connman_service *service,
1010                                         const char *name,
1011                                         enum connman_service_state state)
1012 {
1013         struct service_entry *entry;
1014         enum connman_service_type type;
1015         int idx;
1016
1017         entry = g_try_new0(struct service_entry, 1);
1018         if (entry == NULL)
1019                 return entry;
1020
1021         entry->reason = CONNMAN_SESSION_REASON_UNKNOWN;
1022         entry->state = state;
1023         if (name != NULL)
1024                 entry->name = name;
1025         else
1026                 entry->name = "";
1027         entry->service = service;
1028
1029         idx = __connman_service_get_index(entry->service);
1030         entry->ifname = connman_inet_ifname(idx);
1031         if (entry->ifname == NULL)
1032                 entry->ifname = g_strdup("");
1033
1034         type = connman_service_get_type(entry->service);
1035         entry->bearer = service2bearer(type);
1036
1037         return entry;
1038 }
1039
1040 static void destroy_service_entry(gpointer data)
1041 {
1042         struct service_entry *entry = data;
1043
1044         pending_timeout_remove_all(entry);
1045         g_free(entry->ifname);
1046
1047         g_free(entry);
1048 }
1049
1050 static void populate_service_list(struct connman_session *session)
1051 {
1052         struct service_entry *entry;
1053         GSequenceIter *iter;
1054
1055         session->service_hash =
1056                 g_hash_table_new_full(g_direct_hash, g_direct_equal,
1057                                         NULL, NULL);
1058         session->service_list = __connman_service_get_list(session,
1059                                                         service_match,
1060                                                         create_service_entry,
1061                                                         destroy_service_entry);
1062
1063         g_sequence_sort(session->service_list, sort_services, session);
1064
1065         iter = g_sequence_get_begin_iter(session->service_list);
1066
1067         while (g_sequence_iter_is_end(iter) == FALSE) {
1068                 entry = g_sequence_get(iter);
1069
1070                 DBG("service %p type %s name %s", entry->service,
1071                         service2bearer(connman_service_get_type(entry->service)),
1072                         entry->name);
1073
1074                 g_hash_table_replace(session->service_hash,
1075                                         entry->service, iter);
1076
1077                 iter = g_sequence_iter_next(iter);
1078         }
1079 }
1080
1081 static void session_changed(struct connman_session *session,
1082                                 enum connman_session_trigger trigger)
1083 {
1084         struct session_info *info = session->info;
1085         struct session_info *info_last = session->info_last;
1086         GSequenceIter *service_iter = NULL, *service_iter_last = NULL;
1087         GSequence *service_list_last;
1088         GHashTable *service_hash_last;
1089
1090         /*
1091          * TODO: This only a placeholder for the 'real' algorithm to
1092          * play a bit around. So we are going to improve it step by step.
1093          */
1094
1095         DBG("session %p trigger %s reason %s", session, trigger2string(trigger),
1096                                                 reason2string(info->reason));
1097
1098         if (info->entry != NULL) {
1099                 info->state = service_to_session_state(info->entry->state);
1100                 if (info_last->state != info->state)
1101                         session->info_dirty = TRUE;
1102         }
1103
1104         switch (trigger) {
1105         case CONNMAN_SESSION_TRIGGER_UNKNOWN:
1106                 DBG("ignore session changed event");
1107                 return;
1108         case CONNMAN_SESSION_TRIGGER_SETTING:
1109                 if (info->allowed_bearers != info_last->allowed_bearers) {
1110
1111                         service_hash_last = session->service_hash;
1112                         service_list_last = session->service_list;
1113
1114                         populate_service_list(session);
1115
1116                         if (info->entry != NULL) {
1117                                 service_iter_last = g_hash_table_lookup(
1118                                                         service_hash_last,
1119                                                         info->entry->service);
1120                                 service_iter = g_hash_table_lookup(
1121                                                         session->service_hash,
1122                                                         info->entry->service);
1123                         }
1124
1125                         if (service_iter == NULL && service_iter_last != NULL) {
1126                                 /*
1127                                  * The currently selected service is
1128                                  * not part of this session anymore.
1129                                  */
1130                                 deselect_and_disconnect(session, info->reason);
1131                         }
1132
1133                         g_hash_table_remove_all(service_hash_last);
1134                         g_sequence_free(service_list_last);
1135                 }
1136
1137                 if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED) {
1138                         select_and_connect(session,
1139                                         CONNMAN_SESSION_REASON_FREE_RIDE);
1140                 }
1141
1142                 break;
1143         case CONNMAN_SESSION_TRIGGER_CONNECT:
1144                 if (info->state >= CONNMAN_SESSION_STATE_CONNECTED) {
1145                         if (info->entry->reason == CONNMAN_SESSION_REASON_CONNECT)
1146                                 break;
1147                         info->entry->reason = CONNMAN_SESSION_REASON_CONNECT;
1148                         __connman_service_session_inc(info->entry->service);
1149                         break;
1150                 }
1151
1152                 if (info->entry != NULL &&
1153                                 is_connecting(info->entry->state) == TRUE) {
1154                         break;
1155                 }
1156
1157                 select_and_connect(session,
1158                                 CONNMAN_SESSION_REASON_CONNECT);
1159
1160                 break;
1161         case CONNMAN_SESSION_TRIGGER_DISCONNECT:
1162                 deselect_and_disconnect(session,
1163                                         CONNMAN_SESSION_REASON_DISCONNECT);
1164
1165                 break;
1166         case CONNMAN_SESSION_TRIGGER_PERIODIC:
1167                 if (info->state >= CONNMAN_SESSION_STATE_CONNECTED) {
1168                         info->entry->reason = CONNMAN_SESSION_REASON_PERIODIC;
1169                         __connman_service_session_inc(info->entry->service);
1170                         break;
1171                 }
1172
1173                 select_and_connect(session,
1174                                 CONNMAN_SESSION_REASON_PERIODIC);
1175
1176                 break;
1177         case CONNMAN_SESSION_TRIGGER_SERVICE:
1178                 if (info->entry != NULL &&
1179                         (is_connecting(info->entry->state) == TRUE ||
1180                                 is_connected(info->entry->state) == TRUE)) {
1181                         break;
1182                 }
1183
1184                 deselect_and_disconnect(session, info->reason);
1185
1186                 if (info->reason == CONNMAN_SESSION_REASON_FREE_RIDE ||
1187                                 info->stay_connected == TRUE) {
1188                         select_and_connect(session, info->reason);
1189                 }
1190
1191                 break;
1192         case CONNMAN_SESSION_TRIGGER_ECALL:
1193                 if (info->state == CONNMAN_SESSION_STATE_DISCONNECTED &&
1194                                 info->entry != NULL &&
1195                                 info->entry->service != NULL) {
1196                         deselect_and_disconnect(session, info->reason);
1197                 }
1198
1199                 break;
1200         }
1201
1202         session_notify(session);
1203 }
1204
1205 static DBusMessage *connect_session(DBusConnection *conn,
1206                                         DBusMessage *msg, void *user_data)
1207 {
1208         struct connman_session *session = user_data;
1209         struct session_info *info = session->info;
1210
1211         DBG("session %p", session);
1212
1213         if (ecall_info != NULL && ecall_info != info)
1214                 return __connman_error_failed(msg, EBUSY);
1215
1216         session_changed(session, CONNMAN_SESSION_TRIGGER_CONNECT);
1217
1218         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1219 }
1220
1221 static DBusMessage *disconnect_session(DBusConnection *conn,
1222                                         DBusMessage *msg, void *user_data)
1223 {
1224         struct connman_session *session = user_data;
1225         struct session_info *info = session->info;
1226
1227         DBG("session %p", session);
1228
1229         if (ecall_info != NULL && ecall_info != info)
1230                 return __connman_error_failed(msg, EBUSY);
1231
1232         session_changed(session, CONNMAN_SESSION_TRIGGER_DISCONNECT);
1233
1234         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1235 }
1236
1237 static void update_ecall_sessions(struct connman_session *session)
1238 {
1239         struct session_info *info = session->info;
1240         struct connman_session *session_iter;
1241         GHashTableIter iter;
1242         gpointer key, value;
1243
1244         g_hash_table_iter_init(&iter, session_hash);
1245
1246         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1247                 session_iter = value;
1248
1249                 if (session_iter == session)
1250                         continue;
1251
1252                 session_iter->info->ecall = info->ecall;
1253                 session_iter->info_dirty = TRUE;
1254
1255                 session_changed(session_iter, CONNMAN_SESSION_TRIGGER_ECALL);
1256         }
1257 }
1258
1259 static void update_ecall(struct connman_session *session)
1260 {
1261         struct session_info *info = session->info;
1262         struct session_info *info_last = session->info_last;
1263
1264         DBG("session %p ecall_info %p ecall %d -> %d", session,
1265                 ecall_info, info_last->ecall, info->ecall);
1266
1267         if (ecall_info == NULL) {
1268                 if (!(info_last->ecall == FALSE && info->ecall == TRUE))
1269                         goto err;
1270
1271                 ecall_info = info;
1272         } else if (ecall_info == info) {
1273                 if (!(info_last->ecall == TRUE && info->ecall == FALSE))
1274                         goto err;
1275
1276                 ecall_info = NULL;
1277         } else {
1278                 goto err;
1279         }
1280
1281         update_ecall_sessions(session);
1282
1283         session->info_dirty = TRUE;
1284         return;
1285
1286 err:
1287         /* not a valid transition */
1288         info->ecall = info_last->ecall;
1289 }
1290
1291 static DBusMessage *change_session(DBusConnection *conn,
1292                                         DBusMessage *msg, void *user_data)
1293 {
1294         struct connman_session *session = user_data;
1295         struct session_info *info = session->info;
1296         struct session_info *info_last = session->info_last;
1297         DBusMessageIter iter, value;
1298         const char *name;
1299         GSList *allowed_bearers;
1300
1301         DBG("session %p", session);
1302         if (dbus_message_iter_init(msg, &iter) == FALSE)
1303                 return __connman_error_invalid_arguments(msg);
1304
1305         dbus_message_iter_get_basic(&iter, &name);
1306         dbus_message_iter_next(&iter);
1307         dbus_message_iter_recurse(&iter, &value);
1308
1309         switch (dbus_message_iter_get_arg_type(&value)) {
1310         case DBUS_TYPE_ARRAY:
1311                 if (g_str_equal(name, "AllowedBearers") == TRUE) {
1312                         allowed_bearers = session_parse_allowed_bearers(&value);
1313
1314                         g_slist_foreach(info->allowed_bearers,
1315                                         cleanup_bearer_info, NULL);
1316                         g_slist_free(info->allowed_bearers);
1317
1318                         if (allowed_bearers == NULL) {
1319                                 allowed_bearers = session_allowed_bearers_any();
1320
1321                                 if (allowed_bearers == NULL)
1322                                         return __connman_error_failed(msg, ENOMEM);
1323                         }
1324
1325                         info->allowed_bearers = allowed_bearers;
1326
1327                         session->info_dirty = TRUE;
1328                 } else {
1329                         goto err;
1330                 }
1331                 break;
1332         case DBUS_TYPE_BOOLEAN:
1333                 if (g_str_equal(name, "Priority") == TRUE) {
1334                         dbus_message_iter_get_basic(&value,
1335                                         &info->priority);
1336
1337                         if (info_last->priority != info->priority)
1338                                 session->info_dirty = TRUE;
1339                 } else if (g_str_equal(name, "AvoidHandover") == TRUE) {
1340                         dbus_message_iter_get_basic(&value,
1341                                         &info->avoid_handover);
1342
1343                         if (info_last->avoid_handover != info->avoid_handover)
1344                                 session->info_dirty = TRUE;
1345                 } else if (g_str_equal(name, "StayConnected") == TRUE) {
1346                         dbus_message_iter_get_basic(&value,
1347                                         &info->stay_connected);
1348
1349                         if (info_last->stay_connected != info->stay_connected)
1350                                 session->info_dirty = TRUE;
1351                 } else if (g_str_equal(name, "EmergencyCall") == TRUE) {
1352                         dbus_message_iter_get_basic(&value,
1353                                         &info->ecall);
1354
1355                         update_ecall(session);
1356                 } else {
1357                         goto err;
1358                 }
1359                 break;
1360         case DBUS_TYPE_UINT32:
1361                 if (g_str_equal(name, "PeriodicConnect") == TRUE) {
1362                         dbus_message_iter_get_basic(&value,
1363                                         &info->periodic_connect);
1364
1365                         if (info_last->periodic_connect != info->periodic_connect)
1366                                 session->info_dirty = TRUE;
1367                 } else if (g_str_equal(name, "IdleTimeout") == TRUE) {
1368                         dbus_message_iter_get_basic(&value,
1369                                         &info->idle_timeout);
1370
1371                         if (info_last->idle_timeout != info->idle_timeout)
1372                                 session->info_dirty = TRUE;
1373                 } else {
1374                         goto err;
1375                 }
1376                 break;
1377         case DBUS_TYPE_STRING:
1378                 if (g_str_equal(name, "RoamingPolicy") == TRUE) {
1379                         const char *val;
1380                         dbus_message_iter_get_basic(&value, &val);
1381                         info->roaming_policy =
1382                                         string2roamingpolicy(val);
1383
1384                         if (info_last->roaming_policy != info->roaming_policy)
1385                                 session->info_dirty = TRUE;
1386                 } else {
1387                         goto err;
1388                 }
1389                 break;
1390         default:
1391                 goto err;
1392         }
1393
1394         if (session->info_dirty == TRUE)
1395                 session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING);
1396
1397         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1398
1399 err:
1400         return __connman_error_invalid_arguments(msg);
1401 }
1402
1403 static void release_session(gpointer key, gpointer value, gpointer user_data)
1404 {
1405         struct connman_session *session = value;
1406         DBusMessage *message;
1407
1408         DBG("owner %s path %s", session->owner, session->notify_path);
1409
1410         if (session->notify_watch > 0)
1411                 g_dbus_remove_watch(connection, session->notify_watch);
1412
1413         g_dbus_unregister_interface(connection, session->session_path,
1414                                                 CONNMAN_SESSION_INTERFACE);
1415
1416         message = dbus_message_new_method_call(session->owner,
1417                                                 session->notify_path,
1418                                                 CONNMAN_NOTIFICATION_INTERFACE,
1419                                                 "Release");
1420         if (message == NULL)
1421                 return;
1422
1423         dbus_message_set_no_reply(message, TRUE);
1424
1425         g_dbus_send_message(connection, message);
1426 }
1427
1428 static int session_disconnect(struct connman_session *session)
1429 {
1430         DBG("session %p, %s", session, session->owner);
1431
1432         if (session->notify_watch > 0)
1433                 g_dbus_remove_watch(connection, session->notify_watch);
1434
1435         g_dbus_unregister_interface(connection, session->session_path,
1436                                                 CONNMAN_SESSION_INTERFACE);
1437
1438         deselect_and_disconnect(session,
1439                                 CONNMAN_SESSION_REASON_DISCONNECT);
1440
1441         g_hash_table_remove(session_hash, session->session_path);
1442
1443         return 0;
1444 }
1445
1446 static void owner_disconnect(DBusConnection *conn, void *user_data)
1447 {
1448         struct connman_session *session = user_data;
1449
1450         DBG("session %p, %s died", session, session->owner);
1451
1452         session_disconnect(session);
1453 }
1454
1455 static DBusMessage *destroy_session(DBusConnection *conn,
1456                                         DBusMessage *msg, void *user_data)
1457 {
1458         struct connman_session *session = user_data;
1459         struct session_info *info = session->info;
1460
1461         DBG("session %p", session);
1462
1463         if (ecall_info != NULL && ecall_info != info)
1464                 return __connman_error_failed(msg, EBUSY);
1465
1466         session_disconnect(session);
1467
1468         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1469 }
1470
1471 static GDBusMethodTable session_methods[] = {
1472         { "Destroy",    "",   "", destroy_session    },
1473         { "Connect",    "",   "", connect_session    },
1474         { "Disconnect", "",   "", disconnect_session },
1475         { "Change",     "sv", "", change_session     },
1476         { },
1477 };
1478
1479 int __connman_session_create(DBusMessage *msg)
1480 {
1481         const char *owner, *notify_path;
1482         char *session_path = NULL;
1483         DBusMessageIter iter, array;
1484         struct connman_session *session = NULL;
1485         struct session_info *info, *info_last;
1486
1487         connman_bool_t priority = FALSE, avoid_handover = FALSE;
1488         connman_bool_t stay_connected = FALSE, ecall = FALSE;
1489         enum connman_session_roaming_policy roaming_policy =
1490                                 CONNMAN_SESSION_ROAMING_POLICY_FORBIDDEN;
1491         GSList *allowed_bearers = NULL;
1492         unsigned int periodic_connect = 0;
1493         unsigned int idle_timeout = 0;
1494
1495         int err;
1496
1497         owner = dbus_message_get_sender(msg);
1498
1499         DBG("owner %s", owner);
1500
1501         if (ecall_info != NULL) {
1502                 /*
1503                  * If there is an emergency call already going on,
1504                  * ignore session creation attempt
1505                  */
1506                 err = -EBUSY;
1507                 goto err;
1508         }
1509
1510         dbus_message_iter_init(msg, &iter);
1511         dbus_message_iter_recurse(&iter, &array);
1512
1513         while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
1514                 DBusMessageIter entry, value;
1515                 const char *key, *val;
1516
1517                 dbus_message_iter_recurse(&array, &entry);
1518                 dbus_message_iter_get_basic(&entry, &key);
1519
1520                 dbus_message_iter_next(&entry);
1521                 dbus_message_iter_recurse(&entry, &value);
1522
1523                 switch (dbus_message_iter_get_arg_type(&value)) {
1524                 case DBUS_TYPE_ARRAY:
1525                         if (g_str_equal(key, "AllowedBearers") == TRUE) {
1526                                 allowed_bearers =
1527                                         session_parse_allowed_bearers(&value);
1528                         } else {
1529                                 return -EINVAL;
1530                         }
1531                         break;
1532                 case DBUS_TYPE_BOOLEAN:
1533                         if (g_str_equal(key, "Priority") == TRUE) {
1534                                 dbus_message_iter_get_basic(&value,
1535                                                         &priority);
1536                         } else if (g_str_equal(key, "AvoidHandover") == TRUE) {
1537                                 dbus_message_iter_get_basic(&value,
1538                                                         &avoid_handover);
1539                         } else if (g_str_equal(key, "StayConnected") == TRUE) {
1540                                 dbus_message_iter_get_basic(&value,
1541                                                         &stay_connected);
1542                         } else if (g_str_equal(key, "EmergencyCall") == TRUE) {
1543                                 dbus_message_iter_get_basic(&value,
1544                                                         &ecall);
1545                         } else {
1546                                 return -EINVAL;
1547                         }
1548                         break;
1549                 case DBUS_TYPE_UINT32:
1550                         if (g_str_equal(key, "PeriodicConnect") == TRUE) {
1551                                 dbus_message_iter_get_basic(&value,
1552                                                         &periodic_connect);
1553                         } else if (g_str_equal(key, "IdleTimeout") == TRUE) {
1554                                 dbus_message_iter_get_basic(&value,
1555                                                         &idle_timeout);
1556                         } else {
1557                                 return -EINVAL;
1558                         }
1559                         break;
1560                 case DBUS_TYPE_STRING:
1561                         if (g_str_equal(key, "RoamingPolicy") == TRUE) {
1562                                 dbus_message_iter_get_basic(&value, &val);
1563                                 roaming_policy = string2roamingpolicy(val);
1564                         } else {
1565                                 return -EINVAL;
1566                         }
1567                 }
1568                 dbus_message_iter_next(&array);
1569         }
1570
1571         dbus_message_iter_next(&iter);
1572         dbus_message_iter_get_basic(&iter, &notify_path);
1573
1574         if (notify_path == NULL) {
1575                 err = -EINVAL;
1576                 goto err;
1577         }
1578
1579         session_path = g_strdup_printf("/sessions%s", notify_path);
1580         if (session_path == NULL) {
1581                 err = -ENOMEM;
1582                 goto err;
1583         }
1584
1585         session = g_hash_table_lookup(session_hash, session_path);
1586         if (session != NULL) {
1587                 session = NULL;
1588                 err = -EEXIST;
1589                 goto err;
1590         }
1591
1592         session = g_try_new0(struct connman_session, 1);
1593         if (session == NULL) {
1594                 err = -ENOMEM;
1595                 goto err;
1596         }
1597
1598         session->info = g_try_new0(struct session_info, 1);
1599         if (session->info == NULL) {
1600                 err = -ENOMEM;
1601                 goto err;
1602         }
1603
1604         session->info_last = g_try_new0(struct session_info, 1);
1605         if (session->info_last == NULL) {
1606                 err = -ENOMEM;
1607                 goto err;
1608         }
1609
1610         info = session->info;
1611         info_last = session->info_last;
1612
1613         session->owner = g_strdup(owner);
1614         session->session_path = session_path;
1615         session->notify_path = g_strdup(notify_path);
1616         session->notify_watch =
1617                 g_dbus_add_disconnect_watch(connection, session->owner,
1618                                         owner_disconnect, session, NULL);
1619
1620         info->state = CONNMAN_SESSION_STATE_DISCONNECTED;
1621         info->priority = priority;
1622         info->avoid_handover = avoid_handover;
1623         info->stay_connected = stay_connected;
1624         info->periodic_connect = periodic_connect;
1625         info->idle_timeout = idle_timeout;
1626         info->ecall = ecall;
1627         info->roaming_policy = roaming_policy;
1628         info->entry = NULL;
1629         info->marker = 0;
1630
1631         if (allowed_bearers == NULL) {
1632                 info->allowed_bearers =
1633                                 session_allowed_bearers_any();
1634
1635                 if (info->allowed_bearers == NULL) {
1636                         err = -ENOMEM;
1637                         goto err;
1638                 }
1639         } else {
1640                 info->allowed_bearers = allowed_bearers;
1641         }
1642
1643         g_hash_table_replace(session_hash, session->session_path, session);
1644
1645         DBG("add %s", session->session_path);
1646
1647         if (g_dbus_register_interface(connection, session->session_path,
1648                                         CONNMAN_SESSION_INTERFACE,
1649                                         session_methods, NULL,
1650                                         NULL, session, NULL) == FALSE) {
1651                 connman_error("Failed to register %s", session->session_path);
1652                 g_hash_table_remove(session_hash, session->session_path);
1653                 session = NULL;
1654
1655                 err = -EINVAL;
1656                 goto err;
1657         }
1658
1659         g_dbus_send_reply(connection, msg,
1660                                 DBUS_TYPE_OBJECT_PATH, &session->session_path,
1661                                 DBUS_TYPE_INVALID);
1662
1663
1664         populate_service_list(session);
1665         if (info->ecall == TRUE) {
1666                 ecall_info = info;
1667                 update_ecall_sessions(session);
1668         }
1669
1670         info_last->state = info->state;
1671         info_last->priority = info->priority;
1672         info_last->avoid_handover = info->avoid_handover;
1673         info_last->stay_connected = info->stay_connected;
1674         info_last->periodic_connect = info->periodic_connect;
1675         info_last->idle_timeout = info->idle_timeout;
1676         info_last->ecall = info->ecall;
1677         info_last->roaming_policy = info->roaming_policy;
1678         info_last->entry = info->entry;
1679         info_last->marker = info->marker;
1680         info_last->allowed_bearers = info->allowed_bearers;
1681
1682         session->info_dirty = TRUE;
1683         session->append_all = TRUE;
1684
1685         session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING);
1686
1687         return 0;
1688
1689 err:
1690         connman_error("Failed to create session");
1691
1692         if (session != NULL) {
1693                 if (session->info_last != NULL)
1694                         g_free(session->info_last);
1695                 if (session->info != NULL)
1696                         g_free(session->info);
1697                 g_free(session);
1698         }
1699
1700         g_free(session_path);
1701
1702         g_slist_foreach(allowed_bearers, cleanup_bearer_info, NULL);
1703         g_slist_free(allowed_bearers);
1704
1705         return err;
1706 }
1707
1708 int __connman_session_destroy(DBusMessage *msg)
1709 {
1710         const char *owner, *session_path;
1711         struct connman_session *session;
1712
1713         owner = dbus_message_get_sender(msg);
1714
1715         DBG("owner %s", owner);
1716
1717         dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &session_path,
1718                                                         DBUS_TYPE_INVALID);
1719         if (session_path == NULL)
1720                 return -EINVAL;
1721
1722         session = g_hash_table_lookup(session_hash, session_path);
1723         if (session == NULL)
1724                 return -EINVAL;
1725
1726         if (g_strcmp0(owner, session->owner) != 0)
1727                 return -EACCES;
1728
1729         session_disconnect(session);
1730
1731         return 0;
1732 }
1733
1734 connman_bool_t __connman_session_mode()
1735 {
1736         return sessionmode;
1737 }
1738
1739 void __connman_session_set_mode(connman_bool_t enable)
1740 {
1741         DBG("enable %d", enable);
1742
1743         if (sessionmode != enable) {
1744                 sessionmode = enable;
1745
1746                 connman_dbus_property_changed_basic(CONNMAN_MANAGER_PATH,
1747                                 CONNMAN_MANAGER_INTERFACE, "SessionMode",
1748                                 DBUS_TYPE_BOOLEAN, &sessionmode);
1749         }
1750
1751         if (sessionmode == TRUE)
1752                 __connman_service_disconnect_all();
1753 }
1754
1755 static void service_add(struct connman_service *service,
1756                         const char *name)
1757 {
1758         GHashTableIter iter;
1759         GSequenceIter *iter_service_list;
1760         gpointer key, value;
1761         struct connman_session *session;
1762         struct service_entry *entry;
1763
1764         DBG("service %p", service);
1765
1766         g_hash_table_iter_init(&iter, session_hash);
1767
1768         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1769                 session = value;
1770
1771                 if (service_match(session, service) == FALSE)
1772                         continue;
1773
1774                 entry = create_service_entry(service, name,
1775                                                 CONNMAN_SERVICE_STATE_IDLE);
1776                 if (entry == NULL)
1777                         continue;
1778
1779                 iter_service_list =
1780                         g_sequence_insert_sorted(session->service_list,
1781                                                         entry, sort_services,
1782                                                         session);
1783
1784                 g_hash_table_replace(session->service_hash, service,
1785                                         iter_service_list);
1786
1787                 session_changed(session, CONNMAN_SESSION_TRIGGER_SERVICE);
1788         }
1789 }
1790
1791 static void service_remove(struct connman_service *service)
1792 {
1793
1794         GHashTableIter iter;
1795         gpointer key, value;
1796         struct connman_session *session;
1797         struct session_info *info;
1798
1799         DBG("service %p", service);
1800
1801         g_hash_table_iter_init(&iter, session_hash);
1802
1803         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1804                 GSequenceIter *iter;
1805                 session = value;
1806                 info = session->info;
1807
1808                 iter = g_hash_table_lookup(session->service_hash, service);
1809                 if (iter == NULL)
1810                         continue;
1811
1812                 g_sequence_remove(iter);
1813
1814                 if (info->entry != NULL && info->entry->service == service)
1815                         info->entry = NULL;
1816                 session_changed(session, CONNMAN_SESSION_TRIGGER_SERVICE);
1817         }
1818 }
1819
1820 static void service_state_changed(struct connman_service *service,
1821                                         enum connman_service_state state)
1822 {
1823         GHashTableIter iter;
1824         gpointer key, value;
1825
1826         DBG("service %p state %d", service, state);
1827
1828         g_hash_table_iter_init(&iter, session_hash);
1829
1830         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1831                 struct connman_session *session = value;
1832                 GSequenceIter *service_iter;
1833
1834                 service_iter = g_hash_table_lookup(session->service_hash, service);
1835                 if (service_iter != NULL) {
1836                         struct service_entry *entry;
1837
1838                         entry = g_sequence_get(service_iter);
1839                         entry->state = state;
1840                 }
1841
1842                 session_changed(session,
1843                                 CONNMAN_SESSION_TRIGGER_SERVICE);
1844         }
1845 }
1846
1847 static void ipconfig_changed(struct connman_service *service,
1848                                 struct connman_ipconfig *ipconfig)
1849 {
1850         GHashTableIter iter;
1851         gpointer key, value;
1852         struct connman_session *session;
1853         struct session_info *info;
1854         enum connman_ipconfig_type type;
1855
1856         DBG("service %p ipconfig %p", service, ipconfig);
1857
1858         type = __connman_ipconfig_get_config_type(ipconfig);
1859
1860         g_hash_table_iter_init(&iter, session_hash);
1861
1862         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1863                 session = value;
1864                 info = session->info;
1865
1866                 if (info->entry != NULL && info->entry->service == service) {
1867                         if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
1868                                 ipconfig_ipv4_changed(session);
1869                         else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
1870                                 ipconfig_ipv6_changed(session);
1871                 }
1872         }
1873 }
1874
1875 static struct connman_notifier session_notifier = {
1876         .name                   = "session",
1877         .service_add            = service_add,
1878         .service_remove         = service_remove,
1879         .service_state_changed  = service_state_changed,
1880         .ipconfig_changed       = ipconfig_changed,
1881 };
1882
1883 int __connman_session_init(void)
1884 {
1885         int err;
1886
1887         DBG("");
1888
1889         connection = connman_dbus_get_connection();
1890         if (connection == NULL)
1891                 return -1;
1892
1893         err = connman_notifier_register(&session_notifier);
1894         if (err < 0) {
1895                 dbus_connection_unref(connection);
1896                 return err;
1897         }
1898
1899         session_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
1900                                                 NULL, cleanup_session);
1901
1902         sessionmode = FALSE;
1903         return 0;
1904 }
1905
1906 void __connman_session_cleanup(void)
1907 {
1908         DBG("");
1909
1910         if (connection == NULL)
1911                 return;
1912
1913         connman_notifier_unregister(&session_notifier);
1914
1915         g_hash_table_foreach(session_hash, release_session, NULL);
1916         g_hash_table_destroy(session_hash);
1917         session_hash = NULL;
1918
1919         dbus_connection_unref(connection);
1920 }