session: Move reason into service_entry
[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 <gdbus.h>
28
29 #include "connman.h"
30
31 static DBusConnection *connection;
32 static GHashTable *session_hash;
33 static connman_bool_t sessionmode;
34 static struct connman_session *ecall_session;
35
36 enum connman_session_trigger {
37         CONNMAN_SESSION_TRIGGER_UNKNOWN         = 0,
38         CONNMAN_SESSION_TRIGGER_SETTING         = 1,
39         CONNMAN_SESSION_TRIGGER_CONNECT         = 2,
40         CONNMAN_SESSION_TRIGGER_DISCONNECT      = 3,
41         CONNMAN_SESSION_TRIGGER_PERIODIC        = 4,
42         CONNMAN_SESSION_TRIGGER_SERVICE         = 5,
43         CONNMAN_SESSION_TRIGGER_ECALL           = 6,
44 };
45
46 enum connman_session_reason {
47         CONNMAN_SESSION_REASON_UNKNOWN          = 0,
48         CONNMAN_SESSION_REASON_CONNECT          = 1,
49         CONNMAN_SESSION_REASON_FREE_RIDE        = 2,
50         CONNMAN_SESSION_REASON_PERIODIC         = 3,
51 };
52
53 enum connman_session_roaming_policy {
54         CONNMAN_SESSION_ROAMING_POLICY_UNKNOWN          = 0,
55         CONNMAN_SESSION_ROAMING_POLICY_DEFAULT          = 1,
56         CONNMAN_SESSION_ROAMING_POLICY_ALWAYS           = 2,
57         CONNMAN_SESSION_ROAMING_POLICY_FORBIDDEN        = 3,
58         CONNMAN_SESSION_ROAMING_POLICY_NATIONAL         = 4,
59         CONNMAN_SESSION_ROAMING_POLICY_INTERNATIONAL    = 5,
60 };
61
62 struct service_entry {
63         /* track why this service was selected */
64         enum connman_session_reason reason;
65         struct connman_service *service;
66 };
67
68 struct session_info {
69         char *bearer;
70         const char *name;
71         char *ifname;
72         connman_bool_t online;
73         connman_bool_t priority;
74         GSList *allowed_bearers;
75         connman_bool_t avoid_handover;
76         connman_bool_t stay_connected;
77         unsigned int periodic_connect;
78         unsigned int idle_timeout;
79         connman_bool_t ecall;
80         enum connman_session_roaming_policy roaming_policy;
81         unsigned int marker;
82
83         struct service_entry *entry;
84 };
85
86 struct connman_session {
87         char *owner;
88         char *session_path;
89         char *notify_path;
90         guint notify_watch;
91
92         connman_bool_t append_all;
93         connman_bool_t info_dirty;
94         struct session_info info;
95         struct session_info info_last;
96
97         GSequence *service_list;
98         GHashTable *service_hash;
99 };
100
101 struct bearer_info {
102         char *name;
103         connman_bool_t match_all;
104         enum connman_service_type service_type;
105 };
106
107 static const char *trigger2string(enum connman_session_trigger trigger)
108 {
109         switch (trigger) {
110         case CONNMAN_SESSION_TRIGGER_UNKNOWN:
111                 break;
112         case CONNMAN_SESSION_TRIGGER_SETTING:
113                 return "setting";
114         case CONNMAN_SESSION_TRIGGER_CONNECT:
115                 return "connect";
116         case CONNMAN_SESSION_TRIGGER_DISCONNECT:
117                 return "disconnect";
118         case CONNMAN_SESSION_TRIGGER_PERIODIC:
119                 return "periodic";
120         case CONNMAN_SESSION_TRIGGER_SERVICE:
121                 return "service";
122         case CONNMAN_SESSION_TRIGGER_ECALL:
123                 return "ecall";
124         }
125
126         return NULL;
127 }
128
129 static const char *reason2string(enum connman_session_reason reason)
130 {
131         switch (reason) {
132         case CONNMAN_SESSION_REASON_UNKNOWN:
133                 break;
134         case CONNMAN_SESSION_REASON_CONNECT:
135                 return "connect";
136         case CONNMAN_SESSION_REASON_FREE_RIDE:
137                 return "free-ride";
138         case CONNMAN_SESSION_REASON_PERIODIC:
139                 return "periodic";
140         }
141
142         return NULL;
143 }
144
145 static const char *roamingpolicy2string(enum connman_session_roaming_policy policy)
146 {
147         switch (policy) {
148         case CONNMAN_SESSION_ROAMING_POLICY_UNKNOWN:
149                 break;
150         case CONNMAN_SESSION_ROAMING_POLICY_DEFAULT:
151                 return "default";
152         case CONNMAN_SESSION_ROAMING_POLICY_ALWAYS:
153                 return "always";
154         case CONNMAN_SESSION_ROAMING_POLICY_FORBIDDEN:
155                 return "forbidden";
156         case CONNMAN_SESSION_ROAMING_POLICY_NATIONAL:
157                 return "national";
158         case CONNMAN_SESSION_ROAMING_POLICY_INTERNATIONAL:
159                 return "international";
160         }
161
162         return NULL;
163 }
164
165 static enum connman_session_roaming_policy string2roamingpolicy(const char *policy)
166 {
167         if (g_strcmp0(policy, "default") == 0)
168                 return CONNMAN_SESSION_ROAMING_POLICY_DEFAULT;
169         else if (g_strcmp0(policy, "always") == 0)
170                 return CONNMAN_SESSION_ROAMING_POLICY_ALWAYS;
171         else if (g_strcmp0(policy, "forbidden") == 0)
172                 return CONNMAN_SESSION_ROAMING_POLICY_FORBIDDEN;
173         else if (g_strcmp0(policy, "national") == 0)
174                 return CONNMAN_SESSION_ROAMING_POLICY_NATIONAL;
175         else if (g_strcmp0(policy, "international") == 0)
176                 return CONNMAN_SESSION_ROAMING_POLICY_INTERNATIONAL;
177         else
178                 return CONNMAN_SESSION_ROAMING_POLICY_UNKNOWN;
179 }
180
181 static enum connman_service_type bearer2service(const char *bearer)
182 {
183         if (bearer == NULL)
184                 return CONNMAN_SERVICE_TYPE_UNKNOWN;
185
186         if (g_strcmp0(bearer, "ethernet") == 0)
187                 return CONNMAN_SERVICE_TYPE_ETHERNET;
188         else if (g_strcmp0(bearer, "wifi") == 0)
189                 return CONNMAN_SERVICE_TYPE_WIFI;
190         else if (g_strcmp0(bearer, "wimax") == 0)
191                 return CONNMAN_SERVICE_TYPE_WIMAX;
192         else if (g_strcmp0(bearer, "bluetooth") == 0)
193                 return CONNMAN_SERVICE_TYPE_BLUETOOTH;
194         else if (g_strcmp0(bearer, "3g") == 0)
195                 return CONNMAN_SERVICE_TYPE_CELLULAR;
196         else
197                 return CONNMAN_SERVICE_TYPE_UNKNOWN;
198 }
199
200 static char *service2bearer(enum connman_service_type type)
201 {
202         switch (type) {
203         case CONNMAN_SERVICE_TYPE_ETHERNET:
204                 return "ethernet";
205         case CONNMAN_SERVICE_TYPE_WIFI:
206                 return "wifi";
207         case CONNMAN_SERVICE_TYPE_WIMAX:
208                 return "wimax";
209         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
210                 return "bluetooth";
211         case CONNMAN_SERVICE_TYPE_CELLULAR:
212                 return "3g";
213         case CONNMAN_SERVICE_TYPE_UNKNOWN:
214         case CONNMAN_SERVICE_TYPE_SYSTEM:
215         case CONNMAN_SERVICE_TYPE_GPS:
216         case CONNMAN_SERVICE_TYPE_VPN:
217         case CONNMAN_SERVICE_TYPE_GADGET:
218                 return "";
219         }
220
221         return "";
222 }
223
224 static void cleanup_bearer_info(gpointer data, gpointer user_data)
225 {
226         struct bearer_info *info = data;
227
228         g_free(info->name);
229         g_free(info);
230 }
231
232 static GSList *session_parse_allowed_bearers(DBusMessageIter *iter)
233 {
234         struct bearer_info *info;
235         DBusMessageIter array;
236         GSList *list = NULL;
237
238         dbus_message_iter_recurse(iter, &array);
239
240         while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
241                 char *bearer = NULL;
242
243                 dbus_message_iter_get_basic(&array, &bearer);
244
245                 info = g_try_new0(struct bearer_info, 1);
246                 if (info == NULL) {
247                         g_slist_foreach(list, cleanup_bearer_info, NULL);
248                         g_slist_free(list);
249
250                         return NULL;
251                 }
252
253                 info->name = g_strdup(bearer);
254                 info->service_type = bearer2service(info->name);
255
256                 if (info->service_type == CONNMAN_SERVICE_TYPE_UNKNOWN &&
257                                 g_strcmp0(info->name, "*") == 0) {
258                         info->match_all = TRUE;
259                 } else {
260                         info->match_all = FALSE;
261                 }
262
263                 list = g_slist_append(list, info);
264
265                 dbus_message_iter_next(&array);
266         }
267
268         return list;
269 }
270
271 static GSList *session_allowed_bearers_any(void)
272 {
273         struct bearer_info *info;
274         GSList *list = NULL;
275
276         info = g_try_new0(struct bearer_info, 1);
277         if (info == NULL) {
278                 g_slist_free(list);
279
280                 return NULL;
281         }
282
283         info->name = g_strdup("");
284         info->match_all = TRUE;
285         info->service_type = CONNMAN_SERVICE_TYPE_UNKNOWN;
286
287         list = g_slist_append(list, info);
288
289         return list;
290 }
291
292 static void append_allowed_bearers(DBusMessageIter *iter, void *user_data)
293 {
294         struct session_info *info = user_data;
295         GSList *list;
296
297         for (list = info->allowed_bearers;
298                         list != NULL; list = list->next) {
299                 struct bearer_info *info = list->data;
300
301                 dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
302                                                 &info->name);
303         }
304 }
305
306 static void append_ipconfig_ipv4(DBusMessageIter *iter, void *user_data)
307 {
308         struct connman_service *service = user_data;
309         struct connman_ipconfig *ipconfig_ipv4;
310
311         if (service == NULL)
312                 return;
313
314         ipconfig_ipv4 = __connman_service_get_ip4config(service);
315         if (ipconfig_ipv4 == NULL)
316                 return;
317
318         __connman_ipconfig_append_ipv4(ipconfig_ipv4, iter);
319 }
320
321 static void append_ipconfig_ipv6(DBusMessageIter *iter, void *user_data)
322 {
323         struct connman_service *service = user_data;
324         struct connman_ipconfig *ipconfig_ipv4, *ipconfig_ipv6;
325
326         if (service == NULL)
327                 return;
328
329         ipconfig_ipv4 = __connman_service_get_ip4config(service);
330         ipconfig_ipv6 = __connman_service_get_ip6config(service);
331         if (ipconfig_ipv6 == NULL)
332                 return;
333
334         __connman_ipconfig_append_ipv6(ipconfig_ipv6, iter, ipconfig_ipv4);
335 }
336
337 static void append_notify(DBusMessageIter *dict,
338                                         struct connman_session *session)
339 {
340         struct session_info *info = &session->info;
341         struct session_info *info_last = &session->info_last;
342         const char *policy;
343         struct connman_service *service;
344
345         if (session->append_all == TRUE ||
346                         info->bearer != info_last->bearer) {
347                 connman_dbus_dict_append_basic(dict, "Bearer",
348                                                 DBUS_TYPE_STRING,
349                                                 &info->bearer);
350                 info_last->bearer = info->bearer;
351         }
352
353         if (session->append_all == TRUE ||
354                         info->online != info_last->online) {
355                 connman_dbus_dict_append_basic(dict, "Online",
356                                                 DBUS_TYPE_BOOLEAN,
357                                                 &info->online);
358                 info_last->online = info->online;
359         }
360
361         if (session->append_all == TRUE ||
362                         info->name != info_last->name) {
363                 connman_dbus_dict_append_basic(dict, "Name",
364                                                 DBUS_TYPE_STRING,
365                                                 &info->name);
366                 info_last->name = info->name;
367         }
368
369         if (session->append_all == TRUE ||
370                         info->entry != info_last->entry) {
371                 if (info->entry == NULL)
372                         service = NULL;
373                 else
374                         service = info->entry->service;
375
376                 connman_dbus_dict_append_dict(dict, "IPv4",
377                                                 append_ipconfig_ipv4,
378                                                 service);
379
380                 connman_dbus_dict_append_dict(dict, "IPv6",
381                                                 append_ipconfig_ipv6,
382                                                 service);
383
384                 connman_dbus_dict_append_basic(dict, "Interface",
385                                                 DBUS_TYPE_STRING,
386                                                 &info->ifname);
387
388                 info_last->ifname = info->ifname;
389                 info_last->entry = info->entry;
390         }
391
392
393         if (session->append_all == TRUE ||
394                         info->priority != info_last->priority) {
395                 connman_dbus_dict_append_basic(dict, "Priority",
396                                                 DBUS_TYPE_BOOLEAN,
397                                                 &info->priority);
398                 info_last->priority = info->priority;
399         }
400
401         if (session->append_all == TRUE ||
402                         info->allowed_bearers != info_last->allowed_bearers) {
403                 connman_dbus_dict_append_array(dict, "AllowedBearers",
404                                                 DBUS_TYPE_STRING,
405                                                 append_allowed_bearers,
406                                                 info);
407                 info_last->allowed_bearers = info->allowed_bearers;
408         }
409
410         if (session->append_all == TRUE ||
411                         info->avoid_handover != info_last->avoid_handover) {
412                 connman_dbus_dict_append_basic(dict, "AvoidHandover",
413                                                 DBUS_TYPE_BOOLEAN,
414                                                 &info->avoid_handover);
415                 info_last->avoid_handover = info->avoid_handover;
416         }
417
418         if (session->append_all == TRUE ||
419                         info->stay_connected != info_last->stay_connected) {
420                 connman_dbus_dict_append_basic(dict, "StayConnected",
421                                                 DBUS_TYPE_BOOLEAN,
422                                                 &info->stay_connected);
423                 info_last->stay_connected = info->stay_connected;
424         }
425
426         if (session->append_all == TRUE ||
427                         info->periodic_connect != info_last->periodic_connect) {
428                 connman_dbus_dict_append_basic(dict, "PeriodicConnect",
429                                                 DBUS_TYPE_UINT32,
430                                                 &info->periodic_connect);
431                 info_last->periodic_connect = info->periodic_connect;
432         }
433
434         if (session->append_all == TRUE ||
435                         info->idle_timeout != info_last->idle_timeout) {
436                 connman_dbus_dict_append_basic(dict, "IdleTimeout",
437                                                 DBUS_TYPE_UINT32,
438                                                 &info->idle_timeout);
439                 info_last->idle_timeout = info->idle_timeout;
440         }
441
442         if (session->append_all == TRUE ||
443                         info->ecall != info_last->ecall) {
444                 connman_dbus_dict_append_basic(dict, "EmergencyCall",
445                                                 DBUS_TYPE_BOOLEAN,
446                                                 &info->ecall);
447                 info_last->ecall = info->ecall;
448         }
449
450         if (session->append_all == TRUE ||
451                         info->roaming_policy != info_last->roaming_policy) {
452                 policy = roamingpolicy2string(info->roaming_policy);
453                 connman_dbus_dict_append_basic(dict, "RoamingPolicy",
454                                                 DBUS_TYPE_STRING,
455                                                 &policy);
456                 info_last->roaming_policy = info->roaming_policy;
457         }
458
459         if (session->append_all == TRUE ||
460                         info->marker != info_last->marker) {
461                 connman_dbus_dict_append_basic(dict, "SessionMarker",
462                                                 DBUS_TYPE_UINT32,
463                                                 &info->marker);
464                 info_last->marker = info->marker;
465         }
466
467         session->append_all = FALSE;
468         session->info_dirty = FALSE;
469 }
470
471 static gboolean session_notify(gpointer user_data)
472 {
473         struct connman_session *session = user_data;
474         DBusMessage *msg;
475         DBusMessageIter array, dict;
476
477         if (session->info_dirty == FALSE)
478                 return 0;
479
480         DBG("session %p owner %s notify_path %s", session,
481                 session->owner, session->notify_path);
482
483         msg = dbus_message_new_method_call(session->owner, session->notify_path,
484                                                 CONNMAN_NOTIFICATION_INTERFACE,
485                                                 "Update");
486         if (msg == NULL)
487                 return FALSE;
488
489         dbus_message_iter_init_append(msg, &array);
490         connman_dbus_dict_open(&array, &dict);
491
492         append_notify(&dict, session);
493
494         connman_dbus_dict_close(&array, &dict);
495
496         g_dbus_send_message(connection, msg);
497
498         session->info_dirty = FALSE;
499
500         return FALSE;
501 }
502
503 static void ipconfig_ipv4_changed(struct connman_session *session)
504 {
505         struct session_info *info = &session->info;
506
507         connman_dbus_setting_changed_dict(session->owner, session->notify_path,
508                                                 "IPv4", append_ipconfig_ipv4,
509                                                 info->entry->service);
510 }
511
512 static void ipconfig_ipv6_changed(struct connman_session *session)
513 {
514         struct session_info *info = &session->info;
515
516         connman_dbus_setting_changed_dict(session->owner, session->notify_path,
517                                                 "IPv6", append_ipconfig_ipv6,
518                                                 info->entry->service);
519 }
520
521 static connman_bool_t service_type_match(struct connman_session *session,
522                                         struct connman_service *service)
523 {
524         struct session_info *info = &session->info;
525         GSList *list;
526
527         for (list = info->allowed_bearers;
528                         list != NULL; list = list->next) {
529                 struct bearer_info *info = list->data;
530                 enum connman_service_type service_type;
531
532                 if (info->match_all == TRUE)
533                         return TRUE;
534
535                 service_type = connman_service_get_type(service);
536                 if (info->service_type == service_type)
537                         return TRUE;
538         }
539
540         return FALSE;
541 }
542
543 static connman_bool_t service_match(struct connman_session *session,
544                                         struct connman_service *service)
545 {
546         if (service_type_match(session, service) == FALSE)
547                 return FALSE;
548
549         return TRUE;
550 }
551
552 static int service_type_weight(enum connman_service_type type)
553 {
554         /*
555          * The session doesn't care which service
556          * to use. Nevertheless we have to sort them
557          * according their type. The ordering is
558          *
559          * 1. Ethernet
560          * 2. Bluetooth
561          * 3. WiFi/WiMAX
562          * 4. GSM/UTMS/3G
563          */
564
565         switch (type) {
566         case CONNMAN_SERVICE_TYPE_ETHERNET:
567                 return 4;
568         case CONNMAN_SERVICE_TYPE_BLUETOOTH:
569                 return 3;
570         case CONNMAN_SERVICE_TYPE_WIFI:
571         case CONNMAN_SERVICE_TYPE_WIMAX:
572                 return 2;
573         case CONNMAN_SERVICE_TYPE_CELLULAR:
574                 return 1;
575         case CONNMAN_SERVICE_TYPE_UNKNOWN:
576         case CONNMAN_SERVICE_TYPE_SYSTEM:
577         case CONNMAN_SERVICE_TYPE_GPS:
578         case CONNMAN_SERVICE_TYPE_VPN:
579         case CONNMAN_SERVICE_TYPE_GADGET:
580                 break;
581         }
582
583         return 0;
584 }
585
586 static gint sort_allowed_bearers(struct connman_service *service_a,
587                                         struct connman_service *service_b,
588                                         struct connman_session *session)
589 {
590         struct session_info *info = &session->info;
591         GSList *list;
592         enum connman_service_type type_a, type_b;
593         int weight_a, weight_b;
594
595         type_a = connman_service_get_type(service_a);
596         type_b = connman_service_get_type(service_b);
597
598         for (list = info->allowed_bearers;
599                         list != NULL; list = list->next) {
600                 struct bearer_info *info = list->data;
601
602                 if (info->match_all == TRUE) {
603                         if (type_a != type_b) {
604                                 weight_a = service_type_weight(type_a);
605                                 weight_b = service_type_weight(type_b);
606
607                                 if (weight_a > weight_b)
608                                         return -1;
609
610                                 if (weight_a < weight_b)
611                                         return 1;
612
613                                 return 0;
614                         }
615                 }
616
617                 if (type_a == info->service_type &&
618                                 type_b == info->service_type) {
619                         return 0;
620                 }
621
622                 if (type_a == info->service_type &&
623                                 type_b != info->service_type) {
624                         return -1;
625                 }
626
627                 if (type_a != info->service_type &&
628                                 type_b == info->service_type) {
629                         return 1;
630                 }
631         }
632
633         return 0;
634 }
635
636 static gint sort_services(gconstpointer a, gconstpointer b, gpointer user_data)
637 {
638         struct connman_service *service_a = (void *)a;
639         struct connman_service *service_b = (void *)b;
640         struct connman_session *session = user_data;
641
642         return sort_allowed_bearers(service_a, service_b, session);
643 }
644
645 static void cleanup_session(gpointer user_data)
646 {
647         struct connman_session *session = user_data;
648         struct session_info *info = &session->info;
649
650         DBG("remove %s", session->session_path);
651
652         g_hash_table_destroy(session->service_hash);
653         g_sequence_free(session->service_list);
654
655         if (info->entry != NULL &&
656                         info->entry->reason == CONNMAN_SESSION_REASON_CONNECT) {
657                 __connman_service_disconnect(info->entry->service);
658         }
659
660         g_slist_foreach(info->allowed_bearers, cleanup_bearer_info, NULL);
661         g_slist_free(info->allowed_bearers);
662
663         g_free(session->owner);
664         g_free(session->session_path);
665         g_free(session->notify_path);
666
667         g_free(session);
668 }
669
670 static void release_session(gpointer key, gpointer value, gpointer user_data)
671 {
672         struct connman_session *session = value;
673         DBusMessage *message;
674
675         DBG("owner %s path %s", session->owner, session->notify_path);
676
677         if (session->notify_watch > 0)
678                 g_dbus_remove_watch(connection, session->notify_watch);
679
680         g_dbus_unregister_interface(connection, session->session_path,
681                                                 CONNMAN_SESSION_INTERFACE);
682
683         message = dbus_message_new_method_call(session->owner,
684                                                 session->notify_path,
685                                                 CONNMAN_NOTIFICATION_INTERFACE,
686                                                 "Release");
687         if (message == NULL)
688                 return;
689
690         dbus_message_set_no_reply(message, TRUE);
691
692         g_dbus_send_message(connection, message);
693 }
694
695 static int session_disconnect(struct connman_session *session)
696 {
697         DBG("session %p, %s", session, session->owner);
698
699         if (session->notify_watch > 0)
700                 g_dbus_remove_watch(connection, session->notify_watch);
701
702         g_dbus_unregister_interface(connection, session->session_path,
703                                                 CONNMAN_SESSION_INTERFACE);
704
705         g_hash_table_remove(session_hash, session->session_path);
706
707         return 0;
708 }
709
710 static void owner_disconnect(DBusConnection *conn, void *user_data)
711 {
712         struct connman_session *session = user_data;
713
714         DBG("session %p, %s died", session, session->owner);
715
716         session_disconnect(session);
717 }
718
719 static DBusMessage *destroy_session(DBusConnection *conn,
720                                         DBusMessage *msg, void *user_data)
721 {
722         struct connman_session *session = user_data;
723
724         DBG("session %p", session);
725
726         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
727 }
728
729 static void update_info(struct session_info *info)
730 {
731         enum connman_service_type type;
732         int idx;
733
734         if (info->entry != NULL) {
735                 type = connman_service_get_type(info->entry->service);
736                 info->bearer = service2bearer(type);
737
738                 info->online = __connman_service_is_connected(info->entry->service);
739                 info->name = __connman_service_get_name(info->entry->service);
740                 if (info->name == NULL)
741                         info->name = "";
742
743                 idx = __connman_service_get_index(info->entry->service);
744                 info->ifname = connman_inet_ifname(idx);
745                 if (info->ifname == NULL)
746                         info->ifname = "";
747         } else {
748                 info->bearer = "";
749                 info->online = FALSE;
750                 info->name = "";
751                 info->ifname = "";
752         }
753 }
754
755 static connman_bool_t explicit_connect(enum connman_session_reason reason)
756 {
757         switch (reason) {
758         case CONNMAN_SESSION_REASON_UNKNOWN:
759         case CONNMAN_SESSION_REASON_FREE_RIDE:
760                 break;
761         case CONNMAN_SESSION_REASON_CONNECT:
762         case CONNMAN_SESSION_REASON_PERIODIC:
763                 return TRUE;
764         }
765
766         return FALSE;
767 }
768
769 static void test_and_disconnect(struct connman_session *session)
770 {
771         struct session_info *info = &session->info;
772
773         if (explicit_connect(info->entry->reason) == FALSE)
774                 goto out;
775
776         if (__connman_service_session_dec(info->entry->service) == TRUE)
777                 goto out;
778
779         if (ecall_session != NULL && ecall_session != session)
780                 goto out;
781
782         __connman_service_disconnect(info->entry->service);
783
784 out:
785         info->entry = NULL;
786 }
787
788 static void select_and_connect(struct connman_session *session,
789                                 enum connman_session_reason reason)
790 {
791         struct session_info *info = &session->info;
792         struct service_entry *entry = NULL;
793         GSequenceIter *iter;
794         connman_bool_t do_connect = FALSE;
795
796         DBG("session %p reason %s", session, reason2string(reason));
797
798         iter = g_sequence_get_begin_iter(session->service_list);
799
800         while (g_sequence_iter_is_end(iter) == FALSE) {
801                 entry = g_sequence_get(iter);
802
803                 if (__connman_service_is_connecting(entry->service) == TRUE ||
804                                 __connman_service_is_connected(entry->service) == TRUE) {
805                         break;
806                 }
807
808                 if (__connman_service_is_idle(entry->service) == TRUE &&
809                                 explicit_connect(reason) == TRUE) {
810                         do_connect = TRUE;
811                         break;
812                 }
813
814                 entry = NULL;
815
816                 iter = g_sequence_iter_next(iter);
817         }
818
819         if (info->entry != NULL && info->entry != entry)
820                 test_and_disconnect(session);
821
822         if (entry != NULL) {
823                 info->entry = entry;
824                 info->entry->reason = reason;
825
826                 if (explicit_connect(reason) == TRUE)
827                         __connman_service_session_inc(info->entry->service);
828
829                 if (do_connect == TRUE)
830                         __connman_service_connect(info->entry->service);
831         }
832 }
833
834 static void session_changed(struct connman_session *session,
835                                 enum connman_session_trigger trigger)
836 {
837         struct session_info *info = &session->info;
838         struct session_info *info_last = &session->info_last;
839         GSequenceIter *iter;
840
841         /*
842          * TODO: This only a placeholder for the 'real' algorithm to
843          * play a bit around. So we are going to improve it step by step.
844          */
845
846         DBG("session %p trigger %s", session, trigger2string(trigger));
847
848         switch (trigger) {
849         case CONNMAN_SESSION_TRIGGER_UNKNOWN:
850                 DBG("ignore session changed event");
851                 return;
852         case CONNMAN_SESSION_TRIGGER_SETTING:
853                 if (info->entry != NULL) {
854                         iter = g_hash_table_lookup(session->service_hash,
855                                                         info->entry->service);
856                         if (iter == NULL) {
857                                 /*
858                                  * This service is not part of this
859                                  * session anymore.
860                                  */
861                                 test_and_disconnect(session);
862                         }
863                 }
864
865                 if (info->online == FALSE) {
866                         select_and_connect(session,
867                                         CONNMAN_SESSION_REASON_FREE_RIDE);
868                 }
869
870                 break;
871         case CONNMAN_SESSION_TRIGGER_CONNECT:
872                 if (info->online == TRUE) {
873                         info->entry->reason = CONNMAN_SESSION_REASON_CONNECT;
874                         __connman_service_session_inc(info->entry->service);
875                         break;
876                 }
877
878                 select_and_connect(session,
879                                 CONNMAN_SESSION_REASON_CONNECT);
880
881                 break;
882         case CONNMAN_SESSION_TRIGGER_DISCONNECT:
883                 test_and_disconnect(session);
884
885                 break;
886         case CONNMAN_SESSION_TRIGGER_PERIODIC:
887                 if (info->online == TRUE) {
888                         info->entry->reason = CONNMAN_SESSION_REASON_PERIODIC;
889                         __connman_service_session_inc(info->entry->service);
890                         break;
891                 }
892
893                 select_and_connect(session,
894                                 CONNMAN_SESSION_REASON_PERIODIC);
895
896                 break;
897         case CONNMAN_SESSION_TRIGGER_SERVICE:
898                 if (info->online == TRUE)
899                         break;
900
901                 if (info->stay_connected == TRUE) {
902                         DBG("StayConnected");
903                         select_and_connect(session,
904                                         CONNMAN_SESSION_REASON_CONNECT);
905
906                         break;
907                 }
908
909                 select_and_connect(session,
910                                 CONNMAN_SESSION_REASON_FREE_RIDE);
911
912                 break;
913         case CONNMAN_SESSION_TRIGGER_ECALL:
914                 if (info->online == FALSE && info->entry->service != NULL)
915                         test_and_disconnect(session);
916
917                 break;
918         }
919
920         if (info->entry != info_last->entry) {
921                 update_info(info);
922                 session->info_dirty = TRUE;
923         }
924
925         session_notify(session);
926 }
927
928 static DBusMessage *connect_session(DBusConnection *conn,
929                                         DBusMessage *msg, void *user_data)
930 {
931         struct connman_session *session = user_data;
932
933         DBG("session %p", session);
934
935         if (ecall_session != NULL && ecall_session != session)
936                 return __connman_error_failed(msg, EBUSY);
937
938         session_changed(session, CONNMAN_SESSION_TRIGGER_CONNECT);
939
940         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
941 }
942
943 static DBusMessage *disconnect_session(DBusConnection *conn,
944                                         DBusMessage *msg, void *user_data)
945 {
946         struct connman_session *session = user_data;
947
948         DBG("session %p", session);
949
950         if (ecall_session != NULL && ecall_session != session)
951                 return __connman_error_failed(msg, EBUSY);
952
953         session_changed(session, CONNMAN_SESSION_TRIGGER_DISCONNECT);
954
955         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
956 }
957
958 static struct service_entry *create_service_entry(struct connman_service *service,
959                                         const char *name,
960                                         enum connman_service_state state)
961 {
962         struct service_entry *entry;
963
964         entry = g_try_new0(struct service_entry, 1);
965         if (entry == NULL)
966                 return entry;
967
968         entry->reason = CONNMAN_SESSION_REASON_UNKNOWN;
969         entry->service = service;
970
971         return entry;
972 }
973
974 static void destroy_service_entry(gpointer data)
975 {
976         struct service_entry *entry = data;
977
978         g_free(entry);
979 }
980
981 static void update_allowed_bearers(struct connman_session *session)
982 {
983         struct service_entry *entry;
984         GSequenceIter *iter;
985
986         if (session->service_list != NULL) {
987                 g_hash_table_remove_all(session->service_hash);
988                 g_sequence_free(session->service_list);
989         }
990
991         session->service_list = __connman_service_get_list(session,
992                                                         service_match,
993                                                         create_service_entry,
994                                                         destroy_service_entry);
995
996         g_sequence_sort(session->service_list, sort_services, session);
997
998         iter = g_sequence_get_begin_iter(session->service_list);
999
1000         while (g_sequence_iter_is_end(iter) == FALSE) {
1001                 entry = g_sequence_get(iter);
1002
1003                 DBG("service %p type %s name %s", entry->service,
1004                         service2bearer(connman_service_get_type(entry->service)),
1005                         __connman_service_get_name(entry->service));
1006
1007                 g_hash_table_replace(session->service_hash,
1008                                         entry->service, iter);
1009
1010                 iter = g_sequence_iter_next(iter);
1011         }
1012
1013         session->info_dirty = TRUE;
1014 }
1015
1016 static void update_ecall_sessions(struct connman_session *session)
1017 {
1018         struct session_info *info = &session->info;
1019         struct connman_session *session_iter;
1020         GHashTableIter iter;
1021         gpointer key, value;
1022
1023         g_hash_table_iter_init(&iter, session_hash);
1024
1025         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1026                 session_iter = value;
1027
1028                 if (session_iter == session)
1029                         continue;
1030
1031                 session_iter->info.ecall = info->ecall;
1032                 session_iter->info_dirty = TRUE;
1033
1034                 session_changed(session_iter, CONNMAN_SESSION_TRIGGER_ECALL);
1035         }
1036 }
1037
1038 static void update_ecall(struct connman_session *session)
1039 {
1040         struct session_info *info = &session->info;
1041         struct session_info *info_last = &session->info_last;
1042
1043         DBG("session %p ecall_session %p ecall %d -> %d", session,
1044                 ecall_session, info_last->ecall, info->ecall);
1045
1046         if (ecall_session == NULL) {
1047                 if (!(info_last->ecall == FALSE && info->ecall == TRUE))
1048                         goto err;
1049
1050                 ecall_session = session;
1051         } else if (ecall_session == session) {
1052                 if (!(info_last->ecall == TRUE && info->ecall == FALSE))
1053                         goto err;
1054
1055                 ecall_session = NULL;
1056         } else {
1057                 goto err;
1058         }
1059
1060         update_ecall_sessions(session);
1061
1062         session->info_dirty = TRUE;
1063         return;
1064
1065 err:
1066         /* not a valid transition */
1067         info->ecall = info_last->ecall;
1068 }
1069
1070 static DBusMessage *change_session(DBusConnection *conn,
1071                                         DBusMessage *msg, void *user_data)
1072 {
1073         struct connman_session *session = user_data;
1074         struct session_info *info = &session->info;
1075         struct session_info *info_last = &session->info_last;
1076         DBusMessageIter iter, value;
1077         const char *name;
1078         GSList *allowed_bearers;
1079
1080         DBG("session %p", session);
1081         if (dbus_message_iter_init(msg, &iter) == FALSE)
1082                 return __connman_error_invalid_arguments(msg);
1083
1084         dbus_message_iter_get_basic(&iter, &name);
1085         dbus_message_iter_next(&iter);
1086         dbus_message_iter_recurse(&iter, &value);
1087
1088         switch (dbus_message_iter_get_arg_type(&value)) {
1089         case DBUS_TYPE_ARRAY:
1090                 if (g_str_equal(name, "AllowedBearers") == TRUE) {
1091                         allowed_bearers = session_parse_allowed_bearers(&value);
1092
1093                         g_slist_foreach(info->allowed_bearers,
1094                                         cleanup_bearer_info, NULL);
1095                         g_slist_free(info->allowed_bearers);
1096
1097                         if (allowed_bearers == NULL) {
1098                                 allowed_bearers = session_allowed_bearers_any();
1099
1100                                 if (allowed_bearers == NULL)
1101                                         return __connman_error_failed(msg, ENOMEM);
1102                         }
1103
1104                         info->allowed_bearers = allowed_bearers;
1105
1106                         update_allowed_bearers(session);
1107                 } else {
1108                         goto err;
1109                 }
1110                 break;
1111         case DBUS_TYPE_BOOLEAN:
1112                 if (g_str_equal(name, "Priority") == TRUE) {
1113                         dbus_message_iter_get_basic(&value,
1114                                         &info->priority);
1115
1116                         if (info_last->priority != info->priority)
1117                                 session->info_dirty = TRUE;
1118                 } else if (g_str_equal(name, "AvoidHandover") == TRUE) {
1119                         dbus_message_iter_get_basic(&value,
1120                                         &info->avoid_handover);
1121
1122                         if (info_last->avoid_handover != info->avoid_handover)
1123                                 session->info_dirty = TRUE;
1124                 } else if (g_str_equal(name, "StayConnected") == TRUE) {
1125                         dbus_message_iter_get_basic(&value,
1126                                         &info->stay_connected);
1127
1128                         if (info_last->stay_connected != info->stay_connected)
1129                                 session->info_dirty = TRUE;
1130                 } else if (g_str_equal(name, "EmergencyCall") == TRUE) {
1131                         dbus_message_iter_get_basic(&value,
1132                                         &info->ecall);
1133
1134                         update_ecall(session);
1135                 } else {
1136                         goto err;
1137                 }
1138                 break;
1139         case DBUS_TYPE_UINT32:
1140                 if (g_str_equal(name, "PeriodicConnect") == TRUE) {
1141                         dbus_message_iter_get_basic(&value,
1142                                         &info->periodic_connect);
1143
1144                         if (info_last->periodic_connect != info->periodic_connect)
1145                                 session->info_dirty = TRUE;
1146                 } else if (g_str_equal(name, "IdleTimeout") == TRUE) {
1147                         dbus_message_iter_get_basic(&value,
1148                                         &info->idle_timeout);
1149
1150                         if (info_last->idle_timeout != info->idle_timeout)
1151                                 session->info_dirty = TRUE;
1152                 } else {
1153                         goto err;
1154                 }
1155                 break;
1156         case DBUS_TYPE_STRING:
1157                 if (g_str_equal(name, "RoamingPolicy") == TRUE) {
1158                         const char *val;
1159                         dbus_message_iter_get_basic(&value, &val);
1160                         info->roaming_policy =
1161                                         string2roamingpolicy(val);
1162
1163                         if (info_last->roaming_policy != info->roaming_policy)
1164                                 session->info_dirty = TRUE;
1165                 } else {
1166                         goto err;
1167                 }
1168                 break;
1169         default:
1170                 goto err;
1171         }
1172
1173         if (session->info_dirty == TRUE)
1174                 session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING);
1175
1176         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1177
1178 err:
1179         return __connman_error_invalid_arguments(msg);
1180 }
1181
1182 static GDBusMethodTable session_methods[] = {
1183         { "Destroy",    "",   "", destroy_session    },
1184         { "Connect",    "",   "", connect_session    },
1185         { "Disconnect", "",   "", disconnect_session },
1186         { "Change",     "sv", "", change_session     },
1187         { },
1188 };
1189
1190 int __connman_session_create(DBusMessage *msg)
1191 {
1192         const char *owner, *notify_path;
1193         char *session_path = NULL;
1194         DBusMessageIter iter, array;
1195         struct connman_session *session;
1196         struct session_info *info, *info_last;
1197
1198         connman_bool_t priority = FALSE, avoid_handover = FALSE;
1199         connman_bool_t stay_connected = FALSE, ecall = FALSE;
1200         enum connman_session_roaming_policy roaming_policy =
1201                                 CONNMAN_SESSION_ROAMING_POLICY_FORBIDDEN;
1202         GSList *allowed_bearers = NULL;
1203         unsigned int periodic_connect = 0;
1204         unsigned int idle_timeout = 0;
1205
1206         int err;
1207
1208         owner = dbus_message_get_sender(msg);
1209
1210         DBG("owner %s", owner);
1211
1212         if (ecall_session != NULL) {
1213                 /*
1214                  * If there is an emergency call already going on,
1215                  * ignore session creation attempt
1216                  */
1217                 err = -EBUSY;
1218                 goto err;
1219         }
1220
1221         dbus_message_iter_init(msg, &iter);
1222         dbus_message_iter_recurse(&iter, &array);
1223
1224         while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
1225                 DBusMessageIter entry, value;
1226                 const char *key, *val;
1227
1228                 dbus_message_iter_recurse(&array, &entry);
1229                 dbus_message_iter_get_basic(&entry, &key);
1230
1231                 dbus_message_iter_next(&entry);
1232                 dbus_message_iter_recurse(&entry, &value);
1233
1234                 switch (dbus_message_iter_get_arg_type(&value)) {
1235                 case DBUS_TYPE_ARRAY:
1236                         if (g_str_equal(key, "AllowedBearers") == TRUE) {
1237                                 allowed_bearers =
1238                                         session_parse_allowed_bearers(&value);
1239                         } else {
1240                                 return -EINVAL;
1241                         }
1242                         break;
1243                 case DBUS_TYPE_BOOLEAN:
1244                         if (g_str_equal(key, "Priority") == TRUE) {
1245                                 dbus_message_iter_get_basic(&value,
1246                                                         &priority);
1247                         } else if (g_str_equal(key, "AvoidHandover") == TRUE) {
1248                                 dbus_message_iter_get_basic(&value,
1249                                                         &avoid_handover);
1250                         } else if (g_str_equal(key, "StayConnected") == TRUE) {
1251                                 dbus_message_iter_get_basic(&value,
1252                                                         &stay_connected);
1253                         } else if (g_str_equal(key, "EmergencyCall") == TRUE) {
1254                                 dbus_message_iter_get_basic(&value,
1255                                                         &ecall);
1256                         } else {
1257                                 return -EINVAL;
1258                         }
1259                         break;
1260                 case DBUS_TYPE_UINT32:
1261                         if (g_str_equal(key, "PeriodicConnect") == TRUE) {
1262                                 dbus_message_iter_get_basic(&value,
1263                                                         &periodic_connect);
1264                         } else if (g_str_equal(key, "IdleTimeout") == TRUE) {
1265                                 dbus_message_iter_get_basic(&value,
1266                                                         &idle_timeout);
1267                         } else {
1268                                 return -EINVAL;
1269                         }
1270                         break;
1271                 case DBUS_TYPE_STRING:
1272                         if (g_str_equal(key, "RoamingPolicy") == TRUE) {
1273                                 dbus_message_iter_get_basic(&value, &val);
1274                                 roaming_policy = string2roamingpolicy(val);
1275                         } else {
1276                                 return -EINVAL;
1277                         }
1278                 }
1279                 dbus_message_iter_next(&array);
1280         }
1281
1282         dbus_message_iter_next(&iter);
1283         dbus_message_iter_get_basic(&iter, &notify_path);
1284
1285         if (notify_path == NULL) {
1286                 err = -EINVAL;
1287                 goto err;
1288         }
1289
1290         session_path = g_strdup_printf("/sessions%s", notify_path);
1291         if (session_path == NULL) {
1292                 err = -ENOMEM;
1293                 goto err;
1294         }
1295
1296         session = g_hash_table_lookup(session_hash, session_path);
1297         if (session != NULL) {
1298                 err = -EEXIST;
1299                 goto err;
1300         }
1301
1302         session = g_try_new0(struct connman_session, 1);
1303         if (session == NULL) {
1304                 err = -ENOMEM;
1305                 goto err;
1306         }
1307
1308         info = &session->info;
1309         info_last = &session->info_last;
1310
1311         session->owner = g_strdup(owner);
1312         session->session_path = session_path;
1313         session->notify_path = g_strdup(notify_path);
1314         session->notify_watch =
1315                 g_dbus_add_disconnect_watch(connection, session->owner,
1316                                         owner_disconnect, session, NULL);
1317
1318         session->service_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
1319                                                 NULL, NULL);
1320
1321         info->bearer = "";
1322         info->online = FALSE;
1323         info->priority = priority;
1324         info->avoid_handover = avoid_handover;
1325         info->stay_connected = stay_connected;
1326         info->periodic_connect = periodic_connect;
1327         info->idle_timeout = idle_timeout;
1328         info->ecall = ecall;
1329         info->roaming_policy = roaming_policy;
1330         info->entry = NULL;
1331         info->marker = 0;
1332
1333         if (allowed_bearers == NULL) {
1334                 info->allowed_bearers =
1335                                 session_allowed_bearers_any();
1336
1337                 if (info->allowed_bearers == NULL) {
1338                         err = -ENOMEM;
1339                         goto err;
1340                 }
1341         } else {
1342                 info->allowed_bearers = allowed_bearers;
1343         }
1344
1345         g_hash_table_replace(session_hash, session->session_path, session);
1346
1347         DBG("add %s", session->session_path);
1348
1349         if (g_dbus_register_interface(connection, session->session_path,
1350                                         CONNMAN_SESSION_INTERFACE,
1351                                         session_methods, NULL,
1352                                         NULL, session, NULL) == FALSE) {
1353                 connman_error("Failed to register %s", session->session_path);
1354                 g_hash_table_remove(session_hash, session->session_path);
1355                 session = NULL;
1356
1357                 err = -EINVAL;
1358                 goto err;
1359         }
1360
1361         g_dbus_send_reply(connection, msg,
1362                                 DBUS_TYPE_OBJECT_PATH, &session->session_path,
1363                                 DBUS_TYPE_INVALID);
1364
1365
1366         update_allowed_bearers(session);
1367         update_info(info);
1368         if (info->ecall == TRUE) {
1369                 ecall_session = session;
1370                 update_ecall_sessions(session);
1371         }
1372
1373         info_last->bearer = info->bearer;
1374         info_last->online = info->online;
1375         info_last->priority = info->priority;
1376         info_last->avoid_handover = info->avoid_handover;
1377         info_last->stay_connected = info->stay_connected;
1378         info_last->periodic_connect = info->periodic_connect;
1379         info_last->idle_timeout = info->idle_timeout;
1380         info_last->ecall = info->ecall;
1381         info_last->roaming_policy = info->roaming_policy;
1382         info_last->entry = info->entry;
1383         info_last->marker = info->marker;
1384         info_last->allowed_bearers = info->allowed_bearers;
1385         update_info(info_last);
1386
1387         session->info_dirty = TRUE;
1388         session->append_all = TRUE;
1389
1390         session_changed(session, CONNMAN_SESSION_TRIGGER_SETTING);
1391
1392         return 0;
1393
1394 err:
1395         connman_error("Failed to create session");
1396         g_free(session_path);
1397
1398         g_slist_foreach(allowed_bearers, cleanup_bearer_info, NULL);
1399         g_slist_free(allowed_bearers);
1400
1401         return err;
1402 }
1403
1404 int __connman_session_destroy(DBusMessage *msg)
1405 {
1406         const char *owner, *session_path;
1407         struct connman_session *session;
1408
1409         owner = dbus_message_get_sender(msg);
1410
1411         DBG("owner %s", owner);
1412
1413         dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &session_path,
1414                                                         DBUS_TYPE_INVALID);
1415         if (session_path == NULL)
1416                 return -EINVAL;
1417
1418         session = g_hash_table_lookup(session_hash, session_path);
1419         if (session == NULL)
1420                 return -EINVAL;
1421
1422         if (g_strcmp0(owner, session->owner) != 0)
1423                 return -EACCES;
1424
1425         session_disconnect(session);
1426
1427         return 0;
1428 }
1429
1430 connman_bool_t __connman_session_mode()
1431 {
1432         return sessionmode;
1433 }
1434
1435 void __connman_session_set_mode(connman_bool_t enable)
1436 {
1437         DBG("enable %d", enable);
1438
1439         if (sessionmode == enable)
1440                 return;
1441
1442         sessionmode = enable;
1443
1444         if (sessionmode == TRUE)
1445                 __connman_service_disconnect_all();
1446 }
1447
1448 static void service_add(struct connman_service *service)
1449 {
1450         GHashTableIter iter;
1451         GSequenceIter *iter_service_list;
1452         gpointer key, value;
1453         struct connman_session *session;
1454
1455         DBG("service %p", service);
1456
1457         g_hash_table_iter_init(&iter, session_hash);
1458
1459         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1460                 session = value;
1461
1462                 if (service_match(session, service) == FALSE)
1463                         continue;
1464
1465                 iter_service_list =
1466                         g_sequence_insert_sorted(session->service_list,
1467                                                         service, sort_services,
1468                                                         session);
1469
1470                 g_hash_table_replace(session->service_hash, service,
1471                                         iter_service_list);
1472
1473                 session_changed(session, CONNMAN_SESSION_TRIGGER_SERVICE);
1474         }
1475 }
1476
1477 static void service_remove(struct connman_service *service)
1478 {
1479
1480         GHashTableIter iter;
1481         gpointer key, value;
1482         struct connman_session *session;
1483         struct session_info *info;
1484
1485         DBG("service %p", service);
1486
1487         g_hash_table_iter_init(&iter, session_hash);
1488
1489         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1490                 GSequenceIter *iter;
1491                 session = value;
1492                 info = &session->info;
1493
1494                 iter = g_hash_table_lookup(session->service_hash, service);
1495                 if (iter == NULL)
1496                         continue;
1497
1498                 g_sequence_remove(iter);
1499
1500                 info->entry = NULL;
1501                 session_changed(session, CONNMAN_SESSION_TRIGGER_SERVICE);
1502         }
1503 }
1504
1505 static void service_state_changed(struct connman_service *service,
1506                                         enum connman_service_state state)
1507 {
1508         GHashTableIter iter;
1509         gpointer key, value;
1510         struct connman_session *session;
1511         struct session_info *info, *info_last;
1512
1513         DBG("service %p state %d", service, state);
1514
1515         g_hash_table_iter_init(&iter, session_hash);
1516
1517         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1518                 session = value;
1519                 info = &session->info;
1520                 info_last = &session->info_last;
1521
1522                 if (info->entry != NULL && info->entry->service == service) {
1523                         info->online = __connman_service_is_connected(service);
1524                         if (info_last->online != info->online)
1525                                 session->info_dirty = TRUE;
1526                 }
1527
1528                 session_changed(session,
1529                                 CONNMAN_SESSION_TRIGGER_SERVICE);
1530         }
1531 }
1532
1533 static void ipconfig_changed(struct connman_service *service,
1534                                 struct connman_ipconfig *ipconfig)
1535 {
1536         GHashTableIter iter;
1537         gpointer key, value;
1538         struct connman_session *session;
1539         struct session_info *info;
1540         enum connman_ipconfig_type type;
1541
1542         DBG("service %p ipconfig %p", service, ipconfig);
1543
1544         type = __connman_ipconfig_get_config_type(ipconfig);
1545
1546         g_hash_table_iter_init(&iter, session_hash);
1547
1548         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
1549                 session = value;
1550                 info = &session->info;
1551
1552                 if (info->entry != NULL && info->entry->service == service) {
1553                         if (type == CONNMAN_IPCONFIG_TYPE_IPV4)
1554                                 ipconfig_ipv4_changed(session);
1555                         else if (type == CONNMAN_IPCONFIG_TYPE_IPV6)
1556                                 ipconfig_ipv6_changed(session);
1557                 }
1558         }
1559 }
1560
1561 static struct connman_notifier session_notifier = {
1562         .name                   = "session",
1563         .service_add            = service_add,
1564         .service_remove         = service_remove,
1565         .service_state_changed  = service_state_changed,
1566         .ipconfig_changed       = ipconfig_changed,
1567 };
1568
1569 int __connman_session_init(void)
1570 {
1571         int err;
1572
1573         DBG("");
1574
1575         connection = connman_dbus_get_connection();
1576         if (connection == NULL)
1577                 return -1;
1578
1579         err = connman_notifier_register(&session_notifier);
1580         if (err < 0) {
1581                 dbus_connection_unref(connection);
1582                 return err;
1583         }
1584
1585         session_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
1586                                                 NULL, cleanup_session);
1587
1588         sessionmode = FALSE;
1589         return 0;
1590 }
1591
1592 void __connman_session_cleanup(void)
1593 {
1594         DBG("");
1595
1596         if (connection == NULL)
1597                 return;
1598
1599         connman_notifier_unregister(&session_notifier);
1600
1601         g_hash_table_foreach(session_hash, release_session, NULL);
1602         g_hash_table_destroy(session_hash);
1603
1604         dbus_connection_unref(connection);
1605 }