ofono/src/ussd.c

427 lines
9.2 KiB
C

/*
*
* 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);
}