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