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