ofono/src/call-forwarding.c

1573 lines
39 KiB
C

/*
*
* 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 <config.h>
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <glib.h>
#include <gdbus.h>
#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[72];
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;
}