diff options
author | Denis Kenzior <denis.kenzior@intel.com> | 2009-05-05 21:13:14 -0700 |
---|---|---|
committer | Marcel Holtmann <marcel.holtmann@intel.com> | 2009-05-05 21:14:19 -0700 |
commit | a78b36290bed783fb636735d66257bf138f68123 (patch) | |
tree | 623c253319a84a11ec22317acddb05e3ae7f529f /src/ussd.c | |
parent | 838583f4988ba132cab57d5c3ddbac80072c5284 (diff) | |
download | ofono-a78b36290bed783fb636735d66257bf138f68123.tar.bz2 |
Add implementation of telephony daemon
Diffstat (limited to 'src/ussd.c')
-rw-r--r-- | src/ussd.c | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/src/ussd.c b/src/ussd.c new file mode 100644 index 00000000..2429507a --- /dev/null +++ b/src/ussd.c @@ -0,0 +1,426 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2009 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 + +#define _GNU_SOURCE +#include <string.h> +#include <stdio.h> + +#include <dbus/dbus.h> +#include <glib.h> +#include <gdbus.h> + +#include "ofono.h" + +#include "dbus-gsm.h" +#include "modem.h" +#include "driver.h" +#include "common.h" +#include "ussd.h" + +#define SUPPLEMENTARY_SERVICES_INTERFACE "org.ofono.SupplementaryServices" + +#define USSD_FLAG_PENDING 0x1 + +enum ussd_state { + USSD_STATE_IDLE = 0, + USSD_STATE_ACTIVE = 1, + USSD_STATE_USER_ACTION = 2 +}; + +static struct ussd_data *ussd_create() +{ + struct ussd_data *r; + + r = g_try_new0(struct ussd_data, 1); + + return r; +} + +static void ussd_destroy(gpointer data) +{ + struct ofono_modem *modem = data; + struct ussd_data *ussd = modem->ussd; + + g_free(ussd); +} + +struct ss_control_entry { + char *service; + ss_control_cb_t cb; +}; + +static struct ss_control_entry *ss_control_entry_create(const char *service, + ss_control_cb_t cb) +{ + struct ss_control_entry *r; + + r = g_try_new0(struct ss_control_entry, 1); + + if (!r) + return r; + + r->service = g_strdup(service); + r->cb = cb; + + return r; +} + +static void ss_control_entry_destroy(struct ss_control_entry *ca) +{ + g_free(ca->service); + g_free(ca); +} + +static gint ss_control_entry_compare(gconstpointer a, gconstpointer b) +{ + const struct ss_control_entry *ca = a; + const struct ss_control_entry *cb = b; + int ret; + + ret = strcmp(ca->service, cb->service); + + if (ret) + return ret; + + if (ca->cb < cb->cb) + return -1; + + if (ca->cb > cb->cb) + return 1; + + return 0; +} + +static gint ss_control_entry_find_by_service(gconstpointer a, gconstpointer b) +{ + const struct ss_control_entry *ca = a; + //const char *cb = b; + + return strcmp(ca->service, b); +} + +gboolean ss_control_register(struct ofono_modem *modem, const char *str, + ss_control_cb_t cb) +{ + //struct ussd_data *ussd = modem->ussd; + struct ss_control_entry *entry; + + if (!modem) + return FALSE; + + entry = ss_control_entry_create(str, cb); + + if (!entry) + return FALSE; + + modem->ss_control_list = g_slist_append(modem->ss_control_list, entry); + + return TRUE; +} + +void ss_control_unregister(struct ofono_modem *modem, const char *str, + ss_control_cb_t cb) +{ + //struct ussd_data *ussd = modem->ussd; + const struct ss_control_entry entry = { (char *)str, cb }; + GSList *l; + + if (!modem) + return; + + l = g_slist_find_custom(modem->ss_control_list, &entry, + ss_control_entry_compare); + + if (!l) + return; + + ss_control_entry_destroy(l->data); + modem->ss_control_list = g_slist_remove(modem->ss_control_list, + l->data); +} + +static gboolean recognized_control_string(struct ofono_modem *modem, + const char *ss_str, + DBusMessage *msg) +{ + //struct ussd_data *ussd = modem->ussd; + char *str = g_strdup(ss_str); + char *sc, *sia, *sib, *sic, *dn; + int type; + gboolean ret = FALSE; + + ofono_debug("parsing control string"); + + if (parse_ss_control_string(str, &type, &sc, &sia, &sib, &sic, &dn)) { + GSList *l = modem->ss_control_list; + + ofono_debug("Got parse result: %d, %s, %s, %s, %s, %s", + type, sc, sia, sib, sic, dn); + + while ((l = g_slist_find_custom(l, sc, + ss_control_entry_find_by_service)) != NULL) { + struct ss_control_entry *entry = l->data; + + if (entry->cb(modem, type, sc, sia, sib, sic, dn, msg)) { + ret = TRUE; + goto out; + } + + l = l->next; + } + } + + /* TODO: Handle all strings that control voice calls */ + + /* TODO: Handle Multiple subscriber profile DN*59#SEND and *59#SEND + */ + + /* Note: SIM PIN/PIN2 change and unblock and IMEI presentation + * procedures are not handled by the daemon since they are not followed + * by SEND and are not valid USSD requests. + */ + + /* TODO: Handle Password registration according to 22.030 Section 6.5.4 + */ + +out: + g_free(str); + + return ret; +} + +void ofono_ussd_notify(struct ofono_modem *modem, int status, const char *str) +{ + struct ussd_data *ussd = modem->ussd; + DBusConnection *conn = dbus_gsm_connection(); + const char *ussdstr = "USSD"; + const char sig[] = { DBUS_TYPE_STRING, 0 }; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter variant; + + if (status == USSD_STATUS_NOT_SUPPORTED) { + ussd->state = USSD_STATE_IDLE; + reply = dbus_gsm_not_supported(ussd->pending); + goto out; + } + + if (status == USSD_STATUS_TIMED_OUT) { + ussd->state = USSD_STATE_IDLE; + reply = dbus_gsm_timed_out(ussd->pending); + goto out; + } + + /* TODO: Rework this in the Agent framework */ + if (ussd->state == USSD_STATE_ACTIVE) { + if (status == USSD_STATUS_ACTION_REQUIRED) { + ofono_error("Unable to handle action required ussd"); + return; + } + + reply = dbus_message_new_method_return(ussd->pending); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, + &ussdstr); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, sig, + &variant); + + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + &str); + + dbus_message_iter_close_container(&iter, &variant); + + ussd->state = USSD_STATE_IDLE; + } else { + ofono_error("Received an unsolicited USSD, ignoring for now..."); + ofono_debug("USSD is: status: %d, %s", status, str); + + return; + } + +out: + g_dbus_send_message(conn, reply); + + dbus_message_unref(ussd->pending); + ussd->pending = NULL; +} + +static void ussd_callback(const struct ofono_error *error, void *data) +{ + struct ussd_data *ussd = data; + DBusConnection *conn = dbus_gsm_connection(); + DBusMessage *reply; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) + ofono_debug("ussd request failed with error: %s", + telephony_error_to_str(error)); + + ussd->flags &= ~USSD_FLAG_PENDING; + + if (!ussd->pending) + return; + + if (error->type == OFONO_ERROR_TYPE_NO_ERROR) { + ussd->state = USSD_STATE_ACTIVE; + return; + } + + reply = dbus_gsm_failed(ussd->pending); + + g_dbus_send_message(conn, reply); + + dbus_message_unref(ussd->pending); + ussd->pending = NULL; +} + +static DBusMessage *ussd_initiate(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct ofono_modem *modem = data; + struct ussd_data *ussd = modem->ussd; + const char *str; + + if (ussd->flags & USSD_FLAG_PENDING) + return dbus_gsm_busy(msg); + + if (ussd->state == USSD_STATE_ACTIVE) + return dbus_gsm_busy(msg); + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &str, + DBUS_TYPE_INVALID) == FALSE) + return dbus_gsm_invalid_args(msg); + + if (strlen(str) == 0) + return dbus_gsm_invalid_format(msg); + + ofono_debug("checking if this is a recognized control string"); + if (recognized_control_string(modem, str, msg)) + return NULL; + + ofono_debug("No.., checking if this is a USSD string"); + if (!valid_ussd_string(str)) + return dbus_gsm_invalid_format(msg); + + ofono_debug("OK, running USSD request"); + + if (!ussd->ops->request) + return dbus_gsm_not_implemented(msg); + + ussd->flags |= USSD_FLAG_PENDING; + ussd->pending = dbus_message_ref(msg); + + ussd->ops->request(modem, str, ussd_callback, ussd); + + return NULL; +} + +static void ussd_cancel_callback(const struct ofono_error *err, void *data) +{ + //struct ussd_data *ussd = data; +} + +static DBusMessage *ussd_cancel(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct ofono_modem *modem = data; + struct ussd_data *ussd = modem->ussd; + + if (ussd->flags & USSD_FLAG_PENDING) + return dbus_gsm_busy(msg); + + if (ussd->state == USSD_STATE_IDLE) + return dbus_gsm_not_active(msg); + + if (!ussd->ops->cancel) + return dbus_gsm_not_implemented(msg); + + ussd->flags |= USSD_FLAG_PENDING; + ussd->pending = dbus_message_ref(msg); + + ussd->ops->cancel(modem, ussd_cancel_callback, ussd); + + return NULL; +} + +static GDBusMethodTable ussd_methods[] = { + { "Initiate", "s", "sv", ussd_initiate, + G_DBUS_METHOD_FLAG_ASYNC }, + { "Cancel", "", "", ussd_cancel, + G_DBUS_METHOD_FLAG_ASYNC }, + { } +}; + +static GDBusSignalTable ussd_signals[] = { + { } +}; + +int ofono_ussd_register(struct ofono_modem *modem, struct ofono_ussd_ops *ops) +{ + DBusConnection *conn = dbus_gsm_connection(); + + if (modem == NULL) + return -1; + + if (ops == NULL) + return -1; + + modem->ussd = ussd_create(); + + if (modem->ussd == NULL) + return -1; + + modem->ussd->ops = ops; + + if (!g_dbus_register_interface(conn, modem->path, + SUPPLEMENTARY_SERVICES_INTERFACE, + ussd_methods, ussd_signals, NULL, + modem, ussd_destroy)) { + ofono_error("Could not create %s interface", + SUPPLEMENTARY_SERVICES_INTERFACE); + + ussd_destroy(modem->ussd); + + return -1; + } + + modem_add_interface(modem, SUPPLEMENTARY_SERVICES_INTERFACE); + + return 0; +} + +void ofono_ussd_unregister(struct ofono_modem *modem) +{ + DBusConnection *conn = dbus_gsm_connection(); + + if (modem->ussd == NULL) + return; + + modem_remove_interface(modem, SUPPLEMENTARY_SERVICES_INTERFACE); + g_dbus_unregister_interface(conn, modem->path, + SUPPLEMENTARY_SERVICES_INTERFACE); +} |