/* * * 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 #endif #define _GNU_SOURCE #include #include #include #include #include #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); }