--- modules/history-agent.c | 686 +++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 686 insertions(+), 0 deletions(-) create mode 100644 modules/history-agent.c diff --git a/modules/history-agent.c b/modules/history-agent.c new file mode 100644 index 0000000..6979195 --- /dev/null +++ b/modules/history-agent.c @@ -0,0 +1,686 @@ +/* + * + * 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 +#include +#include + +#define OFONO_API_SUBJECT_TO_CHANGE +#include +#include +#include +#include +#include "common.h" +#include "storage.h" + +#define HISTORY_SPOOL_MODE 0600 +#define HISTORY_SPOOL_BASE STORAGEDIR "/%s/history" +#define HISTORY_SPOOL_PATH HISTORY_SPOOL_BASE "/%08x" + +#define HISTORY_INTERFACE "org.ofono.History" +#define AGENT_INTERFACE "org.ofono.HistoryAgent" + +struct history_agent { + DBusConnection *conn; + const char *path; + char *imsi; + + char *agent_path; + char *agent_owner; + guint agent_watch; + + GQueue *queue; + guint32 next_idx; + guint32 current_idx; + DBusPendingCall *agent_call; +}; + +static void process_queue(struct history_agent *history); + +static void agent_reply_cb(DBusPendingCall *call, void *data) +{ + struct history_agent *history = data; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError error; + char *path; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, reply) == TRUE) { + ofono_error("Agent method failed: %s", error.name); + dbus_error_free(&error); + goto done; + } + + DBG("index: %08x", history->current_idx); + + path = g_strdup_printf(HISTORY_SPOOL_PATH, history->imsi, + history->current_idx); + unlink(path); + g_free(path); + +done: + dbus_message_unref(reply); + + history->agent_call = NULL; + + process_queue(history); +} + +static const char *buffer_next(const unsigned char **buffer, size_t *size) +{ + const void *ptr = *buffer; + guint32 len; + + if (*size < 5) + return NULL; + + len = *((guint32 *) ptr); + if (len == 0) + return NULL; + + if (len + 4 > *size) + return NULL; + + *size -= len + 4; + *buffer += len + 4; + + return ptr + 4; +} + +static void process_history(struct history_agent *history, + const unsigned char *buffer, size_t size) +{ + const unsigned char *ptr = buffer; + size_t len = size; + DBusMessage *msg; + DBusMessageIter iter, dict; + const char *method; + + method = buffer_next(&ptr, &len); + if (method == NULL) + return; + + DBG("method: %s", method); + + msg = dbus_message_new_method_call(history->agent_owner, + history->agent_path, + AGENT_INTERFACE, method); + if (msg == NULL) + return; + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, &dict); + + while (1) { + const char *key, *val; + + key = buffer_next(&ptr, &len); + if (key == NULL) + break; + + val = buffer_next(&ptr, &len); + if (val == NULL) + break; + + ofono_dbus_dict_append(&dict, key, DBUS_TYPE_STRING, &val); + } + + dbus_message_iter_close_container(&iter, &dict); + + if (dbus_connection_send_with_reply(history->conn, msg, + &history->agent_call, -1) == FALSE) { + ofono_error("Sending D-Bus method failed"); + goto done; + } + + dbus_pending_call_set_notify(history->agent_call, agent_reply_cb, + history, NULL); + + dbus_pending_call_unref(history->agent_call); + +done: + dbus_message_unref(msg); +} + +static void process_queue(struct history_agent *history) +{ + int fd; + struct stat st; + unsigned char *addr; + char *path; + gpointer head; + + DBG("owner: %s", history->agent_owner); + + if (history->agent_owner == NULL) + return; + + if (history->agent_call != NULL) + return; + + head = g_queue_pop_head(history->queue); + if (head == NULL) { + history->next_idx = 0; + return; + } + + history->current_idx = GPOINTER_TO_UINT(head); + + DBG("index: %08x", history->current_idx); + + path = g_strdup_printf(HISTORY_SPOOL_PATH, history->imsi, + history->current_idx); + fd = TFR(open(path, O_RDONLY)); + g_free(path); + + if (fd < 0) { + ofono_error("Failed to open history index %08x", + history->current_idx); + return; + } + + if (TFR(fstat(fd, &st)) < 0) + goto done; + + addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) + goto done; + + process_history(history, addr, st.st_size); + + munmap(addr, st.st_size); + +done: + TFR(close(fd)); +} + +static gint compare_index(gconstpointer a, gconstpointer b, + gpointer user_data) +{ + guint32 one = GPOINTER_TO_UINT@; + guint32 two = GPOINTER_TO_UINT(b); + + return one - two; +} + +static void process_spool(struct history_agent *history) +{ + GDir *dir; + char *path; + gpointer tail; + + path = g_strdup_printf(HISTORY_SPOOL_BASE, history->imsi); + + DBG("path: %s", path); + + dir = g_dir_open(path, 0, NULL); + + g_free(path); + + if (dir == NULL) + return; + + while (1) { + const char *path; + guint32 idx; + + path = g_dir_read_name(dir); + if (path == NULL) + break; + + idx = strtol(path, NULL, 16); + if (errno == ERANGE || errno == EINVAL) + continue; + + DBG("index: %08x", idx); + + g_queue_insert_sorted(history->queue, GUINT_TO_POINTER(idx), + compare_index, NULL); + } + + g_dir_close(dir); + + tail = g_queue_peek_tail(history->queue); + if (tail == NULL) + return; + + history->next_idx = GPOINTER_TO_UINT(tail); +} + +static void array_append(GByteArray *array, const char *str) +{ + guint32 len; + + if (str == NULL) { + len = 0; + g_byte_array_append(array, (guint8 *) &len, 4); + } else { + len = strlen(str) + 1; + g_byte_array_append(array, (guint8 *) &len, 4); + g_byte_array_append(array, (guint8 *) str, len); + } +} + +static void create_history(struct history_agent *history, + const char *method, ...) +{ + GByteArray *array; + va_list args; + + DBG("method: %s", method); + + array = g_byte_array_sized_new(128); + if (array == NULL) + return; + + array_append(array, method); + + va_start(args, method); + + while (1) { + const char *key, *val; + + key = va_arg(args, const char *); + if (key == NULL) + break; + + array_append(array, key); + + val = va_arg(args, const char *); + + array_append(array, val); + } + + va_end(args); + + array_append(array, NULL); + + history->next_idx++; + + DBG("index: %08x", history->next_idx); + + write_file(array->data, array->len, HISTORY_SPOOL_MODE, + HISTORY_SPOOL_PATH, history->imsi, history->next_idx); + + g_byte_array_free(array, TRUE); + + g_queue_push_tail(history->queue, GUINT_TO_POINTER(history->next_idx)); + + process_queue(history); +} + +static void free_agent_data(struct history_agent *history) +{ + if (history->agent_call != NULL) { + dbus_pending_call_cancel(history->agent_call); + history->agent_call = NULL; + } + + g_free(history->agent_path); + history->agent_path = NULL; + + g_free(history->agent_owner); + history->agent_owner = NULL; +} + +static void agent_disconnect_cb(DBusConnection *conn, void *data) +{ + struct history_agent *history = data; + + history->agent_watch = 0; + + free_agent_data(history); +} + +static DBusMessage *register_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct history_agent *history = data; + const char *path, *sender; + + sender = dbus_message_get_sender(msg); + + DBG("sender: %s", sender); + + if (history->agent_owner != NULL) + return __ofono_error_busy(msg); + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID) == FALSE) + return __ofono_error_invalid_args(msg); + + if (!__ofono_dbus_valid_object_path(path)) + return __ofono_error_invalid_format(msg); + + history->agent_path = g_strdup(path); + history->agent_owner = g_strdup(sender); + + history->agent_watch = g_dbus_add_disconnect_watch(history->conn, + sender, agent_disconnect_cb, + history, NULL); + + process_queue(history); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct history_agent *history = data; + const char *path, *sender; + + sender = dbus_message_get_sender(msg); + + DBG("sender: %s", sender); + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID) == FALSE) + return __ofono_error_invalid_args(msg); + + if (history->agent_owner == NULL) + return __ofono_error_not_available(msg); + + if (g_str_equal(sender, history->agent_owner) == FALSE) + return __ofono_error_access_denied(msg); + + g_dbus_remove_watch(history->conn, history->agent_watch); + history->agent_watch = 0; + + free_agent_data(history); + + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable history_methods[] = { + { "RegisterAgent", "o", "", register_agent }, + { "UnregisterAgent", "o", "", unregister_agent }, + { } +}; + +static void send_release(struct history_agent *history) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(history->agent_owner, + history->agent_path, + AGENT_INTERFACE, "Release"); + if (msg == NULL) + return; + + dbus_message_set_no_reply(msg, TRUE); + + g_dbus_send_message(history->conn, msg); +} + +static void free_history(struct history_agent *history) +{ + g_queue_free(history->queue); + + g_free(history->imsi); + g_free(history); +} + +static int history_probe(struct ofono_history_context *context) +{ + struct history_agent *history; + struct ofono_atom *atom; + struct ofono_sim *sim; + + DBG("modem: %p", context->modem); + + history = g_try_new0(struct history_agent, 1); + if (history == NULL) + return -ENOMEM; + + history->conn = ofono_dbus_get_connection(); + history->path = ofono_modem_get_path(context->modem); + + atom = __ofono_modem_find_atom(context->modem, OFONO_ATOM_TYPE_SIM); + sim = __ofono_atom_get_data(atom); + + history->imsi = g_strdup(ofono_sim_get_imsi(sim)); + + DBG("imsi: %s", history->imsi); + + history->queue = g_queue_new(); + + process_spool(history); + + if (g_dbus_register_interface(history->conn, history->path, + HISTORY_INTERFACE, history_methods, + NULL, NULL, history, NULL) == FALSE) { + ofono_error("Could not create %s interface", + HISTORY_INTERFACE); + free_history(history); + return -EIO; + } + + ofono_modem_add_interface(context->modem, HISTORY_INTERFACE); + + context->data = history; + + return 0; +} + +static void history_remove(struct ofono_history_context *context) +{ + struct history_agent *history = context->data; + + DBG("modem: %p", context->modem); + + ofono_modem_remove_interface(context->modem, HISTORY_INTERFACE); + + if (history->agent_watch > 0) { + g_dbus_remove_watch(history->conn, history->agent_watch); + + send_release(history); + } + + free_agent_data(history); + + g_dbus_unregister_interface(history->conn, history->path, + HISTORY_INTERFACE); + + free_history(history); +} + +static void history_call_ended(struct ofono_history_context *context, + const struct ofono_call *call, + time_t start, time_t end) +{ + struct history_agent *history = context->data; + char stime_str[29], etime_str[29]; + const char *number_str, *name_str, *dir_str; + + strftime(stime_str, 28, "%Y-%m-%dT%H:%M:%S%z", localtime(&start)); + stime_str[28] = '\0'; + + strftime(etime_str, 28, "%Y-%m-%dT%H:%M:%S%z", localtime(&end)); + etime_str[28] = '\0'; + + dir_str = call->direction == 0 ? "outgoing" : "incoming"; + + if (call->clip_validity == 0) + number_str = phone_number_to_string(&call->phone_number); + else + number_str = ""; + + if (call->cnap_validity == 0) + name_str = call->name; + else + name_str = ""; + + create_history(history, "CallEnded", "Number", number_str, + "Name", name_str, + "Direction", dir_str, + "StartTime", stime_str, + "EndTime", etime_str, NULL); +} + +static void history_call_missed(struct ofono_history_context *context, + const struct ofono_call *call, + time_t when) +{ + struct history_agent *history = context->data; + char time_str[29]; + const char *number_str, *name_str; + + strftime(time_str, 28, "%Y-%m-%dT%H:%M:%S%z", localtime(&when)); + time_str[28] = '\0'; + + if (call->clip_validity == 0) + number_str = phone_number_to_string(&call->phone_number); + else + number_str = ""; + + if (call->cnap_validity == 0) + name_str = call->name; + else + name_str = ""; + + create_history(history, "CallMissed", "Number", number_str, + "Name", name_str, + "LocalTime", time_str, NULL); +} + +static void history_sms_received(struct ofono_history_context *context, + const struct ofono_uuid *uuid, + const char *from, + const struct tm *remote, + const struct tm *local, + const char *text) +{ + struct history_agent *history = context->data; + char ltime_str[29], rtime_str[29]; + const char *uuid_str; + + strftime(rtime_str, 28, "%Y-%m-%dT%H:%M:%S%z", remote); + rtime_str[28] = '\0'; + + strftime(ltime_str, 28, "%Y-%m-%dT%H:%M:%S%z", local); + ltime_str[28] = '\0'; + + uuid_str = ofono_uuid_to_str(uuid); + + create_history(history, "MessageReceived", "Identifier", uuid_str, + "Sender", from, + "Text", text, + "RemoteTime", rtime_str, + "LocalTime", ltime_str, NULL); +} + +static void history_sms_send_pending(struct ofono_history_context *context, + const struct ofono_uuid *uuid, + const char *to, time_t when, + const char *text) +{ + struct history_agent *history = context->data; + char time_str[29]; + const char *uuid_str; + + strftime(time_str, 28, "%Y-%m-%dT%H:%M:%S%z", localtime(&when)); + time_str[28] = '\0'; + + uuid_str = ofono_uuid_to_str(uuid); + + create_history(history, "MessageSubmitted", "Identifier", uuid_str, + "Receiver", to, + "Text", text, + "LocalTime", time_str, NULL); +} + +static void history_sms_send_status(struct ofono_history_context *context, + const struct ofono_uuid *uuid, time_t when, + enum ofono_history_sms_status status) +{ + struct history_agent *history = context->data; + char time_str[29]; + const char *uuid_str, *status_str = NULL; + + switch (status) { + case OFONO_HISTORY_SMS_STATUS_PENDING: + status_str = "pending"; + break; + case OFONO_HISTORY_SMS_STATUS_SUBMITTED: + status_str = "submitted"; + break; + case OFONO_HISTORY_SMS_STATUS_SUBMIT_FAILED: + status_str = "submit-failed"; + break; + case OFONO_HISTORY_SMS_STATUS_DELIVERED: + status_str = "delivered"; + break; + case OFONO_HISTORY_SMS_STATUS_DELIVER_FAILED: + status_str = "deliver-failed"; + break; + } + + if (status_str == NULL) + return; + + strftime(time_str, 28, "%Y-%m-%dT%H:%M:%S%z", localtime(&when)); + time_str[28] = '\0'; + + uuid_str = ofono_uuid_to_str(uuid); + + create_history(history, "MessageStatus", "Identifier", uuid_str, + "Status", status_str, + "LocalTime", time_str, NULL); +} + +static struct ofono_history_driver history_driver = { + .name = "History Agent", + .probe = history_probe, + .remove = history_remove, + .call_ended = history_call_ended, + .call_missed = history_call_missed, + .sms_received = history_sms_received, + .sms_send_pending = history_sms_send_pending, + .sms_send_status = history_sms_send_status, +}; + +static int history_agent_init(void) +{ + return ofono_history_driver_register(&history_driver); +} + +static void history_agent_exit(void) +{ + ofono_history_driver_unregister(&history_driver); +} + +OFONO_PLUGIN_DEFINE(history_agent, "History Agent Plugin", + VERSION, OFONO_PLUGIN_PRIORITY_DEFAULT, + history_agent_init, history_agent_exit) -- 1.7.4