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