/* * * oFono - Open Source Telephony * * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "ofono.h" #include "common.h" #include "simutil.h" #define CALL_FORWARDING_FLAG_CACHED 0x1 #define CALL_FORWARDING_FLAG_CPHS_CFF 0x2 /* According to 27.007 Spec */ #define DEFAULT_NO_REPLY_TIMEOUT 20 #define is_cfu_enabled(_cf) \ ({ \ cf_find_unconditional(_cf) ? TRUE : FALSE; \ }) enum call_forwarding_type { CALL_FORWARDING_TYPE_UNCONDITIONAL = 0, CALL_FORWARDING_TYPE_BUSY = 1, CALL_FORWARDING_TYPE_NO_REPLY = 2, CALL_FORWARDING_TYPE_NOT_REACHABLE = 3, CALL_FORWARDING_TYPE_ALL = 4, CALL_FORWARDING_TYPE_ALL_CONDITIONAL = 5 }; struct ofono_call_forwarding { GSList *cf_conditions[4]; int flags; DBusMessage *pending; int query_next; int query_end; struct cf_ss_request *ss_req; struct ofono_sim *sim; struct ofono_sim_context *sim_context; unsigned char cfis_record_id; struct ofono_ussd *ussd; unsigned int ussd_watch; const struct ofono_call_forwarding_driver *driver; void *driver_data; struct ofono_atom *atom; }; struct cf_ss_request { int ss_type; int cf_type; int cls; GSList *cf_list[4]; }; static GSList *g_drivers = NULL; static void get_query_next_cf_cond(struct ofono_call_forwarding *cf); static void set_query_next_cf_cond(struct ofono_call_forwarding *cf); static void ss_set_query_next_cf_cond(struct ofono_call_forwarding *cf); static gint cf_cond_compare(gconstpointer a, gconstpointer b) { const struct ofono_call_forwarding_condition *ca = a; const struct ofono_call_forwarding_condition *cb = b; return ca->cls - cb->cls; } static struct ofono_call_forwarding_condition *cf_cond_find(GSList *l, int cls) { struct ofono_call_forwarding_condition *c; for (; l; l = l->next) { c = l->data; if (c->cls == cls) return c; } return NULL; } static int cf_cond_find_timeout(GSList *l, int cls) { struct ofono_call_forwarding_condition *cond = cf_cond_find(l, cls); return cond ? cond->time : DEFAULT_NO_REPLY_TIMEOUT; } static void cf_cond_list_print(GSList *l) { struct ofono_call_forwarding_condition *cond; for (; l ; l = l->next) { cond = l->data; DBG("CF Condition status: %d, class: %d, number: %s," " number_type: %d, time: %d", cond->status, cond->cls, cond->phone_number.number, cond->phone_number.type, cond->time); } } static GSList *cf_cond_list_create(int total, const struct ofono_call_forwarding_condition *list) { GSList *l = NULL; int i; int j; struct ofono_call_forwarding_condition *cond; /* * Specification is not really clear how the results are reported, * so assume both multiple list items & compound values of class * are possible */ for (i = 0; i < total; i++) { for (j = 1; j <= BEARER_CLASS_PAD; j = j << 1) { if (!(list[i].cls & j)) continue; if (list[i].status == 0) continue; cond = g_try_new0( struct ofono_call_forwarding_condition, 1); if (cond == NULL) continue; memcpy(cond, &list[i], sizeof(struct ofono_call_forwarding_condition)); cond->cls = j; l = g_slist_insert_sorted(l, cond, cf_cond_compare); } } return l; } static inline void cf_clear_all(struct ofono_call_forwarding *cf) { int i; for (i = 0; i < 4; i++) { g_slist_free_full(cf->cf_conditions[i], g_free); cf->cf_conditions[i] = NULL; } } static const char *cf_type_lut[] = { "Unconditional", "Busy", "NoReply", "NotReachable", "All", "AllConditional" }; static void sim_cfis_update_cb(int ok, void *data) { if (!ok) ofono_info("Failed to update EFcfis"); } static void sim_cphs_cff_update_cb(int ok, void *data) { if (!ok) ofono_info("Failed to update EFcphs-cff"); } static inline struct ofono_call_forwarding_condition *cf_find_unconditional( struct ofono_call_forwarding *cf) { return cf_cond_find( cf->cf_conditions[CALL_FORWARDING_TYPE_UNCONDITIONAL], BEARER_CLASS_VOICE); } static void sim_set_cf_indicator(struct ofono_call_forwarding *cf) { struct ofono_call_forwarding_condition *cfu_voice = cf_find_unconditional(cf); if (cf->cfis_record_id) { unsigned char data[16]; int number_len; memset(data, 0xff, sizeof(data)); /* Profile Identifier */ data[0] = 0x01; if (cfu_voice) { number_len = strlen(cfu_voice->phone_number.number); /* CFU indicator Status - Voice */ data[1] = 0x01; number_len = (number_len + 1) / 2; data[2] = number_len + 1; data[3] = cfu_voice->phone_number.type; sim_encode_bcd_number(cfu_voice->phone_number.number, data + 4); } else { data[1] = 0x00; data[2] = 1; data[3] = 128; } ofono_sim_write(cf->sim_context, SIM_EFCFIS_FILEID, sim_cfis_update_cb, OFONO_SIM_FILE_STRUCTURE_FIXED, cf->cfis_record_id, data, sizeof(data), cf); return; } if (cf->flags & CALL_FORWARDING_FLAG_CPHS_CFF) { unsigned char cff_voice = cfu_voice ? 0x0A : 0x05; ofono_sim_write(cf->sim_context, SIM_EF_CPHS_CFF_FILEID, sim_cphs_cff_update_cb, OFONO_SIM_FILE_STRUCTURE_TRANSPARENT, 0, &cff_voice, sizeof(cff_voice), cf); } } static void set_new_cond_list(struct ofono_call_forwarding *cf, int type, GSList *list) { GSList *old = cf->cf_conditions[type]; DBusConnection *conn = ofono_dbus_get_connection(); const char *path = __ofono_atom_get_path(cf->atom); GSList *l; GSList *o; struct ofono_call_forwarding_condition *lc; struct ofono_call_forwarding_condition *oc; const char *number; dbus_uint16_t timeout; char attr[64]; char tattr[64]; gboolean update_sim = FALSE; gboolean old_cfu; gboolean new_cfu; if ((cf->flags & CALL_FORWARDING_FLAG_CPHS_CFF) || cf->cfis_record_id > 0) old_cfu = is_cfu_enabled(cf); else old_cfu = FALSE; for (l = list; l; l = l->next) { lc = l->data; /* * New condition lists might have attributes we don't care about * triggered by e.g. ss control magic strings just skip them * here. For now we only support Voice, although Fax & all Data * basic services are applicable as well. */ if (lc->cls > BEARER_CLASS_VOICE) continue; timeout = lc->time; number = phone_number_to_string(&lc->phone_number); snprintf(attr, sizeof(attr), "%s%s", bearer_class_to_string(lc->cls), cf_type_lut[type]); if (type == CALL_FORWARDING_TYPE_NO_REPLY) snprintf(tattr, sizeof(tattr), "%sTimeout", attr); oc = cf_cond_find(old, lc->cls); if (oc) { /* On the old list, must be active */ if (oc->phone_number.type != lc->phone_number.type || strcmp(oc->phone_number.number, lc->phone_number.number)) { ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, attr, DBUS_TYPE_STRING, &number); if (type == CALL_FORWARDING_TYPE_UNCONDITIONAL) update_sim = TRUE; } if (type == CALL_FORWARDING_TYPE_NO_REPLY && oc->time != lc->time) ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, tattr, DBUS_TYPE_UINT16, &timeout); /* Remove from the old list */ old = g_slist_remove(old, oc); g_free(oc); } else { number = phone_number_to_string(&lc->phone_number); ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, attr, DBUS_TYPE_STRING, &number); if (type == CALL_FORWARDING_TYPE_UNCONDITIONAL) update_sim = TRUE; if (type == CALL_FORWARDING_TYPE_NO_REPLY && lc->time != DEFAULT_NO_REPLY_TIMEOUT) ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, tattr, DBUS_TYPE_UINT16, &timeout); } } timeout = DEFAULT_NO_REPLY_TIMEOUT; number = ""; for (o = old; o; o = o->next) { oc = o->data; /* * For now we only support Voice, although Fax & all Data * basic services are applicable as well. */ if (oc->cls > BEARER_CLASS_VOICE) continue; snprintf(attr, sizeof(attr), "%s%s", bearer_class_to_string(oc->cls), cf_type_lut[type]); if (type == CALL_FORWARDING_TYPE_NO_REPLY) snprintf(tattr, sizeof(tattr), "%sTimeout", attr); ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, attr, DBUS_TYPE_STRING, &number); if (type == CALL_FORWARDING_TYPE_UNCONDITIONAL) update_sim = TRUE; if (type == CALL_FORWARDING_TYPE_NO_REPLY && oc->time != DEFAULT_NO_REPLY_TIMEOUT) ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, tattr, DBUS_TYPE_UINT16, &timeout); } g_slist_free_full(old, g_free); cf->cf_conditions[type] = list; if (update_sim == TRUE) sim_set_cf_indicator(cf); if ((cf->flags & CALL_FORWARDING_FLAG_CPHS_CFF) || cf->cfis_record_id > 0) new_cfu = is_cfu_enabled(cf); else new_cfu = FALSE; if (new_cfu != old_cfu) { ofono_bool_t status = new_cfu; int i; /* * Emit signals to mask/unmask conditional cfs on cfu change */ for (i = 0; i < 4; i++) { if (i == CALL_FORWARDING_TYPE_UNCONDITIONAL) continue; lc = cf_cond_find(cf->cf_conditions[i], BEARER_CLASS_VOICE); if (lc == NULL) continue; if (new_cfu) number = ""; else number = phone_number_to_string( &lc->phone_number); ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, cf_type_lut[i], DBUS_TYPE_STRING, &number); } ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, "ForwardingFlagOnSim", DBUS_TYPE_BOOLEAN, &status); } } static inline void property_append_cf_condition(DBusMessageIter *dict, int cls, const char *postfix, const char *value, dbus_uint16_t timeout) { char attr[64]; char tattr[64]; int addt = !strcmp(postfix, "NoReply"); snprintf(attr, sizeof(attr), "%s%s", bearer_class_to_string(cls), postfix); if (addt) snprintf(tattr, sizeof(tattr), "%s%sTimeout", bearer_class_to_string(cls), postfix); ofono_dbus_dict_append(dict, attr, DBUS_TYPE_STRING, &value); if (addt) ofono_dbus_dict_append(dict, tattr, DBUS_TYPE_UINT16, &timeout); } static void property_append_cf_conditions(DBusMessageIter *dict, GSList *cf_list, int mask, const char *postfix) { GSList *l; int i; struct ofono_call_forwarding_condition *cf; const char *number; for (i = 1, l = cf_list; i <= BEARER_CLASS_PAD; i = i << 1) { if (!(mask & i)) continue; while (l && (cf = l->data) && (cf->cls < i)) l = l->next; if (l == NULL || cf->cls != i) { property_append_cf_condition(dict, i, postfix, "", DEFAULT_NO_REPLY_TIMEOUT); continue; } number = phone_number_to_string(&cf->phone_number); property_append_cf_condition(dict, i, postfix, number, cf->time); } } static DBusMessage *cf_get_properties_reply(DBusMessage *msg, struct ofono_call_forwarding *cf) { DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; int i; dbus_bool_t status; gboolean cfu_enabled; GSList *cf_list; reply = dbus_message_new_method_return(msg); if (reply == NULL) return NULL; dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, OFONO_PROPERTIES_ARRAY_SIGNATURE, &dict); cfu_enabled = is_cfu_enabled(cf); for (i = 0; i < 4; i++) { /* * Report conditional cfs as empty when CFU is active */ if (cfu_enabled && (i != CALL_FORWARDING_TYPE_UNCONDITIONAL)) cf_list = NULL; else cf_list = cf->cf_conditions[i]; property_append_cf_conditions(&dict, cf_list, BEARER_CLASS_VOICE, cf_type_lut[i]); } if ((cf->flags & CALL_FORWARDING_FLAG_CPHS_CFF) || cf->cfis_record_id > 0) status = cfu_enabled; else status = FALSE; ofono_dbus_dict_append(&dict, "ForwardingFlagOnSim", DBUS_TYPE_BOOLEAN, &status); dbus_message_iter_close_container(&iter, &dict); return reply; } static void get_query_cf_callback(const struct ofono_error *error, int total, const struct ofono_call_forwarding_condition *list, void *data) { struct ofono_call_forwarding *cf = data; if (error->type == OFONO_ERROR_TYPE_NO_ERROR) { GSList *l = cf_cond_list_create(total, list); set_new_cond_list(cf, cf->query_next, l); DBG("%s conditions:", cf_type_lut[cf->query_next]); cf_cond_list_print(l); if (cf->query_next == CALL_FORWARDING_TYPE_NOT_REACHABLE) cf->flags |= CALL_FORWARDING_FLAG_CACHED; } if (cf->query_next == CALL_FORWARDING_TYPE_NOT_REACHABLE) { __ofono_dbus_pending_reply(&cf->pending, cf_get_properties_reply(cf->pending, cf)); return; } cf->query_next++; get_query_next_cf_cond(cf); } static inline void get_query_next_cf_cond(struct ofono_call_forwarding *cf) { cf->driver->query(cf, cf->query_next, BEARER_CLASS_DEFAULT, get_query_cf_callback, cf); } static DBusMessage *cf_get_properties(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_call_forwarding *cf = data; struct ofono_modem *modem = __ofono_atom_get_modem(cf->atom); if ((cf->flags & CALL_FORWARDING_FLAG_CACHED) || ofono_modem_get_online(modem) == FALSE) return cf_get_properties_reply(msg, cf); if (cf->driver->query == NULL) return __ofono_error_not_implemented(msg); if (__ofono_call_forwarding_is_busy(cf) || __ofono_ussd_is_busy(cf->ussd)) return __ofono_error_busy(msg); cf->pending = dbus_message_ref(msg); cf->query_next = 0; get_query_next_cf_cond(cf); return NULL; } static gboolean cf_condition_enabled_property(struct ofono_call_forwarding *cf, const char *property, int *out_type, int *out_cls) { int i; int j; int len; const char *prefix; for (i = 1; i <= BEARER_CLASS_VOICE; i = i << 1) { prefix = bearer_class_to_string(i); len = strlen(prefix); if (strncmp(property, prefix, len)) continue; /* * We check the 4 call forwarding types, e.g. * unconditional, busy, no reply, not reachable */ for (j = 0; j < 4; j++) if (!strcmp(property+len, cf_type_lut[j])) { *out_type = j; *out_cls = i; return TRUE; } } return FALSE; } static gboolean cf_condition_timeout_property(const char *property, int *out_cls) { int i; int len; const char *prefix; for (i = 1; i <= BEARER_CLASS_VOICE; i = i << 1) { prefix = bearer_class_to_string(i); len = strlen(prefix); if (strncmp(property, prefix, len)) continue; if (!strcmp(property+len, "NoReplyTimeout")) { *out_cls = i; return TRUE; } } return FALSE; } static void set_query_cf_callback(const struct ofono_error *error, int total, const struct ofono_call_forwarding_condition *list, void *data) { struct ofono_call_forwarding *cf = data; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { ofono_error("Setting succeeded, but query failed"); cf->flags &= ~CALL_FORWARDING_FLAG_CACHED; __ofono_dbus_pending_reply(&cf->pending, __ofono_error_failed(cf->pending)); return; } if (cf->query_next == cf->query_end) __ofono_dbus_pending_reply(&cf->pending, dbus_message_new_method_return(cf->pending)); set_new_cond_list(cf, cf->query_next, cf_cond_list_create(total, list)); DBG("%s conditions:", cf_type_lut[cf->query_next]); cf_cond_list_print(cf->cf_conditions[cf->query_next]); if (cf->query_next == cf->query_end) return; cf->query_next++; set_query_next_cf_cond(cf); } static void set_query_next_cf_cond(struct ofono_call_forwarding *cf) { cf->driver->query(cf, cf->query_next, BEARER_CLASS_DEFAULT, set_query_cf_callback, cf); } static void set_property_callback(const struct ofono_error *error, void *data) { struct ofono_call_forwarding *cf = data; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { DBG("Error occurred during set/erasure"); __ofono_dbus_pending_reply(&cf->pending, __ofono_error_failed(cf->pending)); return; } /* Successfully set, query the entire set just in case */ set_query_next_cf_cond(cf); } static DBusMessage *set_property_request(struct ofono_call_forwarding *cf, DBusMessage *msg, int type, int cls, struct ofono_phone_number *ph, int timeout) { if (ph->number[0] != '\0' && cf->driver->registration == NULL) return __ofono_error_not_implemented(msg); if (ph->number[0] == '\0' && cf->driver->erasure == NULL) return __ofono_error_not_implemented(msg); cf->pending = dbus_message_ref(msg); cf->query_next = type; cf->query_end = type; DBG("Farming off request, will be erasure: %d", ph->number[0] == '\0'); if (ph->number[0] != '\0') cf->driver->registration(cf, type, cls, ph, timeout, set_property_callback, cf); else cf->driver->erasure(cf, type, cls, set_property_callback, cf); return NULL; } static DBusMessage *cf_set_property(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_call_forwarding *cf = data; struct ofono_modem *modem = __ofono_atom_get_modem(cf->atom); DBusMessageIter iter; DBusMessageIter var; const char *property; int cls; int type; if (ofono_modem_get_online(modem) == FALSE) return __ofono_error_not_available(msg); if (__ofono_call_forwarding_is_busy(cf) || __ofono_ussd_is_busy(cf->ussd)) return __ofono_error_busy(msg); if (!dbus_message_iter_init(msg, &iter)) return __ofono_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) return __ofono_error_invalid_args(msg); dbus_message_iter_get_basic(&iter, &property); dbus_message_iter_next(&iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) return __ofono_error_invalid_args(msg); dbus_message_iter_recurse(&iter, &var); if (cf_condition_timeout_property(property, &cls)) { dbus_uint16_t timeout; struct ofono_call_forwarding_condition *c; type = CALL_FORWARDING_TYPE_NO_REPLY; if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_UINT16) return __ofono_error_invalid_args(msg); dbus_message_iter_get_basic(&var, &timeout); if (timeout < 1 || timeout > 30) return __ofono_error_invalid_format(msg); c = cf_cond_find(cf->cf_conditions[type], cls); if (c == NULL) return __ofono_error_failed(msg); return set_property_request(cf, msg, type, cls, &c->phone_number, timeout); } else if (cf_condition_enabled_property(cf, property, &type, &cls)) { struct ofono_phone_number ph; const char *number; int timeout; ph.number[0] = '\0'; ph.type = 129; if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING) return __ofono_error_invalid_args(msg); dbus_message_iter_get_basic(&var, &number); if (strlen(number) > 0 && !valid_phone_number_format(number)) return __ofono_error_invalid_format(msg); /* * Don't set conditional cfs when cfu is active */ if (type != CALL_FORWARDING_TYPE_UNCONDITIONAL && number[0] != '\0' && is_cfu_enabled(cf)) return __ofono_error_not_available(msg); if (number[0] != '\0') string_to_phone_number(number, &ph); timeout = cf_cond_find_timeout(cf->cf_conditions[type], cls); return set_property_request(cf, msg, type, cls, &ph, timeout); } return __ofono_error_invalid_args(msg); } static void disable_conditional_callback(const struct ofono_error *error, void *data) { struct ofono_call_forwarding *cf = data; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { DBG("Error occurred during conditional erasure"); __ofono_dbus_pending_reply(&cf->pending, __ofono_error_failed(cf->pending)); return; } /* Query the three conditional cf types */ cf->query_next = CALL_FORWARDING_TYPE_BUSY; cf->query_end = CALL_FORWARDING_TYPE_NOT_REACHABLE; set_query_next_cf_cond(cf); } static void disable_all_callback(const struct ofono_error *error, void *data) { struct ofono_call_forwarding *cf = data; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { DBG("Error occurred during erasure of all"); __ofono_dbus_pending_reply(&cf->pending, __ofono_error_failed(cf->pending)); return; } /* Query all cf types */ cf->query_next = CALL_FORWARDING_TYPE_UNCONDITIONAL; cf->query_end = CALL_FORWARDING_TYPE_NOT_REACHABLE; set_query_next_cf_cond(cf); } static DBusMessage *cf_disable_all(DBusConnection *conn, DBusMessage *msg, void *data) { struct ofono_call_forwarding *cf = data; const char *strtype; int type; if (cf->driver->erasure == NULL) return __ofono_error_not_implemented(msg); if (__ofono_call_forwarding_is_busy(cf) || __ofono_ussd_is_busy(cf->ussd)) return __ofono_error_busy(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &strtype, DBUS_TYPE_INVALID) == FALSE) return __ofono_error_invalid_args(msg); if (!strcmp(strtype, "all") || !strcmp(strtype, "")) type = CALL_FORWARDING_TYPE_ALL; else if (!strcmp(strtype, "conditional")) type = CALL_FORWARDING_TYPE_ALL_CONDITIONAL; else return __ofono_error_invalid_format(msg); cf->pending = dbus_message_ref(msg); if (type == CALL_FORWARDING_TYPE_ALL) cf->driver->erasure(cf, type, BEARER_CLASS_DEFAULT, disable_all_callback, cf); else cf->driver->erasure(cf, type, BEARER_CLASS_DEFAULT, disable_conditional_callback, cf); return NULL; } static const GDBusMethodTable cf_methods[] = { { GDBUS_ASYNC_METHOD("GetProperties", NULL, GDBUS_ARGS({ "properties", "a{sv}" }), cf_get_properties) }, { GDBUS_ASYNC_METHOD("SetProperty", GDBUS_ARGS({ "property", "s" }, { "value", "v" }), NULL, cf_set_property) }, { GDBUS_ASYNC_METHOD("DisableAll", GDBUS_ARGS({ "type", "s" }), NULL, cf_disable_all) }, { } }; static const GDBusSignalTable cf_signals[] = { { GDBUS_SIGNAL("PropertyChanged", GDBUS_ARGS({ "name", "s" }, { "value", "v" })) }, { } }; static DBusMessage *cf_ss_control_reply(struct ofono_call_forwarding *cf, struct cf_ss_request *req) { const char *context = "CallForwarding"; const char *sig = "(ssa{sv})"; const char *ss_type = ss_control_type_to_string(req->ss_type); const char *cf_type = cf_type_lut[req->cf_type]; DBusMessageIter iter; DBusMessageIter variant; DBusMessageIter vstruct; DBusMessageIter dict; DBusMessage *reply; reply = dbus_message_new_method_return(cf->pending); dbus_message_iter_init_append(reply, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &context); dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, sig, &variant); dbus_message_iter_open_container(&variant, DBUS_TYPE_STRUCT, NULL, &vstruct); dbus_message_iter_append_basic(&vstruct, DBUS_TYPE_STRING, &ss_type); dbus_message_iter_append_basic(&vstruct, DBUS_TYPE_STRING, &cf_type); dbus_message_iter_open_container(&vstruct, DBUS_TYPE_ARRAY, OFONO_PROPERTIES_ARRAY_SIGNATURE, &dict); if (req->cf_type == CALL_FORWARDING_TYPE_UNCONDITIONAL || req->cf_type == CALL_FORWARDING_TYPE_ALL) property_append_cf_conditions(&dict, req->cf_list[CALL_FORWARDING_TYPE_UNCONDITIONAL], req->cls, cf_type_lut[CALL_FORWARDING_TYPE_UNCONDITIONAL]); if (req->cf_type == CALL_FORWARDING_TYPE_NO_REPLY || req->cf_type == CALL_FORWARDING_TYPE_ALL || req->cf_type == CALL_FORWARDING_TYPE_ALL_CONDITIONAL) property_append_cf_conditions(&dict, req->cf_list[CALL_FORWARDING_TYPE_NO_REPLY], req->cls, cf_type_lut[CALL_FORWARDING_TYPE_NO_REPLY]); if (req->cf_type == CALL_FORWARDING_TYPE_NOT_REACHABLE || req->cf_type == CALL_FORWARDING_TYPE_ALL || req->cf_type == CALL_FORWARDING_TYPE_ALL_CONDITIONAL) property_append_cf_conditions(&dict, req->cf_list[CALL_FORWARDING_TYPE_NOT_REACHABLE], req->cls, cf_type_lut[CALL_FORWARDING_TYPE_NOT_REACHABLE]); if (req->cf_type == CALL_FORWARDING_TYPE_BUSY || req->cf_type == CALL_FORWARDING_TYPE_ALL || req->cf_type == CALL_FORWARDING_TYPE_ALL_CONDITIONAL) property_append_cf_conditions(&dict, req->cf_list[CALL_FORWARDING_TYPE_BUSY], req->cls, cf_type_lut[CALL_FORWARDING_TYPE_BUSY]); dbus_message_iter_close_container(&vstruct, &dict); dbus_message_iter_close_container(&variant, &vstruct); dbus_message_iter_close_container(&iter, &variant); return reply; } static void ss_set_query_cf_callback(const struct ofono_error *error, int total, const struct ofono_call_forwarding_condition *list, void *data) { struct ofono_call_forwarding *cf = data; GSList *l; DBusMessage *reply; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { ofono_error("Query failed with error: %s", telephony_error_to_str(error)); cf->flags &= ~CALL_FORWARDING_FLAG_CACHED; reply = __ofono_error_from_error(error, cf->pending); __ofono_dbus_pending_reply(&cf->pending, reply); return; } l = cf_cond_list_create(total, list); DBG("%s conditions:", cf_type_lut[cf->query_next]); cf_cond_list_print(l); cf->ss_req->cf_list[cf->query_next] = l; if (cf->query_next == cf->query_end) { reply = cf_ss_control_reply(cf, cf->ss_req); __ofono_dbus_pending_reply(&cf->pending, reply); g_free(cf->ss_req); cf->ss_req = NULL; } set_new_cond_list(cf, cf->query_next, l); if (cf->query_next != cf->query_end) { cf->query_next++; ss_set_query_next_cf_cond(cf); } } static void ss_set_query_next_cf_cond(struct ofono_call_forwarding *cf) { int cls; cls = (cf->ss_req->ss_type == SS_CONTROL_TYPE_QUERY) ? cf->ss_req->cls : BEARER_CLASS_DEFAULT; if (cls == BEARER_CLASS_SS_DEFAULT) cls = BEARER_CLASS_DEFAULT; cf->driver->query(cf, cf->query_next, cls, ss_set_query_cf_callback, cf); } static void cf_ss_control_callback(const struct ofono_error *error, void *data) { struct ofono_call_forwarding *cf = data; if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { DBG("CF ss control set/erasure failed with error: %s", telephony_error_to_str(error)); __ofono_dbus_pending_reply(&cf->pending, __ofono_error_from_error(error, cf->pending)); g_free(cf->ss_req); cf->ss_req = NULL; return; } ss_set_query_next_cf_cond(cf); } static gboolean cf_ss_control(int type, const char *sc, const char *sia, const char *sib, const char *sic, const char *dn, DBusMessage *msg, void *data) { struct ofono_call_forwarding *cf = data; DBusConnection *conn = ofono_dbus_get_connection(); int cls = BEARER_CLASS_SS_DEFAULT; int timeout = DEFAULT_NO_REPLY_TIMEOUT; int cf_type; DBusMessage *reply; struct ofono_phone_number ph; void *operation = NULL; /* Before we do anything, make sure we're actually initialized */ if (cf == NULL) return FALSE; if (__ofono_call_forwarding_is_busy(cf)) { reply = __ofono_error_busy(msg); g_dbus_send_message(conn, reply); return TRUE; } DBG("Received call forwarding ss control request"); DBG("type: %d, sc: %s, sia: %s, sib: %s, sic: %s, dn: %s", type, sc, sia, sib, sic, dn); if (!strcmp(sc, "21")) cf_type = CALL_FORWARDING_TYPE_UNCONDITIONAL; else if (!strcmp(sc, "67")) cf_type = CALL_FORWARDING_TYPE_BUSY; else if (!strcmp(sc, "61")) cf_type = CALL_FORWARDING_TYPE_NO_REPLY; else if (!strcmp(sc, "62")) cf_type = CALL_FORWARDING_TYPE_NOT_REACHABLE; else if (!strcmp(sc, "002")) cf_type = CALL_FORWARDING_TYPE_ALL; else if (!strcmp(sc, "004")) cf_type = CALL_FORWARDING_TYPE_ALL_CONDITIONAL; else return FALSE; if (strlen(sia) && (type == SS_CONTROL_TYPE_QUERY || type == SS_CONTROL_TYPE_ERASURE || type == SS_CONTROL_TYPE_DEACTIVATION)) goto error; /* * Activation / Registration is figured context specific according to * 22.030 Section 6.5.2 "The UE shall determine from the context * whether, an entry of a single *, activation or registration * was intended." */ if (type == SS_CONTROL_TYPE_ACTIVATION && strlen(sia) > 0) type = SS_CONTROL_TYPE_REGISTRATION; if (type == SS_CONTROL_TYPE_REGISTRATION && !valid_phone_number_format(sia)) goto error; if (strlen(sib) > 0) { long service_code; char *end; service_code = strtoul(sib, &end, 10); if (end == sib || *end != '\0') goto error; cls = mmi_service_code_to_bearer_class(service_code); if (cls == 0) goto error; } if (strlen(sic) > 0) { char *end; if (type != SS_CONTROL_TYPE_REGISTRATION) goto error; if (cf_type != CALL_FORWARDING_TYPE_ALL && cf_type != CALL_FORWARDING_TYPE_ALL_CONDITIONAL && cf_type != CALL_FORWARDING_TYPE_NO_REPLY) goto error; timeout = strtoul(sic, &end, 10); if (end == sic || *end != '\0') goto error; if (timeout < 1 || timeout > 30) goto error; } switch (type) { case SS_CONTROL_TYPE_REGISTRATION: operation = cf->driver->registration; break; case SS_CONTROL_TYPE_ACTIVATION: operation = cf->driver->activation; break; case SS_CONTROL_TYPE_DEACTIVATION: operation = cf->driver->deactivation; break; case SS_CONTROL_TYPE_ERASURE: operation = cf->driver->erasure; break; case SS_CONTROL_TYPE_QUERY: operation = cf->driver->query; break; } if (operation == NULL) { reply = __ofono_error_not_implemented(msg); g_dbus_send_message(conn, reply); return TRUE; } cf->ss_req = g_try_new0(struct cf_ss_request, 1); if (cf->ss_req == NULL) { reply = __ofono_error_failed(msg); g_dbus_send_message(conn, reply); return TRUE; } cf->ss_req->ss_type = type; cf->ss_req->cf_type = cf_type; cf->ss_req->cls = cls; cf->pending = dbus_message_ref(msg); switch (cf->ss_req->cf_type) { case CALL_FORWARDING_TYPE_ALL: cf->query_next = CALL_FORWARDING_TYPE_UNCONDITIONAL; cf->query_end = CALL_FORWARDING_TYPE_NOT_REACHABLE; break; case CALL_FORWARDING_TYPE_ALL_CONDITIONAL: cf->query_next = CALL_FORWARDING_TYPE_BUSY; cf->query_end = CALL_FORWARDING_TYPE_NOT_REACHABLE; break; default: cf->query_next = cf->ss_req->cf_type; cf->query_end = cf->ss_req->cf_type; break; } /* * Some modems don't understand all classes very well, particularly * the older models. So if the bearer class is the default, we * just use the more commonly understood value of 7 since BEARER_SMS * is not applicable to CallForwarding conditions according to 22.004 * Annex A */ if (cls == BEARER_CLASS_SS_DEFAULT) cls = BEARER_CLASS_DEFAULT; switch (cf->ss_req->ss_type) { case SS_CONTROL_TYPE_REGISTRATION: string_to_phone_number(sia, &ph); cf->driver->registration(cf, cf_type, cls, &ph, timeout, cf_ss_control_callback, cf); break; case SS_CONTROL_TYPE_ACTIVATION: cf->driver->activation(cf, cf_type, cls, cf_ss_control_callback, cf); break; case SS_CONTROL_TYPE_DEACTIVATION: cf->driver->deactivation(cf, cf_type, cls, cf_ss_control_callback, cf); break; case SS_CONTROL_TYPE_ERASURE: cf->driver->erasure(cf, cf_type, cls, cf_ss_control_callback, cf); break; case SS_CONTROL_TYPE_QUERY: ss_set_query_next_cf_cond(cf); break; } return TRUE; error: reply = __ofono_error_invalid_format(msg); g_dbus_send_message(conn, reply); return TRUE; } static void cf_register_ss_controls(struct ofono_call_forwarding *cf) { __ofono_ussd_ssc_register(cf->ussd, "21", cf_ss_control, cf, NULL); __ofono_ussd_ssc_register(cf->ussd, "67", cf_ss_control, cf, NULL); __ofono_ussd_ssc_register(cf->ussd, "61", cf_ss_control, cf, NULL); __ofono_ussd_ssc_register(cf->ussd, "62", cf_ss_control, cf, NULL); __ofono_ussd_ssc_register(cf->ussd, "002", cf_ss_control, cf, NULL); __ofono_ussd_ssc_register(cf->ussd, "004", cf_ss_control, cf, NULL); } static void cf_unregister_ss_controls(struct ofono_call_forwarding *cf) { __ofono_ussd_ssc_unregister(cf->ussd, "21"); __ofono_ussd_ssc_unregister(cf->ussd, "67"); __ofono_ussd_ssc_unregister(cf->ussd, "61"); __ofono_ussd_ssc_unregister(cf->ussd, "62"); __ofono_ussd_ssc_unregister(cf->ussd, "002"); __ofono_ussd_ssc_unregister(cf->ussd, "004"); } gboolean __ofono_call_forwarding_is_busy(struct ofono_call_forwarding *cf) { return cf->pending ? TRUE : FALSE; } static void sim_cfis_read_cb(int ok, int total_length, int record, const unsigned char *data, int record_length, void *userdata) { struct ofono_call_forwarding *cf = userdata; DBusConnection *conn = ofono_dbus_get_connection(); const char *path = __ofono_atom_get_path(cf->atom); if (!ok || record_length < 16 || total_length < record_length) { cf->cfis_record_id = 0; return; } /* * Multiple Subscriber Profile number which can have values 1-4. * Profile id 1 is assumed as the current profile. */ if (data[0] != 1) return; cf->cfis_record_id = record; if (cf->flags & CALL_FORWARDING_FLAG_CACHED) return; /* * For now we only support Voice, although Fax & all Data * basic services are applicable as well. */ if (data[1] & 0x01) { int ton_npi; int number_len; const char *number; char attr[64]; struct ofono_call_forwarding_condition *cond; dbus_bool_t status; number_len = data[2]; ton_npi = data[3]; if (number_len > 11 || ton_npi == 0xff) return; cond = g_try_new0(struct ofono_call_forwarding_condition, 1); if (cond == NULL) return; status = TRUE; cond->status = TRUE; cond->cls = BEARER_CLASS_VOICE; cond->time = 0; cond->phone_number.type = ton_npi; sim_extract_bcd_number(data + 4, number_len - 1, cond->phone_number.number); number = phone_number_to_string(&cond->phone_number); snprintf(attr, sizeof(attr), "%s%s", bearer_class_to_string(BEARER_CLASS_VOICE), cf_type_lut[CALL_FORWARDING_TYPE_UNCONDITIONAL]); cf->cf_conditions[CALL_FORWARDING_TYPE_UNCONDITIONAL] = g_slist_append(NULL, cond); ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, attr, DBUS_TYPE_STRING, &number); ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, "ForwardingFlagOnSim", DBUS_TYPE_BOOLEAN, &status); } } static void sim_cphs_cff_read_cb(int ok, int total_length, int record, const unsigned char *data, int record_length, void *userdata) { struct ofono_call_forwarding *cf = userdata; DBusConnection *conn = ofono_dbus_get_connection(); const char *path = __ofono_atom_get_path(cf->atom); dbus_bool_t cfu_voice; if (!ok || total_length < 1) return; cf->flags |= CALL_FORWARDING_FLAG_CPHS_CFF; if (cf->flags & CALL_FORWARDING_FLAG_CACHED) return; /* * For now we only support Voice, although Fax & all Data * basic services are applicable as well. */ if ((data[0] & 0xf) != 0xA) return; cfu_voice = TRUE; ofono_dbus_signal_property_changed(conn, path, OFONO_CALL_FORWARDING_INTERFACE, "ForwardingFlagOnSim", DBUS_TYPE_BOOLEAN, &cfu_voice); } static void call_forwarding_unregister(struct ofono_atom *atom) { struct ofono_call_forwarding *cf = __ofono_atom_get_data(atom); const char *path = __ofono_atom_get_path(cf->atom); DBusConnection *conn = ofono_dbus_get_connection(); struct ofono_modem *modem = __ofono_atom_get_modem(cf->atom); ofono_modem_remove_interface(modem, OFONO_CALL_FORWARDING_INTERFACE); g_dbus_unregister_interface(conn, path, OFONO_CALL_FORWARDING_INTERFACE); if (cf->sim_context) { ofono_sim_context_free(cf->sim_context); cf->sim_context = NULL; } if (cf->ussd) cf_unregister_ss_controls(cf); if (cf->ussd_watch) __ofono_modem_remove_atom_watch(modem, cf->ussd_watch); cf->flags = 0; } static void sim_cfis_changed(int id, void *userdata) { struct ofono_call_forwarding *cf = userdata; if (!(cf->flags & CALL_FORWARDING_FLAG_CACHED)) return; /* * If the values are cached it's because at least one client * requested them and we need to notify them about this * change. However the authoritative source of current * Call-Forwarding settings is the network operator and the * query can take a noticeable amount of time. Instead of * sending PropertyChanged, we reregister the Call Forwarding * atom. The client will invoke GetProperties only if it * is still interested. */ call_forwarding_unregister(cf->atom); ofono_call_forwarding_register(cf); } static void sim_read_cf_indicator(struct ofono_call_forwarding *cf) { if (__ofono_sim_service_available(cf->sim, SIM_UST_SERVICE_CFIS, SIM_SST_SERVICE_CFIS) == TRUE) { ofono_sim_read(cf->sim_context, SIM_EFCFIS_FILEID, OFONO_SIM_FILE_STRUCTURE_FIXED, sim_cfis_read_cb, cf); ofono_sim_add_file_watch(cf->sim_context, SIM_EFCFIS_FILEID, sim_cfis_changed, cf, NULL); } else { ofono_sim_read(cf->sim_context, SIM_EF_CPHS_CFF_FILEID, OFONO_SIM_FILE_STRUCTURE_TRANSPARENT, sim_cphs_cff_read_cb, cf); ofono_sim_add_file_watch(cf->sim_context, SIM_EF_CPHS_CFF_FILEID, sim_cfis_changed, cf, NULL); } } int ofono_call_forwarding_driver_register( const struct ofono_call_forwarding_driver *d) { DBG("driver: %p, name: %s", d, d->name); if (d->probe == NULL) return -EINVAL; g_drivers = g_slist_prepend(g_drivers, (void *) d); return 0; } void ofono_call_forwarding_driver_unregister( const struct ofono_call_forwarding_driver *d) { DBG("driver: %p, name: %s", d, d->name); g_drivers = g_slist_remove(g_drivers, (void *) d); } static void call_forwarding_remove(struct ofono_atom *atom) { struct ofono_call_forwarding *cf = __ofono_atom_get_data(atom); DBG("atom: %p", atom); if (cf == NULL) return; if (cf->driver && cf->driver->remove) cf->driver->remove(cf); cf_clear_all(cf); g_free(cf); } struct ofono_call_forwarding *ofono_call_forwarding_create( struct ofono_modem *modem, unsigned int vendor, const char *driver, void *data) { struct ofono_call_forwarding *cf; GSList *l; if (driver == NULL) return NULL; cf = g_try_new0(struct ofono_call_forwarding, 1); if (cf == NULL) return NULL; cf->atom = __ofono_modem_add_atom(modem, OFONO_ATOM_TYPE_CALL_FORWARDING, call_forwarding_remove, cf); for (l = g_drivers; l; l = l->next) { const struct ofono_call_forwarding_driver *drv = l->data; if (g_strcmp0(drv->name, driver)) continue; if (drv->probe(cf, vendor, data) < 0) continue; cf->driver = drv; break; } return cf; } static void ussd_watch(struct ofono_atom *atom, enum ofono_atom_watch_condition cond, void *data) { struct ofono_call_forwarding *cf = data; if (cond == OFONO_ATOM_WATCH_CONDITION_UNREGISTERED) { cf->ussd = NULL; return; } cf->ussd = __ofono_atom_get_data(atom); cf_register_ss_controls(cf); } void ofono_call_forwarding_register(struct ofono_call_forwarding *cf) { DBusConnection *conn = ofono_dbus_get_connection(); const char *path = __ofono_atom_get_path(cf->atom); struct ofono_modem *modem = __ofono_atom_get_modem(cf->atom); if (!g_dbus_register_interface(conn, path, OFONO_CALL_FORWARDING_INTERFACE, cf_methods, cf_signals, NULL, cf, NULL)) { ofono_error("Could not create %s interface", OFONO_CALL_FORWARDING_INTERFACE); return; } ofono_modem_add_interface(modem, OFONO_CALL_FORWARDING_INTERFACE); cf->sim = __ofono_atom_find(OFONO_ATOM_TYPE_SIM, modem); if (cf->sim) { cf->sim_context = ofono_sim_context_create(cf->sim); sim_read_cf_indicator(cf); } cf->ussd_watch = __ofono_modem_add_atom_watch(modem, OFONO_ATOM_TYPE_USSD, ussd_watch, cf, NULL); __ofono_atom_register(cf->atom, call_forwarding_unregister); } void ofono_call_forwarding_remove(struct ofono_call_forwarding *cf) { __ofono_atom_free(cf->atom); } void ofono_call_forwarding_set_data(struct ofono_call_forwarding *cf, void *data) { cf->driver_data = data; } void *ofono_call_forwarding_get_data(struct ofono_call_forwarding *cf) { return cf->driver_data; }