diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6b2424 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +*~ +mms-engine/mms-engine.pro.user +mms-engine/build +mms-lib/mms-lib.pro.user +mms-lib/build +mms-lib/test/coverage/full.gcov +mms-lib/test/coverage/mms-lib.gcov +mms-lib/test/coverage/results +mms-lib/test/mms_codec/build +mms-lib/test/mms_lib_test.ncb +mms-lib/test/mms_lib_test.opt +mms-lib/test/read_report/build +mms-lib/test/retrieve/build +mms-lib/test/retrieve_cancel/build +mms-ofono/mms-ofono.pro.user +mms-ofono/build +mms-handler-dbus/mms-handler-dbus.pro.user +mms-handler-dbus/build +mms-handler-dbus/test/mms_handler_dbus_server/build +mms-handler-dbus/test/mms_handler_dbus_server/test_mms_handler_dbus_server.pro.user +mms-handler-dbus/test/mms_handler_dbus_client/build +mms-dump/build +mms-dump/mms_dump.ncb +mms-dump/mms_dump.opt diff --git a/README b/README new file mode 100644 index 0000000..3a96976 --- /dev/null +++ b/README @@ -0,0 +1,13 @@ +MMS engine. + +Copyright (C) 2013-2014 Jolla Ltd. +Copyright (C) 2010-2011 Intel Corporation. + +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. diff --git a/mms-dump/Makefile b/mms-dump/Makefile new file mode 100644 index 0000000..5e0551c --- /dev/null +++ b/mms-dump/Makefile @@ -0,0 +1,101 @@ +# -*- Mode: makefile -*- + +.PHONY: all debug release clean + +# Required packages +PKGS = glib-2.0 libwspcodec +LIB_PKGS = $(PKGS) + +# +# Default target +# + +all: debug release + +# +# Sources +# + +SRC = mms-dump.c + +# +# Directories +# + +SRC_DIR = . +BUILD_DIR = build +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +DEBUG_FLAGS = -g +RELEASE_FLAGS = -O2 +DEBUG_DEFS = -DDEBUG +RELEASE_DEFS = +WARNINGS = -Wall +CFLAGS = $(shell pkg-config --cflags $(PKGS)) -MMD +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) + +DEBUG_CFLAGS = $(WARNINGS) $(DEBUG_FLAGS) $(DEBUG_DEFS) $(CFLAGS) +RELEASE_CFLAGS = $(WARNINGS) $(RELEASE_FLAGS) $(RELEASE_DEFS) $(CFLAGS) +DEBUG_LIBS = $(LIBS) +RELEASE_LIBS = $(LIBS) + +# +# Files +# + +DEBUG_OBJS = $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEBUG_EXE_DEPS = $(DEBUG_BUILD_DIR) +RELEASE_EXE_DEPS = $(RELEASE_BUILD_DIR) +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +# +# Rules +# + +EXE = mms-dump +DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) +RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) + +debug: $(DEBUG_EXE) + +release: $(RELEASE_EXE) + +clean: + rm -fr $(BUILD_DIR) $(SRC_DIR)/*~ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_EXE): $(DEBUG_EXE_DEPS) $(DEBUG_OBJS) + $(LD) $(DEBUG_FLAGS) $(DEBUG_OBJS) $(DEBUG_LIBS) -o $@ + +$(RELEASE_EXE): $(RELEASE_EXE_DEPS) $(RELEASE_OBJS) + $(LD) $(RELEASE_FLAGS) $(RELEASE_OBJS) $(RELEASE_LIBS) -o $@ + strip $@ diff --git a/mms-dump/mms-dump.c b/mms-dump/mms-dump.c new file mode 100644 index 0000000..c2788cb --- /dev/null +++ b/mms-dump/mms-dump.c @@ -0,0 +1,968 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include +#include +#include +#include + +static const char pname[] = "mms-dump"; + +enum app_ret_value { + RET_OK, + RET_ERR_CMDLINE, + RET_ERR_IO, + RET_ERR_DECODE +}; + +#define MMS_DUMP_FLAG_VERBOSE (0x01) + +#define WSP_QUOTE (127) + +struct mms_named_value { + const char* name; + unsigned int value; +}; + +typedef gboolean +(*mms_value_decoder)( + enum wsp_value_type type, + const guint8* val, + unsigned int len, + unsigned int flags); + +#define MMS_CONTENT_TYPE "application/vnd.wap.mms-message" + +#define MMS_WELL_KNOWN_HEADERS(h) \ + h(BCC, "Bcc", 0x01, etext )\ + h(CC, "Cc", 0x02, etext )\ + h(CONTENT_LOCATION, "X-Mms-Content-Location", 0x03, text )\ + h(CONTENT_TYPE, "Content-Type", 0x04, unknown )\ + h(DATE, "Date", 0x05, date )\ + h(DELIVERY_REPORT, "X-Mms-Delivery-Report", 0x06, bool )\ + h(DELIVERY_TIME, "X-Mms-Delivery-Time", 0x07, unknown )\ + h(EXPIRY, "X-Mms-Expiry", 0x08, expiry )\ + h(FROM, "From", 0x09, from )\ + h(MESSAGE_CLASS, "X-Mms-Message-Class", 0x0A, mclass )\ + h(MESSAGE_ID, "Message-ID", 0x0B, text )\ + h(MESSAGE_TYPE, "X-Mms-Message-Type", 0x0C, mtype )\ + h(MMS_VERSION, "X-Mms-MMS-Version", 0x0D, version )\ + h(MESSAGE_SIZE, "X-Mms-Message-Size", 0x0E, long )\ + h(PRIORITY, "X-Mms-Priority", 0x0F, prio )\ + h(READ_REPORT, "X-Mms-Read-Report", 0x10, bool )\ + h(REPORT_ALLOWED, "X-Mms-Report-Allowed", 0x11, bool )\ + h(RESPONSE_STATUS, "X-Mms-Response-Status", 0x12, short )\ + h(RESPONSE_TEXT, "X-Mms-Response-Text", 0x13, etext )\ + h(SENDER_VISIBILITY, "X-Mms-Sender-Visibility", 0x14, short )\ + h(STATUS, "X-Mms-Status", 0x15, status )\ + h(SUBJECT, "Subject", 0x16, etext )\ + h(TO, "To", 0x17, etext )\ + h(TRANSACTION_ID, "X-Mms-Transaction-Id", 0x18, text )\ + h(RETRIEVE_STATUS, "X-Mms-Retrieve-Status", 0x19, retrieve )\ + h(RETRIEVE_TEXT, "X-Mms-Retrieve-Text", 0x1A, etext )\ + h(READ_STATUS, "X-Mms-Read-Status", 0x1B, rstatus )\ + h(REPLY_CHARGING, "X-Mms-Reply-Charging", 0x1C, short )\ + h(REPLY_CHARGING_DEADLINE,"X-Mms-Reply-Charging-Deadline",0x1D, unknown )\ + h(REPLY_CHARGING_ID, "X-Mms-Reply-Charging-ID", 0x1E, text )\ + h(REPLY_CHARGING_SIZE, "X-Mms-Reply-Charging-Size", 0x1F, long )\ + h(PREVIOUSLY_SENT_BY, "X-Mms-Previously-Sent-By", 0x20, unknown )\ + h(PREVIOUSLY_SENT_DATE, "X-Mms-Previously-Sent-Date", 0x21, unknown ) + +#define WSP_WELL_KNOWN_HEADERS(h) \ + h(CONTENT_LOCATION, "Content-Location", 0x0E, text )\ + h(CONTENT_DISPOSITION, "Content-Disposition", 0x2E, contdisp )\ + h(CONTENT_ID, "Content-ID", 0x40, quote )\ + h(CONTENT_DISPOSITION2, "Content-Disposition", 0x45, contdisp ) + +#define MMS_HEADER_ID(id) MMS_HEADER_##id +#define MMS_PART_HEADER_ID(id) MMS_PART_HEADER_##id + +typedef enum mms_header { + MMS_HEADER_INVALID, +#define h(id,n,x,t) MMS_HEADER_ID(id) = x, + MMS_WELL_KNOWN_HEADERS(h) +#undef h + MMS_HEADER_END, +} MMS_HEADER; + +typedef enum mms_part_header { +#define h(id,n,x,t) MMS_PART_HEADER_ID(id) = x, + WSP_WELL_KNOWN_HEADERS(h) +#undef h +} MMS_PART_HEADER; + +static +const char* +mms_message_well_known_header_name( + int hdr) +{ + static char unknown[6]; + switch (hdr) { +#define h(id,n,x,t) case MMS_HEADER_ID(id): return n; + MMS_WELL_KNOWN_HEADERS(h) +#undef h + default: + snprintf(unknown, sizeof(unknown), "0x%02X", hdr); + unknown[sizeof(unknown)-1] = 0; + return unknown; + } +} + +static +const char* +mms_part_well_known_header_name( + int hdr) +{ + static char unknown[6]; + switch (hdr) { +#define h(id,n,x,t) case MMS_PART_HEADER_ID(id): return n; + WSP_WELL_KNOWN_HEADERS(h) +#undef h + default: + snprintf(unknown, sizeof(unknown), "0x%02X", hdr); + unknown[sizeof(unknown)-1] = 0; + return unknown; + } +} + +static +const struct mms_named_value* +mms_find_named_value( + const struct mms_named_value* values, + unsigned int num_values, + unsigned int value) +{ + unsigned int i; + for (i=0; i 1) { + unsigned int i; + printf("%02X", val[0]); + for (i=1; i *TEXT End-of-string */ + if (type == WSP_VALUE_TYPE_TEXT && len > 0 && val[0] == 0x22) { + printf("%s", val+1); + mms_value_verbose_dump(val, len, flags); + return TRUE; + } + return FALSE; +} + +static +gboolean +mms_value_decode_enum( + enum wsp_value_type type, + const guint8* val, + unsigned int len, + const struct mms_named_value* values, + unsigned int num_values, + unsigned int flags) +{ + if (type == WSP_VALUE_TYPE_SHORT && len == 1) { + const struct mms_named_value* nv; + nv = mms_find_named_value(values, num_values, val[0]); + if (nv) { + printf("%s", nv->name); + if (flags & MMS_DUMP_FLAG_VERBOSE) printf(" (%u)", nv->value); + return TRUE; + } + } + return mms_value_decode_unknown(type, val, len, flags); +} + +static +gboolean +mms_value_decode_version( + enum wsp_value_type type, + const guint8* val, + unsigned int len, + unsigned int flags) +{ + if (type == WSP_VALUE_TYPE_SHORT && len == 1) { + const unsigned int value = val[0]; + printf("%u.%u", (val[0] & 0x70) >> 4, val[0] & 0x0f); + if (flags & MMS_DUMP_FLAG_VERBOSE) printf(" (%u)", value); + return TRUE; + } + return mms_value_decode_unknown(type, val, len, flags); +} + +static +gboolean +mms_value_decode_long( + enum wsp_value_type type, + const guint8* val, + unsigned int len, + unsigned int flags) +{ + unsigned long long_val; + if (type == WSP_VALUE_TYPE_LONG && len <= sizeof(long_val)) { + unsigned int i; + for (long_val = 0, i=0; iname)) { + gsize bytes_in, bytes_out; + text = tmp = g_convert((char*)val+consumed, len-consumed, + dest_charset, cs->name, &bytes_in, &bytes_out, NULL); + } else { + if (val[consumed] == WSP_QUOTE) consumed++; + if (consumed <= len) text = (char*)val+consumed; + } + if (text) { + printf("%s", text); + mms_value_verbose_dump(val, len, flags); + g_free(tmp); + return TRUE; + } + } + } + } + return mms_value_decode_unknown(type, val, len, flags); +} + +static +gboolean +mms_value_decode_from( + enum wsp_value_type type, + const guint8* val, + unsigned int len, + unsigned int flags) +{ + /* + * Address-present-token Encoded-string-value | Insert-address-token + * Address-present-token = + * Insert-address-token = + */ + if (type == WSP_VALUE_TYPE_LONG && len > 0) { + if (val[0] == 0x81) { + printf(""); + if (flags & MMS_DUMP_FLAG_VERBOSE) printf(" (%u)", val[0]); + return TRUE; + } else if (val[0] == 0x80 && len > 1) { + enum wsp_value_type ftype; + const void* fval = NULL; + unsigned int flen = 0; + if (wsp_decode_field(val+1, len-1, &ftype, &fval, &flen, NULL) && + mms_value_decode_etext(ftype, fval, flen, 0)) { + mms_value_verbose_dump(val, len, flags); + return TRUE; + } + } + } + return mms_value_decode_unknown(type, val, len, flags); +} + +static +gboolean +mms_value_decode_expiry( + enum wsp_value_type type, + const guint8* val, + unsigned int len, + unsigned int flags) +{ + /* + * Absolute-token Date-value | Relative-token Delta-seconds-value + * Absolute-token = + * Relative-token = + */ + gboolean ok = FALSE; + enum wsp_value_type ftype; + const void* fval = NULL; + unsigned int flen = 0; + if (type == WSP_VALUE_TYPE_LONG && len > 1 && + wsp_decode_field(val+1, len-1, &ftype, &fval, &flen, NULL)) { + if (val[0] == 0x80 /* Absolute-token */) { + ok = mms_value_decode_date(ftype, fval, flen, 0); + } else if (val[0] == 0x81 /* Relative-token */) { + time_t t; + if (ftype == WSP_VALUE_TYPE_LONG && flen > 0 && flen <= sizeof(t)) { + unsigned int i; + for (t=0, i=0; i + * Length-quote = + * Length = Uintvar-integer + * + * Disposition = Form-data | Attachment | Inline | Token-text + * Form-data = + * Attachment = + * Inline = + */ + static const struct mms_named_value nv_d [] = { + { "Form-data", 128 }, + { "Attachment", 129 }, + { "Inline", 130 } + }; + + static const struct mms_named_value nv_p [] = { + { "Q", 0x00 }, + { "Charset", 0x01 }, + { "Level", 0x02 }, + { "Type", 0x03 }, + { "Name", 0x05 }, + { "Filename", 0x06 }, + { "Differences", 0x07 }, + { "Padding", 0x08 }, + { "Type", 0x09 }, + { "Start", 0x0A }, + { "Start-info", 0x0B }, + { "Comment", 0x0C }, + { "Domain", 0x0D }, + { "Max-Age", 0x0E }, + { "Path", 0x0F }, + { "Secure", 0x10 }, + { "SEC", 0x11 }, + { "MAC", 0x12 }, + { "Creation-date", 0x13 }, + { "Modification-date", 0x14 }, + { "Read-date", 0x15 }, + { "Size", 0x16 }, + { "Name", 0x17 }, + { "Filename", 0x18 }, + { "Start", 0x19 }, + { "Start-info", 0x1A }, + { "Comment", 0x1B }, + { "Domain", 0x1C }, + { "Path", 0x1D } + }; + + if ((type == WSP_VALUE_TYPE_LONG || + type == WSP_VALUE_TYPE_SHORT) && len > 0) { + const struct mms_named_value* nv; + nv = mms_find_named_value(nv_d, G_N_ELEMENTS(nv_d), val[0]); + if (nv) { + struct wsp_parameter_iter pi; + struct wsp_parameter p; + wsp_parameter_iter_init(&pi, val + 1, len - 1); + printf("%s", nv->name); + while (wsp_parameter_iter_next(&pi, &p)) { + nv = mms_find_named_value(nv_p, G_N_ELEMENTS(nv_p), p.type); + if (nv) { + printf("; %s=", nv->name); + } else { + printf(";0x%02x=", p.type); + } + switch (p.value) { + case WSP_PARAMETER_VALUE_TEXT: + printf("%s", p.text); + break; + case WSP_PARAMETER_VALUE_INT: + printf("%u", p.integer); + break; + case WSP_PARAMETER_VALUE_DATE: + mms_value_print_date(p.date); + break; + case WSP_PARAMETER_VALUE_Q: + printf("%g", p.q); + break;; + } + } + mms_value_verbose_dump(val, len, flags); + return TRUE; + } + } + return mms_value_decode_unknown(type, val, len, flags); +} + +#define mms_value_decode_short mms_value_decode_unknown +#define mms_value_decode_text mms_value_decode_unknown + +static +mms_value_decoder +mms_message_value_decoder_for_header( + const char* hdr) +{ +#define h(id,n,x,t) if (!strcmp(n,hdr)) return &mms_value_decode_##t; + MMS_WELL_KNOWN_HEADERS(h); +#undef h + return &mms_value_decode_unknown; +} + +static +mms_value_decoder +mms_part_value_decoder_for_header( + const char* hdr) +{ +#define h(id,n,x,t) if (!strcmp(n,hdr)) return &mms_value_decode_##t; + WSP_WELL_KNOWN_HEADERS(h); +#undef h + return &mms_value_decode_unknown; +} + +static +gboolean +mms_decode_headers( + struct wsp_header_iter* iter, + const char* prefix, + unsigned int flags, + const char* (*well_known_header_name)(int hdr), + mms_value_decoder (*value_decoder_for_header)(const char* hdr)) +{ + while (wsp_header_iter_next(iter)) { + const guint8* hdr = wsp_header_iter_get_hdr(iter); + const guint8* val = wsp_header_iter_get_val(iter); + unsigned int val_len = wsp_header_iter_get_val_len(iter); + mms_value_decoder dec; + const char* hdr_name; + + switch (wsp_header_iter_get_hdr_type(iter)) { + case WSP_HEADER_TYPE_WELL_KNOWN: + hdr_name = well_known_header_name(hdr[0] & 0x7f); + break; + case WSP_HEADER_TYPE_APPLICATION: + hdr_name = (const char*)hdr; + break; + default: + return FALSE; + } + printf("%s%s: ", prefix, hdr_name); + dec = value_decoder_for_header(hdr_name); + if (!dec(wsp_header_iter_get_val_type(iter), val, val_len, flags)) { + printf("ERROR!\n"); + return FALSE; + } + printf("\n"); + } + return TRUE; +} + +static +gboolean +mms_message_decode_headers( + struct wsp_header_iter* iter, + const char* prefix, + unsigned int flags) +{ + return mms_decode_headers(iter, prefix, flags, + mms_message_well_known_header_name, + mms_message_value_decoder_for_header); +} + +static +gboolean +mms_part_decode_headers( + struct wsp_header_iter* iter, + const char* prefix, + unsigned int flags) +{ + return mms_decode_headers(iter, prefix, flags, + mms_part_well_known_header_name, + mms_part_value_decoder_for_header); +} + +static +const char* +mms_decode_charset( + const unsigned char *pdu, + unsigned int len) +{ + struct wsp_parameter_iter iter; + struct wsp_parameter param; + wsp_parameter_iter_init(&iter, pdu, len); + while (wsp_parameter_iter_next(&iter, ¶m)) { + if (param.type == WSP_PARAMETER_TYPE_CHARSET) + return param.text; + } + return NULL; +} + +static +gboolean +mms_decode_attachments( + struct wsp_header_iter* iter, + unsigned int flags) +{ + struct wsp_multipart_iter mi; + if (wsp_multipart_iter_init(&mi, iter, NULL, NULL)) { + int i; + for (i=0; wsp_multipart_iter_next(&mi); i++) { + unsigned int n; + const void* type; + const unsigned char* ct = wsp_multipart_iter_get_content_type(&mi); + unsigned int ct_len = wsp_multipart_iter_get_content_type_len(&mi); + if (wsp_decode_content_type(ct, ct_len, &type, &n, NULL)) { + struct wsp_header_iter hi; + const char* cs = mms_decode_charset(ct + n, ct_len - n); + const unsigned char* body = wsp_multipart_iter_get_body(&mi); + unsigned int len = wsp_multipart_iter_get_body_len(&mi); + unsigned int off = body - wsp_header_iter_get_pdu(iter); + printf("Attachment #%d:\n", i+1); + if (flags & MMS_DUMP_FLAG_VERBOSE) { + printf("Offset: %u (0x%x)\n", off, off); + printf("Length: %u (0x%x)\n", len, len); + } else { + printf("Offset: %u\n", off); + printf("Length: %u\n", len); + } + if (cs) { + printf(" Content-Type: %s;charset=%s\n", (char*)type, cs); + } else { + printf(" Content-Type: %s\n", (char*)type); + } + wsp_header_iter_init(&hi, wsp_multipart_iter_get_hdr(&mi), + wsp_multipart_iter_get_hdr_len(&mi), 0); + if (mms_part_decode_headers(&hi, " ", flags) && + wsp_header_iter_at_end(&hi)) { + continue; + } + } + return FALSE; + } + return wsp_multipart_iter_close(&mi, iter); + } + return FALSE; +} + +static +int +mms_decode_data( + const guint8* data, + gsize len, + unsigned int flags) +{ + struct wsp_header_iter iter; + + /* Skip WSP Push notification header if we find one */ + if (len >= 3 && data[1] == 6 /* Push PDU */) { + unsigned int hdrlen = 0; + unsigned int off = 0; + const guint8* wsp_data = data+2; + gsize wsp_len = len - 2; + + /* Hdrlen */ + if (wsp_decode_uintvar(wsp_data, wsp_len, &hdrlen, &off) && + (off + hdrlen) <= wsp_len) { + const void* ct = NULL; + wsp_data += off; + wsp_len -= off; + if (wsp_decode_content_type(wsp_data, hdrlen, &ct, &off, NULL) && + strcmp(ct, MMS_CONTENT_TYPE) == 0) { + printf("WSP header:\n %s\n", (char*)ct); + data = wsp_data + hdrlen; + len = wsp_len - hdrlen; + } + } + } + + printf("MMS headers:\n"); + wsp_header_iter_init(&iter, data, len, WSP_HEADER_ITER_FLAG_REJECT_CP | + WSP_HEADER_ITER_FLAG_DETECT_MMS_MULTIPART); + if (mms_message_decode_headers(&iter, " ", flags) && + (wsp_header_iter_at_end(&iter) || + (wsp_header_iter_is_multipart(&iter) && + mms_decode_attachments(&iter, flags) && + wsp_header_iter_at_end(&iter)))) { + return RET_OK; + } else { + printf("Decoding FAILED\n"); + } + return RET_ERR_DECODE; +} + +static +int +mms_decode_file( + const char* fname, + unsigned int flags) +{ + int ret = RET_ERR_IO; + GError* error = NULL; + GMappedFile* map = g_mapped_file_new(fname, FALSE, &error); + if (map) { + const void* data = g_mapped_file_get_contents(map); + const gsize size = g_mapped_file_get_length(map); + ret = mms_decode_data(data, size, flags); + g_mapped_file_unref(map); + } else { + printf("%s: %s\n", pname, error->message); + g_error_free(error); + } + return ret; +} + +int main(int argc, char* argv[]) +{ + int ret = RET_ERR_CMDLINE; + gboolean ok, verbose = FALSE; + GError* error = NULL; + GOptionEntry entries[] = { + { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, + "Enable verbose output", NULL }, + { NULL } + }; + GOptionContext* options = g_option_context_new("FILES"); + g_option_context_add_main_entries(options, entries, NULL); + ok = g_option_context_parse(options, &argc, &argv, &error); + if (ok) { + if (argc > 1) { + int i, flags = 0; + if (verbose) flags |= MMS_DUMP_FLAG_VERBOSE; + for (i=1; i 2) printf("\n%s\n\n", fname); + ret = mms_decode_file(fname, flags); + if (ret != RET_OK) break; + } + } else { + char* help = g_option_context_get_help(options, TRUE, NULL); + printf("%s", help); + g_free(help); + } + } else { + printf("%s: %s\n", pname, error->message); + g_error_free(error); + } + g_option_context_free(options); + return ret; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-dump/test/m-acknowledge.ind b/mms-dump/test/m-acknowledge.ind new file mode 100644 index 0000000..2b944db Binary files /dev/null and b/mms-dump/test/m-acknowledge.ind differ diff --git a/mms-dump/test/m-delivery.ind b/mms-dump/test/m-delivery.ind new file mode 100644 index 0000000..6d28a86 Binary files /dev/null and b/mms-dump/test/m-delivery.ind differ diff --git a/mms-dump/test/m-notification_1.0.ind b/mms-dump/test/m-notification_1.0.ind new file mode 100644 index 0000000..19c60ee Binary files /dev/null and b/mms-dump/test/m-notification_1.0.ind differ diff --git a/mms-dump/test/m-notification_1.1.ind b/mms-dump/test/m-notification_1.1.ind new file mode 100644 index 0000000..043e00e Binary files /dev/null and b/mms-dump/test/m-notification_1.1.ind differ diff --git a/mms-dump/test/m-notification_1.2.ind b/mms-dump/test/m-notification_1.2.ind new file mode 100644 index 0000000..3c683a0 Binary files /dev/null and b/mms-dump/test/m-notification_1.2.ind differ diff --git a/mms-dump/test/m-read-orig.ind b/mms-dump/test/m-read-orig.ind new file mode 100644 index 0000000..7bd8078 Binary files /dev/null and b/mms-dump/test/m-read-orig.ind differ diff --git a/mms-dump/test/m-read-rec.ind b/mms-dump/test/m-read-rec.ind new file mode 100644 index 0000000..7b39a47 Binary files /dev/null and b/mms-dump/test/m-read-rec.ind differ diff --git a/mms-dump/test/m-retrieve_1.0.conf b/mms-dump/test/m-retrieve_1.0.conf new file mode 100644 index 0000000..2488f7c Binary files /dev/null and b/mms-dump/test/m-retrieve_1.0.conf differ diff --git a/mms-dump/test/m-retrieve_1.1.conf b/mms-dump/test/m-retrieve_1.1.conf new file mode 100644 index 0000000..1b7f55f Binary files /dev/null and b/mms-dump/test/m-retrieve_1.1.conf differ diff --git a/mms-dump/test/m-retrieve_1.2.conf b/mms-dump/test/m-retrieve_1.2.conf new file mode 100644 index 0000000..7e25d2d Binary files /dev/null and b/mms-dump/test/m-retrieve_1.2.conf differ diff --git a/mms-engine/Makefile b/mms-engine/Makefile new file mode 100644 index 0000000..9d1626f --- /dev/null +++ b/mms-engine/Makefile @@ -0,0 +1,217 @@ +# -*- Mode: makefile -*- + +.PHONY: all debug release clean cleaner +.PHONY: mms_lib_debug_lib mms_lib_release_lib +.PHONY: mms_ofono_debug_lib mms_ofono_release_lib +.PHONY: mms_handler_debug_lib mms_handler_release_lib + +# Required packages +PKGS = gio-unix-2.0 gio-2.0 glib-2.0 +LIB_PKGS = libwspcodec libsoup-2.4 $(PKGS) + +# +# Default target +# + +all: debug release + +# +# Sources +# + +SRC = main.c mms_engine.c +GEN_SRC = org.nemomobile.MmsEngine.c + +# +# Directories +# + +SRC_DIR = . +BUILD_DIR = build +SPEC_DIR = . +GEN_DIR = $(BUILD_DIR) +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# mms-lib +# + +MMS_LIB_LIB = libmms.a +MMS_LIB_DIR = ../mms-lib +MMS_LIB_BUILD_DIR = $(MMS_LIB_DIR)/build +MMS_LIB_DEBUG_LIB = $(MMS_LIB_BUILD_DIR)/debug/$(MMS_LIB_LIB) +MMS_LIB_RELEASE_LIB = $(MMS_LIB_BUILD_DIR)/release/$(MMS_LIB_LIB) + +# +# mms-ofono +# + +MMS_OFONO_LIB = libmms-ofono.a +MMS_OFONO_DIR = ../mms-ofono +MMS_OFONO_BUILD_DIR = $(MMS_OFONO_DIR)/build +MMS_OFONO_DEBUG_LIB = $(MMS_OFONO_BUILD_DIR)/debug/$(MMS_OFONO_LIB) +MMS_OFONO_RELEASE_LIB = $(MMS_OFONO_BUILD_DIR)/release/$(MMS_OFONO_LIB) + +# +# mms-handler-dbus +# + +MMS_HANDLER_LIB = libmms-handler-dbus.a +MMS_HANDLER_DIR = ../mms-handler-dbus +MMS_HANDLER_BUILD_DIR = $(MMS_HANDLER_DIR)/build +MMS_HANDLER_DEBUG_LIB = $(MMS_HANDLER_BUILD_DIR)/debug/$(MMS_HANDLER_LIB) +MMS_HANDLER_RELEASE_LIB = $(MMS_HANDLER_BUILD_DIR)/release/$(MMS_HANDLER_LIB) + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +DEBUG_FLAGS = -g +RELEASE_FLAGS = -O2 +DEBUG_DEFS = -DDEBUG +RELEASE_DEFS = +WARN = -Wall +CFLAGS = $(shell pkg-config --cflags $(PKGS)) -I. -I$(GEN_DIR) \ + -I$(MMS_LIB_DIR)/include -I$(MMS_OFONO_DIR)/include \ + -I$(MMS_HANDLER_DIR)/include -MMD + +DEBUG_CFLAGS = $(DEBUG_FLAGS) $(DEBUG_DEFS) $(CFLAGS) +RELEASE_CFLAGS = $(RELEASE_FLAGS) $(RELEASE_DEFS) $(CFLAGS) + +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) +DEBUG_LIBS = \ + $(MMS_OFONO_DEBUG_LIB) \ + $(MMS_HANDLER_DEBUG_LIB) \ + $(MMS_LIB_DEBUG_LIB) \ + $(LIBS) +RELEASE_LIBS = \ + $(MMS_OFONO_RELEASE_LIB) \ + $(MMS_HANDLER_RELEASE_LIB) \ + $(MMS_LIB_RELEASE_LIB) \ + $(LIBS) + +# +# Files +# + +.PRECIOUS: $(GEN_SRC:%=$(GEN_DIR)/%) + +DEBUG_OBJS = \ + $(GEN_SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = \ + $(GEN_SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEBUG_DEPS = \ + mms_lib_debug_lib \ + mms_ofono_debug_lib \ + mms_handler_debug_lib +RELEASE_DEPS = \ + mms_lib_release_lib \ + mms_ofono_release_lib \ + mms_handler_release_lib +DEBUG_EXE_DEPS = \ + $(MMS_LIB_DEBUG_LIB) \ + $(MMS_OFONO_DEBUG_LIB) \ + $(MMS_HANDLER_DEBUG_LIB) \ + $(DEBUG_BUILD_DIR) +RELEASE_EXE_DEPS = \ + $(MMS_LIB_RELEASE_LIB) \ + $(MMS_OFONO_RELEASE_LIB) \ + $(MMS_HANDLER_RELEASE_LIB) \ + $(RELEASE_BUILD_DIR) +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +# +# Rules +# + +EXE = mms-engine +DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) +RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) + +debug: $(DEBUG_DEPS) $(DEBUG_EXE) + +release: $(RELEASE_DEPS) $(RELEASE_EXE) + +clean: + rm -fr $(BUILD_DIR) $(SRC_DIR)/*~ + +cleaner: clean + make -C $(MMS_LIB_DIR) clean + make -C $(MMS_OFONO_DIR) clean + make -C $(MMS_HANDLER_DIR) clean + +$(GEN_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +mms_lib_debug_lib: + make -C $(MMS_LIB_DIR) debug + +mms_lib_release_lib: + make -C $(MMS_LIB_DIR) release + +mms_ofono_debug_lib: + make -C $(MMS_OFONO_DIR) debug + +mms_ofono_release_lib: + make -C $(MMS_OFONO_DIR) release + +mms_handler_debug_lib: + make -C $(MMS_HANDLER_DIR) debug + +mms_handler_release_lib: + make -C $(MMS_HANDLER_DIR) release + +$(MMS_LIB_DEBUG_LIB): mms_lib_debug_lib + +$(MMS_LIB_RELEASE_LIB): mms_lib_release_lib + +$(MMS_OFONO_DEBUG_LIB): mms_ofono_debug_lib + +$(MMS_OFONO_RELEASE_LIB): mms_ofono_release_lib + +$(MMS_HANDLER_DEBUG_LIB): mms_handler_debug_lib + +$(MMS_HANDLER_RELEASE_LIB): mms_handler_release_lib + +$(GEN_DIR)/%.c: $(SPEC_DIR)/%.xml + gdbus-codegen --generate-c-code $(@:%.c=%) $< + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_EXE): $(DEBUG_EXE_DEPS) $(DEBUG_OBJS) + $(LD) $(DEBUG_FLAGS) $(DEBUG_OBJS) $(DEBUG_LIBS) -o $@ + +$(RELEASE_EXE): $(RELEASE_EXE_DEPS) $(RELEASE_OBJS) + $(LD) $(RELEASE_FLAGS) $(RELEASE_OBJS) $(RELEASE_LIBS) -o $@ + strip $@ diff --git a/mms-engine/main.c b/mms-engine/main.c new file mode 100644 index 0000000..fcac39f --- /dev/null +++ b/mms-engine/main.c @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include +#include + +#include "mms_engine.h" +#include "mms_ofono_log.h" +#include "mms_lib_log.h" +#include "mms_lib_util.h" +#include "mms_dispatcher.h" + +/* Options configurable from the command line */ +typedef struct mms_app_options { + GBusType bus_type; + gboolean keep_running; + MMSConfig config; +} MMSAppOptions; + +/* All known log modules */ +static MMSLogModule* mms_app_log_modules[] = { + &mms_log_default, +#define MMS_LIB_LOG_MODULE(m) &(m), + MMS_LIB_LOG_MODULES(MMS_LIB_LOG_MODULE) + MMS_OFONO_LOG_MODULES(MMS_LIB_LOG_MODULE) +#undef MMS_LIB_LOG_MODULE +}; + +/* Signal handler */ +static +gboolean +mms_app_signal( + gpointer arg) +{ + GMainLoop* loop = arg; + MMS_INFO("Caught signal, shutting down..."); + if (loop) { + g_idle_add((GSourceFunc)g_main_loop_quit, loop); + } else { + exit(0); + } + return FALSE; +} + +/* D-Bus event handlers */ +static +void +mms_app_bus_acquired( + GDBusConnection* bus, + const gchar* name, + gpointer arg) +{ + MMSEngine* engine = arg; + GError* error = NULL; + MMS_DEBUG("Bus acquired, starting..."); + if (!mms_engine_register(engine, bus, &error)) { + MMS_ERR("Could not start: %s", MMS_ERRMSG(error)); + g_error_free(error); + mms_engine_stop(engine); + } +} + +static +void +mms_app_name_acquired( + GDBusConnection* bus, + const gchar* name, + gpointer arg) +{ + MMS_DEBUG("Acquired service name '%s'", name); +} + +static +void +mms_app_name_lost( + GDBusConnection* bus, + const gchar* name, + gpointer arg) +{ + MMSEngine* engine = arg; + MMS_ERR("'%s' service already running or access denied", name); + mms_engine_stop(engine); +} + +/* Option parsing callbacks */ +static +gboolean +mms_app_option_loglevel( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + return mms_log_parse_option(value, mms_app_log_modules, + G_N_ELEMENTS(mms_app_log_modules), error); +} + +static +gboolean +mms_app_option_logtype( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + if (mms_log_set_type(value, MMS_APP_LOG_PREFIX)) { + return TRUE; + } else { + if (error) { + *error = g_error_new(G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Invalid log type \'%s\'", value); + } + return FALSE; + } +} + +static +gboolean +mms_app_option_verbose( + const gchar* name, + const gchar* value, + gpointer data, + GError** error) +{ + mms_log_default.level = MMS_LOGLEVEL_VERBOSE; + return TRUE; +} + +/* Parses command line and sets up application options */ +static +gboolean +mms_app_parse_options( + MMSAppOptions* opt, + int argc, + char* argv[]) +{ + gboolean ok; + GError* error = NULL; + gboolean session_bus = FALSE; + char* root_dir_help = g_strdup_printf( + "Root directory for MMS files [%s]", + opt->config.root_dir); + char* retry_secs_help = g_strdup_printf( + "Retry period in seconds [%d]", + opt->config.retry_secs); + char* idle_secs_help = g_strdup_printf( + "Inactivity timeout in seconds [%d]", + opt->config.idle_secs); + char* description = mms_log_description(mms_app_log_modules, + G_N_ELEMENTS(mms_app_log_modules)); + + GOptionContext* options; + GOptionEntry entries[] = { + { "session", 0, 0, G_OPTION_ARG_NONE, &session_bus, + "Use session bus (default is system)", NULL }, + { "root-dir", 'd', 0, G_OPTION_ARG_FILENAME, + (void*)&opt->config.root_dir, root_dir_help, "DIR" }, + { "retry-secs", 'r', 0, G_OPTION_ARG_INT, + &opt->config.retry_secs, retry_secs_help, "SEC" }, + { "idle-secs", 'i', 0, G_OPTION_ARG_INT, + &opt->config.idle_secs, idle_secs_help, "SEC" }, + { "keep-running", 'k', 0, G_OPTION_ARG_NONE, &opt->keep_running, + "Keep running after everything is done", NULL }, + { "keep-temp-files", 't', 0, G_OPTION_ARG_NONE, + &opt->config.keep_temp_files, + "Don't delete temporary files", NULL }, + { "attic", 'a', 0, G_OPTION_ARG_NONE, + &opt->config.attic_enabled, + "Store unrecognized push messages in the attic", NULL }, + { "verbose", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, + mms_app_option_verbose, "Be verbose (equivalent to -l=verbose)", + NULL }, + { "log-output", 'o', 0, G_OPTION_ARG_CALLBACK, mms_app_option_logtype, + "Log output [stdout]", "" }, + { "log-level", 'l', 0, G_OPTION_ARG_CALLBACK, mms_app_option_loglevel, + "Set log level (repeatable)", "[MODULE:]LEVEL" }, + { NULL } + }; + + options = g_option_context_new("- part of Jolla MMS system"); + g_option_context_add_main_entries(options, entries, NULL); + g_option_context_set_description(options, description); + ok = g_option_context_parse(options, &argc, &argv, &error); + g_option_context_free(options); + g_free(root_dir_help); + g_free(retry_secs_help); + g_free(idle_secs_help); + g_free(description); + + if (ok) { + MMS_INFO("Starting"); + if (session_bus) { + MMS_DEBUG("Attaching to session bus"); + opt->bus_type = G_BUS_TYPE_SESSION; + } else { + MMS_DEBUG("Attaching to system bus"); + opt->bus_type = G_BUS_TYPE_SYSTEM; + } + return TRUE; + } else { + fprintf(stderr, "%s\n", MMS_ERRMSG(error)); + g_error_free(error); + return FALSE; + } +} + +int main(int argc, char* argv[]) +{ + int result = 1; + MMSAppOptions opt = {0}; + mms_lib_init(); + mms_log_default.name = MMS_APP_LOG_PREFIX; + mms_lib_default_config(&opt.config); + if (mms_app_parse_options(&opt, argc, argv)) { + MMSEngine* engine; + unsigned int engine_flags = 0; + if (opt.keep_running) engine_flags |= MMS_ENGINE_FLAG_KEEP_RUNNING; + + /* Create engine instance. This may fail */ + engine = mms_engine_new(&opt.config, engine_flags, + mms_app_log_modules, G_N_ELEMENTS(mms_app_log_modules)); + if (engine) { + guint name_id; + + /* Setup main loop */ + GMainLoop* loop = g_main_loop_new(NULL, FALSE); + g_unix_signal_add(SIGTERM, mms_app_signal, loop); + g_unix_signal_add(SIGINT, mms_app_signal, loop); + + /* Acquire name, don't allow replacement */ + name_id = g_bus_own_name(opt.bus_type, MMS_ENGINE_SERVICE, + G_BUS_NAME_OWNER_FLAGS_REPLACE, mms_app_bus_acquired, + mms_app_name_acquired, mms_app_name_lost, engine, NULL); + + /* Run the main loop */ + mms_engine_run(engine, loop); + + /* Cleanup and exit */ + g_bus_unown_name(name_id); + g_main_loop_unref(loop); + mms_engine_unref(engine); + } + MMS_INFO("Exiting"); + result = 0; + } + if (mms_log_func == mms_log_syslog) { + closelog(); + } + return result; +} diff --git a/mms-engine/mms-engine.conf b/mms-engine/mms-engine.conf new file mode 100644 index 0000000..17cfb30 --- /dev/null +++ b/mms-engine/mms-engine.conf @@ -0,0 +1,20 @@ + + + + + + diff --git a/mms-engine/mms-engine.pro b/mms-engine/mms-engine.pro new file mode 100644 index 0000000..2553824 --- /dev/null +++ b/mms-engine/mms-engine.pro @@ -0,0 +1,54 @@ +TEMPLATE = app +CONFIG -= qt +CONFIG += link_pkgconfig +PKGCONFIG += libsoup-2.4 gio-unix-2.0 gio-2.0 glib-2.0 dbus-1 libwspcodec +DBUS_INTERFACE_DIR = $$_PRO_FILE_PWD_ +MMS_LIB_DIR = $$_PRO_FILE_PWD_/../mms-lib +MMS_OFONO_DIR = $$_PRO_FILE_PWD_/../mms-ofono +MMS_HANDLER_DIR = $$_PRO_FILE_PWD_/../mms-handler-dbus +INCLUDEPATH += $$MMS_OFONO_DIR/include +INCLUDEPATH += $$MMS_LIB_DIR/include +INCLUDEPATH += $$MMS_HANDLER_DIR/include +QMAKE_CFLAGS += -Wno-unused + +SOURCES += \ + main.c \ + mms_engine.c +HEADERS += \ + mms_engine.h +OTHER_FILES += \ + org.nemomobile.MmsEngine.push.conf \ + org.nemomobile.MmsEngine.dbus.conf \ + org.nemomobile.MmsEngine.service \ + org.nemomobile.MmsEngine.xml + +CONFIG(debug, debug|release) { + DEFINES += DEBUG + DESTDIR = $$_PRO_FILE_PWD_/build/debug + LIBS += $$MMS_OFONO_DIR/build/debug/libmms-ofono.a + LIBS += $$MMS_HANDLER_DIR/build/debug/libmms-handler-dbus.a + LIBS += $$MMS_LIB_DIR/build/debug/libmms-lib.a +} else { + DESTDIR = $$_PRO_FILE_PWD_/build/release + LIBS += $$MMS_OFONO_DIR/build/release/libmms-ofono.a + LIBS += $$MMS_HANDLER_DIR/build/release/libmms-handler-dbus.a + LIBS += $$MMS_LIB_DIR/build/release/libmms-lib.a +} + +MMS_ENGINE_DBUS_XML = $$DBUS_INTERFACE_DIR/org.nemomobile.MmsEngine.xml +MMS_ENGINE_DBUS_H = org.nemomobile.MmsEngine.h +org_nemomobile_mmsengine_h.input = MMS_ENGINE_DBUS_XML +org_nemomobile_mmsengine_h.output = $$MMS_ENGINE_DBUS_H +org_nemomobile_mmsengine_h.commands = gdbus-codegen --generate-c-code \ + org.nemomobile.MmsEngine $$MMS_ENGINE_DBUS_XML +org_nemomobile_mmsengine_h.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_nemomobile_mmsengine_h + +MMS_ENGINE_DBUS_C = org.nemomobile.MmsEngine.c +org_nemomobile_mmsengine_c.input = MMS_ENGINE_DBUS_XML +org_nemomobile_mmsengine_c.output = $$MMS_ENGINE_DBUS_C +org_nemomobile_mmsengine_c.commands = gdbus-codegen --generate-c-code \ + org.nemomobile.MmsEngine $$MMS_ENGINE_DBUS_XML +org_nemomobile_mmsengine_c.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_nemomobile_mmsengine_c +GENERATED_SOURCES += $$MMS_ENGINE_DBUS_C diff --git a/mms-engine/mms_engine.c b/mms-engine/mms_engine.c new file mode 100644 index 0000000..907d2d0 --- /dev/null +++ b/mms-engine/mms_engine.c @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_engine.h" +#include "mms_dispatcher.h" +#include "mms_lib_util.h" +#include "mms_ofono_connman.h" +#include "mms_handler_dbus.h" +#include "mms_log.h" + +/* Generated code */ +#include "org.nemomobile.MmsEngine.h" + +#ifdef DEBUG +# define ENABLE_TEST +#endif + +struct mms_engine { + GObject parent; + const MMSConfig* config; + MMSDispatcher* dispatcher; + MMSDispatcherDelegate dispatcher_delegate; + MMSLogModule** log_modules; + int log_count; + GDBusConnection* engine_bus; + OrgNemomobileMmsEngine* service; + GMainLoop* loop; + gboolean stopped; + gboolean keep_running; + guint start_timeout_id; + gulong push_signal_id; + gulong push_notify_signal_id; + gulong receive_signal_id; + gulong read_report_signal_id; + gulong cancel_signal_id; + gulong set_log_level_signal_id; + gulong set_log_type_signal_id; +#ifdef ENABLE_TEST + gulong test_signal_id; +#endif +}; + +typedef GObjectClass MMSEngineClass; +G_DEFINE_TYPE(MMSEngine, mms_engine, G_TYPE_OBJECT); +#define MMS_ENGINE_TYPE (mms_engine_get_type()) +#define MMS_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_ENGINE_TYPE, MMSEngine)) + +inline static MMSEngine* +mms_engine_from_dispatcher_delegate(MMSDispatcherDelegate* delegate) + { return MMS_CAST(delegate,MMSEngine,dispatcher_delegate); } + +static +gboolean +mms_engine_stop_callback( + gpointer data) +{ + MMSEngine* engine = data; + mms_engine_stop(engine); + mms_engine_unref(engine); + return FALSE; +} + +static +void +mms_engine_stop_schedule( + MMSEngine* engine) +{ + g_idle_add(mms_engine_stop_callback, mms_engine_ref(engine)); +} + +static +gboolean +mms_engine_start_timeout_callback( + gpointer data) +{ + MMSEngine* engine = data; + MMS_ASSERT(engine->start_timeout_id); + MMS_INFO("Shutting down due to inactivity..."); + engine->start_timeout_id = 0; + mms_engine_stop_schedule(engine); + return FALSE; +} + +static +void +mms_engine_start_timeout_cancel( + MMSEngine* engine) +{ + if (engine->start_timeout_id) { + g_source_remove(engine->start_timeout_id); + engine->start_timeout_id = 0; + } +} + +static +void +mms_engine_start_timeout_schedule( + MMSEngine* engine) +{ + mms_engine_start_timeout_cancel(engine); + engine->start_timeout_id = g_timeout_add_seconds(engine->config->idle_secs, + mms_engine_start_timeout_callback, engine); +} + +#ifdef ENABLE_TEST +/* org.nemomobile.MmsEngine.test */ +static +gboolean +mms_engine_handle_test( + OrgNemomobileMmsEngine* service, + GDBusMethodInvocation* call, + MMSEngine* engine) +{ + MMS_DEBUG("Test"); + org_nemomobile_mms_engine_complete_test(service, call); + return TRUE; +} +#endif /* ENABLE_TEST */ + +/* org.nemomobile.MmsEngine.receiveMessage */ +static +gboolean +mms_engine_handle_receive_message( + OrgNemomobileMmsEngine* service, + GDBusMethodInvocation* call, + int database_id, + const char* imsi, + gboolean automatic, + GVariant* data, + MMSEngine* engine) +{ + gsize len = 0; + const guint8* bytes = g_variant_get_fixed_array(data, &len, 1); + MMS_DEBUG("Processing push %u bytes from %s", (guint)len, imsi); + if (imsi && bytes && len) { + char* id = g_strdup_printf("%d", database_id); + GBytes* push = g_bytes_new(bytes, len); + if (mms_dispatcher_receive_message(engine->dispatcher, id, imsi, + automatic, push)) { + if (mms_dispatcher_start(engine->dispatcher)) { + mms_engine_start_timeout_cancel(engine); + } + org_nemomobile_mms_engine_complete_receive_message(service, call); + } else { + g_dbus_method_invocation_return_error(call, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "Unable to receive message"); + } + g_bytes_unref(push); + g_free(id); + } else { + g_dbus_method_invocation_return_error(call, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "Invalid parameters"); + } + return TRUE; +} + +/* org.nemomobile.MmsEngine.sendReadReport */ +static +gboolean +mms_engine_handle_send_read_report( + OrgNemomobileMmsEngine* service, + GDBusMethodInvocation* call, + const char* id, + const char* imsi, + const char* message_id, + const char* to, + int read_status, /* 0: Read 1: Deleted without reading */ + MMSEngine* engine) +{ + MMS_DEBUG_("%s %s %s %s %d", id, imsi, message_id, to, read_status); + if (mms_dispatcher_send_read_report(engine->dispatcher, id, imsi, + message_id, to, (read_status == 1) ? MMS_READ_STATUS_DELETED : + MMS_READ_STATUS_READ)) { + if (mms_dispatcher_start(engine->dispatcher)) { + mms_engine_start_timeout_cancel(engine); + } + org_nemomobile_mms_engine_complete_send_read_report(service, call); + } else { + g_dbus_method_invocation_return_error(call, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "Error submitting read report"); + } + return TRUE; +} + +/* org.nemomobile.MmsEngine.cancel */ +static +gboolean +mms_engine_handle_cancel( + OrgNemomobileMmsEngine* service, + GDBusMethodInvocation* call, + const char* id, + MMSEngine* engine) +{ + MMS_DEBUG_("%s", id); + mms_dispatcher_cancel(engine->dispatcher, id); + org_nemomobile_mms_engine_complete_cancel(service, call); + return TRUE; +} + +/* org.nemomobile.MmsEngine.pushNotify */ +static +gboolean +mms_engine_handle_push_notify( + OrgNemomobileMmsEngine* proxy, + GDBusMethodInvocation* call, + const char* imsi, + const char* type, + GVariant* data, + MMSEngine* engine) +{ + gsize len = 0; + const guint8* bytes = g_variant_get_fixed_array(data, &len, 1); + MMS_DEBUG("Received %u bytes from %s", (guint)len, imsi); + if (!type || g_ascii_strcasecmp(type, MMS_CONTENT_TYPE)) { + MMS_ERR("Unsupported content type %s", type); + g_dbus_method_invocation_return_error(call, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "Unsupported content type"); + } else if (!imsi || !imsi[0]) { + MMS_ERR_("IMSI is missing"); + g_dbus_method_invocation_return_error(call, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "IMSI is missing"); + } else if (!bytes || !len) { + MMS_ERR_("No data provided"); + g_dbus_method_invocation_return_error(call, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "No data provided"); + } else { + GBytes* msg = g_bytes_new(bytes, len); + if (mms_dispatcher_handle_push(engine->dispatcher, imsi, msg)) { + if (mms_dispatcher_start(engine->dispatcher)) { + mms_engine_start_timeout_cancel(engine); + } + org_nemomobile_mms_engine_complete_push(proxy, call); + } else { + g_dbus_method_invocation_return_error(call, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, "Unable to handle push message"); + } + g_bytes_unref(msg); + } + return TRUE; +} + +/* org.nemomobile.MmsEngine.push */ +static +gboolean +mms_engine_handle_push( + OrgNemomobileMmsEngine* proxy, + GDBusMethodInvocation* call, + const char* imsi, + const char* from, + guint32 remote_time, + guint32 local_time, + int dst_port, + int src_port, + const char* type, + GVariant* data, + MMSEngine* eng) +{ + return mms_engine_handle_push_notify(proxy, call, imsi, type, data, eng); +} + +/* org.nemomobile.MmsEngine.setLogLevel */ +static +gboolean +mms_engine_handle_set_log_level( + OrgNemomobileMmsEngine* service, + GDBusMethodInvocation* call, + const char* module, + gint level, + MMSEngine* engine) +{ + MMS_DEBUG_("%s:%d", module, level); + if (module && module[0]) { + int i; + for (i=0; ilog_count; i++) { + MMSLogModule* log = engine->log_modules[i]; + if (log->name && log->name[0] && !strcmp(log->name, module)) { + log->level = level; + break; + } + } + } else { + mms_log_default.level = level; + } + org_nemomobile_mms_engine_complete_set_log_level(service, call); + return TRUE; +} + +/* org.nemomobile.MmsEngine.setLogType */ +static +gboolean +mms_engine_handle_set_log_type( + OrgNemomobileMmsEngine* service, + GDBusMethodInvocation* call, + const char* type, + MMSEngine* engine) +{ + MMS_DEBUG_("%s", type); + mms_log_set_type(type, MMS_APP_LOG_PREFIX); + org_nemomobile_mms_engine_complete_set_log_type(service, call); + return TRUE; +} + +MMSEngine* +mms_engine_new( + const MMSConfig* config, + unsigned int flags, + MMSLogModule* log_modules[], + int log_count) +{ + MMSConnMan* cm = mms_connman_ofono_new(); + if (cm) { + MMSEngine* mms = g_object_new(MMS_ENGINE_TYPE, NULL); + MMSHandler* handler = mms_handler_dbus_new(); + mms->dispatcher = mms_dispatcher_new(config, cm, handler); + mms_connman_unref(cm); + mms_handler_unref(handler); + mms_dispatcher_set_delegate(mms->dispatcher, + &mms->dispatcher_delegate); + + if (flags & MMS_ENGINE_FLAG_KEEP_RUNNING) { + mms->keep_running = TRUE; + } + + mms->config = config; + mms->log_modules = log_modules; + mms->log_count = log_count; + mms->service = org_nemomobile_mms_engine_skeleton_new(); + mms->push_signal_id = + g_signal_connect(mms->service, "handle-push", + G_CALLBACK(mms_engine_handle_push), mms); + mms->push_notify_signal_id = + g_signal_connect(mms->service, "handle-push-notify", + G_CALLBACK(mms_engine_handle_push_notify), mms); + mms->cancel_signal_id = + g_signal_connect(mms->service, "handle-cancel", + G_CALLBACK(mms_engine_handle_cancel), mms); + mms->receive_signal_id = + g_signal_connect(mms->service, "handle-receive-message", + G_CALLBACK(mms_engine_handle_receive_message), mms); + mms->read_report_signal_id = + g_signal_connect(mms->service, "handle-send-read-report", + G_CALLBACK(mms_engine_handle_send_read_report), mms); + mms->set_log_level_signal_id = + g_signal_connect(mms->service, "handle-set-log-level", + G_CALLBACK(mms_engine_handle_set_log_level), mms); + mms->set_log_type_signal_id = + g_signal_connect(mms->service, "handle-set-log-type", + G_CALLBACK(mms_engine_handle_set_log_type), mms); + +#ifdef ENABLE_TEST + mms->test_signal_id = + g_signal_connect(mms->service, "handle-test", + G_CALLBACK(mms_engine_handle_test), mms); +#endif + + return mms; + } + + return NULL; +} + +MMSEngine* +mms_engine_ref( + MMSEngine* engine) +{ + return g_object_ref(MMS_ENGINE(engine)); +} + +void +mms_engine_unref( + MMSEngine* engine) +{ + if (engine) g_object_unref(MMS_ENGINE(engine)); +} + +void +mms_engine_run( + MMSEngine* engine, + GMainLoop* loop) +{ + MMS_ASSERT(!engine->loop); + engine->loop = loop; + engine->stopped = FALSE; + if (!mms_dispatcher_start(engine->dispatcher) && !engine->keep_running) { + mms_engine_start_timeout_schedule(engine); + } + g_main_loop_run(loop); + mms_engine_start_timeout_cancel(engine); + engine->loop = NULL; +} + +void +mms_engine_stop( + MMSEngine* engine) +{ + engine->stopped = TRUE; + if (engine->loop) g_main_loop_quit(engine->loop); +} + +void +mms_engine_unregister( + MMSEngine* engine) +{ + if (engine->engine_bus) { + g_dbus_interface_skeleton_unexport( + G_DBUS_INTERFACE_SKELETON(engine->service)); + g_object_unref(engine->engine_bus); + engine->engine_bus = NULL; + } +} + +gboolean +mms_engine_register( + MMSEngine* engine, + GDBusConnection* bus, + GError** error) +{ + mms_engine_unregister(engine); + if (g_dbus_interface_skeleton_export( + G_DBUS_INTERFACE_SKELETON(engine->service), bus, + MMS_ENGINE_PATH, error)) { + g_object_ref(engine->engine_bus = bus); + return TRUE; + } else { + return FALSE; + } +} + +static +void +mms_engine_delegate_dispatcher_done( + MMSDispatcherDelegate* delegate, + MMSDispatcher* dispatcher) +{ + MMSEngine* engine = mms_engine_from_dispatcher_delegate(delegate); + MMS_DEBUG("All done"); + if (!engine->keep_running) mms_engine_stop_schedule(engine); +} + +/** + * Per object initializer + * + * Only sets up internal state (all values set to zero) + */ +static +void +mms_engine_init( + MMSEngine* engine) +{ + engine->dispatcher_delegate.fn_done = mms_engine_delegate_dispatcher_done; +} + +/** + * First stage of deinitialization (release all references). + * May be called more than once in the lifetime of the object. + */ +static +void +mms_engine_dispose( + GObject* object) +{ + MMSEngine* e = MMS_ENGINE(object); + MMS_VERBOSE_("%p", e); + MMS_ASSERT(!e->loop); + mms_engine_unregister(e); + mms_engine_start_timeout_cancel(e); + if (e->service) { +#ifdef ENABLE_TEST + g_signal_handler_disconnect(e->service, e->test_signal_id); +#endif + g_signal_handler_disconnect(e->service, e->push_signal_id); + g_signal_handler_disconnect(e->service, e->push_notify_signal_id); + g_signal_handler_disconnect(e->service, e->receive_signal_id); + g_signal_handler_disconnect(e->service, e->read_report_signal_id); + g_signal_handler_disconnect(e->service, e->cancel_signal_id); + g_signal_handler_disconnect(e->service, e->set_log_level_signal_id); + g_signal_handler_disconnect(e->service, e->set_log_type_signal_id); + g_object_unref(e->service); + e->service = NULL; + } + if (e->dispatcher) { + mms_dispatcher_set_delegate(e->dispatcher, NULL); + mms_dispatcher_unref(e->dispatcher); + e->dispatcher = NULL; + } + G_OBJECT_CLASS(mms_engine_parent_class)->dispose(object); +} + +/** + * Per class initializer + */ +static +void +mms_engine_class_init( + MMSEngineClass* klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS(klass); + MMS_ASSERT(object_class); + object_class->dispose = mms_engine_dispose; + MMS_VERBOSE_("done"); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-engine/mms_engine.h b/mms-engine/mms_engine.h new file mode 100644 index 0000000..1c62e0b --- /dev/null +++ b/mms-engine/mms_engine.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_ENGINE_H +#define JOLLA_MMS_ENGINE_H + +#include +#include +#include "mms_lib_types.h" + +#define MMS_APP_LOG_PREFIX "mms-engine" + +#define MMS_ENGINE_SERVICE "org.nemomobile.MmsEngine" +#define MMS_ENGINE_PATH "/" + +#define MMS_ENGINE_FLAG_KEEP_RUNNING (0x01) + +typedef struct mms_engine MMSEngine; + +MMSEngine* +mms_engine_new( + const MMSConfig* config, + unsigned int flags, + MMSLogModule* log_modules[], + int log_count); + +MMSEngine* +mms_engine_ref( + MMSEngine* engine); + +void +mms_engine_unref( + MMSEngine* engine); + +void +mms_engine_run( + MMSEngine* engine, + GMainLoop* loop); + +void +mms_engine_stop( + MMSEngine* engine); + +gboolean +mms_engine_register( + MMSEngine* engine, + GDBusConnection* bus, + GError** error); + +void +mms_engine_unregister( + MMSEngine* engine); + +#endif /* JOLLA_MMS_ENGINE_H */ diff --git a/mms-engine/org.nemomobile.MmsEngine.dbus.conf b/mms-engine/org.nemomobile.MmsEngine.dbus.conf new file mode 100644 index 0000000..cf906e3 --- /dev/null +++ b/mms-engine/org.nemomobile.MmsEngine.dbus.conf @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/mms-engine/org.nemomobile.MmsEngine.push.conf b/mms-engine/org.nemomobile.MmsEngine.push.conf new file mode 100644 index 0000000..baf1667 --- /dev/null +++ b/mms-engine/org.nemomobile.MmsEngine.push.conf @@ -0,0 +1,9 @@ +# This file goes to /etc/ofono/push_forwarder.d or /etc/push-agent +# directory depending of who starts MMS engine, push-forwarder oFono +# plugin or push-agent service. +[MMS Engine] +ContentType = application/vnd.wap.mms-message +Interface = org.nemomobile.MmsEngine +Service = org.nemomobile.MmsEngine +Method = push +Path = / diff --git a/mms-engine/org.nemomobile.MmsEngine.service b/mms-engine/org.nemomobile.MmsEngine.service new file mode 100644 index 0000000..a4d9667 --- /dev/null +++ b/mms-engine/org.nemomobile.MmsEngine.service @@ -0,0 +1,5 @@ +# Goes to /usr/share/dbus-1/system-services +[D-BUS Service] +Name=org.nemomobile.MmsEngine +Exec=/usr/sbin/mms-engine -o syslog +User=nemo diff --git a/mms-engine/org.nemomobile.MmsEngine.xml b/mms-engine/org.nemomobile.MmsEngine.xml new file mode 100644 index 0000000..2abd8e3 --- /dev/null +++ b/mms-engine/org.nemomobile.MmsEngine.xml @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mms-handler-dbus/Makefile b/mms-handler-dbus/Makefile new file mode 100644 index 0000000..82981e6 --- /dev/null +++ b/mms-handler-dbus/Makefile @@ -0,0 +1,115 @@ +# -*- Mode: makefile -*- + +.PHONY: clean all debug release + +# Required packages +PKGS = glib-2.0 gio-2.0 gio-unix-2.0 + +# +# Default target +# + +all: debug release + +# +# Sources +# + +SRC = mms_handler_dbus.c +GEN_SRC = org.nemomobile.MmsHandler.c + +# +# Directories +# + +SRC_DIR = src +INCLUDE_DIR = include +BUILD_DIR = build +GEN_DIR = $(BUILD_DIR) +SPEC_DIR = spec +MMS_LIB_INCLUDE = ../mms-lib/include +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +WARN = -Wall +ARFLAGS = rc +INCLUDES = -I$(SRC_DIR) -I$(INCLUDE_DIR) -I$(MMS_LIB_INCLUDE) -I$(GEN_DIR) -I. +CFLAGS = $(WARNINGS) $(INCLUDES) $(shell pkg-config --cflags $(PKGS)) -MMD +DEBUG_FLAGS = -g +RELEASE_FLAGS = -O2 +DEBUG_CFLAGS = $(DEBUG_FLAGS) $(DEBUG_DEFS) $(CFLAGS) +RELEASE_CFLAGS = $(RELEASE_FLAGS) $(RELEASE_DEFS) $(CFLAGS) + +# +# Files +# + +.PRECIOUS: $(GEN_SRC:%=$(GEN_DIR)/%) + +DEBUG_OBJS = \ + $(GEN_SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = \ + $(GEN_SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +# +# Rules +# +LIB = libmms-handler-dbus.a +DEBUG_LIB = $(DEBUG_BUILD_DIR)/$(LIB) +RELEASE_LIB = $(RELEASE_BUILD_DIR)/$(LIB) + +debug: $(DEBUG_LIB) + +release: $(RELEASE_LIB) + +clean: + rm -fr $(BUILD_DIR) *~ $(SRC_DIR)/*~ $(INCLUDE_DIR)/*~ + +$(GEN_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(GEN_DIR)/%.c: $(SPEC_DIR)/%.xml + gdbus-codegen --generate-c-code $(@:%.c=%) $< + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_LIB): $(DEBUG_BUILD_DIR) $(DEBUG_OBJS) + $(AR) $(ARFLAGS) $@ $(DEBUG_OBJS) + +$(RELEASE_LIB): $(RELEASE_BUILD_DIR) $(RELEASE_OBJS) + $(AR) $(ARFLAGS) $@ $(RELEASE_OBJS) diff --git a/mms-handler-dbus/include/mms_handler_dbus.h b/mms-handler-dbus/include/mms_handler_dbus.h new file mode 100644 index 0000000..772134b --- /dev/null +++ b/mms-handler-dbus/include/mms_handler_dbus.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_HANDLER_DBUS_H +#define JOLLA_MMS_HANDLER_DBUS_H + +#include "mms_handler.h" + +MMSHandler* +mms_handler_dbus_new(void); + +#endif /* JOLLA_MMS_HANDLER_DBUS_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ + diff --git a/mms-handler-dbus/mms-handler-dbus.pro b/mms-handler-dbus/mms-handler-dbus.pro new file mode 100644 index 0000000..7796b77 --- /dev/null +++ b/mms-handler-dbus/mms-handler-dbus.pro @@ -0,0 +1,39 @@ +TEMPLATE = lib +CONFIG += staticlib +CONFIG -= qt +CONFIG += link_pkgconfig +PKGCONFIG += glib-2.0 gio-2.0 gio-unix-2.0 +DBUS_SPEC_DIR = $$_PRO_FILE_PWD_/spec +INCLUDEPATH += include +INCLUDEPATH += ../mms-lib/include +QMAKE_CFLAGS += -Wno-unused + +CONFIG(debug, debug|release) { + DEFINES += DEBUG + DESTDIR = $$_PRO_FILE_PWD_/build/debug +} else { + DESTDIR = $$_PRO_FILE_PWD_/build/release +} + +SOURCES += src/mms_handler_dbus.c +HEADERS += include/mms_handler_dbus.h +OTHER_FILES += spec/org.nemomobile.MmsHandler.xml + +# org.nemomobile.MmsHandler +COMMHISTORYIF_XML = $$DBUS_SPEC_DIR/org.nemomobile.MmsHandler.xml +COMMHISTORYIF_GENERATE = gdbus-codegen --generate-c-code \ + org.nemomobile.MmsHandler $$COMMHISTORYIF_XML +COMMHISTORYIF_H = org.nemomobile.MmsHandler.h +org_nemomobile_MmsHandler_h.input = COMMHISTORYIF_XML +org_nemomobile_MmsHandler_h.output = $$COMMHISTORYIF_H +org_nemomobile_MmsHandler_h.commands = $$COMMHISTORYIF_GENERATE +org_nemomobile_MmsHandler_h.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_nemomobile_MmsHandler_h + +COMMHISTORYIF_C = org.nemomobile.MmsHandler.c +org_nemomobile_MmsHandler_c.input = COMMHISTORYIF_XML +org_nemomobile_MmsHandler_c.output = $$COMMHISTORYIF_C +org_nemomobile_MmsHandler_c.commands = $$COMMHISTORYIF_GENERATE +org_nemomobile_MmsHandler_c.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_nemomobile_MmsHandler_c +GENERATED_SOURCES += $$COMMHISTORYIF_C diff --git a/mms-handler-dbus/spec/org.nemomobile.MmsHandler.xml b/mms-handler-dbus/spec/org.nemomobile.MmsHandler.xml new file mode 100644 index 0000000..8687eff --- /dev/null +++ b/mms-handler-dbus/spec/org.nemomobile.MmsHandler.xml @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mms-handler-dbus/src/mms_handler_dbus.c b/mms-handler-dbus/src/mms_handler_dbus.c new file mode 100644 index 0000000..0d574ee --- /dev/null +++ b/mms-handler-dbus/src/mms_handler_dbus.c @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_handler_dbus.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_handler_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-handler-dbus"); + +/* Generated code */ +#include "org.nemomobile.MmsHandler.h" + +/* Class definition */ +typedef MMSHandlerClass MMSHandlerDbusClass; +typedef struct mms_handler_dbus { + MMSHandler handler; + OrgNemomobileMmsHandler* proxy; +} MMSHandlerDbus; + +G_DEFINE_TYPE(MMSHandlerDbus, mms_handler_dbus, MMS_TYPE_HANDLER); +#define MMS_TYPE_HANDLER_DBUS (mms_handler_dbus_get_type()) +#define MMS_HANDLER_DBUS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_HANDLER_DBUS, MMSHandlerDbus)) + +MMSHandler* +mms_handler_dbus_new() +{ + MMSHandlerDbus* db = g_object_new(MMS_TYPE_HANDLER_DBUS, NULL); + return &db->handler; +} + +/** + * Creates D-Bus connection to MMS handler. Caller owns the returned + * reference. + */ +static +OrgNemomobileMmsHandler* +mms_handler_dbus_connect( + MMSHandler* handler) +{ + MMSHandlerDbus* dbus = MMS_HANDLER_DBUS(handler); + if (!dbus->proxy) { + GError* error = NULL; + dbus->proxy = org_nemomobile_mms_handler_proxy_new_for_bus_sync( + G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, + "org.nemomobile.MmsHandler", "/", NULL, &error); + if (!dbus->proxy) { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + } + return dbus->proxy; +} + +static +char* +mms_handler_dbus_message_notify( + MMSHandler* handler, + const char* imsi, + const char* from, + const char* subject, + time_t expiry, + GBytes* push) +{ + char* id = NULL; + OrgNemomobileMmsHandler* proxy = mms_handler_dbus_connect(handler); + if (proxy) { + gsize len = 0; + const void* data = g_bytes_get_data(push, &len); + GError* error = NULL; + GVariant* bytes = g_variant_ref_sink(g_variant_new_from_data( + G_VARIANT_TYPE_BYTESTRING, data, len, TRUE, NULL, NULL)); + if (!org_nemomobile_mms_handler_call_message_notification_sync( + proxy, imsi, from, subject, expiry, bytes, &id, NULL, &error)) { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + g_variant_unref(bytes); + } + return id; +} + +static +gboolean +mms_handler_dbus_message_received( + MMSHandler* handler, + MMSMessage* msg) +{ + gboolean ok = FALSE; + OrgNemomobileMmsHandler* proxy = mms_handler_dbus_connect(handler); + MMS_ASSERT(msg->id && msg->id[0]); + if (msg->id && msg->id[0] && proxy) { + const char* subject = msg->subject ? msg->subject : ""; + GError* error = NULL; + GSList* list = msg->parts; + GVariant* parts; + GVariantBuilder b; + + g_variant_builder_init(&b, G_VARIANT_TYPE("a(sss)")); + while (list) { + const MMSMessagePart* part = list->data; + g_variant_builder_add(&b, "(sss)", part->file, + part->content_type, part->content_id); + list = list->next; + } + + parts = g_variant_ref_sink(g_variant_builder_end(&b)); + ok = org_nemomobile_mms_handler_call_message_received_sync( + proxy, msg->id, msg->message_id, msg->from, (const char**)msg->to, + (const char**)msg->cc, subject, msg->date, msg->priority, + msg->cls, msg->read_report_req, parts, NULL, &error); + if (!ok) { + MMS_ERR("Failed to nofity commhistoryd: %s", MMS_ERRMSG(error)); + g_error_free(error); + } + + g_variant_unref(parts); + } + return ok; +} + +/* Updates message state in the database */ +static +gboolean +mms_handler_dbus_message_receive_state_changed( + MMSHandler* handler, + const char* id, + MMS_RECEIVE_STATE state) +{ + gboolean ok = FALSE; + OrgNemomobileMmsHandler* proxy = mms_handler_dbus_connect(handler); + MMS_ASSERT(id && id[0]); + if (id && id[0] && proxy) { + GError* error = NULL; + if (org_nemomobile_mms_handler_call_message_receive_state_changed_sync( + proxy, id, state, NULL, &error)) { + ok = TRUE; + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + } + return ok; +} + +static +void +mms_handler_dbus_dispose( + GObject* object) +{ + MMSHandlerDbus* dbus = MMS_HANDLER_DBUS(object); + if (dbus->proxy) { + g_object_unref(dbus->proxy); + dbus->proxy = NULL; + } + G_OBJECT_CLASS(mms_handler_dbus_parent_class)->dispose(object); +} + +static +void +mms_handler_dbus_class_init( + MMSHandlerDbusClass* klass) +{ + klass->fn_message_notify = mms_handler_dbus_message_notify; + klass->fn_message_received = mms_handler_dbus_message_received; + klass->fn_message_receive_state_changed = + mms_handler_dbus_message_receive_state_changed; + G_OBJECT_CLASS(klass)->dispose = mms_handler_dbus_dispose; +} + +static +void +mms_handler_dbus_init( + MMSHandlerDbus* dbus) +{ +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-handler-dbus/test/mms_handler_dbus_client/Makefile b/mms-handler-dbus/test/mms_handler_dbus_client/Makefile new file mode 100644 index 0000000..f9abeef --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_client/Makefile @@ -0,0 +1,155 @@ +# -*- Mode: makefile -*- + +.PHONY: all debug release clean +.PHONY: mms_lib_debug_lib mms_lib_release_lib + +# Required packages +LIB_PKGS = libsoup-2.4 glib-2.0 gio-2.0 gio-unix-2.0 +PKGS = $(LIB_PKGS) + +# +# Default target +# + +all: debug release + +# +# Sources +# + +SRC = test_mms_handler_dbus_client.c +SRC1 = mms_handler_dbus.c +GEN_SRC = org.nemomobile.MmsHandler.c + +# +# Directories +# + +SRC_DIR = . +SRC1_DIR = ../../src +BUILD_DIR = build +GEN_DIR = $(BUILD_DIR) +SPEC_DIR = ../../spec +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# mms-lib +# + +MMS_LIB_LIB = libmms.a +MMS_LIB_DIR = ../../../mms-lib +MMS_LIB_BUILD_DIR = $(MMS_LIB_DIR)/build +MMS_LIB_DEBUG_LIB = $(MMS_LIB_BUILD_DIR)/debug/$(MMS_LIB_LIB) +MMS_LIB_RELEASE_LIB = $(MMS_LIB_BUILD_DIR)/release/$(MMS_LIB_LIB) + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +DEBUG_FLAGS = -g +RELEASE_FLAGS = -O2 +DEBUG_DEFS = -DDEBUG +RELEASE_DEFS = +WARNINGS = -Wall +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) +CFLAGS = $(shell pkg-config --cflags $(PKGS)) -I. -I../../include \ + -I$(MMS_LIB_DIR)/include -I$(SRC1_DIR) -I$(GEN_DIR) -MMD + +DEBUG_CFLAGS = $(WARNINGS) $(DEBUG_FLAGS) $(DEBUG_DEFS) $(CFLAGS) +RELEASE_CFLAGS = $(WARNINGS) $(RELEASE_FLAGS) $(RELEASE_DEFS) $(CFLAGS) +DEBUG_LIBS = $(MMS_LIB_DEBUG_LIB) $(LIBS) +RELEASE_LIBS = $(MMS_LIB_RELEASE_LIB) $(LIBS) + +# +# Files +# + +.PRECIOUS: $(GEN_SRC:%=$(GEN_DIR)/%) + +DEBUG_OBJS = \ + $(GEN_SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) \ + $(SRC1:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = \ + $(GEN_SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) \ + $(SRC1:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEBUG_EXE_DEPS = $(MMS_LIB_DEBUG_LIB) $(DEBUG_BUILD_DIR) +RELEASE_EXE_DEPS = $(MMS_LIB_RELEASE_LIB) $(RELEASE_BUILD_DIR) +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +# +# Rules +# + +EXE = test_mms_handler_dbus_client +DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) +RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) + +debug: $(DEBUG_EXE) + +release: $(RELEASE_EXE) + +clean: + make -C $(MMS_LIB_DIR) clean + rm -fr $(BUILD_DIR) $(SRC_DIR)/*~ + +mms_lib_debug_lib: + make -C $(MMS_LIB_DIR) debug + +mms_lib_release_lib: + make -C $(MMS_LIB_DIR) release + +$(MMS_LIB_DEBUG_LIB): mms_lib_debug_lib + +$(MMS_LIB_RELEASE_LIB): mms_lib_release_lib + +$(GEN_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(GEN_DIR)/%.c: $(SPEC_DIR)/%.xml + gdbus-codegen --generate-c-code $(@:%.c=%) $< + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o : $(SRC1_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC1_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_EXE): $(DEBUG_EXE_DEPS) $(DEBUG_OBJS) + $(LD) $(DEBUG_FLAGS) $(DEBUG_OBJS) $(DEBUG_LIBS) -o $@ + +$(RELEASE_EXE): $(RELEASE_EXE_DEPS) $(RELEASE_OBJS) + $(LD) $(RELEASE_FLAGS) $(RELEASE_OBJS) $(RELEASE_LIBS) -o $@ + strip $@ diff --git a/mms-handler-dbus/test/mms_handler_dbus_client/test_mms_handler_dbus_client.c b/mms-handler-dbus/test/mms_handler_dbus_client/test_mms_handler_dbus_client.c new file mode 100644 index 0000000..a743aca --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_client/test_mms_handler_dbus_client.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_handler_dbus.h" + +#define RET_ERR (1) +#define RET_OK (0) + +static int run_test(MMSHandler* handler) +{ + const static guint8 data [] = {0,1,2,3,4,5}; + static const char IMSI[] = "IMSI"; + static const char Sender[] = "Sender"; + static const char Subject[] = "Subject"; + + int ret = RET_ERR; + GBytes* pdu = g_bytes_new(data, sizeof(data)); + time_t date = time(NULL); + time_t expiry = date + 10; + char* id = mms_handler_message_notify(handler, IMSI, Sender, Subject, + expiry, pdu); + g_bytes_unref(pdu); + if (id && id[0]) { + printf("Record id: %s\n", id); + if (mms_handler_message_receive_state_changed(handler, id, + MMS_RECEIVE_STATE_RECEIVING) && + mms_handler_message_receive_state_changed(handler, id, + MMS_RECEIVE_STATE_DECODING)) { + + gboolean ok; + MMSMessage* msg = mms_message_new(); + MMSMessagePart* p1 = g_new0(MMSMessagePart, 1); + MMSMessagePart* p2 = g_new0(MMSMessagePart, 1); + MMSMessagePart* p3 = g_new0(MMSMessagePart, 1); + msg->id = g_strdup(id); + msg->message_id = g_strdup("MessageID"); + msg->from = g_strdup(Sender); + msg->to = g_strsplit("To1,To2", ",", 0); + msg->cc = g_strsplit("Cc1,Cc2,Cc3", ",", 0); + msg->subject = g_strdup(Subject); + msg->date = date; + msg->cls = g_strdup("Personal"); + msg->flags |= MMS_MESSAGE_FLAG_KEEP_FILES; + + p1->content_type = g_strdup("application/smil;charset=utf-8"); + p1->content_id = g_strdup("<0>"); + p1->file = g_strdup("0"); + msg->parts = g_slist_append(msg->parts, p1); + + p2->content_type = g_strdup("text/plain;charset=utf-8"); + p2->content_id = g_strdup(""); + p2->file = g_strdup("text_0011.txt"); + msg->parts = g_slist_append(msg->parts, p2); + + p3->content_type = g_strdup("image/jpeg"); + p3->content_id = g_strdup("<131200181.jpg>"); + p3->file = g_strdup("131200181.jpg"); + msg->parts = g_slist_append(msg->parts, p3); + + ok = mms_handler_message_received(handler, msg); + mms_message_unref(msg); + + if (ok) { + printf("OK\n"); + ret = RET_OK; + } + } + } else { + printf("ERROR: no record id\n"); + } + g_free(id); + return ret; +} + +int main(int argc, char* argv[]) +{ + int ret; + MMSHandler* handler; + +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + /* g_type_init has been deprecated since version 2.36 + * the type system is initialised automagically since then */ + g_type_init(); +#pragma GCC diagnostic pop + + handler = mms_handler_dbus_new(); + ret = run_test(handler); + mms_handler_unref(handler); + return ret; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-handler-dbus/test/mms_handler_dbus_server/main.cpp b/mms-handler-dbus/test/mms_handler_dbus_server/main.cpp new file mode 100644 index 0000000..d3a720e --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_server/main.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mmshandler.h" +#include "mmsadaptor.h" + +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + const char* rootDir = "/tmp/mms"; + if (argc > 1) rootDir = argv[1]; + qDebug() << "Using" << rootDir << "as storage for MMS files"; + MmsHandler* service = new MmsHandler(&app, rootDir); + if (service->isRegistered()) { + new MmsAdaptor(service); + qDebug() << "MmsHandler created"; + return app.exec(); + } else { + qCritical() << "MmsHandler registration failed (already running or DBus not found), exiting"; + return 1; + } +} diff --git a/mms-handler-dbus/test/mms_handler_dbus_server/mmsadaptor.cpp b/mms-handler-dbus/test/mms_handler_dbus_server/mmsadaptor.cpp new file mode 100644 index 0000000..bee1453 --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_server/mmsadaptor.cpp @@ -0,0 +1,74 @@ +/* + * This file was generated by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -c MmsAdaptor -a mmsadaptor ../../spec/org.nemomobile.MmsHandler.xml + * + * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). + * + * This is an auto-generated file. + * Do not edit! All changes made to it will be lost. + */ + +#include "mmsadaptor.h" +#include "mmshandler.h" +#include +#include +#include +#include +#include +#include +#include + +/* + * Implementation of adaptor class MmsAdaptor + */ + +MmsAdaptor::MmsAdaptor(QObject *parent) + : QDBusAbstractAdaptor(parent) +{ + // constructor + setAutoRelaySignals(true); +} + +MmsAdaptor::~MmsAdaptor() +{ + // destructor +} + +void MmsAdaptor::deliveryReport(const QString &imsi, const QString &mmsId, const QString &recipient, int status) +{ + // handle method call org.nemomobile.MmsHandler.deliveryReport + QMetaObject::invokeMethod(parent(), "deliveryReport", Q_ARG(QString, imsi), Q_ARG(QString, mmsId), Q_ARG(QString, recipient), Q_ARG(int, status)); +} + +QString MmsAdaptor::messageNotification(const QString &imsi, const QString &from, const QString &subject, uint expiry, const QByteArray &data) +{ + // handle method call org.nemomobile.MmsHandler.messageNotification + QString recId; + QMetaObject::invokeMethod(parent(), "messageNotification", Q_RETURN_ARG(QString, recId), Q_ARG(QString, imsi), Q_ARG(QString, from), Q_ARG(QString, subject), Q_ARG(uint, expiry), Q_ARG(QByteArray, data)); + return recId; +} + +void MmsAdaptor::messageReceiveStateChanged(const QString &recId, int state) +{ + // handle method call org.nemomobile.MmsHandler.messageReceiveStateChanged + QMetaObject::invokeMethod(parent(), "messageReceiveStateChanged", Q_ARG(QString, recId), Q_ARG(int, state)); +} + +void MmsAdaptor::messageReceived(const QString &recId, const QString &mmsId, const QString &from, const QStringList &to, const QStringList &cc, const QString &subject, uint date, int priority, const QString &cls, bool readReport, MmsPartList parts) +{ + // handle method call org.nemomobile.MmsHandler.messageReceived + static_cast(parent())->messageReceived(recId, mmsId, from, to, cc, subject, date, priority, cls, readReport, parts); +} + +void MmsAdaptor::messageSendStateChanged(const QString &recId, int state) +{ + // handle method call org.nemomobile.MmsHandler.messageSendStateChanged + QMetaObject::invokeMethod(parent(), "messageSendStateChanged", Q_ARG(QString, recId), Q_ARG(int, state)); +} + +void MmsAdaptor::readReport(const QString &imsi, const QString &mmsId, const QString &recipient, int status) +{ + // handle method call org.nemomobile.MmsHandler.readReport + QMetaObject::invokeMethod(parent(), "readReport", Q_ARG(QString, imsi), Q_ARG(QString, mmsId), Q_ARG(QString, recipient), Q_ARG(int, status)); +} + diff --git a/mms-handler-dbus/test/mms_handler_dbus_server/mmsadaptor.h b/mms-handler-dbus/test/mms_handler_dbus_server/mmsadaptor.h new file mode 100644 index 0000000..16aeae9 --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_server/mmsadaptor.h @@ -0,0 +1,96 @@ +/* + * This file was generated by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -c MmsAdaptor -a mmsadaptor ../../spec/org.nemomobile.MmsHandler.xml + * + * qdbusxml2cpp is Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). + * + * This is an auto-generated file. + * This file may have been hand-edited. Look for HAND-EDIT comments + * before re-generating it. + */ + +#ifndef MMSADAPTOR_H_1390561457 +#define MMSADAPTOR_H_1390561457 + +#include "mmspart.h" + +#include +#include +QT_BEGIN_NAMESPACE +class QByteArray; +template class QList; +template class QMap; +class QString; +class QStringList; +class QVariant; +QT_END_NAMESPACE + +/* + * Adaptor class for interface org.nemomobile.MmsHandler + */ +class MmsAdaptor: public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.nemomobile.MmsHandler") + Q_CLASSINFO("D-Bus Introspection", "" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" + "") +public: + MmsAdaptor(QObject *parent); + virtual ~MmsAdaptor(); + +public: // PROPERTIES +public Q_SLOTS: // METHODS + void deliveryReport(const QString &imsi, const QString &mmsId, const QString &recipient, int status); + QString messageNotification(const QString &imsi, const QString &from, const QString &subject, uint expiry, const QByteArray &data); + void messageReceiveStateChanged(const QString &recId, int state); + void messageReceived(const QString &recId, const QString &mmsId, const QString &from, const QStringList &to, const QStringList &cc, const QString &subject, uint date, int priority, const QString &cls, bool readReport, MmsPartList parts); + void messageSendStateChanged(const QString &recId, int state); + void readReport(const QString &imsi, const QString &mmsId, const QString &recipient, int status); +Q_SIGNALS: // SIGNALS +}; + +#endif diff --git a/mms-handler-dbus/test/mms_handler_dbus_server/mmshandler.cpp b/mms-handler-dbus/test/mms_handler_dbus_server/mmshandler.cpp new file mode 100644 index 0000000..8044d83 --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_server/mmshandler.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mmshandler.h" + +MmsHandler::MmsHandler(QObject* parent, QString rootDir) : + QObject(parent), + m_isRegistered(false), + m_rootDir(rootDir) +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + QDBusConnection dbus = QDBusConnection::systemBus(); + if (dbus.isConnected()) { + if (dbus.registerObject("/", this)) { + if (dbus.registerService("org.nemomobile.MmsHandler")) { + m_isRegistered = true; + } else { + qWarning() << "Unable to register service!" << dbus.lastError(); + } + } else { + qWarning() << "Object registration failed!" << dbus.lastError(); + } + } else { + qCritical() << "ERROR: No DBus session bus found!"; + } +} + +QString MmsHandler::messageNotification(QString imsi, QString from, + QString subject, uint expiry, QByteArray data) +{ + qDebug() << "messageNotification" << imsi << from << subject << expiry + << data.size() << "bytes"; + for (int i=1; i<=1000; i++) { + QString id = QString::number(i); + QDir dir(m_rootDir + "/" + id); + if (!dir.exists() && dir.mkpath(dir.path())) { + QString path(dir.filePath("pdu")); + QFile file(path); + if (file.open(QIODevice::WriteOnly)) { + file.write(data); + file.close(); + qDebug() << "Record id" << id; + return id; + } else { + qWarning() << "messageNotification failed to create" << path; + } + } + } + qWarning() << "messageNotification failed to generate message id"; + return QString(); +} + +void MmsHandler::messageReceiveStateChanged(QString recId, int state) +{ + qDebug() << "messageReceiveStateChanged" << recId << state; +} + +void MmsHandler::messageReceived(QString recId, QString mmsId, QString from, + QStringList to, QStringList cc, QString subj, uint date, int priority, + QString cls, bool readReport, MmsPartList parts) +{ + QDir dir(m_rootDir + "/" + recId); + if (dir.exists()) { + qDebug() << "messageReceived" << recId << mmsId << from + << subj << date << priority << cls << readReport; + foreach (QString addr, to) { + qDebug() << " To:" << addr; + } + foreach (QString addr, cc) { + qDebug() << " Cc:" << addr; + } + + qDebug() << parts.size() << "parts"; + QDir partDir(dir.filePath("parts")); + if (partDir.mkpath(partDir.path())) { + foreach (MmsPart part, parts) { + QFileInfo partInfo(part.fileName()); + if (partInfo.isFile()) { + QFile partFile(partInfo.canonicalFilePath()); + qDebug() << " " << part.contentId() << part.contentType() + << partInfo.canonicalFilePath(); + QString destName(partDir.filePath(partInfo.fileName())); + if (!partFile.copy(destName)) { + qWarning() << "Failed to copy" << partFile.fileName() + << "->" << destName; + } + } else { + qDebug() << " " << part.contentId() << part.contentType() + << part.fileName() << "(no such file)"; + } + } + } + } else { + qWarning() << "Invalid record id" << recId; + } +} diff --git a/mms-handler-dbus/test/mms_handler_dbus_server/mmshandler.h b/mms-handler-dbus/test/mms_handler_dbus_server/mmshandler.h new file mode 100644 index 0000000..1c711cb --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_server/mmshandler.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef MMSHANDLER_H +#define MMSHANDLER_H + +#include "mmspart.h" +#include + +class MmsHandler : public QObject +{ + Q_OBJECT +public: + MmsHandler(QObject* parent, QString rootDir); + + bool isRegistered() const { return m_isRegistered; } + +public Q_SLOTS: + QString messageNotification(QString imsi, QString from, QString subject, + uint expiry, QByteArray data); + void messageReceiveStateChanged(QString recId, int state); + void messageReceived(QString recId, QString mmsId, QString from, + QStringList to, QStringList cc, QString subj, uint date, int priority, + QString cls, bool readReport, MmsPartList parts); + +private: + bool m_isRegistered; + QString m_rootDir; +}; + +#endif // MMSHANDLER_H diff --git a/mms-handler-dbus/test/mms_handler_dbus_server/mmspart.cpp b/mms-handler-dbus/test/mms_handler_dbus_server/mmspart.cpp new file mode 100644 index 0000000..a60a635 --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_server/mmspart.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mmspart.h" + +MmsPart::MmsPart(QObject* parent) : + QObject(parent) +{ +} + +MmsPart::MmsPart(const MmsPart& other) : + QObject(other.parent()), + m_fileName(other.m_fileName), + m_contentType(other.m_contentType), + m_contentId(other.m_contentId) +{ +} + +MmsPart& MmsPart::operator=(const MmsPart& other) +{ + m_fileName = other.m_fileName; + m_contentType = other.m_contentType; + m_contentId = other.m_contentId; + return *this; +} + +void MmsPart::marshall(QDBusArgument& arg) const +{ + arg.beginStructure(); + arg << m_fileName << m_contentType << m_contentId; + arg.endStructure(); +} + +void MmsPart::demarshall(const QDBusArgument& arg) +{ + arg.beginStructure(); + arg >> m_fileName >> m_contentType >> m_contentId; + arg.endStructure(); +} + +QDBusArgument& operator<<(QDBusArgument& arg, const MmsPart& part) +{ + part.marshall(arg); + return arg; +} + +const QDBusArgument& operator>>(const QDBusArgument& arg, MmsPart& part) +{ + part.demarshall(arg); + return arg; +} diff --git a/mms-handler-dbus/test/mms_handler_dbus_server/mmspart.h b/mms-handler-dbus/test/mms_handler_dbus_server/mmspart.h new file mode 100644 index 0000000..8e39976 --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_server/mmspart.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef MMSPART_H +#define MMSPART_H + +#include +#include + +class MmsPart : public QObject +{ + Q_OBJECT +public: + explicit MmsPart(QObject* parent = 0); + MmsPart(const MmsPart& other); + MmsPart& operator=(const MmsPart& other); + + inline QString fileName() const { return m_fileName; } + inline QString contentType() const { return m_contentType; } + inline QString contentId() const { return m_contentId; } + + void marshall(QDBusArgument& arg) const; + void demarshall(const QDBusArgument& arg); + +private: + QString m_fileName; + QString m_contentType; + QString m_contentId; +}; + +QDBusArgument& operator<<(QDBusArgument& arg, const MmsPart& part); +const QDBusArgument& operator>>(const QDBusArgument& arg, MmsPart& part); + +typedef QList MmsPartList; + +Q_DECLARE_METATYPE(MmsPart); +Q_DECLARE_METATYPE(MmsPartList); + +#endif // MMSPART_H diff --git a/mms-handler-dbus/test/mms_handler_dbus_server/org.nemomobile.MmsHandler.conf b/mms-handler-dbus/test/mms_handler_dbus_server/org.nemomobile.MmsHandler.conf new file mode 100644 index 0000000..6bd450e --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_server/org.nemomobile.MmsHandler.conf @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/mms-handler-dbus/test/mms_handler_dbus_server/test_mms_handler_dbus_server.pro b/mms-handler-dbus/test/mms_handler_dbus_server/test_mms_handler_dbus_server.pro new file mode 100644 index 0000000..2342e03 --- /dev/null +++ b/mms-handler-dbus/test/mms_handler_dbus_server/test_mms_handler_dbus_server.pro @@ -0,0 +1,26 @@ +TARGET = test_mms_handler_dbus_server +CONFIG += console +CONFIG -= app_bundle + +QT += core +QT -= gui +QT += dbus + +QMAKE_CXXFLAGS *= -Werror -Wall -fno-exceptions -Wno-psabi -Wno-unused-parameter + +TEMPLATE = app + +SOURCES += \ + main.cpp \ + mmsadaptor.cpp \ + mmshandler.cpp \ + mmspart.cpp + +HEADERS += \ + mmsadaptor.h \ + mmshandler.h \ + mmspart.h + +OTHER_FILES += \ + org.nemomobile.MmsHandler.conf \ + ../../spec/org.nemomobile.MmsHandler.xml diff --git a/mms-lib/Makefile b/mms-lib/Makefile new file mode 100644 index 0000000..cbf15e0 --- /dev/null +++ b/mms-lib/Makefile @@ -0,0 +1,107 @@ +# -*- Mode: makefile -*- + +.PHONY: clean all debug release + +# Required packages +PKGS = glib-2.0 libsoup-2.4 libwspcodec + +# +# Default target +# + +all: debug release + +# +# Sources +# + +SRC = mms_codec.c mms_connection.c mms_connman.c mms_dispatcher.c \ + mms_handler.c mms_lib_util.c mms_file_util.c mms_log.c \ + mms_message.c mms_task.c mms_task_ack.c mms_task_decode.c \ + mms_task_notification.c mms_task_notifyresp.c mms_task_publish.c \ + mms_task_read.c mms_task_retrieve.c mms_task_upload.c mms_util.c + +# +# Directories +# + +SRC_DIR = src +INCLUDE_DIR = include +BUILD_DIR = build +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Code coverage +# + +ifndef GCOV +GCOV = 0 +endif + +ifneq ($(GCOV),0) +CFLAGS += --coverage +endif + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +WARNINGS = -Wall +INCLUDES = -I$(SRC_DIR) -I$(INCLUDE_DIR) +CFLAGS += $(WARNINGS) $(INCLUDES) $(shell pkg-config --cflags $(PKGS)) -MMD +DEBUG_CFLAGS = -g -DDEBUG $(CFLAGS) +RELEASE_CFLAGS = -O2 $(CFLAGS) +ARFLAGS = rc + +# +# Files +# + +DEBUG_OBJS = $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +# +# Rules +# +LIB = libmms.a +DEBUG_LIB = $(DEBUG_BUILD_DIR)/$(LIB) +RELEASE_LIB = $(RELEASE_BUILD_DIR)/$(LIB) + +debug: $(DEBUG_LIB) + +release: $(RELEASE_LIB) + +clean: + rm -fr $(BUILD_DIR) *~ $(SRC_DIR)/*~ $(INCLUDE_DIR)/*~ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(DEBUG_LIB): $(DEBUG_BUILD_DIR) $(DEBUG_OBJS) + $(AR) $(ARFLAGS) $@ $(DEBUG_OBJS) + +$(RELEASE_LIB): $(RELEASE_BUILD_DIR) $(RELEASE_OBJS) + $(AR) $(ARFLAGS) $@ $(RELEASE_OBJS) + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ diff --git a/mms-lib/include/mms_connection.h b/mms-lib/include/mms_connection.h new file mode 100644 index 0000000..1c30c52 --- /dev/null +++ b/mms-lib/include/mms_connection.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_CONNECTION_H +#define JOLLA_MMS_CONNECTION_H + +#include "mms_lib_types.h" + +/* Connection errors */ +#define G_MMS_CONNECTION_ERROR mms_connection_error_quark() +GQuark mms_connection__error_quark(void); + +typedef enum _MMS_CONNECTION_ERROR { + MMS_CONNECTION_ERROR_NOT_FOUND, /* Requested SIM not found */ + MMS_CONNECTION_ERROR_NOT_ALLOWED, /* Data transfer disabled */ + MMS_CONNECTION_ERROR_FAILED, /* Connection failed */ + MMS_CONNECTION_ERROR_INTERRUPTED /* Interrupted by something */ +} MMS_CONNECTION_ERROR; + +/* Connection state. There are only two state change sequences allowed: + * OPENING -> FAILED and OPENING -> OPEN -> CLOSE. Once connection fails + * to open or gets closed, it will remain on FAILED or CLOSED state forever. + * The client should drop its reference to the closed or failed connection + * and open a new one when it needs it. */ +typedef enum _MMS_CONNECTION_STATE { + MMS_CONNECTION_STATE_INVALID, /* Invalid state */ + MMS_CONNECTION_STATE_OPENING, /* Connection is being opened */ + MMS_CONNECTION_STATE_FAILED, /* Connection failed to open */ + MMS_CONNECTION_STATE_OPEN, /* Connection is active */ + MMS_CONNECTION_STATE_CLOSED /* Connection has been closed */ +} MMS_CONNECTION_STATE; + +/* Delegate (one per connection) */ +typedef struct mms_connection_delegate MMSConnectionDelegate; +struct mms_connection_delegate { + void (*fn_connection_state_changed)( + MMSConnectionDelegate* delegate, + MMSConnection* connection); +}; + +/* Connection data. The delegate field may be changed by the client at + * any time. */ +struct mms_connection { + GObject parent; + char* imsi; + char* mmsc; + char* mmsproxy; + char* netif; + gboolean user_connection; + MMS_CONNECTION_STATE state; + MMSConnectionDelegate* delegate; +}; + +/* Connection class for implementation */ +typedef struct mms_connection_class { + GObjectClass parent; + void (*fn_close)(MMSConnection* connection); +} MMSConnectionClass; + +GType mms_connection_get_type(void); +#define MMS_TYPE_CONNECTION (mms_connection_get_type()) + +MMSConnection* +mms_connection_ref( + MMSConnection* connection); + +void +mms_connection_unref( + MMSConnection* connection); + +const char* +mms_connection_state_name( + MMSConnection* connection); + +MMS_CONNECTION_STATE +mms_connection_state( + MMSConnection* connection); + +void +mms_connection_close( + MMSConnection* connection); + +#define mms_connection_is_open(connection) \ + (mms_connection_state(connection) == MMS_CONNECTION_STATE_OPEN) + +#endif /* JOLLA_MMS_CONNECTION_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/include/mms_connman.h b/mms-lib/include/mms_connman.h new file mode 100644 index 0000000..235a426 --- /dev/null +++ b/mms-lib/include/mms_connman.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_CONNMAN_H +#define JOLLA_MMS_CONNMAN_H + +#include "mms_lib_types.h" + +typedef struct mms_connman_class { + GObjectClass parent; + MMSConnection* (*fn_open_connection)(MMSConnMan* cm, const char* imsi, + gboolean user_request); +} MMSConnManClass; + +GType mms_connman_get_type(void); +#define MMS_TYPE_CONNMAN (mms_connman_get_type()) + +/* Reference counting */ +MMSConnMan* +mms_connman_ref( + MMSConnMan* cm); + +void +mms_connman_unref( + MMSConnMan* cm); + +/** + * Creates a new connection or returns the reference to an aready active one. + * The caller must release the reference. + */ +MMSConnection* +mms_connman_open_connection( + MMSConnMan* cm, + const char* imsi, + gboolean user_request); + +#endif /* JOLLA_MMS_CONNMAN_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/include/mms_dispatcher.h b/mms-lib/include/mms_dispatcher.h new file mode 100644 index 0000000..7cb041c --- /dev/null +++ b/mms-lib/include/mms_dispatcher.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_DISPATCHER_H +#define JOLLA_MMS_DISPATCHER_H + +#include "mms_lib_types.h" + +/* Delegate (one per dispatcher) */ +typedef struct mms_dispatcher_delegate MMSDispatcherDelegate; +struct mms_dispatcher_delegate { + /* Dispatcher deactivated because it has nothing to do */ + void (*fn_done)( + MMSDispatcherDelegate* delegate, + MMSDispatcher* dispatcher); +}; + +MMSDispatcher* +mms_dispatcher_new( + const MMSConfig* config, + MMSConnMan* cm, + MMSHandler* handler); + +MMSDispatcher* +mms_dispatcher_ref( + MMSDispatcher* dispatcher); + +void +mms_dispatcher_unref( + MMSDispatcher* dispatcher); + +void +mms_dispatcher_set_delegate( + MMSDispatcher* dispatcher, + MMSDispatcherDelegate* delegate); + +gboolean +mms_dispatcher_is_active( + MMSDispatcher* dispatcher); + +gboolean +mms_dispatcher_start( + MMSDispatcher* dispatcher); + +gboolean +mms_dispatcher_handle_push( + MMSDispatcher* dispatcher, + const char* imsi, + GBytes* push); + +gboolean +mms_dispatcher_receive_message( + MMSDispatcher* dispatcher, + const char* id, + const char* imsi, + gboolean automatic, + GBytes* push); + +gboolean +mms_dispatcher_send_read_report( + MMSDispatcher* dispatcher, + const char* id, + const char* imsi, + const char* message_id, + const char* to, + MMSReadStatus status); + +void +mms_dispatcher_cancel( + MMSDispatcher* dispatcher, + const char* id); + +#endif /* JOLLA_MMS_DISPATCHER_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/include/mms_handler.h b/mms-lib/include/mms_handler.h new file mode 100644 index 0000000..14452a1 --- /dev/null +++ b/mms-lib/include/mms_handler.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_HANDLER_H +#define JOLLA_MMS_HANDLER_H + +#include "mms_message.h" + +/* Receive state */ +typedef enum _mmm_receive_state { + MMS_RECEIVE_STATE_INVALID = -1, + MMS_RECEIVE_STATE_RECEIVING, + MMS_RECEIVE_STATE_DEFERRED, + MMS_RECEIVE_STATE_NOSPACE, + MMS_RECEIVE_STATE_DECODING, + MMS_RECEIVE_STATE_DOWNLOAD_ERROR, + MMS_RECEIVE_STATE_DECODING_ERROR +} MMS_RECEIVE_STATE; + +/* Class */ +typedef struct mms_handler_class { + GObjectClass parent; + + /* New incoming message notification. Returns the handler message id + * to start download immediately, NULL or empty string to postpone it. */ + char* (*fn_message_notify)( + MMSHandler* handler, /* Handler instance */ + const char* imsi, /* Subscriber identity */ + const char* from, /* Sender's phone number */ + const char* subject, /* Subject (optional) */ + time_t expiry, /* Message expiry time */ + GBytes* push); /* Raw push message */ + + /* Sets the receive state */ + gboolean (*fn_message_receive_state_changed)( + MMSHandler* handler, /* Handler instance */ + const char* id, /* Handler record id */ + MMS_RECEIVE_STATE state); /* Receive state */ + + /* Message has been successfully received */ + gboolean (*fn_message_received)( + MMSHandler* handler, /* Handler instance */ + MMSMessage* msg); /* Decoded message */ + +} MMSHandlerClass; + +GType mms_handler_get_type(void); +#define MMS_TYPE_HANDLER (mms_handler_get_type()) + +MMSHandler* +mms_handler_ref( + MMSHandler* handler); + +void +mms_handler_unref( + MMSHandler* handler); + +char* +mms_handler_message_notify( + MMSHandler* handler, /* Handler instance */ + const char* imsi, /* Subscriber identity */ + const char* from, /* Sender's phone number */ + const char* subject, /* Subject (optional) */ + time_t expiry, /* Message expiry time */ + GBytes* push); /* Raw push message */ + +gboolean +mms_handler_message_receive_state_changed( + MMSHandler* handler, /* Handler instance */ + const char* id, /* Handler record id */ + MMS_RECEIVE_STATE state); /* Receive state */ + +gboolean +mms_handler_message_received( + MMSHandler* handler, /* Handler instance */ + MMSMessage* msg); /* Decoded message */ + +#endif /* JOLLA_MMS_HANDLER_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/include/mms_lib_log.h b/mms-lib/include/mms_lib_log.h new file mode 100644 index 0000000..65c5042 --- /dev/null +++ b/mms-lib/include/mms_lib_log.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_LIB_LOG_H +#define JOLLA_MMS_LIB_LOG_H + +#include "mms_log.h" + +#define MMS_LIB_LOG_MODULES(log) \ + log(mms_dispatcher_log)\ + log(mms_handler_log)\ + log(mms_message_log)\ + log(mms_util_log)\ + log(mms_task_log)\ + log(mms_task_decode_log)\ + log(mms_task_notification_log)\ + log(mms_task_retrieve_log)\ + log(mms_task_upload_log)\ + log(mms_task_publish_log)\ + log(mms_connman_log)\ + log(mms_connection_log) + +MMS_LIB_LOG_MODULES(MMS_LOG_MODULE_DECL) + +#endif /* JOLLA_MMS_LIB_LOG_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/include/mms_lib_types.h b/mms-lib/include/mms_lib_types.h new file mode 100644 index 0000000..76fd40d --- /dev/null +++ b/mms-lib/include/mms_lib_types.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_LIB_TYPES_H +#define JOLLA_MMS_LIB_TYPES_H + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +#else +# include +# include +#endif + +#include +#include + +/* Static configuration, chosen at startup and never changing since then */ +typedef struct mms_config { + const char* root_dir; /* Root directory for storing MMS files */ + const char* user_agent; /* User agent string */ + int retry_secs; /* Retry timeout in seconds */ + int idle_secs; /* Idle timeout */ + gboolean keep_temp_files; /* Keep temporary files around */ + gboolean attic_enabled; /* Keep unrecognized push message in attic */ + gboolean send_dr; /* Allow sending delivery reports */ +} MMSConfig; + +/* Types */ +typedef GObject MMSHandler; +typedef GObject MMSConnMan; +typedef struct mms_log_module MMSLogModule; +typedef struct mms_dispatcher MMSDispatcher; +typedef struct mms_connection MMSConnection; +typedef struct mms_message MMSPdu; +typedef struct _mms_message MMSMessage; + +/* MMS content type */ +#define MMS_CONTENT_TYPE "application/vnd.wap.mms-message" + +/* MMS read status */ +typedef enum mms_read_status { + MMS_READ_STATUS_READ, /* Message has been read */ + MMS_READ_STATUS_DELETED /* Message has been deleted without reading */ +} MMSReadStatus; + +/* Convenience macros */ +#define MMS_CAST(address,type,field) ((type *)( \ + (char*)(address) - \ + (char*)(&((type *)0)->field))) + +#endif /* JOLLA_MMS_LIB_TYPES_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/include/mms_lib_util.h b/mms-lib/include/mms_lib_util.h new file mode 100644 index 0000000..ca78a2c --- /dev/null +++ b/mms-lib/include/mms_lib_util.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_LIB_UTIL_H +#define JOLLA_MMS_LIB_UTIL_H + +#include "mms_lib_types.h" + +/* One-time initialization */ +void +mms_lib_init(void); + +/* Reset configuration to default */ +void +mms_lib_default_config( + MMSConfig* config); + +#endif /* JOLLA_MMS_LIB_UTIL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/include/mms_log.h b/mms-lib/include/mms_log.h new file mode 100644 index 0000000..fcb112d --- /dev/null +++ b/mms-lib/include/mms_log.h @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_LOG_H +#define JOLLA_MMS_LOG_H + +#include "mms_lib_types.h" +#include + +/* Log levels */ +#define MMS_LOGLEVEL_GLOBAL (-1) +#define MMS_LOGLEVEL_NONE (0) +#define MMS_LOGLEVEL_ERR (1) +#define MMS_LOGLEVEL_WARN (2) +#define MMS_LOGLEVEL_INFO (3) +#define MMS_LOGLEVEL_DEBUG (4) +#define MMS_LOGLEVEL_VERBOSE (5) + +/* Allow these to be redefined */ +#ifndef MMS_LOGLEVEL_MAX +# ifdef DEBUG +# define MMS_LOGLEVEL_MAX MMS_LOGLEVEL_VERBOSE +# else +# define MMS_LOGLEVEL_MAX MMS_LOGLEVEL_DEBUG +# endif +#endif /* MMS_LOGLEVEL_MAX */ + +#ifndef MMS_LOGLEVEL_DEFAULT +# ifdef DEBUG +# define MMS_LOGLEVEL_DEFAULT MMS_LOGLEVEL_DEBUG +# else +# define MMS_LOGLEVEL_DEFAULT MMS_LOGLEVEL_INFO +# endif +#endif /* MMS_LOGLEVEL_DEFAULT */ + +/* Do we need a separate log level for ASSERTs? */ +#ifndef MMS_LOGLEVEL_ASSERT +# ifdef DEBUG +# define MMS_LOGLEVEL_ASSERT MMS_LOGLEVEL_ERR +# else + /* No asserts in release build */ +# define MMS_LOGLEVEL_ASSERT (MMS_LOGLEVEL_MAX+1) +# endif +#endif + +/* Log module */ +struct mms_log_module { + const char* name; + const int max_level; + int level; +}; + +/* Command line parsing helper. Option format is [module]:level + * where level can be either a number or log level name ("none", err etc.) */ +gboolean +mms_log_parse_option( + const char* opt, /* String to parse */ + MMSLogModule** modules, /* Known modules */ + int count, /* Number of known modules */ + GError** error); /* Optional error message */ + +/* Set log type by name ("syslog", "stdout" or "glib"). This is also + * primarily for parsing command line options */ +gboolean +mms_log_set_type( + const char* type, + const char* default_name); + +const char* +mms_log_get_type( + void); + +/* Generates the string containg description of log levels and list of + * log modules. The caller must deallocate the string with g_free */ +char* +mms_log_description( + MMSLogModule** modules, /* Known modules */ + int count); /* Number of known modules */ + +/* Logging function */ +void +mms_log( + const MMSLogModule* module, /* Calling module (NULL for default) */ + int level, /* Message log level */ + const char* format, /* Message format */ + ...) G_GNUC_PRINTF(3,4); /* Followed by arguments */ + +void +mms_logv( + const MMSLogModule* module, + int level, + const char* format, + va_list va); + +#ifdef unix +# define MMS_LOG_SYSLOG +#endif + +/* Available log handlers */ +#define MMS_DEFINE_LOG_FN(fn) void fn(const char* name, int level, \ + const char* format, va_list va) +MMS_DEFINE_LOG_FN(mms_log_stdout); +MMS_DEFINE_LOG_FN(mms_log_glib); +#ifdef MMS_LOG_SYSLOG +MMS_DEFINE_LOG_FN(mms_log_syslog); +#endif + +/* Log configuration */ +#define MMS_LOG_MODULE_DECL(m) extern MMSLogModule m; +MMS_LOG_MODULE_DECL(mms_log_default) +typedef MMS_DEFINE_LOG_FN((*MMSLogFunc)); +extern MMSLogFunc mms_log_func; +extern gboolean mms_log_stdout_timestamp; + +/* Log module (optional) */ +#define MMS_LOG_MODULE_DEFINE_(mod,name) \ + MMSLogModule mod = {name, \ + MMS_LOGLEVEL_MAX, MMS_LOGLEVEL_GLOBAL} +#ifdef MMS_LOG_MODULE_NAME +extern MMSLogModule MMS_LOG_MODULE_NAME; +# define MMS_LOG_MODULE_CURRENT (&MMS_LOG_MODULE_NAME) +# define MMS_LOG_MODULE_DEFINE(name) \ + MMS_LOG_MODULE_DEFINE_(MMS_LOG_MODULE_NAME,name) +#else +# define MMS_LOG_MODULE_CURRENT NULL +#endif + +/* Logging macros */ + +#define MMS_LOG_NOTHING ((void)0) +#define MMS_ERRMSG(err) (((err) && (err)->message) ? (err)->message : \ + "Unknown error") + +#if !defined(MMS_LOG_VARARGS) && defined(__GNUC__) +# define MMS_LOG_VARARGS +#endif + +#ifndef MMS_LOG_VARARGS +# define MMS_LOG_VA_NONE(x) static inline void MMS_##x(const char* f, ...) {} +# define MMS_LOG_VA(x) static inline void MMS_##x(const char* f, ...) { \ + if (f && f[0]) { \ + va_list va; va_start(va,f); \ + mms_logv(MMS_LOG_MODULE_CURRENT, MMS_LOGLEVEL_##x, f, va); \ + va_end(va); \ + } \ +} +#endif /* MMS_LOG_VARARGS */ + +#define MMS_LOG_ENABLED (MMS_LOGLEVEL_MAX >= MMS_LOGLEVEL_NONE) +#define MMS_LOG_ERR (MMS_LOGLEVEL_MAX >= MMS_LOGLEVEL_ERR) +#define MMS_LOG_WARN (MMS_LOGLEVEL_MAX >= MMS_LOGLEVEL_WARN) +#define MMS_LOG_INFO (MMS_LOGLEVEL_MAX >= MMS_LOGLEVEL_INFO) +#define MMS_LOG_DEBUG (MMS_LOGLEVEL_MAX >= MMS_LOGLEVEL_DEBUG) +#define MMS_LOG_VERBOSE (MMS_LOGLEVEL_MAX >= MMS_LOGLEVEL_VERBOSE) +#define MMS_LOG_ASSERT (MMS_LOGLEVEL_MAX >= MMS_LOGLEVEL_ASSERT) + +#if MMS_LOG_ASSERT +void +mms_log_assert( + const MMSLogModule* module, /* Calling module (NULL for default) */ + const char* expr, /* Assert expression */ + const char* file, /* File name */ + int line); /* Line number */ +# define MMS_ASSERT(expr) ((expr) ? MMS_LOG_NOTHING : \ + mms_log_assert(MMS_LOG_MODULE_CURRENT, #expr, __FILE__, __LINE__)) +# define MMS_VERIFY(expr) MMS_ASSERT(expr) +#else +# define MMS_ASSERT(expr) +# define MMS_VERIFY(expr) (expr) +#endif + +#ifdef MMS_LOG_VARARGS +# if MMS_LOG_ERR +# define MMS_ERR(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_ERR, f, ##args) +# define MMS_ERR_(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_ERR, "%s() " f, __FUNCTION__, ##args) +# else +# define MMS_ERR(f,args...) MMS_LOG_NOTHING +# define MMS_ERR_(f,args...) MMS_LOG_NOTHING +# endif /* MMS_LOG_ERR */ +#else +# define MMS_ERR_ MMS_ERR +# if MMS_LOG_ERR + MMS_LOG_VA(ERR) +# else + MMS_LOG_VA_NONE(ERR) +# endif /* MMS_LOG_ERR */ +#endif /* MMS_LOG_VARARGS */ + +#ifdef MMS_LOG_VARARGS +# if MMS_LOG_WARN +# define MMS_WARN(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_WARN, f, ##args) +# define MMS_WARN_(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_WARN, "%s() " f, __FUNCTION__, ##args) +# else +# define MMS_WARN(f,args...) MMS_LOG_NOTHING +# define MMS_WARN_(f,args...) MMS_LOG_NOTHING +# endif /* MMS_LOGL_WARN */ +#else +# define MMS_WARN_ MMS_WARN +# if MMS_LOG_WARN + MMS_LOG_VA(WARN) +# else + MMS_LOG_VA_NONE(WARN) +# endif /* MMS_LOGL_WARN */ +# endif /* MMS_LOG_VARARGS */ + +#ifdef MMS_LOG_VARARGS +# if MMS_LOG_INFO +# define MMS_INFO(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_INFO, f, ##args) +# define MMS_INFO_(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_INFO, "%s() " f, __FUNCTION__, ##args) +# else +# define MMS_INFO(f,args...) MMS_LOG_NOTHING +# define MMS_INFO_(f,args...) MMS_LOG_NOTHING +# endif /* MMS_LOG_INFO */ +#else +# define MMS_INFO_ MMS_INFO +# if MMS_LOG_INFO + MMS_LOG_VA(INFO) +# else + MMS_LOG_VA_NONE(INFO) +# endif /* MMS_LOG_INFO */ +#endif /* MMS_LOG_VARARGS */ + +#ifdef MMS_LOG_VARARGS +# if MMS_LOG_DEBUG +# define MMS_DEBUG(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_DEBUG, f, ##args) +# define MMS_DEBUG_(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_DEBUG, "%s() " f, __FUNCTION__, ##args) +# else +# define MMS_DEBUG(f,args...) MMS_LOG_NOTHING +# define MMS_DEBUG_(f,args...) MMS_LOG_NOTHING +# endif /* MMS_LOG_DEBUG */ +#else +# define MMS_DEBUG_ MMS_DEBUG +# if MMS_LOG_DEBUG + MMS_LOG_VA(DEBUG) +# else + MMS_LOG_VA_NONE(DEBUG) +# endif /* MMS_LOG_DEBUG */ +#endif /* MMS_LOG_VARARGS */ + +#ifdef MMS_LOG_VARARGS +# if MMS_LOG_VERBOSE +# define MMS_VERBOSE(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_VERBOSE, f, ##args) +# define MMS_VERBOSE_(f,args...) mms_log(MMS_LOG_MODULE_CURRENT, \ + MMS_LOGLEVEL_VERBOSE, "%s() " f, __FUNCTION__, ##args) +# else +# define MMS_VERBOSE(f,args...) MMS_LOG_NOTHING +# define MMS_VERBOSE_(f,args...) MMS_LOG_NOTHING +# endif /* MMS_LOG_VERBOSE */ +#else +# define MMS_VERBOSE_ MMS_VERBOSE +# if MMS_LOG_VERBOSE + MMS_LOG_VA(VERBOSE) +# else + MMS_LOG_VA_NONE(VERBOSE) +# endif /* MMS_LOG_VERBOSE */ +#endif /* MMS_LOG_VARARGS */ + +#endif /* JOLLA_MMS_LOG_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/include/mms_message.h b/mms-lib/include/mms_message.h new file mode 100644 index 0000000..56670d7 --- /dev/null +++ b/mms-lib/include/mms_message.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_MESSAGE_H +#define JOLLA_MMS_MESSAGE_H + +#include "mms_lib_types.h" + +typedef enum mms_message_prority { + MMS_PRIORITY_LOW, + MMS_PRIORITY_NORMAL, + MMS_PRIORITY_HIGH +} MMS_PRIORITY; + +struct _mms_message { + gint ref_count; /* Reference count */ + char* id; /* Database record ID */ + char* message_id; /* Message-ID */ + char* from; /* Sender */ + char** to; /* To: list */ + char** cc; /* Cc: list */ + char* subject; /* Subject */ + time_t date; /* Original send date */ + MMS_PRIORITY priority; /* Message priority */ + char* cls; /* Message class */ + gboolean read_report_req; /* Request for read report */ + char* parts_dir; /* Where parts are stored */ + GSList* parts; /* Message parts */ + int flags; /* Message flags: */ + +#define MMS_MESSAGE_FLAG_KEEP_FILES (0x01) /* Don't delete files */ + +}; + +typedef struct _mms_message_part { + char* content_type; /* Content-Type */ + char* content_id; /* Content-ID */ + char* file; /* File name */ +} MMSMessagePart; + +MMSMessage* +mms_message_new( + void); + +MMSMessage* +mms_message_ref( + MMSMessage* msg); + +void +mms_message_unref( + MMSMessage* msg); + +#endif /* JOLLA_MMS_MESSAGE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/mms-lib.pro b/mms-lib/mms-lib.pro new file mode 100644 index 0000000..737bf7b --- /dev/null +++ b/mms-lib/mms-lib.pro @@ -0,0 +1,53 @@ +TEMPLATE = lib +CONFIG += staticlib +CONFIG -= qt +CONFIG += link_pkgconfig +PKGCONFIG += glib-2.0 libsoup-2.4 libwspcodec +INCLUDEPATH += include +QMAKE_CFLAGS += -Wno-unused + +CONFIG(debug, debug|release) { + DEFINES += DEBUG + DESTDIR = $$_PRO_FILE_PWD_/build/debug +} else { + DESTDIR = $$_PRO_FILE_PWD_/build/release +} + +SOURCES += \ + src/mms_codec.c \ + src/mms_connection.c \ + src/mms_connman.c \ + src/mms_dispatcher.c \ + src/mms_file_util.c \ + src/mms_handler.c \ + src/mms_message.c \ + src/mms_lib_util.c \ + src/mms_log.c \ + src/mms_task.c \ + src/mms_task_ack.c \ + src/mms_task_decode.c \ + src/mms_task_notification.c \ + src/mms_task_notifyresp.c \ + src/mms_task_publish.c \ + src/mms_task_read.c \ + src/mms_task_retrieve.c \ + src/mms_task_upload.c \ + src/mms_util.c + +HEADERS += \ + src/mms_codec.h \ + src/mms_file_util.h \ + src/mms_task.h \ + src/mms_util.h \ + +HEADERS += \ + include/mms_connection.h \ + include/mms_connman.h \ + include/mms_database.h \ + include/mms_dispatcher.h \ + include/mms_handler.h \ + include/mms_lib_log.h \ + include/mms_lib_types.h \ + include/mms_lib_util.h \ + include/mms_log.h \ + include/mms_message.h diff --git a/mms-lib/src/mms_codec.c b/mms-lib/src/mms_codec.c new file mode 100644 index 0000000..9ac0692 --- /dev/null +++ b/mms-lib/src/mms_codec.c @@ -0,0 +1,1968 @@ +/* + * + * Multimedia Messaging Service + * + * Copyright (C) 2010-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 + +#ifdef _WIN32 +# define ssize_t int +# define write _write +# include +# define uninitialized_var(x) x +#else +# include +# define uninitialized_var(x) x = x +#endif + +#include + +#include "wsputil.h" +#include "mms_codec.h" + +#define MAX_ENC_VALUE_BYTES 6 + +#ifdef TEMP_FAILURE_RETRY +#define TFR TEMP_FAILURE_RETRY +#else +#define TFR +#endif + +enum mms_message_value_bool { + MMS_MESSAGE_VALUE_BOOL_YES = 128, + MMS_MESSAGE_VALUE_BOOL_NO = 129, +}; + +enum header_flag { + HEADER_FLAG_MANDATORY = 1, + HEADER_FLAG_ALLOW_MULTI = 2, + HEADER_FLAG_PRESET_POS = 4, + HEADER_FLAG_MARKED = 8, +}; + +enum mms_header { + MMS_HEADER_BCC = 0x01, + MMS_HEADER_CC = 0x02, + MMS_HEADER_CONTENT_LOCATION = 0x03, + MMS_HEADER_CONTENT_TYPE = 0x04, + MMS_HEADER_DATE = 0x05, + MMS_HEADER_DELIVERY_REPORT = 0x06, + MMS_HEADER_DELIVERY_TIME = 0x07, + MMS_HEADER_EXPIRY = 0x08, + MMS_HEADER_FROM = 0x09, + MMS_HEADER_MESSAGE_CLASS = 0x0a, + MMS_HEADER_MESSAGE_ID = 0x0b, + MMS_HEADER_MESSAGE_TYPE = 0x0c, + MMS_HEADER_MMS_VERSION = 0x0d, + MMS_HEADER_MESSAGE_SIZE = 0x0e, + MMS_HEADER_PRIORITY = 0x0f, + MMS_HEADER_READ_REPORT = 0x10, + MMS_HEADER_REPORT_ALLOWED = 0x11, + MMS_HEADER_RESPONSE_STATUS = 0x12, + MMS_HEADER_RESPONSE_TEXT = 0x13, + MMS_HEADER_SENDER_VISIBILITY = 0x14, + MMS_HEADER_STATUS = 0x15, + MMS_HEADER_SUBJECT = 0x16, + MMS_HEADER_TO = 0x17, + MMS_HEADER_TRANSACTION_ID = 0x18, + MMS_HEADER_RETRIEVE_STATUS = 0x19, + MMS_HEADER_RETRIEVE_TEXT = 0x1a, + MMS_HEADER_READ_STATUS = 0x1b, + __MMS_HEADER_MAX = 0x1c, + MMS_HEADER_INVALID = 0x80, +}; + +enum mms_part_header { + MMS_PART_HEADER_CONTENT_LOCATION = 0x0e, + MMS_PART_HEADER_CONTENT_ID = 0x40, +}; + +/* + * Reference: IANA http://www.iana.org/assignments/character-sets + */ +static const struct { + unsigned int mib_enum; + const char *charset; +} charset_assignments[] = { + { 3, "US-ASCII" }, + { 4, "ISO_8859-1" }, + { 5, "ISO_8859-2" }, + { 6, "ISO_8859-3" }, + { 7, "ISO_8859-4" }, + { 8, "ISO_8859-5" }, + { 9, "ISO_8859-6" }, + { 10, "ISO_8859-7" }, + { 11, "ISO_8859-8" }, + { 12, "ISO_8859-9" }, + { 13, "ISO-8859-10" }, + { 17, "Shift_JIS" }, + { 18, "EUC-JP" }, + { 36, "KS_C_5601-1987" }, + { 37, "ISO-2022-KR" }, + { 38, "EUC-KR" }, + { 39, "ISO-2022-JP" }, + { 40, "ISO-2022-JP-2" }, + { 81, "ISO_8859-6-E" }, + { 82, "ISO_8859-6-I" }, + { 84, "ISO_8859-8-E" }, + { 85, "ISO_8859-8-I" }, + { 106, "UTF-8" }, + { 109, "ISO-8859-13" }, + { 110, "ISO-8859-14" }, + { 111, "ISO-8859-15" }, + { 112, "ISO-8859-16" }, + { 113, "GBK" }, + { 114, "GB18030" }, + { 1000, "ISO-10646-UCS-2" }, + { 1001, "ISO-10646-UCS-4" }, + { 1004, "ISO-10646-J-1" }, + { 1012, "UTF-7" }, + { 1013, "UTF-16BE" }, + { 1014, "UTF-16LE" }, + { 1015, "UTF-16" }, + { 1017, "UTF-32" }, + { 1018, "UTF-32BE" }, + { 1019, "UTF-32LE" }, + { 2025, "GB2312" }, + { 2026, "Big5" }, + { 2027, "macintosh" }, + { 2084, "KOI8-R" }, + { 2109, "windows-874" }, + { 2250, "windows-1250" }, + { 2251, "windows-1251" }, + { 2252, "windows-1252" }, + { 2253, "windows-1253" }, + { 2254, "windows-1254" }, + { 2255, "windows-1255" }, + { 2256, "windows-1256" }, + { 2257, "windows-1257" }, + { 2258, "windows-1258" } +}; + +#define FB_SIZE 256 + +struct file_buffer { + unsigned char buf[FB_SIZE]; + unsigned int size; + unsigned int fsize; + int fd; +}; + +typedef gboolean (*header_handler)(struct wsp_header_iter *, void *); +typedef gboolean (*header_encoder)(struct file_buffer *, enum mms_header, + void *); + +char *mms_content_type_get_param_value(const char *content_type, + const char *param_name) +{ + struct wsp_text_header_iter iter; + + if (wsp_text_header_iter_init(&iter, content_type) == FALSE) + return NULL; + + while (wsp_text_header_iter_param_next(&iter) == TRUE) { + const char *key = wsp_text_header_iter_get_key(&iter); + + if (g_str_equal(key, param_name) == TRUE) + return g_strdup(wsp_text_header_iter_get_value(&iter)); + } + + return NULL; +} + +static const char *charset_index2string(unsigned int index) +{ + int low = 0; + int high = G_N_ELEMENTS(charset_assignments) - 1; + + while (low <= high) { + const int mid = (low + high)/2; + const unsigned int val = charset_assignments[mid].mib_enum; + if (val < index) { + low = mid + 1; + } else if (val > index) { + high = mid - 1; + } else { + return charset_assignments[mid].charset; + } + } + + return NULL; +} + +static gboolean extract_short(struct wsp_header_iter *iter, void *user) +{ + unsigned char *out = user; + const unsigned char *p; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_SHORT) + return FALSE; + + p = wsp_header_iter_get_val(iter); + *out = p[0]; + + return TRUE; +} + +static const char *decode_text(struct wsp_header_iter *iter) +{ + const unsigned char *p; + unsigned int l; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_TEXT) + return NULL; + + p = wsp_header_iter_get_val(iter); + l = wsp_header_iter_get_val_len(iter); + + return wsp_decode_text(p, l, NULL); +} + +static gboolean extract_text(struct wsp_header_iter *iter, void *user) +{ + char **out = user; + const char *text; + + text = decode_text(iter); + if (text == NULL) + return FALSE; + + *out = g_strdup(text); + + return TRUE; +} + +static gboolean extract_text_array_element(struct wsp_header_iter *iter, + void *user) +{ + char **out = user; + const char *element; + char *tmp; + + element = decode_text(iter); + if (element == NULL) + return FALSE; + + if (*out == NULL) { + *out = g_strdup(element); + return TRUE; + } + + tmp = g_strjoin(",", *out, element, NULL); + if (tmp == NULL) + return FALSE; + + g_free(*out); + + *out = tmp; + + return TRUE; +} + +static char *decode_encoded_string_with_mib_enum(const unsigned char *p, + unsigned int l) +{ + unsigned int mib_enum; + unsigned int consumed; + const char *text; + const char *from_codeset; + const char *to_codeset = "UTF-8"; + gsize bytes_read; + gsize bytes_written; + + if (wsp_decode_integer(p, l, &mib_enum, &consumed) == FALSE) + return NULL; + + if (mib_enum == 106) { + /* header is UTF-8 already */ + text = wsp_decode_text(p + consumed, l - consumed, NULL); + + return g_strdup(text); + } + + /* convert to UTF-8 */ + from_codeset = charset_index2string(mib_enum); + if (from_codeset == NULL) + return NULL; + + return g_convert((const char *) p + consumed, l - consumed, + to_codeset, from_codeset, + &bytes_read, &bytes_written, NULL); +} + +static gboolean extract_encoded_text(struct wsp_header_iter *iter, void *user) +{ + char **out = user; + const unsigned char *p; + unsigned int l; + const char *text; + char *uninitialized_var(dec_text); + + p = wsp_header_iter_get_val(iter); + l = wsp_header_iter_get_val_len(iter); + + switch (wsp_header_iter_get_val_type(iter)) { + case WSP_VALUE_TYPE_TEXT: + /* Text-string */ + text = wsp_decode_text(p, l, NULL); + dec_text = g_strdup(text); + break; + case WSP_VALUE_TYPE_LONG: + /* (Value-len) Char-set Text-string */ + dec_text = decode_encoded_string_with_mib_enum(p, l); + break; + case WSP_VALUE_TYPE_SHORT: + dec_text = NULL; + break; + } + + if (dec_text == NULL) + return FALSE; + + *out = dec_text; + + return TRUE; +} + +static gboolean extract_date(struct wsp_header_iter *iter, void *user) +{ + time_t *out = user; + const unsigned char *p; + unsigned int l; + unsigned int i; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_LONG) + return FALSE; + + p = wsp_header_iter_get_val(iter); + l = wsp_header_iter_get_val_len(iter); + + if (l > 4) + return FALSE; + + for (i = 0, *out = 0; i < l; i++) + *out = *out << 8 | p[i]; + + /* It is possible to overflow time_t on 32 bit systems */ + *out = *out & 0x7fffffff; + + return TRUE; +} + +static gboolean extract_absolute_relative_date(struct wsp_header_iter *iter, + void *user) +{ + time_t *out = user; + const unsigned char *p; + unsigned int l; + unsigned int i; + unsigned int seconds; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_LONG) + return FALSE; + + p = wsp_header_iter_get_val(iter); + l = wsp_header_iter_get_val_len(iter); + + if (l < 2 || l > 5) + return FALSE; + + if (p[0] != 128 && p[0] != 129) + return FALSE; + + for (i = 2, seconds = 0; i < l; i++) + seconds = seconds << 8 | p[i]; + + if (p[0] == 129) { + *out = time(NULL); + *out += seconds; + } else + *out = seconds; + + /* It is possible to overflow time_t on 32 bit systems */ + *out = *out & 0x7fffffff; + + return TRUE; +} + +static gboolean extract_boolean(struct wsp_header_iter *iter, void *user) +{ + gboolean *out = user; + const unsigned char *p; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_SHORT) + return FALSE; + + p = wsp_header_iter_get_val(iter); + + if (p[0] == MMS_MESSAGE_VALUE_BOOL_YES) { + *out = TRUE; + return TRUE; + } else if (p[0] == MMS_MESSAGE_VALUE_BOOL_NO) { + *out = FALSE; + return TRUE; + } else { + return TRUE; + } +} + +static gboolean extract_from(struct wsp_header_iter *iter, void *user) +{ + char **out = user; + const unsigned char *p; + unsigned int l; + const char *text; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_LONG) + return FALSE; + + p = wsp_header_iter_get_val(iter); + l = wsp_header_iter_get_val_len(iter); + + if (p[0] != 128 && p[0] != 129) + return FALSE; + + if (p[0] == 129) { + *out = NULL; + return TRUE; + } + + text = wsp_decode_text(p + 1, l - 1, NULL); + if (text == NULL) + return FALSE; + + *out = g_strdup(text); + + return TRUE; +} + +static gboolean extract_message_class(struct wsp_header_iter *iter, void *user) +{ + char **out = user; + const unsigned char *p; + unsigned int l; + const char *text; + + if (wsp_header_iter_get_val_type(iter) == WSP_VALUE_TYPE_LONG) + return FALSE; + + p = wsp_header_iter_get_val(iter); + + if (wsp_header_iter_get_val_type(iter) == WSP_VALUE_TYPE_SHORT) { + switch (p[0]) { + case 128: + *out = g_strdup(MMS_MESSAGE_CLASS_PERSONAL); + return TRUE; + case 129: + *out = g_strdup(MMS_MESSAGE_CLASS_ADVERTISEMENT); + return TRUE; + case 130: + *out = g_strdup(MMS_MESSAGE_CLASS_INFORMATIONAL); + return TRUE; + case 131: + *out = g_strdup(MMS_MESSAGE_CLASS_AUTO); + return TRUE; + default: + return FALSE; + } + } + + l = wsp_header_iter_get_val_len(iter); + + text = wsp_decode_token_text(p, l, NULL); + if (text == NULL) + return FALSE; + + *out = g_strdup(text); + + return TRUE; +} + +static gboolean extract_sender_visibility(struct wsp_header_iter *iter, + void *user) +{ + enum mms_message_sender_visibility *out = user; + const unsigned char *p; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_SHORT) + return FALSE; + + p = wsp_header_iter_get_val(iter); + + if (p[0] != 128 && p[0] != 129) + return FALSE; + + *out = p[0]; + + return TRUE; +} + +static gboolean extract_priority(struct wsp_header_iter *iter, void *user) +{ + enum mms_message_priority *out = user; + const unsigned char *p; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_SHORT) + return FALSE; + + p = wsp_header_iter_get_val(iter); + + switch (p[0]) { + case MMS_MESSAGE_PRIORITY_LOW: + case MMS_MESSAGE_PRIORITY_NORMAL: + case MMS_MESSAGE_PRIORITY_HIGH: + *out = p[0]; + return TRUE; + } + + return FALSE; +} + +static gboolean extract_rsp_status(struct wsp_header_iter *iter, void *user) +{ + unsigned char *out = user; + const unsigned char *p; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_SHORT) + return FALSE; + + p = wsp_header_iter_get_val(iter); + + switch (p[0]) { + case MMS_MESSAGE_RSP_STATUS_OK: + case MMS_MESSAGE_RSP_STATUS_ERR_UNSUPPORTED_MESSAGE: + case MMS_MESSAGE_RSP_STATUS_ERR_TRANS_FAILURE: + case MMS_MESSAGE_RSP_STATUS_ERR_TRANS_NETWORK_PROBLEM: + case MMS_MESSAGE_RSP_STATUS_ERR_PERM_FAILURE: + case MMS_MESSAGE_RSP_STATUS_ERR_PERM_SERVICE_DENIED: + case MMS_MESSAGE_RSP_STATUS_ERR_PERM_MESSAGE_FORMAT_CORRUPT: + case MMS_MESSAGE_RSP_STATUS_ERR_PERM_SENDING_ADDRESS_UNRESOLVED: + case MMS_MESSAGE_RSP_STATUS_ERR_PERM_CONTENT_NOT_ACCEPTED: + case MMS_MESSAGE_RSP_STATUS_ERR_PERM_LACK_OF_PREPAID: + *out = p[0]; + return TRUE; + } + + return FALSE; +} + +static gboolean extract_status(struct wsp_header_iter *iter, void *user) +{ + enum mms_message_delivery_status *out = user; + const unsigned char *p; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_SHORT) + return FALSE; + + p = wsp_header_iter_get_val(iter); + + switch (p[0]) { + case MMS_MESSAGE_DELIVERY_STATUS_EXPIRED: + case MMS_MESSAGE_DELIVERY_STATUS_RETRIEVED: + case MMS_MESSAGE_DELIVERY_STATUS_REJECTED: + case MMS_MESSAGE_DELIVERY_STATUS_DEFERRED: + case MMS_MESSAGE_DELIVERY_STATUS_UNRECOGNISED: + case MMS_MESSAGE_DELIVERY_STATUS_INDETERMINATE: + case MMS_MESSAGE_DELIVERY_STATUS_FORWARDED: + case MMS_MESSAGE_DELIVERY_STATUS_UNREACHABLE: + *out = p[0]; + return TRUE; + } + + return FALSE; +} + +static gboolean extract_retrieve_status(struct wsp_header_iter *iter, void *user) +{ + unsigned char *out = user; + const unsigned char *p; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_SHORT) + return FALSE; + + p = wsp_header_iter_get_val(iter); + + switch (p[0]) { + case MMS_MESSAGE_RETRIEVE_STATUS_OK: + case MMS_MESSAGE_RETRIEVE_STATUS_ERR_TRANS_FAILURE: + case MMS_MESSAGE_RETRIEVE_STATUS_ERR_TRANS_MESSAGE_NOT_FOUND: + case MMS_MESSAGE_RETRIEVE_STATUS_ERR_TRANS_NETWORK_PROBLEM: + case MMS_MESSAGE_RETRIEVE_STATUS_ERR_PERM_FAILURE: + case MMS_MESSAGE_RETRIEVE_STATUS_ERR_PERM_SERVICE_DENIED: + case MMS_MESSAGE_RETRIEVE_STATUS_ERR_PERM_MESSAGE_NOT_FOUND: + case MMS_MESSAGE_RETRIEVE_STATUS_ERR_PERM_CONTENT_UNSUPPORTED: + *out = p[0]; + return TRUE; + } + + return FALSE; +} + +static gboolean extract_read_status(struct wsp_header_iter *iter, void *user) +{ + enum mms_message_read_status *out = user; + const unsigned char *p; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_SHORT) + return FALSE; + + p = wsp_header_iter_get_val(iter); + + switch (p[0]) { + case MMS_MESSAGE_READ_STATUS_READ: + case MMS_MESSAGE_READ_STATUS_DELETED: + *out = p[0]; + return TRUE; + } + + return FALSE; +} + +static gboolean extract_unsigned(struct wsp_header_iter *iter, void *user) +{ + unsigned long *out = user; + const unsigned char *p; + unsigned int l; + unsigned int i; + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_LONG) + return FALSE; + + p = wsp_header_iter_get_val(iter); + l = wsp_header_iter_get_val_len(iter); + + if (l > sizeof(unsigned long)) + return FALSE; + + for (i = 0, *out = 0; i < l; i++) + *out = *out << 8 | p[i]; + + return TRUE; +} + +static header_handler handler_for_type(enum mms_header header) +{ + switch (header) { + case MMS_HEADER_BCC: + return &extract_text_array_element; + case MMS_HEADER_CC: + return &extract_text_array_element; + case MMS_HEADER_CONTENT_LOCATION: + return &extract_text; + case MMS_HEADER_CONTENT_TYPE: + return &extract_text; + case MMS_HEADER_DATE: + return &extract_date; + case MMS_HEADER_DELIVERY_REPORT: + return &extract_boolean; + case MMS_HEADER_DELIVERY_TIME: + return &extract_absolute_relative_date; + case MMS_HEADER_EXPIRY: + return &extract_absolute_relative_date; + case MMS_HEADER_FROM: + return &extract_from; + case MMS_HEADER_MESSAGE_CLASS: + return &extract_message_class; + case MMS_HEADER_MESSAGE_ID: + return &extract_text; + case MMS_HEADER_MESSAGE_TYPE: + return &extract_short; + case MMS_HEADER_MMS_VERSION: + return &extract_short; + case MMS_HEADER_MESSAGE_SIZE: + return &extract_unsigned; + case MMS_HEADER_PRIORITY: + return &extract_priority; + case MMS_HEADER_READ_REPORT: + return &extract_boolean; + case MMS_HEADER_REPORT_ALLOWED: + return &extract_boolean; + case MMS_HEADER_RESPONSE_STATUS: + return &extract_rsp_status; + case MMS_HEADER_RESPONSE_TEXT: + return &extract_encoded_text; + case MMS_HEADER_SENDER_VISIBILITY: + return &extract_sender_visibility; + case MMS_HEADER_STATUS: + return &extract_status; + case MMS_HEADER_SUBJECT: + return &extract_encoded_text; + case MMS_HEADER_TO: + return &extract_text_array_element; + case MMS_HEADER_TRANSACTION_ID: + return &extract_text; + case MMS_HEADER_RETRIEVE_STATUS: + return &extract_retrieve_status; + case MMS_HEADER_RETRIEVE_TEXT: + return &extract_encoded_text; + case MMS_HEADER_READ_STATUS: + return &extract_read_status; + case MMS_HEADER_INVALID: + case __MMS_HEADER_MAX: + return NULL; + } + + return NULL; +} + +struct header_handler_entry { + int flags; + void *data; + int pos; +}; + +static gboolean mms_parse_headers(struct wsp_header_iter *iter, + enum mms_header orig_header, ...) +{ + struct header_handler_entry entries[__MMS_HEADER_MAX + 1]; + va_list args; + const unsigned char *p; + int i; + enum mms_header header; + + memset(&entries, 0, sizeof(entries)); + + va_start(args, orig_header); + header = orig_header; + + while (header != MMS_HEADER_INVALID) { + entries[header].flags = va_arg(args, int); + entries[header].data = va_arg(args, void *); + + header = va_arg(args, enum mms_header); + } + + va_end(args); + + for (i = 1; wsp_header_iter_next(iter); i++) { + unsigned char h; + header_handler handler; + + /* Skip application headers */ + if (wsp_header_iter_get_hdr_type(iter) != + WSP_HEADER_TYPE_WELL_KNOWN) + continue; + + p = wsp_header_iter_get_hdr(iter); + h = p[0] & 0x7f; + + handler = handler_for_type(h); + if (handler == NULL) + return FALSE; + + /* Unsupported header, skip */ + if (entries[h].data == NULL) + continue; + + /* Skip multiply present headers unless explicitly requested */ + if ((entries[h].flags & HEADER_FLAG_MARKED) && + !(entries[h].flags & HEADER_FLAG_ALLOW_MULTI)) + continue; + + /* Parse the header */ + if (handler(iter, entries[h].data) == FALSE) + return FALSE; + + entries[h].pos = i; + entries[h].flags |= HEADER_FLAG_MARKED; + } + + for (i = 0; i < __MMS_HEADER_MAX + 1; i++) { + if ((entries[i].flags & HEADER_FLAG_MANDATORY) && + !(entries[i].flags & HEADER_FLAG_MARKED)) + return FALSE; + } + + /* + * Here we check for header positions. This function assumes that + * headers marked with PRESET_POS are in the beginning of the message + * and follow the same order as given in the va_arg list. The headers + * marked this way have to be contiguous. + */ + for (i = 0; i < __MMS_HEADER_MAX + 1; i++) { + int check_flags = HEADER_FLAG_PRESET_POS | HEADER_FLAG_MARKED; + int expected_pos = 1; + + if ((entries[i].flags & check_flags) != check_flags) + continue; + + va_start(args, orig_header); + header = orig_header; + + while (header != MMS_HEADER_INVALID && (int)header != i) { + va_arg(args, int); + va_arg(args, void *); + + if (entries[header].flags & HEADER_FLAG_MARKED) + expected_pos += 1; + + header = va_arg(args, enum mms_header); + } + + va_end(args); + + if (entries[i].pos != expected_pos) + return FALSE; + } + + return TRUE; +} + +static gboolean decode_notification_ind(struct wsp_header_iter *iter, + struct mms_message *out) +{ + return mms_parse_headers(iter, MMS_HEADER_TRANSACTION_ID, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->transaction_id, + MMS_HEADER_MMS_VERSION, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->version, + MMS_HEADER_FROM, + 0, &out->ni.from, + MMS_HEADER_SUBJECT, + 0, &out->ni.subject, + MMS_HEADER_MESSAGE_CLASS, + HEADER_FLAG_MANDATORY, &out->ni.cls, + MMS_HEADER_MESSAGE_SIZE, + HEADER_FLAG_MANDATORY, &out->ni.size, + MMS_HEADER_EXPIRY, + HEADER_FLAG_MANDATORY, &out->ni.expiry, + MMS_HEADER_CONTENT_LOCATION, + HEADER_FLAG_MANDATORY, &out->ni.location, + MMS_HEADER_INVALID); +} + +static gboolean decode_notify_resp_ind(struct wsp_header_iter *iter, + struct mms_message *out) +{ + return mms_parse_headers(iter, MMS_HEADER_TRANSACTION_ID, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->transaction_id, + MMS_HEADER_MMS_VERSION, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->version, + MMS_HEADER_STATUS, + HEADER_FLAG_MANDATORY, &out->nri.notify_status, + MMS_HEADER_INVALID); +} + +static gboolean decode_acknowledge_ind(struct wsp_header_iter *iter, + struct mms_message *out) +{ + return mms_parse_headers(iter, MMS_HEADER_TRANSACTION_ID, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->transaction_id, + MMS_HEADER_MMS_VERSION, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->version, + MMS_HEADER_REPORT_ALLOWED, + 0, &out->ai.report, + MMS_HEADER_INVALID); +} + +static gboolean decode_delivery_ind(struct wsp_header_iter *iter, + struct mms_message *out) +{ + return mms_parse_headers(iter, MMS_HEADER_MMS_VERSION, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->version, + MMS_HEADER_MESSAGE_ID, + HEADER_FLAG_MANDATORY, &out->di.msgid, + MMS_HEADER_TO, + HEADER_FLAG_MANDATORY, &out->di.to, + MMS_HEADER_DATE, + HEADER_FLAG_MANDATORY, &out->di.date, + MMS_HEADER_STATUS, + HEADER_FLAG_MANDATORY, &out->di.dr_status, + MMS_HEADER_INVALID); +} + +static gboolean decode_read_ind(struct wsp_header_iter *iter, + struct mms_message *out) +{ + return mms_parse_headers(iter, MMS_HEADER_MMS_VERSION, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->version, + MMS_HEADER_MESSAGE_ID, + HEADER_FLAG_MANDATORY, &out->ri.msgid, + MMS_HEADER_TO, + HEADER_FLAG_MANDATORY, &out->ri.to, + MMS_HEADER_FROM, + HEADER_FLAG_MANDATORY, &out->ri.from, + MMS_HEADER_DATE, + HEADER_FLAG_MANDATORY, &out->ri.date, + MMS_HEADER_READ_STATUS, + HEADER_FLAG_MANDATORY, &out->ri.rr_status, + MMS_HEADER_INVALID); +} + +static const char *decode_attachment_charset(const unsigned char *pdu, + unsigned int len) +{ + struct wsp_parameter_iter iter; + struct wsp_parameter param; + + wsp_parameter_iter_init(&iter, pdu, len); + + while (wsp_parameter_iter_next(&iter, ¶m)) { + if (param.type == WSP_PARAMETER_TYPE_CHARSET) + return param.text; + } + + return NULL; +} + +static gboolean extract_content_id(struct wsp_header_iter *iter, void *user) +{ + char **out = user; + const unsigned char *p; + unsigned int l; + const char *text; + + p = wsp_header_iter_get_val(iter); + l = wsp_header_iter_get_val_len(iter); + + if (wsp_header_iter_get_val_type(iter) != WSP_VALUE_TYPE_TEXT) + return FALSE; + + text = wsp_decode_quoted_string(p, l, NULL); + + if (text == NULL) + return FALSE; + + *out = g_strdup(text); + + return TRUE; +} + +static gboolean attachment_parse_headers(struct wsp_header_iter *iter, + struct mms_attachment *part) +{ + while (wsp_header_iter_next(iter)) { + const unsigned char *hdr = wsp_header_iter_get_hdr(iter); + unsigned char h; + + /* Skip application headers */ + if (wsp_header_iter_get_hdr_type(iter) != + WSP_HEADER_TYPE_WELL_KNOWN) + continue; + + h = hdr[0] & 0x7f; + + switch (h) { + case MMS_PART_HEADER_CONTENT_ID: + if (extract_content_id(iter, &part->content_id) + == FALSE) + return FALSE; + break; + case MMS_PART_HEADER_CONTENT_LOCATION: + break; + } + } + + return TRUE; +} + +static void free_attachment(gpointer data, gpointer user_data) +{ + struct mms_attachment *attach = data; + + g_free(attach->content_type); + g_free(attach->content_id); + + g_free(attach); +} + +static gboolean mms_parse_attachments(struct wsp_header_iter *iter, + struct mms_message *out) +{ + struct wsp_multipart_iter mi; + const void *ct; + unsigned int ct_len; + unsigned int consumed; + + if (wsp_multipart_iter_init(&mi, iter, &ct, &ct_len) == FALSE) + return FALSE; + + while (wsp_multipart_iter_next(&mi) == TRUE) { + struct mms_attachment *part; + struct wsp_header_iter hi; + const void *mimetype; + const char *charset; + + ct = wsp_multipart_iter_get_content_type(&mi); + ct_len = wsp_multipart_iter_get_content_type_len(&mi); + + if (wsp_decode_content_type(ct, ct_len, &mimetype, + &consumed, NULL) == FALSE) + return FALSE; + + charset = decode_attachment_charset( + (const unsigned char *)ct + consumed, + ct_len - consumed); + + wsp_header_iter_init(&hi, wsp_multipart_iter_get_hdr(&mi), + wsp_multipart_iter_get_hdr_len(&mi), + 0); + + part = g_try_new0(struct mms_attachment, 1); + if (part == NULL) + return FALSE; + + if (attachment_parse_headers(&hi, part) == FALSE) { + free_attachment(part, NULL); + return FALSE; + } + + if (wsp_header_iter_at_end(&hi) == FALSE) { + free_attachment(part, NULL); + return FALSE; + } + + if (charset == NULL) + part->content_type = g_strdup(mimetype); + else + part->content_type = g_strconcat(mimetype, ";charset=", + charset, NULL); + + part->length = wsp_multipart_iter_get_body_len(&mi); + part->offset = (const unsigned char *) + wsp_multipart_iter_get_body(&mi) - + wsp_header_iter_get_pdu(iter); + + out->attachments = g_slist_prepend(out->attachments, part); + } + + if (wsp_multipart_iter_close(&mi, iter) == FALSE) + return FALSE; + + out->attachments = g_slist_reverse(out->attachments); + + return TRUE; +} + +static gboolean decode_retrieve_conf(struct wsp_header_iter *iter, + struct mms_message *out) +{ + if (mms_parse_headers(iter, MMS_HEADER_TRANSACTION_ID, + HEADER_FLAG_PRESET_POS, &out->transaction_id, + MMS_HEADER_MMS_VERSION, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->version, + MMS_HEADER_FROM, + 0, &out->rc.from, + MMS_HEADER_TO, + HEADER_FLAG_ALLOW_MULTI, &out->rc.to, + MMS_HEADER_CC, + HEADER_FLAG_ALLOW_MULTI, &out->rc.cc, + MMS_HEADER_SUBJECT, + 0, &out->rc.subject, + MMS_HEADER_MESSAGE_CLASS, + 0, &out->rc.cls, + MMS_HEADER_PRIORITY, + 0, &out->rc.priority, + MMS_HEADER_MESSAGE_ID, + 0, &out->rc.msgid, + MMS_HEADER_DATE, + HEADER_FLAG_MANDATORY, &out->rc.date, + MMS_HEADER_INVALID) == FALSE) + return FALSE; + + if (wsp_header_iter_at_end(iter) == TRUE) + return TRUE; + + if (wsp_header_iter_is_multipart(iter) == FALSE) + return FALSE; + + if (mms_parse_attachments(iter, out) == FALSE) + return FALSE; + + if (wsp_header_iter_at_end(iter) == FALSE) + return FALSE; + + return TRUE; +} + +static gboolean decode_send_conf(struct wsp_header_iter *iter, + struct mms_message *out) +{ + return mms_parse_headers(iter, MMS_HEADER_TRANSACTION_ID, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->transaction_id, + MMS_HEADER_MMS_VERSION, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->version, + MMS_HEADER_RESPONSE_STATUS, + HEADER_FLAG_MANDATORY, &out->sc.rsp_status, + MMS_HEADER_MESSAGE_ID, + 0, &out->sc.msgid, + MMS_HEADER_INVALID); +} + +static gboolean decode_send_req(struct wsp_header_iter *iter, + struct mms_message *out) +{ + if (mms_parse_headers(iter, MMS_HEADER_TRANSACTION_ID, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->transaction_id, + MMS_HEADER_MMS_VERSION, + HEADER_FLAG_MANDATORY | HEADER_FLAG_PRESET_POS, + &out->version, + MMS_HEADER_TO, + HEADER_FLAG_ALLOW_MULTI, &out->sr.to, + MMS_HEADER_INVALID) == FALSE) + return FALSE; + + if (wsp_header_iter_at_end(iter) == TRUE) + return TRUE; + + if (wsp_header_iter_is_multipart(iter) == FALSE) + return FALSE; + + if (mms_parse_attachments(iter, out) == FALSE) + return FALSE; + + if (wsp_header_iter_at_end(iter) == FALSE) + return FALSE; + + return TRUE; +} + +#define CHECK_WELL_KNOWN_HDR(hdr) \ + if (wsp_header_iter_next(&iter) == FALSE) \ + return FALSE; \ + \ + if (wsp_header_iter_get_hdr_type(&iter) != \ + WSP_HEADER_TYPE_WELL_KNOWN) \ + return FALSE; \ + \ + p = wsp_header_iter_get_hdr(&iter); \ + \ + if ((p[0] & 0x7f) != hdr) \ + return FALSE \ + +gboolean mms_message_decode(const unsigned char *pdu, + unsigned int len, struct mms_message *out) +{ + unsigned int flags = 0; + struct wsp_header_iter iter; + const unsigned char *p; + unsigned char octet; + + memset(out, 0, sizeof(*out)); + + flags |= WSP_HEADER_ITER_FLAG_REJECT_CP; + flags |= WSP_HEADER_ITER_FLAG_DETECT_MMS_MULTIPART; + wsp_header_iter_init(&iter, pdu, len, flags); + + CHECK_WELL_KNOWN_HDR(MMS_HEADER_MESSAGE_TYPE); + + if (extract_short(&iter, &octet) == FALSE) + return FALSE; + + out->type = octet; + + switch (out->type) { + case MMS_MESSAGE_TYPE_SEND_REQ: + return decode_send_req(&iter, out); + case MMS_MESSAGE_TYPE_SEND_CONF: + return decode_send_conf(&iter, out); + case MMS_MESSAGE_TYPE_NOTIFICATION_IND: + return decode_notification_ind(&iter, out); + case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: + return decode_notify_resp_ind(&iter, out); + case MMS_MESSAGE_TYPE_RETRIEVE_CONF: + return decode_retrieve_conf(&iter, out); + case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: + return decode_acknowledge_ind(&iter, out); + case MMS_MESSAGE_TYPE_DELIVERY_IND: + return decode_delivery_ind(&iter, out); + case MMS_MESSAGE_TYPE_READ_REC_IND: + case MMS_MESSAGE_TYPE_READ_ORIG_IND: + return decode_read_ind(&iter, out); + } + + return FALSE; +} + +void mms_message_free(struct mms_message *msg) +{ + switch (msg->type) { + case MMS_MESSAGE_TYPE_SEND_REQ: + g_free(msg->sr.to); + g_free(msg->sr.content_type); + break; + case MMS_MESSAGE_TYPE_SEND_CONF: + g_free(msg->sc.msgid); + break; + case MMS_MESSAGE_TYPE_NOTIFICATION_IND: + g_free(msg->ni.from); + g_free(msg->ni.subject); + g_free(msg->ni.cls); + g_free(msg->ni.location); + break; + case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: + break; + case MMS_MESSAGE_TYPE_RETRIEVE_CONF: + g_free(msg->rc.from); + g_free(msg->rc.to); + g_free(msg->rc.cc); + g_free(msg->rc.subject); + g_free(msg->rc.cls); + g_free(msg->rc.msgid); + break; + case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: + break; + case MMS_MESSAGE_TYPE_DELIVERY_IND: + g_free(msg->di.msgid); + g_free(msg->di.to); + break; + case MMS_MESSAGE_TYPE_READ_REC_IND: + case MMS_MESSAGE_TYPE_READ_ORIG_IND: + g_free(msg->ri.msgid); + g_free(msg->ri.to); + g_free(msg->ri.from); + break; + } + + g_free(msg->transaction_id); + + if (msg->attachments != NULL) { + g_slist_foreach(msg->attachments, free_attachment, NULL); + g_slist_free(msg->attachments); + } + + g_free(msg); +} + +static void fb_init(struct file_buffer *fb, int fd) +{ + fb->size = 0; + fb->fsize = 0; + fb->fd = fd; +} + +static gboolean fb_flush(struct file_buffer *fb) +{ + unsigned int size; + ssize_t len; + + if (fb->size == 0) + return TRUE; + + len = write(fb->fd, fb->buf, fb->size); + if (len < 0) + return FALSE; + + size = len; + + if (size != fb->size) + return FALSE; + + fb->fsize += size; + + fb->size = 0; + + return TRUE; +} + +static unsigned int fb_get_file_size(struct file_buffer *fb) +{ + return fb->fsize + fb->size; +} + +static void *fb_request(struct file_buffer *fb, unsigned int count) +{ + if (fb->size + count < FB_SIZE) { + void *ptr = fb->buf + fb->size; + fb->size += count; + return ptr; + } + + if (fb_flush(fb) == FALSE) + return NULL; + + if (count > FB_SIZE) + return NULL; + + fb->size = count; + + return fb->buf; +} + +static void *fb_request_field(struct file_buffer *fb, unsigned char token, + unsigned int len) +{ + unsigned char *ptr; + + ptr = fb_request(fb, len + 1); + if (ptr == NULL) + return NULL; + + ptr[0] = token | 0x80; + + return ptr + 1; +} + +static gboolean fb_copy(struct file_buffer *fb, const void *buf, unsigned int c) +{ + unsigned int written; + ssize_t len; + + if (fb_flush(fb) == FALSE) + return FALSE; + + len = TFR(write(fb->fd, buf, c)); + if (len < 0) + return FALSE; + + written = len; + + if (written != c) + return FALSE; + + fb->fsize += written; + + return TRUE; +} + +static gboolean fb_put_value_length(struct file_buffer *fb, unsigned int val) +{ + unsigned int count; + + if (fb->size + MAX_ENC_VALUE_BYTES > FB_SIZE) { + if (fb_flush(fb) == FALSE) + return FALSE; + } + + if (wsp_encode_value_length(val, fb->buf + fb->size, FB_SIZE - fb->size, + &count) == FALSE) + return FALSE; + + fb->size += count; + + return TRUE; +} + +static gboolean fb_put_uintvar(struct file_buffer *fb, unsigned int val) +{ + unsigned int count; + + if (fb->size + MAX_ENC_VALUE_BYTES > FB_SIZE) { + if (fb_flush(fb) == FALSE) + return FALSE; + } + + if (wsp_encode_uintvar(val, fb->buf + fb->size, FB_SIZE - fb->size, + &count) == FALSE) + return FALSE; + + fb->size += count; + + return TRUE; +} + +static gboolean encode_short(struct file_buffer *fb, + enum mms_header header, void *user) +{ + char *ptr; + unsigned int *wk = user; + + ptr = fb_request_field(fb, header, 1); + if (ptr == NULL) + return FALSE; + + *ptr = *wk | 0x80; + + return TRUE; +} + +static gboolean encode_boolean(struct file_buffer *fb, + enum mms_header header, void *user) +{ + char *ptr; + gboolean *value = user; + + ptr = fb_request_field(fb, header, 1); + if (ptr == NULL) + return FALSE; + + *ptr = *value ? MMS_MESSAGE_VALUE_BOOL_YES : MMS_MESSAGE_VALUE_BOOL_NO; + + return TRUE; +} + +static gboolean encode_from(struct file_buffer *fb, + enum mms_header header, void *user) +{ + char *ptr; + char **text = user; + + if (strlen(*text) > 0) + return FALSE; + + /* From: header token + value length + Insert-address-token */ + ptr = fb_request_field(fb, header, 2); + if (ptr == NULL) + return FALSE; + + ptr[0] = 1; + ptr[1] = '\x81'; + + return TRUE; +} + +static gboolean encode_date(struct file_buffer *fb, + enum mms_header header, void *user) +{ + time_t date = *((time_t *)user); + guint8 octets[sizeof(date)]; + guint8 *ptr; + int i, len; + + /* + * Date: Long-integer + * In seconds from 1970-01-01, 00:00:00 GMT. + * + * Long-integer = Short-length Multi-octet-integer + * Short-length = + * Multi-octet-integer = 1*30 OCTET + * + * The content octets shall be an unsigned integer value with the + * most significant octet encoded first (big-endian representation). + * The minimum number of octets must be used to encode the value. + */ + for (i=sizeof(date)-1; i>=0; i--, date >>= 8) { + /* Most significant byte first */ + octets[i] = (guint8)date; + } + /* Skip most significant zeros */ + for (i=0; i<(int)(sizeof(date)-1) && !octets[i]; i++); + len = sizeof(date) - i; + + ptr = fb_request_field(fb, header, len+1); + if (ptr == NULL) + return FALSE; + + ptr[0] = (guint8)len; + memcpy(ptr+1, octets+i, len); + return TRUE; +} + +static gboolean encode_text(struct file_buffer *fb, + enum mms_header header, void *user) +{ + char *ptr; + char **text = user; + unsigned int len; + + len = strlen(*text) + 1; + + ptr = fb_request_field(fb, header, len); + if (ptr == NULL) + return FALSE; + + strcpy(ptr, *text); + + return TRUE; +} + +static gboolean encode_quoted_string(struct file_buffer *fb, + enum mms_header header, void *user) +{ + char *ptr; + char **text = user; + unsigned int len; + + len = strlen(*text) + 1; + + ptr = fb_request_field(fb, header, len + 3); + if (ptr == NULL) + return FALSE; + + ptr[0] = '"'; + ptr[1] = '<'; + strcpy(ptr + 2, *text); + ptr[len + 1] = '>'; + ptr[len + 2] = '\0'; + + return TRUE; +} + +static gboolean encode_text_array_element(struct file_buffer *fb, + enum mms_header header, void *user) +{ + char **text = user; + char **tos; + int i; + + tos = g_strsplit(*text, ",", 0); + + for (i = 0; tos[i] != NULL; i++) { + if (encode_text(fb, header, &tos[i]) == FALSE) { + g_strfreev(tos); + return FALSE; + } + } + + g_strfreev(tos); + + return TRUE; +} + +static gboolean encode_content_type(struct file_buffer *fb, + enum mms_header header, void *user) +{ + char *ptr; + char **hdr = user; + unsigned int len; + unsigned int ct; + unsigned int ct_len; + unsigned int type_len; + unsigned int start_len; + const char *ct_str; + const char *uninitialized_var(type); + const char *uninitialized_var(start); + struct wsp_text_header_iter iter; + + if (wsp_text_header_iter_init(&iter, *hdr) == FALSE) + return FALSE; + + if (g_ascii_strcasecmp("Content-Type", + wsp_text_header_iter_get_key(&iter)) != 0) + return FALSE; + + ct_str = wsp_text_header_iter_get_value(&iter); + + if (wsp_get_well_known_content_type(ct_str, &ct) == TRUE) + ct_len = 1; + else + ct_len = strlen(ct_str) + 1; + + len = ct_len; + + type_len = 0; + start_len = 0; + + while (wsp_text_header_iter_param_next(&iter) == TRUE) { + if (g_ascii_strcasecmp("type", + wsp_text_header_iter_get_key(&iter)) == 0) { + type = wsp_text_header_iter_get_value(&iter); + type_len = strlen(type) + 1; + len += 1 + type_len; + } else if (g_ascii_strcasecmp("start", + wsp_text_header_iter_get_key(&iter)) == 0) { + start = wsp_text_header_iter_get_value(&iter); + start_len = strlen(start) + 1; + len += 1 + start_len; + } + } + + if (len == 1) + return encode_short(fb, header, &ct); + + ptr = fb_request(fb, 1); + if (ptr == NULL) + return FALSE; + + *ptr = header | 0x80; + + /* Encode content type value length */ + if (fb_put_value_length(fb, len) == FALSE) + return FALSE; + + /* Encode content type including parameters */ + ptr = fb_request(fb, ct_len); + if (ptr == NULL) + return FALSE; + + if (ct_len == 1) + *ptr = ct | 0x80; + else + strcpy(ptr, ct_str); + + if (type_len > 0) { + ptr = fb_request_field(fb, WSP_PARAMETER_TYPE_CONTENT_TYPE, + type_len); + if (ptr == NULL) + return FALSE; + + strcpy(ptr, type); + } + + if (start_len > 0) { + ptr = fb_request_field(fb, WSP_PARAMETER_TYPE_START_DEFUNCT, + start_len); + if (ptr == NULL) + return FALSE; + + strcpy(ptr, start); + } + + return TRUE; +} + +static header_encoder encoder_for_type(enum mms_header header) +{ + switch (header) { + case MMS_HEADER_BCC: + return &encode_text_array_element; + case MMS_HEADER_CC: + return &encode_text_array_element; + case MMS_HEADER_CONTENT_LOCATION: + return NULL; + case MMS_HEADER_CONTENT_TYPE: + return &encode_content_type; + case MMS_HEADER_DATE: + return &encode_date; + case MMS_HEADER_DELIVERY_REPORT: + return &encode_boolean; + case MMS_HEADER_DELIVERY_TIME: + return NULL; + case MMS_HEADER_EXPIRY: + return NULL; + case MMS_HEADER_FROM: + return &encode_from; + case MMS_HEADER_MESSAGE_CLASS: + return NULL; + case MMS_HEADER_MESSAGE_ID: + return &encode_text; + case MMS_HEADER_MESSAGE_TYPE: + return &encode_short; + case MMS_HEADER_MMS_VERSION: + return &encode_short; + case MMS_HEADER_MESSAGE_SIZE: + return NULL; + case MMS_HEADER_PRIORITY: + return NULL; + case MMS_HEADER_READ_REPORT: + return &encode_boolean; + case MMS_HEADER_REPORT_ALLOWED: + return &encode_boolean; + case MMS_HEADER_RESPONSE_STATUS: + return NULL; + case MMS_HEADER_RESPONSE_TEXT: + return NULL; + case MMS_HEADER_SENDER_VISIBILITY: + return NULL; + case MMS_HEADER_STATUS: + return &encode_short; + case MMS_HEADER_SUBJECT: + return NULL; + case MMS_HEADER_TO: + return &encode_text_array_element; + case MMS_HEADER_TRANSACTION_ID: + return &encode_text; + case MMS_HEADER_RETRIEVE_STATUS: + return &encode_short; + case MMS_HEADER_RETRIEVE_TEXT: + return NULL; + case MMS_HEADER_READ_STATUS: + return &encode_short; + case MMS_HEADER_INVALID: + case __MMS_HEADER_MAX: + return NULL; + } + + return NULL; +} + +static gboolean mms_encode_send_req_part_header(struct mms_attachment *part, + struct file_buffer *fb) +{ + char *ptr; + unsigned int len; + unsigned int ct; + unsigned int ct_len; + unsigned int cs_len; + const char *ct_str; + const char *uninitialized_var(cs_str); + unsigned int ctp_len; + unsigned int cid_len; + unsigned char ctp_val[MAX_ENC_VALUE_BYTES]; + unsigned char cs_val[MAX_ENC_VALUE_BYTES]; + unsigned int cs; + struct wsp_text_header_iter iter; + + /* + * Compute Headers length: content-type [+ params] [+ content-id] + * ex. : "Content-Type:text/plain; charset=us-ascii" + */ + if (wsp_text_header_iter_init(&iter, part->content_type) == FALSE) + return FALSE; + + if (g_ascii_strcasecmp("Content-Type", + wsp_text_header_iter_get_key(&iter)) != 0) + return FALSE; + + ct_str = wsp_text_header_iter_get_value(&iter); + + if (wsp_get_well_known_content_type(ct_str, &ct) == TRUE) + ct_len = 1; + else + ct_len = strlen(ct_str) + 1; + + len = ct_len; + + cs_len = 0; + + while (wsp_text_header_iter_param_next(&iter) == TRUE) { + const char *key = wsp_text_header_iter_get_key(&iter); + + if (g_ascii_strcasecmp("charset", key) == 0) { + cs_str = wsp_text_header_iter_get_value(&iter); + if (cs_str == NULL) + return FALSE; + + len += 1; + + if (wsp_get_well_known_charset(cs_str, &cs) == FALSE) + return FALSE; + + if (wsp_encode_integer(cs, cs_val, MAX_ENC_VALUE_BYTES, + &cs_len) == FALSE) + return FALSE; + + len += cs_len; + } + } + + if (wsp_encode_value_length(len, ctp_val, MAX_ENC_VALUE_BYTES, + &ctp_len) == FALSE) + return FALSE; + + len += ctp_len; + + /* Compute content-id header length : token + (Quoted String) */ + if (part->content_id != NULL) { + cid_len = 1 + strlen(part->content_id) + 3 + 1; + len += cid_len; + } else + cid_len = 0; + + /* Encode total headers length */ + if (fb_put_uintvar(fb, len) == FALSE) + return FALSE; + + /* Encode data length */ + if (fb_put_uintvar(fb, part->length) == FALSE) + return FALSE; + + /* Encode content-type */ + ptr = fb_request(fb, ctp_len); + if (ptr == NULL) + return FALSE; + + memcpy(ptr, &ctp_val, ctp_len); + + ptr = fb_request(fb, ct_len); + if (ptr == NULL) + return FALSE; + + if (ct_len == 1) + ptr[0] = ct | 0x80; + else + strcpy(ptr, ct_str); + + /* Encode "charset" param */ + if (cs_len > 0) { + ptr = fb_request_field(fb, WSP_PARAMETER_TYPE_CHARSET, cs_len); + if (ptr == NULL) + return FALSE; + + memcpy(ptr, &cs_val, cs_len); + } + + /* Encode content-id */ + if (part->content_id != NULL) { + if (encode_quoted_string(fb, MMS_PART_HEADER_CONTENT_ID, + &part->content_id) == FALSE) + return FALSE; + } + + return TRUE; +} + +static gboolean mms_encode_send_req_part(struct mms_attachment *part, + struct file_buffer *fb) +{ + if (mms_encode_send_req_part_header(part, fb) == FALSE) + return FALSE; + + part->offset = fb_get_file_size(fb); + + return fb_copy(fb, part->data, part->length); +} + +static gboolean mms_encode_headers(struct file_buffer *fb, + enum mms_header orig_header, ...) +{ + va_list args; + void *data; + enum mms_header header; + header_encoder encoder; + + va_start(args, orig_header); + header = orig_header; + + while (header != MMS_HEADER_INVALID) { + data = va_arg(args, void *); + + encoder = encoder_for_type(header); + if (encoder == NULL) + return FALSE; + + if (data && encoder(fb, header, data) == FALSE) + return FALSE; + + header = va_arg(args, enum mms_header); + } + + va_end(args); + + return TRUE; +} + +static gboolean mms_encode_notify_resp_ind(struct mms_message *msg, + struct file_buffer *fb) +{ + if (mms_encode_headers(fb, MMS_HEADER_MESSAGE_TYPE, &msg->type, + MMS_HEADER_TRANSACTION_ID, &msg->transaction_id, + MMS_HEADER_MMS_VERSION, &msg->version, + MMS_HEADER_STATUS, &msg->nri.notify_status, + MMS_HEADER_INVALID) == FALSE) + return FALSE; + + return fb_flush(fb); +} + + +static gboolean mms_encode_acknowledge_ind(struct mms_message *msg, + struct file_buffer *fb) +{ + if (mms_encode_headers(fb, MMS_HEADER_MESSAGE_TYPE, &msg->type, + MMS_HEADER_TRANSACTION_ID, &msg->transaction_id, + MMS_HEADER_MMS_VERSION, &msg->version, + MMS_HEADER_REPORT_ALLOWED, &msg->ai.report, + MMS_HEADER_INVALID) == FALSE) + return FALSE; + + return fb_flush(fb); +} + +static gboolean mms_encode_read_rec_ind(struct mms_message *msg, + struct file_buffer *fb) +{ + const char *empty_from = ""; + if (mms_encode_headers(fb, MMS_HEADER_MESSAGE_TYPE, &msg->type, + MMS_HEADER_MMS_VERSION, &msg->version, + MMS_HEADER_MESSAGE_ID, &msg->ri.msgid, + MMS_HEADER_TO, &msg->ri.to, + MMS_HEADER_FROM, &empty_from, + MMS_HEADER_DATE, &msg->ri.date, + MMS_HEADER_READ_STATUS, &msg->ri.rr_status, + MMS_HEADER_INVALID) == FALSE) + return FALSE; + + return fb_flush(fb); +} + +static gboolean mms_encode_send_req(struct mms_message *msg, + struct file_buffer *fb) +{ + const char *empty_from = ""; + GSList *item; + + if (mms_encode_headers(fb, MMS_HEADER_MESSAGE_TYPE, &msg->type, + MMS_HEADER_TRANSACTION_ID, &msg->transaction_id, + MMS_HEADER_MMS_VERSION, &msg->version, + MMS_HEADER_FROM, &empty_from, + MMS_HEADER_TO, &msg->sr.to, + MMS_HEADER_DELIVERY_REPORT, &msg->sr.dr, + MMS_HEADER_CONTENT_TYPE, &msg->sr.content_type, + MMS_HEADER_INVALID) == FALSE) + return FALSE; + + if (msg->attachments == NULL) + goto done; + + if (fb_put_uintvar(fb, g_slist_length(msg->attachments)) == FALSE) + return FALSE; + + for (item = msg->attachments; item != NULL; item = g_slist_next(item)) { + if (mms_encode_send_req_part(item->data, fb) == FALSE) + return FALSE; + } + +done: + return fb_flush(fb); +} + +gboolean mms_message_encode(struct mms_message *msg, int fd) +{ + struct file_buffer fb; + + fb_init(&fb, fd); + + switch (msg->type) { + case MMS_MESSAGE_TYPE_SEND_REQ: + return mms_encode_send_req(msg, &fb); + case MMS_MESSAGE_TYPE_SEND_CONF: + case MMS_MESSAGE_TYPE_NOTIFICATION_IND: + return FALSE; + case MMS_MESSAGE_TYPE_NOTIFYRESP_IND: + return mms_encode_notify_resp_ind(msg, &fb); + case MMS_MESSAGE_TYPE_RETRIEVE_CONF: + return FALSE; + case MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND: + return mms_encode_acknowledge_ind(msg, &fb); + case MMS_MESSAGE_TYPE_DELIVERY_IND: + return FALSE; + case MMS_MESSAGE_TYPE_READ_REC_IND: + return mms_encode_read_rec_ind(msg, &fb); + case MMS_MESSAGE_TYPE_READ_ORIG_IND: + return FALSE; + } + + return FALSE; +} + +const char *mms_message_status_get_string(enum mms_message_status status) +{ + switch (status) { + case MMS_MESSAGE_STATUS_DOWNLOADED: + return "downloaded"; + case MMS_MESSAGE_STATUS_RECEIVED: + return "received"; + case MMS_MESSAGE_STATUS_READ: + return "read"; + case MMS_MESSAGE_STATUS_SENT: + return "sent"; + case MMS_MESSAGE_STATUS_DRAFT: + return "draft"; + } + + return NULL; +} diff --git a/mms-lib/src/mms_codec.h b/mms-lib/src/mms_codec.h new file mode 100644 index 0000000..377f436 --- /dev/null +++ b/mms-lib/src/mms_codec.h @@ -0,0 +1,200 @@ +/* + * + * Multimedia Messaging Service + * + * Copyright (C) 2010-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 + * + */ + +enum mms_message_type { + MMS_MESSAGE_TYPE_SEND_REQ = 128, + MMS_MESSAGE_TYPE_SEND_CONF = 129, + MMS_MESSAGE_TYPE_NOTIFICATION_IND = 130, + MMS_MESSAGE_TYPE_NOTIFYRESP_IND = 131, + MMS_MESSAGE_TYPE_RETRIEVE_CONF = 132, + MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND = 133, + MMS_MESSAGE_TYPE_DELIVERY_IND = 134, + MMS_MESSAGE_TYPE_READ_REC_IND = 135, + MMS_MESSAGE_TYPE_READ_ORIG_IND = 136, +}; + +enum mms_message_status { + MMS_MESSAGE_STATUS_DOWNLOADED, + MMS_MESSAGE_STATUS_RECEIVED, + MMS_MESSAGE_STATUS_READ, + MMS_MESSAGE_STATUS_SENT, + MMS_MESSAGE_STATUS_DRAFT +}; + +enum mms_message_rsp_status { + MMS_MESSAGE_RSP_STATUS_OK = 128, + MMS_MESSAGE_RSP_STATUS_ERR_UNSUPPORTED_MESSAGE = 136, + MMS_MESSAGE_RSP_STATUS_ERR_TRANS_FAILURE = 192, + MMS_MESSAGE_RSP_STATUS_ERR_TRANS_NETWORK_PROBLEM = 195, + MMS_MESSAGE_RSP_STATUS_ERR_PERM_FAILURE = 224, + MMS_MESSAGE_RSP_STATUS_ERR_PERM_SERVICE_DENIED = 225, + MMS_MESSAGE_RSP_STATUS_ERR_PERM_MESSAGE_FORMAT_CORRUPT = 226, + MMS_MESSAGE_RSP_STATUS_ERR_PERM_SENDING_ADDRESS_UNRESOLVED = 227, + MMS_MESSAGE_RSP_STATUS_ERR_PERM_CONTENT_NOT_ACCEPTED = 229, + MMS_MESSAGE_RSP_STATUS_ERR_PERM_LACK_OF_PREPAID = 235, +}; + +enum mms_message_notify_status { + MMS_MESSAGE_NOTIFY_STATUS_RETRIEVED = 129, + MMS_MESSAGE_NOTIFY_STATUS_REJECTED = 130, + MMS_MESSAGE_NOTIFY_STATUS_DEFERRED = 131, + MMS_MESSAGE_NOTIFY_STATUS_UNRECOGNISED = 132, +}; + +enum mms_message_delivery_status { + MMS_MESSAGE_DELIVERY_STATUS_EXPIRED = 128, + MMS_MESSAGE_DELIVERY_STATUS_RETRIEVED = 129, + MMS_MESSAGE_DELIVERY_STATUS_REJECTED = 130, + MMS_MESSAGE_DELIVERY_STATUS_DEFERRED = 131, + MMS_MESSAGE_DELIVERY_STATUS_UNRECOGNISED = 132, + MMS_MESSAGE_DELIVERY_STATUS_INDETERMINATE = 133, + MMS_MESSAGE_DELIVERY_STATUS_FORWARDED = 134, + MMS_MESSAGE_DELIVERY_STATUS_UNREACHABLE = 135, +}; + +enum mms_message_retrieve_status { + MMS_MESSAGE_RETRIEVE_STATUS_OK = 128, + MMS_MESSAGE_RETRIEVE_STATUS_ERR_TRANS_FAILURE = 192, + MMS_MESSAGE_RETRIEVE_STATUS_ERR_TRANS_MESSAGE_NOT_FOUND = 193, + MMS_MESSAGE_RETRIEVE_STATUS_ERR_TRANS_NETWORK_PROBLEM = 194, + MMS_MESSAGE_RETRIEVE_STATUS_ERR_PERM_FAILURE = 224, + MMS_MESSAGE_RETRIEVE_STATUS_ERR_PERM_SERVICE_DENIED = 225, + MMS_MESSAGE_RETRIEVE_STATUS_ERR_PERM_MESSAGE_NOT_FOUND = 226, + MMS_MESSAGE_RETRIEVE_STATUS_ERR_PERM_CONTENT_UNSUPPORTED = 227, +}; + +enum mms_message_read_status { + MMS_MESSAGE_READ_STATUS_READ = 128, + MMS_MESSAGE_READ_STATUS_DELETED = 129, +}; + +enum mms_message_sender_visibility { + MMS_MESSAGE_SENDER_VISIBILITY_HIDE = 128, + MMS_MESSAGE_SENDER_VISIBILITY_SHOW = 129, +}; + +enum mms_message_priority { + MMS_MESSAGE_PRIORITY_LOW = 128, + MMS_MESSAGE_PRIORITY_NORMAL = 129, + MMS_MESSAGE_PRIORITY_HIGH = 130 +}; + +enum mms_message_version { + MMS_MESSAGE_VERSION_1_0 = 0x90, + MMS_MESSAGE_VERSION_1_1 = 0x91, + MMS_MESSAGE_VERSION_1_2 = 0x92, + MMS_MESSAGE_VERSION_1_3 = 0x93, +}; + +#define MMS_MESSAGE_CLASS_PERSONAL "Personal" +#define MMS_MESSAGE_CLASS_ADVERTISEMENT "Advertisement" +#define MMS_MESSAGE_CLASS_INFORMATIONAL "Informational" +#define MMS_MESSAGE_CLASS_AUTO "Auto" + +struct mms_notification_ind { + char *from; + char *subject; + char *cls; + unsigned int size; + time_t expiry; + char *location; +}; + +struct mms_retrieve_conf { + enum mms_message_status status; + char *from; + char *to; + char *cc; + char *subject; + char *cls; + enum mms_message_priority priority; + char *msgid; + time_t date; +}; + +struct mms_send_req { + enum mms_message_status status; + char *to; + time_t date; + char *content_type; + gboolean dr; +}; + +struct mms_send_conf { + enum mms_message_rsp_status rsp_status; + char *msgid; +}; + +struct mms_notification_resp_ind { + enum mms_message_notify_status notify_status; +}; + +struct mms_acknowledge_ind { + gboolean report; +}; + +struct mms_delivery_ind { + enum mms_message_delivery_status dr_status; + char *msgid; + char *to; + time_t date; +}; + +struct mms_read_ind { + enum mms_message_read_status rr_status; + char *msgid; + char *to; + char *from; + time_t date; +}; + +struct mms_attachment { + unsigned char *data; + size_t offset; + size_t length; + char *content_type; + char *content_id; +}; + +struct mms_message { + enum mms_message_type type; + char *transaction_id; + unsigned char version; + GSList *attachments; + union { + struct mms_notification_ind ni; + struct mms_retrieve_conf rc; + struct mms_send_req sr; + struct mms_send_conf sc; + struct mms_notification_resp_ind nri; + struct mms_delivery_ind di; + struct mms_read_ind ri; + struct mms_acknowledge_ind ai; + }; +}; + +char *mms_content_type_get_param_value(const char *content_type, + const char *param_name); +gboolean mms_message_decode(const unsigned char *pdu, + unsigned int len, struct mms_message *out); +gboolean mms_message_encode(struct mms_message *msg, int fd); +void mms_message_free(struct mms_message *msg); +const char *mms_message_status_get_string(enum mms_message_status status); diff --git a/mms-lib/src/mms_connection.c b/mms-lib/src/mms_connection.c new file mode 100644 index 0000000..53d254e --- /dev/null +++ b/mms-lib/src/mms_connection.c @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_connection.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_connection_log +#include "mms_lib_log.h" + +G_DEFINE_TYPE(MMSConnection, mms_connection, G_TYPE_OBJECT); +#define MMS_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MMS_TYPE_CONNECTION, MMSConnectionClass)) +#define MMS_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MMS_TYPE_CONNECTION, MMSConnection)) + +GQuark +mms_connection_error_quark() +{ + return g_quark_from_static_string("mms-connection-error-quark"); +} + +MMSConnection* +mms_connection_ref( + MMSConnection* conn) +{ + if (conn) g_object_ref(MMS_CONNECTION(conn)); + return conn; +} + +void +mms_connection_unref( + MMSConnection* conn) +{ + if (conn) g_object_unref(MMS_CONNECTION(conn)); +} + +const char* +mms_connection_state_name( + MMSConnection* conn) +{ + static const char* names[] = {"????","OPENING","FAILED","OPEN","CLOSED"}; + return names[mms_connection_state(conn)]; +} + +MMS_CONNECTION_STATE +mms_connection_state( + MMSConnection* conn) +{ + return conn ? conn->state : MMS_CONNECTION_STATE_INVALID; +} + +void +mms_connection_close( + MMSConnection* conn) +{ + if (conn) MMS_CONNECTION_GET_CLASS(conn)->fn_close(conn); +} + +/** + * Final stage of deinitialization + */ +static +void +mms_connection_finalize( + GObject* object) +{ + MMSConnection* conn = MMS_CONNECTION(object); + MMS_VERBOSE_("%p", conn); + MMS_ASSERT(!conn->delegate); + g_free(conn->imsi); + g_free(conn->mmsc); + g_free(conn->mmsproxy); + g_free(conn->netif); + G_OBJECT_CLASS(mms_connection_parent_class)->finalize(object); +} + +/** + * Per class initializer + */ +static +void +mms_connection_class_init( + MMSConnectionClass* klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS(klass); + object_class->finalize = mms_connection_finalize; +} + +/** + * Per instance initializer + */ +static +void +mms_connection_init( + MMSConnection* conn) +{ + MMS_VERBOSE_("%p", conn); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_connman.c b/mms-lib/src/mms_connman.c new file mode 100644 index 0000000..c2fc2f5 --- /dev/null +++ b/mms-lib/src/mms_connman.c @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_connman.h" + +G_DEFINE_TYPE(MMSConnMan, mms_connman, G_TYPE_OBJECT); +#define MMS_CONNMAN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_CONNMAN, MMSConnMan)) +#define MMS_CONNMAN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), \ + MMS_TYPE_CONNMAN, MMSConnManClass)) + +MMSConnMan* +mms_connman_ref( + MMSConnMan* cm) +{ + if (cm) g_object_ref(MMS_CONNMAN(cm)); + return cm; +} + +void +mms_connman_unref( + MMSConnMan* cm) +{ + if (cm) g_object_unref(MMS_CONNMAN(cm)); +} + +/** + * Per class initializer + */ +static +void +mms_connman_class_init( + MMSConnManClass* klass) +{ +} + +/** + * Per instance initializer + */ +static +void +mms_connman_init( + MMSConnMan* cm) +{ +} + +/** + * Creates a new connection or returns the reference to an aready active one. + * The caller must release the reference. + */ +MMSConnection* +mms_connman_open_connection( + MMSConnMan* cm, + const char* imsi, + gboolean user_request) +{ + if (cm) { + MMSConnManClass* klass = MMS_CONNMAN_GET_CLASS(cm); + if (klass->fn_open_connection) { + return klass->fn_open_connection(cm, imsi, user_request); + } + } + return NULL; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_dispatcher.c b/mms-lib/src/mms_dispatcher.c new file mode 100644 index 0000000..68b5ec4 --- /dev/null +++ b/mms-lib/src/mms_dispatcher.c @@ -0,0 +1,656 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_dispatcher.h" +#include "mms_handler.h" +#include "mms_connection.h" +#include "mms_connman.h" +#include "mms_file_util.h" +#include "mms_codec.h" +#include "mms_util.h" +#include "mms_task.h" + +#include + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_dispatcher_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-dispatcher"); + +struct mms_dispatcher { + gint ref_count; + const MMSConfig* config; + MMSTask* active_task; + MMSTaskDelegate task_delegate; + MMSHandler* handler; + MMSConnMan* cm; + MMSConnection* connection; + MMSConnectionDelegate connection_delegate; + MMSDispatcherDelegate* delegate; + GQueue* tasks; + guint next_run_id; + guint network_idle_id; +}; + +typedef void (*MMSDispatcherIdleCallbackProc)(MMSDispatcher* disp); +typedef struct mms_dispatcher_idle_callback { + MMSDispatcher* dispatcher; + MMSDispatcherIdleCallbackProc proc; +} MMSDispatcherIdleCallback; + +inline static MMSDispatcher* +mms_dispatcher_from_task_delegate(MMSTaskDelegate* delegate) + { return MMS_CAST(delegate,MMSDispatcher,task_delegate); } +inline static MMSDispatcher* +mms_dispatcher_from_connection_delegate(MMSConnectionDelegate* delegate) + { return MMS_CAST(delegate,MMSDispatcher,connection_delegate); } + +static +void +mms_dispatcher_run( + MMSDispatcher* disp); + +/** + * Close the network connection + */ +static +void +mms_dispatcher_close_connection( + MMSDispatcher* disp) +{ + if (disp->connection) { + disp->connection->delegate = NULL; + mms_connection_close(disp->connection); + mms_connection_unref(disp->connection); + disp->connection = NULL; + + if (!mms_dispatcher_is_active(disp)) { + /* Report to delegate that we are done */ + if (disp->delegate && disp->delegate->fn_done) { + disp->delegate->fn_done(disp->delegate, disp); + } + } + } + if (disp->network_idle_id) { + g_source_remove(disp->network_idle_id); + disp->network_idle_id = 0; + } +} + +/** + * Run loop callbacks + */ +static +void +mms_dispatcher_callback_free( + gpointer data) +{ + MMSDispatcherIdleCallback* call = data; + mms_dispatcher_unref(call->dispatcher); + g_free(call); +} + +static +gboolean +mms_dispatcher_idle_callback_cb( + gpointer data) +{ + MMSDispatcherIdleCallback* call = data; + call->proc(call->dispatcher); + return FALSE; +} + +static +guint +mms_dispatcher_callback_schedule( + MMSDispatcher* disp, + MMSDispatcherIdleCallbackProc proc) +{ + MMSDispatcherIdleCallback* call = g_new0(MMSDispatcherIdleCallback,1); + call->dispatcher = mms_dispatcher_ref(disp); + call->proc = proc; + return g_idle_add_full(G_PRIORITY_HIGH, mms_dispatcher_idle_callback_cb, + call, mms_dispatcher_callback_free); +} + +static +guint +mms_dispatcher_timeout_callback_schedule( + MMSDispatcher* disp, + guint interval, + MMSDispatcherIdleCallbackProc proc) +{ + MMSDispatcherIdleCallback* call = g_new0(MMSDispatcherIdleCallback,1); + call->dispatcher = mms_dispatcher_ref(disp); + call->proc = proc; + return g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, interval, + mms_dispatcher_idle_callback_cb, call, mms_dispatcher_callback_free); +} + +/** + * Network idle timeout + */ + +static +void +mms_dispatcher_network_idle_run( + MMSDispatcher* disp) +{ + MMS_ASSERT(disp->network_idle_id); + disp->network_idle_id = 0; + mms_dispatcher_close_connection(disp); +} + +static +void +mms_dispatcher_network_idle_check( + MMSDispatcher* disp) +{ + if (disp->connection && !disp->network_idle_id) { + /* Schedule idle inactivity timeout callback */ + MMS_VERBOSE("Network connection is inactive"); + disp->network_idle_id = mms_dispatcher_timeout_callback_schedule(disp, + disp->config->idle_secs, mms_dispatcher_network_idle_run); + } +} + +static +void +mms_dispatcher_network_idle_cancel( + MMSDispatcher* disp) +{ + if (disp->network_idle_id) { + MMS_VERBOSE("Cancel network inactivity timeout"); + g_source_remove(disp->network_idle_id); + disp->network_idle_id = 0; + } +} + +/** + * Dispatcher run on a fresh stack + */ +static +void +mms_dispatcher_next_run( + MMSDispatcher* disp) +{ + MMS_ASSERT(disp->next_run_id); + MMS_ASSERT(!disp->active_task); + disp->next_run_id = 0; + if (!disp->active_task) { + mms_dispatcher_run(disp); + } +} + +static +void +mms_dispatcher_next_run_schedule( + MMSDispatcher* disp) +{ + if (disp->next_run_id) g_source_remove(disp->next_run_id); + disp->next_run_id = mms_dispatcher_callback_schedule(disp, + mms_dispatcher_next_run); +} + +/** + * Set the delegate that receives dispatcher notifications. + * One delegate per dispatcher. + */ +void +mms_dispatcher_set_delegate( + MMSDispatcher* disp, + MMSDispatcherDelegate* delegate) +{ + MMS_ASSERT(!disp->delegate || !delegate); + disp->delegate = delegate; +} + +/** + * Checks if dispatcher has something to do. + */ +gboolean +mms_dispatcher_is_active( + MMSDispatcher* disp) +{ + return disp && (disp->connection || disp->active_task || + !g_queue_is_empty(disp->tasks)); +} + +/** + * Picks the next task for processing. Reference is passed to the caller. + * Caller must eventually dereference the task or place it back to the queue. + */ +static +MMSTask* +mms_dispatcher_pick_next_task( + MMSDispatcher* disp) +{ + GList* entry; + gboolean connection_in_use = FALSE; + + /* Check the current connection */ + if (disp->connection) { + + /* Don't interfere with the task transmiting the data */ + for (entry = disp->tasks->head; entry; entry = entry->next) { + MMSTask* task = entry->data; + if (task->state == MMS_TASK_STATE_TRANSMITTING) { + MMS_ASSERT(!strcmp(task->imsi, disp->connection->imsi)); + return NULL; + } + } + + /* Look for another task that has use for the existing connection + * before we close it */ + for (entry = disp->tasks->head; entry; entry = entry->next) { + MMSTask* task = entry->data; + if (task->state == MMS_TASK_STATE_NEED_CONNECTION || + task->state == MMS_TASK_STATE_NEED_USER_CONNECTION) { + if (!strcmp(task->imsi, disp->connection->imsi)) { + if (mms_connection_state(disp->connection) == + MMS_CONNECTION_STATE_OPEN) { + /* Found a task that can use this connection */ + g_queue_delete_link(disp->tasks, entry); + mms_dispatcher_network_idle_cancel(disp); + return task; + } + connection_in_use = TRUE; + } + } + } + } + + if (connection_in_use) { + /* Connection is needed but isn't open yet, make sure that network + * inactivity timer is off while connection is being established */ + mms_dispatcher_network_idle_cancel(disp); + } else { + /* Then look for a task that needs any sort of network connection */ + for (entry = disp->tasks->head; entry; entry = entry->next) { + MMSTask* task = entry->data; + if ((task->state == MMS_TASK_STATE_NEED_CONNECTION || + task->state == MMS_TASK_STATE_NEED_USER_CONNECTION)) { + mms_dispatcher_close_connection(disp); + disp->connection = mms_connman_open_connection( + disp->cm, task->imsi, FALSE); + if (disp->connection) { + disp->connection->delegate = &disp->connection_delegate; + g_queue_delete_link(disp->tasks, entry); + return task; + } else { + mms_task_network_unavailable(task); + } + } + } + } + + /* Finally look for a runnable task that doesn't need network */ + for (entry = disp->tasks->head; entry; entry = entry->next) { + MMSTask* task = entry->data; + if (task->state == MMS_TASK_STATE_READY || + task->state == MMS_TASK_STATE_DONE) { + g_queue_delete_link(disp->tasks, entry); + return task; + } + } + + /* Nothing found, we are done for now */ + return NULL; +} + +/** + * Task dispatch loop. + */ +static +void +mms_dispatcher_run( + MMSDispatcher* disp) +{ + MMSTask* task; + MMS_ASSERT(!disp->active_task); + while ((task = mms_dispatcher_pick_next_task(disp)) != NULL) { + MMS_DEBUG("%s %s", task->name, mms_task_state_name(task->state)); + disp->active_task = task; + switch (task->state) { + case MMS_TASK_STATE_READY: + mms_task_run(task); + break; + + case MMS_TASK_STATE_NEED_CONNECTION: + case MMS_TASK_STATE_NEED_USER_CONNECTION: + MMS_ASSERT(disp->connection); + if (mms_connection_is_open(disp->connection)) { + /* Connection is already active, send/receive the data */ + mms_task_transmit(task, disp->connection); + } + break; + + default: + break; + } + + if (task->state == MMS_TASK_STATE_DONE) { + task->delegate = NULL; + mms_task_unref(task); + } else { + g_queue_push_tail(disp->tasks, task); + } + disp->active_task = NULL; + } + + if (disp->connection) { + /* Check if network connection is being used */ + GList* entry; + gboolean connection_in_use = FALSE; + for (entry = disp->tasks->head; entry; entry = entry->next) { + MMSTask* task = entry->data; + if (task->state == MMS_TASK_STATE_NEED_CONNECTION || + task->state == MMS_TASK_STATE_NEED_USER_CONNECTION || + task->state == MMS_TASK_STATE_TRANSMITTING) { + connection_in_use = TRUE; + break; + } + } + if (connection_in_use) { + /* It's in use, disable idle inactivity callback */ + mms_dispatcher_network_idle_cancel(disp); + } else { + /* Make sure that network inactivity timer is ticking */ + mms_dispatcher_network_idle_check(disp); + } + } + + if (!mms_dispatcher_is_active(disp)) { + /* Report to delegate that we are done */ + if (disp->delegate && disp->delegate->fn_done) { + disp->delegate->fn_done(disp->delegate, disp); + } + } +} + +/** + * Starts task processing. + */ +gboolean +mms_dispatcher_start( + MMSDispatcher* disp) +{ + int err = g_mkdir_with_parents(disp->config->root_dir, MMS_DIR_PERM); + if (!err || errno == EEXIST) { + if (!g_queue_is_empty(disp->tasks)) { + mms_dispatcher_next_run_schedule(disp); + return TRUE; + } + } else { + MMS_ERR("Failed to create %s: %s", disp->config->root_dir, + strerror(errno)); + } + return FALSE; +} + +static +void +mms_dispatcher_queue_task( + MMSDispatcher* disp, + MMSTask* task) +{ + task->delegate = &disp->task_delegate; + g_queue_push_tail(disp->tasks, mms_task_ref(task)); +} + +/** + * Creates a WAP push receive task and adds it to the queue. + */ +gboolean +mms_dispatcher_handle_push( + MMSDispatcher* disp, + const char* imsi, + GBytes* push) +{ + MMSTask* task = mms_task_notification_new(disp->config, disp->handler, + imsi, push); + if (task) { + mms_dispatcher_queue_task(disp, task); + mms_task_unref(task); + return TRUE; + } else { + return FALSE; + } +} + +/** + * Creates download task and adds it to the queue. + */ +gboolean +mms_dispatcher_receive_message( + MMSDispatcher* disp, + const char* id, + const char* imsi, + gboolean automatic, + GBytes* bytes) +{ + gboolean ok = FALSE; + MMSPdu* pdu = mms_decode_bytes(bytes); + if (pdu) { + MMS_ASSERT(pdu->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND); + if (pdu->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND) { + MMSTask* task = mms_task_retrieve_new(disp->config, + disp->handler, id, imsi, pdu); + if (task) { + mms_dispatcher_queue_task(disp, task); + mms_task_unref(task); + ok = TRUE; + } + } + mms_message_free(pdu); + } else { + MMS_ERR("Unable to parse MMS push data"); + } + return ok; +} + +/** + * Sends read report + */ +gboolean +mms_dispatcher_send_read_report( + MMSDispatcher* disp, + const char* id, + const char* imsi, + const char* message_id, + const char* to, + MMSReadStatus status) +{ + gboolean ok = FALSE; + MMSTask* task = mms_task_read_new(disp->config, disp->handler, id, imsi, + message_id, to, status); + if (task) { + mms_dispatcher_queue_task(disp, task); + mms_task_unref(task); + ok = TRUE; + } + return ok; +} + +/** + * Cancels al the activity associated with the specified message + */ +void +mms_dispatcher_cancel( + MMSDispatcher* disp, + const char* id) +{ + GList* entry; + for (entry = disp->tasks->head; entry; entry = entry->next) { + MMSTask* task = entry->data; + if (!id || !strcmp(task->id, id)) { + mms_task_cancel(task); + } + } + if (disp->active_task && (!id || !strcmp(disp->active_task->id, id))) { + mms_task_cancel(disp->active_task); + } +} + +/** + * Connection delegate callbacks + */ +static +void +mms_dispatcher_delegate_connection_state_changed( + MMSConnectionDelegate* delegate, + MMSConnection* conn) +{ + MMSDispatcher* disp = mms_dispatcher_from_connection_delegate(delegate); + MMS_CONNECTION_STATE state = mms_connection_state(conn); + MMS_VERBOSE_("%s %s", conn->imsi, mms_connection_state_name(conn)); + MMS_ASSERT(conn == disp->connection); + if (state == MMS_CONNECTION_STATE_FAILED || + state == MMS_CONNECTION_STATE_CLOSED) { + GList* entry; + mms_dispatcher_close_connection(disp); + for (entry = disp->tasks->head; entry; entry = entry->next) { + MMSTask* task = entry->data; + switch (task->state) { + case MMS_TASK_STATE_NEED_CONNECTION: + case MMS_TASK_STATE_NEED_USER_CONNECTION: + case MMS_TASK_STATE_TRANSMITTING: + if (!strcmp(conn->imsi, task->imsi)) { + mms_task_network_unavailable(task); + } + default: + break; + } + } + } + if (!disp->active_task) { + mms_dispatcher_next_run_schedule(disp); + } +} + +/** + * Task delegate callbacks + */ +static +void +mms_dispatcher_delegate_task_queue( + MMSTaskDelegate* delegate, + MMSTask* task) +{ + MMSDispatcher* disp = mms_dispatcher_from_task_delegate(delegate); + mms_dispatcher_queue_task(disp, task); + if (!disp->active_task) { + mms_dispatcher_next_run_schedule(disp); + } +} + +static +void +mms_dispatcher_delegate_task_state_changed( + MMSTaskDelegate* delegate, + MMSTask* task) +{ + MMSDispatcher* disp = mms_dispatcher_from_task_delegate(delegate); + if (!disp->active_task) { + mms_dispatcher_next_run_schedule(disp); + } +} + +/** + * Creates the dispatcher object. Caller must clal mms_dispatcher_unref + * when it no longer needs it. + */ +MMSDispatcher* +mms_dispatcher_new( + const MMSConfig* config, + MMSConnMan* cm, + MMSHandler* handler) +{ + MMSDispatcher* disp = g_new0(MMSDispatcher, 1); + disp->ref_count = 1; + disp->config = config; + disp->tasks = g_queue_new(); + disp->handler = mms_handler_ref(handler); + disp->cm = mms_connman_ref(cm); + disp->task_delegate.fn_task_queue = + mms_dispatcher_delegate_task_queue; + disp->task_delegate.fn_task_state_changed = + mms_dispatcher_delegate_task_state_changed; + disp->connection_delegate.fn_connection_state_changed = + mms_dispatcher_delegate_connection_state_changed; + return disp; +} + +/** + * Deinitializer + */ +static +void +mms_dispatcher_finalize( + MMSDispatcher* disp) +{ + MMSTask* task; + char* msg_dir = g_strconcat(disp->config->root_dir, + "/" MMS_MESSAGE_DIR "/", NULL); + + MMS_VERBOSE_(""); + mms_dispatcher_close_connection(disp); + while ((task = g_queue_pop_head(disp->tasks)) != NULL) { + task->delegate = NULL; + mms_task_cancel(task); + mms_task_unref(task); + } + g_queue_free(disp->tasks); + mms_handler_unref(disp->handler); + mms_connman_unref(disp->cm); + + /* Try to remove the message directory */ + remove(msg_dir); + g_free(msg_dir); +} + +/** + * Reference counting. NULL argument is safely ignored. + */ +MMSDispatcher* +mms_dispatcher_ref( + MMSDispatcher* disp) +{ + if (disp) { + MMS_ASSERT(disp->ref_count > 0); + g_atomic_int_inc(&disp->ref_count); + } + return disp; +} + +void +mms_dispatcher_unref( + MMSDispatcher* disp) +{ + if (disp) { + MMS_ASSERT(disp->ref_count > 0); + if (g_atomic_int_dec_and_test(&disp->ref_count)) { + mms_dispatcher_finalize(disp); + g_free(disp); + } + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_file_util.c b/mms-lib/src/mms_file_util.c new file mode 100644 index 0000000..3af33bc --- /dev/null +++ b/mms-lib/src/mms_file_util.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_file_util.h" +#include "mms_log.h" +#include + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +/** + * Removes both the file and the directory containing it, if it's empty. + */ +void +mms_remove_file_and_dir( + const char* file) +{ + char* dir = g_path_get_dirname(file); + remove(file); + remove(dir); + g_free(dir); +} + +/** + * Creates a file in the specified directory. Creates the directory if + * it doesn't exist. If file already exists, truncates it. Returns file + * discriptor positioned at the beginning of the new file or -1 if an I/O + * error occurs. + */ +int +mms_create_file( + const char* dir, + const char* file, + char** path) +{ + int fd = -1; + int err = g_mkdir_with_parents(dir, MMS_DIR_PERM); + if (!err || errno == EEXIST) { + char* fname = g_strconcat(dir, "/", file, NULL); + fd = open(fname, O_CREAT|O_RDWR|O_TRUNC|O_BINARY, MMS_FILE_PERM); + if (fd < 0) { + MMS_ERR("Failed to create %s: %s", fname, strerror(errno)); + } else if (path) { + *path = fname; + fname = NULL; + } + g_free(fname); + } else { + MMS_ERR("Failed to create directory %s: %s", dir, strerror(errno)); + } + return fd; +} + +/** + * Writes data to a file, creating the directory hierarhy if necessary. + */ +gboolean +mms_write_file( + const char* dir, + const char* file, + const void* data, + gsize size, + char** path) +{ + gboolean saved = FALSE; + int err = g_mkdir_with_parents(dir, MMS_DIR_PERM); + if (!err || errno == EEXIST) { + GError* error = NULL; + char* fname = g_strconcat(dir, "/", file, NULL); + unlink(fname); + if (g_file_set_contents(fname, data, size, &error)) { + MMS_VERBOSE("Created %s", fname); + chmod(fname, MMS_FILE_PERM); + saved = TRUE; + if (path) { + *path = fname; + fname = NULL; + } + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + g_free(fname); + } else { + MMS_ERR("Failed to create directory %s: %s", dir, strerror(errno)); + } + return saved; +} + +/** + * Same as mms_write_file, only works with GBytes + */ +gboolean +mms_write_bytes( + const char* dir, + const char* file, + GBytes* bytes, + char** path) +{ + gsize len = 0; + const guint8* data = g_bytes_get_data(bytes, &len); + return mms_write_file(dir, file, data, len, path); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ + diff --git a/mms-lib/src/mms_file_util.h b/mms-lib/src/mms_file_util.h new file mode 100644 index 0000000..15c0b5a --- /dev/null +++ b/mms-lib/src/mms_file_util.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_FILE_UTIL_H +#define JOLLA_MMS_FILE_UTIL_H + +#include "mms_lib_types.h" + +/* Permissions for MMS files and directories */ +#define MMS_DIR_PERM (0755) +#define MMS_FILE_PERM (0644) + +/* Directories and files */ +#define MMS_ATTIC_DIR "attic" +#define MMS_MESSAGE_DIR "msg" +#define MMS_PARTS_DIR "parts" + +#define MMS_NOTIFICATION_IND_FILE "m-notification.ind" +#define MMS_NOTIFYRESP_IND_FILE "m-notifyresp.ind" +#define MMS_RETRIEVE_CONF_FILE "m-retrieve.conf" +#define MMS_ACKNOWLEDGE_IND_FILE "m-acknowledge.ind" +#define MMS_DELIVERY_IND_FILE "m-delivery.ind" +#define MMS_READ_REC_IND_FILE "m-read-rec.ind" +#define MMS_READ_ORIG_IND_FILE "m-read-orig.ind" +#define MMS_UNRECOGNIZED_PUSH_FILE "push.pdu" + +void +mms_remove_file_and_dir( + const char* file); + +int +mms_create_file( + const char* dir, + const char* fname, + char** path); + +gboolean +mms_write_file( + const char* dir, + const char* file, + const void* data, + gsize size, + char** path); + +gboolean +mms_write_bytes( + const char* dir, + const char* file, + GBytes* bytes, + char** path); + +#define mms_message_dir(config,id) \ + (g_strconcat((config)->root_dir, "/" MMS_MESSAGE_DIR "/" , id, NULL)) +#define mms_task_dir(task) \ + mms_message_dir((task)->config,(task)->id) +#define mms_task_file(task,file) \ + (g_strconcat((task)->config->root_dir, "/" MMS_MESSAGE_DIR "/" , \ + (task)->id, "/", file, NULL)) + +#endif /* JOLLA_MMS_FILE_UTIL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_handler.c b/mms-lib/src/mms_handler.c new file mode 100644 index 0000000..526acdb --- /dev/null +++ b/mms-lib/src/mms_handler.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_handler.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_handler_log +#include "mms_lib_log.h" + +G_DEFINE_TYPE(MMSHandler, mms_handler, G_TYPE_OBJECT); + +#define MMS_HANDLER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MMS_TYPE_HANDLER, MMSHandler)) +#define MMS_HANDLER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), MMS_TYPE_HANDLER, MMSHandlerClass)) +#define MMS_HANDLER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MMS_TYPE_HANDLER, MMSHandlerClass)) + +static +void +mms_handler_finalize( + GObject* object) +{ + MMS_VERBOSE_("%p", object); + G_OBJECT_CLASS(mms_handler_parent_class)->finalize(object); +} + +static +void +mms_handler_class_init( + MMSHandlerClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = mms_handler_finalize; +} + +static +void +mms_handler_init( + MMSHandler* h) +{ + MMS_VERBOSE_("%p", h); +} + +MMSHandler* +mms_handler_ref( + MMSHandler* h) +{ + if (h) { + MMS_ASSERT(MMS_HANDLER(h)); + g_object_ref(h); + } + return h; +} + +void +mms_handler_unref( + MMSHandler* h) +{ + if (h) { + MMS_ASSERT(MMS_HANDLER(h)); + g_object_unref(h); + } +} + +char* +mms_handler_message_notify( + MMSHandler* h, + const char* imsi, + const char* from, + const char* subj, + time_t exp, + GBytes* push) +{ + if (h) { + MMSHandlerClass* klass = MMS_HANDLER_GET_CLASS(h); + if (klass->fn_message_notify) { + if (!from) from = ""; + if (!subj) subj = ""; + return klass->fn_message_notify(h, imsi, from, subj, exp, push); + } + MMS_ERR("mms_handler_message_notify not implemented"); + } + return NULL; +} + +gboolean +mms_handler_message_receive_state_changed( + MMSHandler* h, + const char* id, + MMS_RECEIVE_STATE state) +{ + if (h) { + MMSHandlerClass* klass = MMS_HANDLER_GET_CLASS(h); + if (klass->fn_message_receive_state_changed) { + return klass->fn_message_receive_state_changed(h, id, state); + } + MMS_ERR("mms_handler_message_receive_state_changed not implemented"); + } + return FALSE; +} + +gboolean +mms_handler_message_received( + MMSHandler* h, + MMSMessage* msg) +{ + if (h) { + MMSHandlerClass* klass = MMS_HANDLER_GET_CLASS(h); + if (klass->fn_message_received) { + return klass->fn_message_received(h, msg); + } + MMS_ERR("mms_handler_message_received not implemented"); + } + return FALSE; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_lib_util.c b/mms-lib/src/mms_lib_util.c new file mode 100644 index 0000000..2854eb0 --- /dev/null +++ b/mms-lib/src/mms_lib_util.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_lib_util.h" +#include "mms_file_util.h" +#include "mms_message.h" +#include "mms_task.h" +#include "mms_log.h" + +#define MMS_DEFAULT_ROOT_DIR "/var/mms" +#define MMS_DEFAULT_USER_AGENT "Jolla MMS" +#define MMS_DEFAULT_RETRY_SECS (15) +#define MMS_DEFAULT_IDLE_SECS (20) + +/** + * One-time initialization + */ +void +mms_lib_init(void) +{ +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + /* g_type_init has been deprecated since version 2.36 + * the type system is initialised automagically since then */ + g_type_init(); +#ifdef __GNUC__ +# pragma GCC diagnostic pop +#endif +} + +/** + * Reset configuration to default + */ +void +mms_lib_default_config( + MMSConfig* config) +{ + config->root_dir = MMS_DEFAULT_ROOT_DIR; + config->user_agent = MMS_DEFAULT_USER_AGENT; + config->retry_secs = MMS_DEFAULT_RETRY_SECS; + config->idle_secs = MMS_DEFAULT_IDLE_SECS; + config->keep_temp_files = FALSE; + config->attic_enabled = FALSE; + config->send_dr = TRUE; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_log.c b/mms-lib/src/mms_log.c new file mode 100644 index 0000000..425b7f4 --- /dev/null +++ b/mms-lib/src/mms_log.c @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_log.h" +#include +#include + +#ifdef _WIN32 +# ifdef DEBUG +# include +# endif +# define vsnprintf _vsnprintf +# define snprintf _snprintf +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +#endif + +/* Allows timestamps in stdout log */ +gboolean mms_log_stdout_timestamp = TRUE; + +/* Log configuration */ +MMSLogFunc mms_log_func = mms_log_stdout; +MMSLogModule mms_log_default = { + NULL, /* name */ + MMS_LOGLEVEL_MAX, /* max_level */ + MMS_LOGLEVEL_DEFAULT /* level */ +}; + +/* Log level descriptions */ +static const struct _mms_log_level { + const char* name; + const char* description; +} mms_log_levels [] = { + { "none", "Disable log output" }, + { "error", "Errors only"}, + { "warning", "From warning level to errors" }, + { "info", "From information level to errors" }, + { "debug", "From debug messages to errors" }, + { "verbose", "From verbose trace messages to errors" } +}; + +static const char MMS_LOG_TYPE_STDOUT[] = "stdout"; +static const char MMS_LOG_TYPE_GLIB[] = "glib"; +static const char MMS_LOG_TYPE_CUSTOM[] = "custom"; +#ifdef MMS_LOG_SYSLOG +static const char MMS_LOG_TYPE_SYSLOG[] = "syslog"; +#endif + +G_STATIC_ASSERT(G_N_ELEMENTS(mms_log_levels) > MMS_LOGLEVEL_MAX); +G_STATIC_ASSERT(G_N_ELEMENTS(mms_log_levels) > MMS_LOGLEVEL_DEFAULT); + +/* Forwards output to stdout */ +void +mms_log_stdout( + const char* name, + int level, + const char* format, + va_list va) +{ + char t[32]; + char buf[512]; + const char* prefix = ""; + if (mms_log_stdout_timestamp) { + time_t now; + time(&now); + strftime(t, sizeof(t), "%Y-%m-%d %H:%M:%S ", localtime(&now)); + } else { + t[0] = 0; + } + switch (level) { + case MMS_LOGLEVEL_WARN: prefix = "WARNING: "; break; + case MMS_LOGLEVEL_ERR: prefix = "ERROR: "; break; + default: break; + } + vsnprintf(buf, sizeof(buf), format, va); + buf[sizeof(buf)-1] = 0; +#if defined(DEBUG) && defined(_WIN32) + { + char s[1023]; + if (name) { + snprintf(s, sizeof(s), "%s[%s] %s%s\n", t, name, prefix, buf); + } else { + snprintf(s, sizeof(s), "%s%s%s\n", t, prefix, buf); + } + OutputDebugString(s); + } +#endif + if (name) { + printf("%s[%s] %s%s\n", t, name, prefix, buf); + } else { + printf("%s%s%s\n", t, prefix, buf); + } +} + +/* Formards output to syslog */ +#ifdef MMS_LOG_SYSLOG +#include +void +mms_log_syslog( + const char* name, + int level, + const char* format, + va_list va) +{ + int priority; + const char* prefix = NULL; + switch (level) { + default: + case MMS_LOGLEVEL_VERBOSE: + priority = LOG_DEBUG; + break; + case MMS_LOGLEVEL_DEBUG: + priority = LOG_INFO; + break; + case MMS_LOGLEVEL_INFO: + priority = LOG_NOTICE; + break; + case MMS_LOGLEVEL_WARN: + priority = LOG_WARNING; + prefix = "WARNING! "; + break; + case MMS_LOGLEVEL_ERR: + priority = LOG_ERR; + prefix = "ERROR! "; + break; + } + if (name || prefix) { + char buf[512]; + vsnprintf(buf, sizeof(buf), format, va); + if (!prefix) prefix = ""; + if (name) { + syslog(priority, "[%s] %s%s", name, prefix, buf); + } else { + syslog(priority, "%s%s", prefix, buf); + } + } else { + vsyslog(priority, format, va); + } +} +#endif /* MMS_LOG_SYSLOG */ + +/* Forwards output to g_logv */ +void +mms_log_glib( + const char* name, + int level, + const char* format, + va_list va) +{ + GLogLevelFlags flags; + switch (level) { + default: + case MMS_LOGLEVEL_VERBOSE: flags = G_LOG_LEVEL_DEBUG; break; + case MMS_LOGLEVEL_DEBUG: flags = G_LOG_LEVEL_INFO; break; + case MMS_LOGLEVEL_INFO: flags = G_LOG_LEVEL_MESSAGE; break; + case MMS_LOGLEVEL_WARN: flags = G_LOG_LEVEL_WARNING; break; + case MMS_LOGLEVEL_ERR: flags = G_LOG_LEVEL_CRITICAL; break; + } + g_logv(name, flags, format, va); +} + +/* Logging function */ +void +mms_logv( + const MMSLogModule* module, + int level, + const char* format, + va_list va) +{ + if (level != MMS_LOGLEVEL_NONE) { + MMSLogFunc log = mms_log_func; + if (log) { + int max_level; + if (module) { + max_level = (module->level < 0) ? + mms_log_default.level : + module->level; + } else { + module = &mms_log_default; + max_level = mms_log_default.level; + } + if (level <= max_level) { + log(module->name, level, format, va); + } + } + } +} + +void +mms_log( + const MMSLogModule* module, + int level, + const char* format, + ...) +{ + va_list va; + va_start(va, format); + mms_logv(module, level, format, va); + va_end(va); +} + +void +mms_log_assert( + const MMSLogModule* module, + const char* expr, + const char* file, + int line) +{ + mms_log(module, MMS_LOGLEVEL_ASSERT, "Assert %s failed at %s:%d\r", + expr, file, line); +} + +/* mms_log_parse_option helper */ +static +int +mms_log_parse_level( + const char* str, + GError** error) +{ + if (str && str[0]) { + guint i; + const size_t len = strlen(str); + if (len == 1) { + const char* valid_numbers = "012345"; + const char* number = strchr(valid_numbers, str[0]); + if (number) { + return number - valid_numbers; + } + } + + for (i=0; i= 0) { + int i; + const size_t namelen = sep - opt; + for (i=0; iname, opt, namelen)) { + MMS_ASSERT(modules[i]->max_level >= modlevel); + modules[i]->level = modlevel; + return TRUE; + } + } + if (error) { + *error = g_error_new(G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, + "Unknown log module '%.*s'", (int)namelen, opt); + } + } + } else { + const int deflevel = mms_log_parse_level(opt, error); + if (deflevel >= 0) { + mms_log_default.level = deflevel; + return TRUE; + } + } + return FALSE; +} + +/* Generates the string containg description of log levels and list of + * log modules. The caller must deallocate the string with g_free */ +char* +mms_log_description( + MMSLogModule** modules, /* Known modules */ + int count) /* Number of known modules */ +{ + int i; + GString* desc = g_string_sized_new(128); + g_string_append(desc, "Log Levels:\n"); + for (i=0; i<=MMS_LOGLEVEL_MAX; i++) { + g_string_append_printf(desc, " %d, ", i); + g_string_append_printf(desc, "%-8s ", mms_log_levels[i].name); + g_string_append(desc, mms_log_levels[i].description); + if (i == MMS_LOGLEVEL_DEFAULT) g_string_append(desc, " (default)"); + g_string_append(desc, "\n"); + } + if (modules) { + g_string_append(desc, "\nLog Modules:\n"); + for (i=0; iname); + } + } + return g_string_free(desc, FALSE); +} + +gboolean +mms_log_set_type( + const char* type, + const char* default_name) +{ +#ifdef MMS_LOG_SYSLOG + if (!strcasecmp(type, MMS_LOG_TYPE_SYSLOG)) { + if (mms_log_func != mms_log_syslog) { + openlog(NULL, LOG_PID | LOG_CONS, LOG_USER); + } + mms_log_default.name = NULL; + mms_log_func = mms_log_syslog; + return TRUE; + } + if (mms_log_func == mms_log_syslog) { + closelog(); + } +#endif /* MMS_LOG_SYSLOG */ + mms_log_default.name = default_name; + if (!strcasecmp(type, MMS_LOG_TYPE_STDOUT)) { + mms_log_func = mms_log_stdout; + return TRUE; + } else if (!strcasecmp(type, MMS_LOG_TYPE_GLIB)) { + mms_log_func = mms_log_glib; + return TRUE; + } + return FALSE; +} + +const char* +mms_log_get_type() +{ + return (mms_log_func == mms_log_stdout) ? MMS_LOG_TYPE_STDOUT : +#ifdef MMS_LOG_SYSLOG + (mms_log_func == mms_log_syslog) ? MMS_LOG_TYPE_SYSLOG : +#endif /* MMS_LOG_SYSLOG */ + (mms_log_func == mms_log_glib) ? MMS_LOG_TYPE_STDOUT : + MMS_LOG_TYPE_CUSTOM; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_message.c b/mms-lib/src/mms_message.c new file mode 100644 index 0000000..88d58b8 --- /dev/null +++ b/mms-lib/src/mms_message.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_message.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_message_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-message"); + +static +void +mms_message_part_free( + gpointer data, + gpointer user_data) +{ + MMSMessagePart* part = data; + MMSMessage* msg = user_data; + g_free(part->content_type); + g_free(part->content_id); + if (part->file) { + if (!(msg->flags & MMS_MESSAGE_FLAG_KEEP_FILES)) remove(part->file); + g_free(part->file); + } + g_free(part); +} + +static +void +mms_message_finalize( + MMSMessage* msg) +{ + MMS_VERBOSE_("%p", msg); + g_free(msg->id); + g_free(msg->message_id); + g_free(msg->from); + g_strfreev(msg->to); + g_strfreev(msg->cc); + g_free(msg->subject); + g_free(msg->cls); + g_slist_foreach(msg->parts, mms_message_part_free, msg); + g_slist_free(msg->parts); + if (msg->parts_dir) { + if (!(msg->flags & MMS_MESSAGE_FLAG_KEEP_FILES)) remove(msg->parts_dir); + g_free(msg->parts_dir); + } +} + +MMSMessage* +mms_message_new() +{ + MMSMessage* msg = g_new0(MMSMessage, 1); + MMS_VERBOSE_("%p", msg); + msg->ref_count = 1; + msg->priority = MMS_PRIORITY_NORMAL; + return msg; +} + +MMSMessage* +mms_message_ref( + MMSMessage* msg) +{ + if (msg) { + MMS_ASSERT(msg->ref_count > 0); + g_atomic_int_inc(&msg->ref_count); + } + return msg; +} + +void +mms_message_unref( + MMSMessage* msg) +{ + if (msg) { + MMS_ASSERT(msg->ref_count > 0); + if (g_atomic_int_dec_and_test(&msg->ref_count)) { + mms_message_finalize(msg); + g_free(msg); + } + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task.c b/mms-lib/src/mms_task.c new file mode 100644 index 0000000..c113ee3 --- /dev/null +++ b/mms-lib/src/mms_task.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_handler.h" +#include "mms_file_util.h" + +#ifdef _WIN32 +# define snprintf _snprintf +#endif + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_task_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-task"); + +#define MMS_TASK_DEFAULT_LIFETIME (600) + +G_DEFINE_TYPE(MMSTask, mms_task, G_TYPE_OBJECT); + +#define MMS_TASK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), MMS_TYPE_TASK, MMSTask)) +#define MMS_TASK_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), MMS_TYPE_TASK, MMSTaskClass)) + +static +void +mms_task_wakeup_free( + gpointer data) +{ + mms_task_unref(data); +} + +static +gboolean +mms_task_wakeup_callback( + gpointer data) +{ + MMSTask* task = data; + task->wakeup_id = 0; + MMS_ASSERT(task->state == MMS_TASK_STATE_SLEEP); + mms_task_set_state(task, MMS_TASK_STATE_READY); + return FALSE; +} + +gboolean +mms_task_schedule_wakeup( + MMSTask* task, + unsigned int secs) +{ + const time_t now = time(NULL); + if (!secs) secs = task->config->retry_secs; + + /* Cancel the previous sleep */ + if (task->wakeup_id) { + MMS_ASSERT(task->state == MMS_TASK_STATE_SLEEP); + g_source_remove(task->wakeup_id); + task->wakeup_id = 0; + } + + if (now < task->deadline) { + /* Don't sleep past deadline */ + const unsigned int max_secs = task->deadline - now; + if (secs > max_secs) secs = max_secs; + /* Schedule wakeup */ + task->wakeup_time = now + secs; + task->wakeup_id = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + secs, mms_task_wakeup_callback, mms_task_ref(task), + mms_task_wakeup_free); + MMS_ASSERT(task->wakeup_id); + MMS_VERBOSE("%s sleeping for %u sec", task->name, secs); + } + + return (task->wakeup_id > 0); +} + +gboolean +mms_task_sleep( + MMSTask* task, + unsigned int secs) +{ + gboolean ok = mms_task_schedule_wakeup(task, secs); + mms_task_set_state(task, ok ? MMS_TASK_STATE_SLEEP : MMS_TASK_STATE_DONE); + return ok; +} + +static +void +mms_task_cancel_cb( + MMSTask* task) +{ + if (task->wakeup_id) { + MMS_ASSERT(task->state == MMS_TASK_STATE_SLEEP); + g_source_remove(task->wakeup_id); + task->wakeup_id = 0; + } + task->flags |= MMS_TASK_FLAG_CANCELLED; + mms_task_set_state(task, MMS_TASK_STATE_DONE); +} + +static +void +mms_task_finalize( + GObject* object) +{ + MMSTask* task = MMS_TASK(object); + MMS_VERBOSE_("%p", task); + MMS_ASSERT(!task->delegate); + MMS_ASSERT(!task->wakeup_id); + g_free(task->name); + g_free(task->id); + g_free(task->imsi); + mms_handler_unref(task->handler); + G_OBJECT_CLASS(mms_task_parent_class)->finalize(object); +} + +static +void +mms_task_class_init( + MMSTaskClass* klass) +{ + klass->fn_cancel = mms_task_cancel_cb; + G_OBJECT_CLASS(klass)->finalize = mms_task_finalize; +} + +static +void +mms_task_init( + MMSTask* task) +{ + MMS_VERBOSE_("%p", task); +} + +void* +mms_task_alloc( + GType type, + const MMSConfig* config, + MMSHandler* handler, + const char* name, + const char* id, + const char* imsi) +{ + MMSTask* task = g_object_new(type, NULL); + const time_t now = time(NULL); + time_t max_lifetime = MMS_TASK_GET_CLASS(task)->max_lifetime; + if (!max_lifetime) max_lifetime = MMS_TASK_DEFAULT_LIFETIME; + task->config = config; + task->handler = mms_handler_ref(handler); + if (name) { + task->name = id ? + g_strdup_printf("%s[%.08s]", name, id) : + g_strdup(name); + } + task->id = g_strdup(id); + task->imsi = g_strdup(imsi); + task->deadline = now + max_lifetime; + return task; +} + +MMSTask* +mms_task_ref( + MMSTask* task) +{ + if (task) g_object_ref(MMS_TASK(task)); + return task; +} + +void +mms_task_unref( + MMSTask* task) +{ + if (task) g_object_unref(MMS_TASK(task)); +} + +void +mms_task_run( + MMSTask* task) +{ + MMS_ASSERT(task->state == MMS_TASK_STATE_READY); + MMS_TASK_GET_CLASS(task)->fn_run(task); + time(&task->last_run_time); + MMS_ASSERT(task->state != MMS_TASK_STATE_READY); +} + +void +mms_task_transmit( + MMSTask* task, + MMSConnection* connection) +{ + MMS_ASSERT(task->state == MMS_TASK_STATE_NEED_CONNECTION || + task->state == MMS_TASK_STATE_NEED_USER_CONNECTION); + MMS_TASK_GET_CLASS(task)->fn_transmit(task, connection); + time(&task->last_run_time); + MMS_ASSERT(task->state != MMS_TASK_STATE_NEED_CONNECTION && + task->state != MMS_TASK_STATE_NEED_USER_CONNECTION); +} + +void +mms_task_network_unavailable( + MMSTask* task) +{ + if (task->state != MMS_TASK_STATE_DONE) { + MMS_ASSERT(task->state == MMS_TASK_STATE_NEED_CONNECTION || + task->state == MMS_TASK_STATE_NEED_USER_CONNECTION || + task->state == MMS_TASK_STATE_TRANSMITTING); + MMS_TASK_GET_CLASS(task)->fn_network_unavailable(task); + MMS_ASSERT(task->state != MMS_TASK_STATE_NEED_CONNECTION && + task->state != MMS_TASK_STATE_NEED_USER_CONNECTION && + task->state != MMS_TASK_STATE_TRANSMITTING); + time(&task->last_run_time); + } +} + +void +mms_task_cancel( + MMSTask* task) +{ + MMS_DEBUG_("%s", task->name); + MMS_TASK_GET_CLASS(task)->fn_cancel(task); +} + +void +mms_task_set_state( + MMSTask* task, + MMS_TASK_STATE state) +{ + if (task->state != state) { + MMS_DEBUG("%s %s -> %s", task->name, + mms_task_state_name(task->state), + mms_task_state_name(state)); + if (state == MMS_TASK_STATE_SLEEP && !task->wakeup_id) { + if (!mms_task_schedule_wakeup(task, task->config->retry_secs)) { + MMS_DEBUG("%s SLEEP -> DONE (no time left)", task->name); + state = MMS_TASK_STATE_DONE; + } + } + task->state = state; + if (task->delegate && task->delegate->fn_task_state_changed) { + task->delegate->fn_task_state_changed(task->delegate, task); + } + } +} + +/* Utilities */ + +static const char* mms_task_names[] = {"READY", "NEED_CONNECTION", + "NEED_USER_CONNECTION", "TRANSMITTING", "WORKING", "SLEEP", "DONE" +}; +G_STATIC_ASSERT(G_N_ELEMENTS(mms_task_names) == MMS_TASK_STATE_COUNT); + +const char* +mms_task_state_name( + MMS_TASK_STATE state) +{ + if (state >= 0 && state < G_N_ELEMENTS(mms_task_names)) { + return mms_task_names[state]; + } else { + /* This shouldn't happen */ + static char unknown[32]; + snprintf(unknown, sizeof(unknown), "%d ????", state); + return unknown; + } +} + +gboolean +mms_task_queue_and_unref( + MMSTaskDelegate* delegate, + MMSTask* task) +{ + gboolean ok = FALSE; + if (task) { + if (delegate && delegate->fn_task_queue) { + delegate->fn_task_queue(delegate, task); + ok = TRUE; + } + mms_task_unref(task); + } + return ok; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task.h b/mms-lib/src/mms_task.h new file mode 100644 index 0000000..b28b463 --- /dev/null +++ b/mms-lib/src/mms_task.h @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_TASK_H +#define JOLLA_MMS_TASK_H + +#include "mms_lib_types.h" + +/* Claim MMS 1.1 support */ +#define MMS_VERSION MMS_MESSAGE_VERSION_1_1 + +/* mmsutil.h */ +typedef enum mms_message_notify_status MMSNotifyStatus; + +/* Task state */ +typedef enum _MMS_TASK_STATE { + MMS_TASK_STATE_READY, /* Ready to run */ + MMS_TASK_STATE_NEED_CONNECTION, /* Network connection us needed */ + MMS_TASK_STATE_NEED_USER_CONNECTION, /* Connection requested by user */ + MMS_TASK_STATE_TRANSMITTING, /* Sending or receiving the data */ + MMS_TASK_STATE_WORKING, /* Active but not using network */ + MMS_TASK_STATE_SLEEP, /* Will change state later */ + MMS_TASK_STATE_DONE, /* Nothing left to do */ + MMS_TASK_STATE_COUNT /* Number of valid states */ +} MMS_TASK_STATE; + +/* Delegate (one per task) */ +typedef struct mms_task MMSTask; +typedef struct mms_task_delegate MMSTaskDelegate; +struct mms_task_delegate { + /* Submits new task to the queue */ + void (*fn_task_queue)( + MMSTaskDelegate* delegate, + MMSTask* task); + /* Task has changed its state */ + void (*fn_task_state_changed)( + MMSTaskDelegate* delegate, + MMSTask* task); +}; + +/* Task object */ +struct mms_task { + GObject parent; /* Parent object */ + char* name; /* Task name for debug purposes */ + char* id; /* Database record ID */ + char* imsi; /* Associated subscriber identity */ + const MMSConfig* config; /* Immutable configuration */ + MMSHandler* handler; /* Message database interface */ + MMSTaskDelegate* delegate; /* Observer */ + MMS_TASK_STATE state; /* Task state */ + time_t last_run_time; /* Last run time */ + time_t deadline; /* Task deadline */ + time_t wakeup_time; /* Wake up time (if sleeping) */ + guint wakeup_id; /* ID of the wakeup source */ + int flags; /* Flags: */ + +#define MMS_TASK_FLAG_CANCELLED (0x01) /* Task has been cancelled */ + +}; + +typedef struct mms_task_class { + GObjectClass parent; + time_t max_lifetime; /* Maximum lifetime, in seconds */ + /* Invoked in IDLE/RETRY state to get the task going */ + void (*fn_run)(MMSTask* task); + /* Invoked in NEED_[USER_]CONNECTION state */ + void (*fn_transmit)(MMSTask* task, MMSConnection* conn); + /* Invoked in NEED_[USER_]CONNECTION or TRANSMITTING state */ + void (*fn_network_unavailable)(MMSTask* task); + /* May be invoked in any state */ + void (*fn_cancel)(MMSTask* task); +} MMSTaskClass; + +GType mms_task_get_type(void); +#define MMS_TYPE_TASK (mms_task_get_type()) +#define MMS_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), \ + MMS_TYPE_TASK, MMSTaskClass)) + +void* +mms_task_alloc( + GType type, + const MMSConfig* config, + MMSHandler* handler, + const char* name, + const char* id, + const char* imsi); + +MMSTask* +mms_task_ref( + MMSTask* task); + +void +mms_task_unref( + MMSTask* task); + +void +mms_task_run( + MMSTask* task); + +void +mms_task_transmit( + MMSTask* task, + MMSConnection* connection); + +void +mms_task_network_unavailable( + MMSTask* task); + +void +mms_task_cancel( + MMSTask* task); + +void +mms_task_set_state( + MMSTask* task, + MMS_TASK_STATE state); + +gboolean +mms_task_sleep( + MMSTask* task, + unsigned int secs); + +#define mms_task_retry(task) \ + mms_task_sleep(task, 0) + +/* Utilities */ +const char* +mms_task_state_name( + MMS_TASK_STATE state); + +gboolean +mms_task_queue_and_unref( + MMSTaskDelegate* delegate, + MMSTask* task); + +/* Create particular types of tasks */ +MMSTask* +mms_task_notification_new( + const MMSConfig* config, + MMSHandler* handler, + const char* imsi, + GBytes* bytes); + +MMSTask* +mms_task_retrieve_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi, + const MMSPdu* pdu); + +MMSTask* +mms_task_decode_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi, + const char* transaction_id, + const char* file); + +MMSTask* +mms_task_upload_new( + const MMSConfig* config, + MMSHandler* handler, + const char* name, + const char* id, + const char* imsi, + const char* file); + +MMSTask* +mms_task_notifyresp_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi, + const char* transaction_id, + MMSNotifyStatus status); + +MMSTask* +mms_task_ack_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi, + const char* transaction_id); + +MMSTask* +mms_task_read_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi, + const char* message_id, + const char* to, + MMSReadStatus status); + +MMSTask* +mms_task_publish_new( + const MMSConfig* config, + MMSHandler* handler, + MMSMessage* msg); + +MMSTask* +mms_task_delete_new( + const MMSConfig* config, + const char* uuid); + +#endif /* JOLLA_MMS_TASK_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task_ack.c b/mms-lib/src/mms_task_ack.c new file mode 100644 index 0000000..dd1c77a --- /dev/null +++ b/mms-lib/src/mms_task_ack.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_log.h" +#include "mms_codec.h" +#include "mms_file_util.h" + +static +char* +mms_task_ack_create_pdu_file( + const MMSConfig* config, + const char* id, + const char* transaction_id) +{ + char* path = NULL; + char* dir = mms_message_dir(config, id); + int fd = mms_create_file(dir, MMS_ACKNOWLEDGE_IND_FILE, &path); + if (fd >= 0) { + MMSPdu* pdu = g_new0(MMSPdu, 1); + pdu->type = MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND; + pdu->version = MMS_VERSION; + pdu->transaction_id = g_strdup(transaction_id); + pdu->ai.report = config->send_dr; + if (!mms_message_encode(pdu, fd)) { + g_free(path); + path = NULL; + } + mms_message_free(pdu); + close(fd); + } + g_free(dir); + return path; +} + +/* Create MMS delivery acknowledgement task */ +MMSTask* +mms_task_ack_new( + const MMSConfig* cfg, + MMSHandler* h, + const char* id, + const char* imsi, + const char* transaction_id) +{ + char* path = mms_task_ack_create_pdu_file(cfg, id, transaction_id); + if (path) { + MMSTask* task = mms_task_upload_new(cfg, h, "Ack", id, imsi, path); + g_free(path); + return task; + } + return NULL; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task_decode.c b/mms-lib/src/mms_task_decode.c new file mode 100644 index 0000000..132e145 --- /dev/null +++ b/mms-lib/src/mms_task_decode.c @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_util.h" +#include "mms_codec.h" +#include "mms_handler.h" +#include "mms_message.h" +#include "mms_file_util.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_task_decode_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-task-decode"); + +/* Class definition */ +typedef MMSTaskClass MMSTaskDecodeClass; +typedef struct mms_task_decode { + MMSTask task; + GMappedFile* map; + char* transaction_id; + char* file; +} MMSTaskDecode; + +G_DEFINE_TYPE(MMSTaskDecode, mms_task_decode, MMS_TYPE_TASK); +#define MMS_TYPE_TASK_DECODE (mms_task_decode_get_type()) +#define MMS_TASK_DECODE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_TASK_DECODE, MMSTaskDecode)) + +static +gboolean +mms_task_decode_array_contains_string( + const GPtrArray* array, + const char* str) +{ + guint i; + for (i=0; ilen; i++) { + if (!strcmp(array->pdata[i], str)) { + return TRUE; + } + } + return FALSE; +} + +static +char* +mms_task_decode_add_file_name( + GPtrArray* names, + const char* proposed) +{ + const char* src; + char* file = g_new(char, strlen(proposed)+1); + char* dest = file; + for (src = proposed; *src; src++) { + switch (*src) { + case '<': case '>': case '[': case ']': + break; + case '/': case '\\': + *dest++ = '_'; + break; + default: + *dest++ = *src; + break; + } + } + *dest = 0; + while (mms_task_decode_array_contains_string(names, file)) { + char* _file = g_strconcat("_", file, NULL); + g_free(file); + file = _file; + } + g_ptr_array_add(names, file); + return file; +} + +static +MMSMessage* +mms_task_decode_process_retrieve_conf( + MMSTask* task, + const MMSPdu* pdu, + const guint8* pdu_data, + gsize pdu_size) +{ + GSList* entry; + int i, nparts = g_slist_length(pdu->attachments); + GPtrArray* part_files = g_ptr_array_new_full(nparts, g_free); + char* dir = mms_task_dir(task); + const struct mms_retrieve_conf* rc = &pdu->rc; + MMSMessage* msg = mms_message_new(); + +#if MMS_LOG_DEBUG + char date[128]; + strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%S%z", localtime(&rc->date)); + date[sizeof(date)-1] = '\0'; +#endif /* MMS_LOG_DEBUG */ + + MMS_ASSERT(pdu->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF); + MMS_INFO("Processing M-Retrieve.conf"); + MMS_INFO(" From: %s", rc->from); + +#if MMS_LOG_DEBUG + MMS_DEBUG(" To: %s", rc->to); + if (rc->cc) MMS_DEBUG(" Cc: %s", rc->cc); + MMS_DEBUG(" Message-ID: %s", rc->msgid); + MMS_DEBUG(" Transaction-ID: %s", pdu->transaction_id); + if (rc->subject) MMS_DEBUG(" Subject: %s", rc->subject); + MMS_DEBUG(" Date: %s", date); + MMS_DEBUG(" %u parts", nparts); +#endif /* MMS_LOG_DEBUG */ + + if (task->config->keep_temp_files) { + msg->flags |= MMS_MESSAGE_FLAG_KEEP_FILES; + } + + msg->id = g_strdup(task->id); + msg->message_id = g_strdup(rc->msgid); + msg->from = mms_strip_address_type(g_strdup(rc->from)); + msg->to = mms_split_address_list(rc->to); + msg->cc = mms_split_address_list(rc->cc); + msg->subject = g_strdup(rc->subject); + msg->cls = g_strdup(rc->cls ? rc->cls : MMS_MESSAGE_CLASS_PERSONAL); + msg->date = rc->date ? rc->date : time(NULL); + + switch (rc->priority) { + case MMS_MESSAGE_PRIORITY_LOW: + msg->priority = MMS_PRIORITY_LOW; + break; + case MMS_MESSAGE_PRIORITY_NORMAL: + msg->priority = MMS_PRIORITY_NORMAL; + break; + case MMS_MESSAGE_PRIORITY_HIGH: + msg->priority = MMS_PRIORITY_HIGH; + break; + } + + msg->parts_dir = g_strconcat(dir, "/" , MMS_PARTS_DIR, NULL); + for (i=0, entry = pdu->attachments; entry; entry = entry->next, i++) { + struct mms_attachment* attach = entry->data; + const char* id = attach->content_id; + char* path = NULL; + char* file; + if (id && id[0]) { + file = mms_task_decode_add_file_name(part_files, id); + } else { + char* name = g_strdup_printf("part_%d",i); + file = mms_task_decode_add_file_name(part_files, name); + g_free(name); + } + MMS_DEBUG("Part: %s %s", id, attach->content_type); + MMS_ASSERT(attach->offset < pdu_size); + if (mms_write_file(msg->parts_dir, file, pdu_data + attach->offset, + attach->length, &path)) { + MMSMessagePart* part = g_new0(MMSMessagePart, 1); + part->content_type = g_strdup(attach->content_type); + part->content_id = g_strdup(id); + part->file = path; + msg->parts = g_slist_append(msg->parts, part); + } + } + + g_ptr_array_free(part_files, TRUE); + g_free(dir); + return msg; +} + +static +MMSMessage* +mms_task_decode_process_pdu( + MMSTask* task, + const guint8* data, + gsize len) +{ + MMSMessage* msg = NULL; + MMSPdu* pdu = g_new0(MMSPdu, 1); + if (mms_message_decode(data, len, pdu)) { + if (pdu->type == MMS_MESSAGE_TYPE_RETRIEVE_CONF) { + msg = mms_task_decode_process_retrieve_conf(task, pdu, data, len); + } else { + MMS_ERR("Unexpected MMS PDU type %u", (guint)pdu->type); + } + } else { + MMS_ERR("Failed to decode MMS PDU"); + } + mms_message_free(pdu); + return msg; +} + +static +void +mms_task_decode_run( + MMSTask* task) +{ + MMSTaskDecode* dec = MMS_TASK_DECODE(task); + const void* data = g_mapped_file_get_contents(dec->map); + const gsize size = g_mapped_file_get_length(dec->map); + MMSMessage* msg = mms_task_decode_process_pdu(task, data, size); + if (msg) { + mms_task_queue_and_unref(task->delegate, + mms_task_ack_new(task->config, task->handler, task->id, task->imsi, + dec->transaction_id)); + mms_task_queue_and_unref(task->delegate, + mms_task_publish_new(task->config, task->handler, msg)); + mms_message_unref(msg); + } else { + mms_handler_message_receive_state_changed(task->handler, task->id, + MMS_RECEIVE_STATE_DECODING_ERROR); + mms_task_queue_and_unref(task->delegate, + mms_task_notifyresp_new(task->config, task->handler, task->id, + task->imsi, dec->transaction_id, + MMS_MESSAGE_NOTIFY_STATUS_UNRECOGNISED)); + } + mms_task_set_state(task, MMS_TASK_STATE_DONE); +} + +static +void +mms_task_decode_finalize( + GObject* object) +{ + MMSTaskDecode* dec = MMS_TASK_DECODE(object); + if (!dec->task.config->keep_temp_files) { + mms_remove_file_and_dir(dec->file); + } + g_mapped_file_unref(dec->map); + g_free(dec->transaction_id); + g_free(dec->file); + G_OBJECT_CLASS(mms_task_decode_parent_class)->finalize(object); +} + +static +void +mms_task_decode_class_init( + MMSTaskDecodeClass* klass) +{ + klass->fn_run = mms_task_decode_run; + G_OBJECT_CLASS(klass)->finalize = mms_task_decode_finalize; +} + +static +void +mms_task_decode_init( + MMSTaskDecode* decode) +{ +} + +/* Create MMS decode task */ +MMSTask* +mms_task_decode_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi, + const char* transaction_id, + const char* file) +{ + MMSTaskDecode* dec = mms_task_alloc(MMS_TYPE_TASK_DECODE, + config, handler, "Decode", id, imsi); + GError* error = NULL; + dec->map = g_mapped_file_new(file, FALSE, &error); + if (dec->map) { + dec->transaction_id = g_strdup(transaction_id); + dec->file = g_strdup(file); + return &dec->task; + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + return NULL; + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task_notification.c b/mms-lib/src/mms_task_notification.c new file mode 100644 index 0000000..2a36439 --- /dev/null +++ b/mms-lib/src/mms_task_notification.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_util.h" +#include "mms_codec.h" +#include "mms_handler.h" +#include "mms_file_util.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_task_notification_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-task-notification"); + +/* Class definition */ +typedef MMSTaskClass MMSTaskNotificationClass; +typedef struct mms_task_notification { + MMSTask task; + MMSPdu* pdu; + GBytes* push; +} MMSTaskNotification; + +G_DEFINE_TYPE(MMSTaskNotification, mms_task_notification, MMS_TYPE_TASK); +#define MMS_TYPE_TASK_NOTIFICATION (mms_task_notification_get_type()) +#define MMS_TASK_NOTIFICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_TASK_NOTIFICATION, MMSTaskNotification)) + +/** + * Generates dummy task id if necessary. + */ +static +void +mms_task_notification_make_id( + MMSTask* task) +{ + if (!task->id) { + char* tmpl = g_strconcat(task->config->root_dir, + "/" MMS_MESSAGE_DIR "/XXXXXX" , NULL); + char* dir = g_mkdtemp_full(tmpl, MMS_DIR_PERM); + if (dir) task->id = g_path_get_basename(dir); + g_free(tmpl); + } +} + +/** + * Writes the datagram to a file in the message directory. + */ +static +gboolean +mms_task_notification_write_file( + MMSTaskNotification* ind, + const char* file) +{ + char* dir; + gboolean ok; + mms_task_notification_make_id(&ind->task); + dir = mms_task_dir(&ind->task); + ok = mms_write_bytes(dir, file, ind->push, NULL); + g_free(dir); + return ok; +} + +/** + * Handles M-Notification.ind PDU + */ +static +void +mms_task_notification_ind( + MMSTaskNotification* ind) +{ + MMSTask* task = &ind->task; + const struct mms_notification_ind* ni = &ind->pdu->ni; + char* id; + +#if MMS_LOG_DEBUG + char expiry[128]; + strftime(expiry, sizeof(expiry), "%Y-%m-%dT%H:%M:%S%z", + localtime(&ni->expiry)); + expiry[sizeof(expiry)-1] = '\0'; + + MMS_DEBUG("Processing M-Notification.ind"); + MMS_DEBUG(" From: %s", ni->from); + if (ni->subject) MMS_DEBUG(" Subject: %s", ni->subject); + MMS_DEBUG(" Size: %d bytes", ni->size); + MMS_DEBUG(" Location: %s", ni->location); + MMS_DEBUG(" Expiry: %s", expiry); +#endif /* MMS_LOG_DEBUG */ + + id = mms_handler_message_notify(task->handler, task->imsi, + mms_strip_address_type(ni->from), ni->subject, ni->expiry, ind->push); + if (id) { + if (id[0]) { + MMS_DEBUG(" Database id: %s", id); + if (task->id) { + /* Remove temporary directory and files */ + char* dir = mms_task_dir(task); + char* file = mms_task_file(task, MMS_NOTIFICATION_IND_FILE); + remove(file); + remove(dir); + g_free(file); + g_free(dir); + g_free(task->id); + } + task->id = id; + + /* Schedule the download task */ + if (!mms_task_queue_and_unref(task->delegate, + mms_task_retrieve_new(task->config, task->handler, + task->id, task->imsi, ind->pdu))) { + mms_handler_message_receive_state_changed(task->handler, id, + MMS_RECEIVE_STATE_DOWNLOAD_ERROR); + } + } else { + g_free(id); + } + } else if (!mms_task_retry(task)) { + mms_task_notification_make_id(task); + mms_task_queue_and_unref(task->delegate, + mms_task_notifyresp_new(task->config, task->handler, task->id, + task->imsi, ind->pdu->transaction_id, + MMS_MESSAGE_NOTIFY_STATUS_REJECTED)); + } + + if (task->config->keep_temp_files) { + mms_task_notification_write_file(ind, MMS_NOTIFICATION_IND_FILE); + } +} + +/** + * Handles M-Delivery.ind PDU + */ +static +void +mms_task_delivery_ind( + MMSTaskNotification* ind) +{ + MMS_DEBUG("Processing M-Delivery.ind PDU"); + MMS_DEBUG(" MMS message id: %s", ind->pdu->di.msgid); + if (ind->task.config->keep_temp_files) { + mms_task_notification_write_file(ind, MMS_DELIVERY_IND_FILE); + } +} + +/** + * Handles M-Read-Orig.ind PDU + */ +static +void +mms_task_read_orig_ind( + MMSTaskNotification* ind) +{ + MMS_DEBUG("Processing M-Read-Orig.ind"); + MMS_DEBUG(" MMS message id: %s", ind->pdu->ri.msgid); + if (ind->task.config->keep_temp_files) { + mms_task_notification_write_file(ind, MMS_READ_ORIG_IND_FILE); + } +} + +/** + * Handles unrecognized PDU + */ +static +void +mms_task_notification_unrecornized( + const MMSConfig* config, + GBytes* push) +{ + if (config->attic_enabled) { + char* attic_dir = NULL; + int i; + for (i=0; i<100; i++) { + g_free(attic_dir); + attic_dir = g_strdup_printf("%s/" MMS_ATTIC_DIR "/%03d", + config->root_dir, i); + if (!g_file_test(attic_dir, G_FILE_TEST_IS_DIR)) break; + } + mms_write_bytes(attic_dir, MMS_UNRECOGNIZED_PUSH_FILE, push, NULL); + g_free(attic_dir); + } +} + +/** + * Runs the task + */ +static +void +mms_task_notification_run( + MMSTask* task) +{ + MMSTaskNotification* ind = MMS_TASK_NOTIFICATION(task); + switch (ind->pdu->type) { + case MMS_MESSAGE_TYPE_NOTIFICATION_IND: + mms_task_notification_ind(ind); + break; + case MMS_MESSAGE_TYPE_DELIVERY_IND: + mms_task_delivery_ind(ind); + break; + case MMS_MESSAGE_TYPE_READ_ORIG_IND: + mms_task_read_orig_ind(ind); + break; + default: + MMS_INFO("Ignoring MMS push PDU of type %u", ind->pdu->type); + mms_task_notification_unrecornized(task->config, ind->push); + break; + } + if (task->state == MMS_TASK_STATE_READY) { + mms_task_set_state(task, MMS_TASK_STATE_DONE); + } +} + +static +void +mms_task_notification_finalize( + GObject* object) +{ + MMSTaskNotification* ind = MMS_TASK_NOTIFICATION(object); + g_bytes_unref(ind->push); + mms_message_free(ind->pdu); + G_OBJECT_CLASS(mms_task_notification_parent_class)->finalize(object); +} + +static +void +mms_task_notification_class_init( + MMSTaskNotificationClass* klass) +{ + klass->fn_run = mms_task_notification_run; + G_OBJECT_CLASS(klass)->finalize = mms_task_notification_finalize; +} + +static +void +mms_task_notification_init( + MMSTaskNotification* notification) +{ +} + +/* Create MMS notification task */ +MMSTask* +mms_task_notification_new( + const MMSConfig* config, + MMSHandler* handler, + const char* imsi, + GBytes* bytes) +{ + MMSPdu* pdu = mms_decode_bytes(bytes); + if (pdu) { + MMSTaskNotification* ind; + + /* Looks like a legitimate MMS Push PDU */ +#if MMS_LOG_DEBUG + MMS_DEBUG(" MMS version: %u.%u", (pdu->version & 0x70) >> 4, + pdu->version & 0x0f); + if (pdu->transaction_id) { + MMS_DEBUG(" MMS transaction id: %s", pdu->transaction_id); + } +#endif /* MMS_LOG_DEBUG */ + + ind = mms_task_alloc(MMS_TYPE_TASK_NOTIFICATION, + config, handler, "Notification", NULL, imsi); + ind->push = g_bytes_ref(bytes); + ind->pdu = pdu; + if (pdu->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND) { + ind->task.deadline = pdu->ni.expiry; + } + return &ind->task; + } else { + MMS_ERR("Unable to parse MMS push data"); + mms_task_notification_unrecornized(config, bytes); + return NULL; + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task_notifyresp.c b/mms-lib/src/mms_task_notifyresp.c new file mode 100644 index 0000000..f1d2cd2 --- /dev/null +++ b/mms-lib/src/mms_task_notifyresp.c @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_log.h" +#include "mms_codec.h" +#include "mms_file_util.h" + +static +char* +mms_task_notifyresp_create_pdu_file( + const MMSConfig* config, + const char* id, + const char* transaction_id, + MMSNotifyStatus status) +{ + char* path = NULL; + char* dir = mms_message_dir(config, id); + int fd = mms_create_file(dir, MMS_NOTIFYRESP_IND_FILE, &path); + if (fd >= 0) { + MMSPdu* pdu = g_new0(MMSPdu, 1); + pdu->type = MMS_MESSAGE_TYPE_NOTIFYRESP_IND; + pdu->version = MMS_VERSION; + pdu->transaction_id = g_strdup(transaction_id); + pdu->nri.notify_status = status; + if (!mms_message_encode(pdu, fd)) { + g_free(path); + path = NULL; + } + mms_message_free(pdu); + close(fd); + } + g_free(dir); + return path; +} + +/** + * Create MMS retrieve confirmation task + */ +MMSTask* +mms_task_notifyresp_new( + const MMSConfig* cfg, + MMSHandler* handler, + const char* id, + const char* imsi, + const char* tx_id, + MMSNotifyStatus ns) +{ + char* path = mms_task_notifyresp_create_pdu_file(cfg, id, tx_id, ns); + if (path) { + MMSTask* task = mms_task_upload_new(cfg, handler, "NotifyResp", + id, imsi, path); + g_free(path); + return task; + } + return NULL; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task_publish.c b/mms-lib/src/mms_task_publish.c new file mode 100644 index 0000000..a4b4ffc --- /dev/null +++ b/mms-lib/src/mms_task_publish.c @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_handler.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_task_publish_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-task-publish"); + +/* Class definition */ +typedef MMSTaskClass MMSTaskPublishClass; +typedef struct mms_task_publish { + MMSTask task; + MMSMessage* msg; +} MMSTaskPublish; + +G_DEFINE_TYPE(MMSTaskPublish, mms_task_publish, MMS_TYPE_TASK); +#define MMS_TYPE_TASK_PUBLISH (mms_task_publish_get_type()) +#define MMS_TASK_PUBLISH(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_TASK_PUBLISH, MMSTaskPublish)) + +static +void +mms_task_publish_run( + MMSTask* task) +{ + MMSTaskPublish* pub = MMS_TASK_PUBLISH(task); + if (mms_handler_message_received(task->handler, pub->msg)) { + mms_task_set_state(task, MMS_TASK_STATE_DONE); + } else { + mms_task_set_state(task, MMS_TASK_STATE_SLEEP); + } +} + +static +void +mms_task_publish_finalize( + GObject* object) +{ + MMSTaskPublish* pub = MMS_TASK_PUBLISH(object); + mms_message_unref(pub->msg); + G_OBJECT_CLASS(mms_task_publish_parent_class)->finalize(object); +} + +static +void +mms_task_publish_class_init( + MMSTaskPublishClass* klass) +{ + klass->fn_run = mms_task_publish_run; + G_OBJECT_CLASS(klass)->finalize = mms_task_publish_finalize; +} + +static +void +mms_task_publish_init( + MMSTaskPublish* publish) +{ +} + +/* Create MMS publish task */ +MMSTask* +mms_task_publish_new( + const MMSConfig* config, + MMSHandler* handler, + MMSMessage* msg) +{ + MMS_ASSERT(msg && msg->id); + if (msg && msg->id) { + MMSTaskPublish* pub = mms_task_alloc(MMS_TYPE_TASK_PUBLISH, + config, handler, "Publish", msg->id, NULL); + pub->msg = mms_message_ref(msg); + return &pub->task; + } else { + return NULL; + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task_read.c b/mms-lib/src/mms_task_read.c new file mode 100644 index 0000000..d9c1359 --- /dev/null +++ b/mms-lib/src/mms_task_read.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_log.h" +#include "mms_codec.h" +#include "mms_file_util.h" + +static +char* +mms_task_read_create_pdu_file( + const MMSConfig* config, + const char* id, + const char* message_id, + const char* to, + MMSReadStatus status) +{ + char* path = NULL; + char* dir = mms_message_dir(config, id); + int fd = mms_create_file(dir, MMS_READ_REC_IND_FILE, &path); + if (fd >= 0) { + MMSPdu* pdu = g_new0(MMSPdu, 1); + pdu->type = MMS_MESSAGE_TYPE_READ_REC_IND; + pdu->version = MMS_VERSION; + pdu->ri.rr_status = (status == MMS_READ_STATUS_DELETED) ? + MMS_MESSAGE_READ_STATUS_DELETED : MMS_MESSAGE_READ_STATUS_READ; + pdu->ri.msgid = g_strdup(message_id); + pdu->ri.to = g_strdup(to); + time(&pdu->ri.date); + if (!mms_message_encode(pdu, fd)) { + g_free(path); + path = NULL; + } + mms_message_free(pdu); + close(fd); + } + g_free(dir); + return path; +} + +/** + * Create MMS read report task + */ +MMSTask* +mms_task_read_new( + const MMSConfig* cfg, + MMSHandler* h, + const char* id, + const char* imsi, + const char* message_id, + const char* to, + MMSReadStatus rs) +{ + char* path = mms_task_read_create_pdu_file(cfg, id, message_id, to, rs); + if (path) { + MMSTask* task = mms_task_upload_new(cfg, h, "Read", id, imsi, path); + g_free(path); + return task; + } + return NULL; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task_retrieve.c b/mms-lib/src/mms_task_retrieve.c new file mode 100644 index 0000000..e49a6e7 --- /dev/null +++ b/mms-lib/src/mms_task_retrieve.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_connection.h" +#include "mms_file_util.h" +#include "mms_handler.h" +#include "mms_codec.h" +#include "mms_util.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_task_retrieve_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-task-retrieve"); + +/* Class definition */ +typedef MMSTaskClass MMSTaskRetrieveClass; +typedef struct mms_task_retrieve { + MMSTask task; + MMSHttpTransfer* tx; + char* uri; + char* transaction_id; + gulong got_chunk_signal_id; + guint bytes_received; + guint status_code; +} MMSTaskRetrieve; + +G_DEFINE_TYPE(MMSTaskRetrieve, mms_task_retrieve, MMS_TYPE_TASK); +#define MMS_TYPE_TASK_RETRIEVE (mms_task_retrieve_get_type()) +#define MMS_TASK_RETRIEVE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_TASK_RETRIEVE, MMSTaskRetrieve)) + +#define mms_task_retrieve_state(t,rs) \ + mms_handler_message_receive_state_changed((t)->task.handler,\ + (t)->task.id, rs) + +static +void +mms_task_retrieve_got_chunk( + SoupMessage* message, + SoupBuffer* chunk, + MMSTaskRetrieve* retrieve); + +static +void +mms_task_retrieve_run( + MMSTask* task) +{ + mms_task_set_state(task, MMS_TASK_STATE_NEED_CONNECTION); +} + +static +void +mms_task_retrieve_finish_transfer( + MMSTaskRetrieve* retrieve) +{ + if (retrieve->tx) { + g_signal_handler_disconnect(retrieve->tx->message, + retrieve->got_chunk_signal_id); + retrieve->got_chunk_signal_id = 0; + mms_http_transfer_free(retrieve->tx); + retrieve->tx = NULL; + } +} + +static +void +mms_task_retrieve_got_chunk( + SoupMessage* message, + SoupBuffer* buf, + MMSTaskRetrieve* retrieve) +{ + retrieve->bytes_received += buf->length; + MMS_VERBOSE("%u bytes", retrieve->bytes_received); + if (retrieve->tx && + write(retrieve->tx->fd, buf->data, buf->length) != (int)buf->length) { + MMS_ERR("Write error: %s", strerror(errno)); + mms_task_retrieve_finish_transfer(retrieve); + mms_task_set_state(&retrieve->task, MMS_TASK_STATE_SLEEP); + } +} + +static +void +mms_task_retrieve_finished( + SoupSession* session, + SoupMessage* message, + gpointer user_data) +{ + MMSTaskRetrieve* retrieve = user_data; + MMS_ASSERT(retrieve->tx && (retrieve->tx->session == session)); + if (retrieve->tx && (retrieve->tx->session == session)) { + MMS_TASK_STATE next_state = MMS_TASK_STATE_SLEEP; + MMSTask* task = &retrieve->task; + const MMSConfig* config = task->config; + retrieve->status_code = message->status_code; + MMS_DEBUG("Retrieve status %u", retrieve->status_code); + mms_task_retrieve_finish_transfer(retrieve); + if (SOUP_STATUS_IS_SUCCESSFUL(retrieve->status_code)) { + char* file = mms_task_file(task, MMS_RETRIEVE_CONF_FILE); + + /* Content retrieved successfully */ + MMS_DEBUG("Retrieved %s", retrieve->uri); + next_state = MMS_TASK_STATE_DONE; + mms_task_retrieve_state(retrieve, MMS_RECEIVE_STATE_DECODING); + + /* Queue the decoding task */ + mms_task_queue_and_unref(task->delegate, + mms_task_decode_new(task->config, task->handler, task->id, + task->imsi, retrieve->transaction_id, file)); + + g_free(file); + } else { + + /* Will retry if this was an I/O error, otherwise we consider + * it a permanent failure */ + if (SOUP_STATUS_IS_TRANSPORT_ERROR(retrieve->status_code)) { + mms_task_retrieve_state(retrieve, MMS_RECEIVE_STATE_DEFERRED); + } else { + next_state = MMS_TASK_STATE_DONE; + MMS_WARN("Retrieve error %u", retrieve->status_code); + } + } + + if (!config->keep_temp_files) { + char* dir = mms_task_dir(task); + char* file = g_strconcat(dir, "/" MMS_RETRIEVE_CONF_FILE, NULL); + remove(file); + remove(dir); + g_free(file); + g_free(dir); + } + + /* Switch the state */ + mms_task_set_state(task, next_state); + } else { + MMS_VERBOSE_("ignoring stale completion message"); + } +} + +static +void +mms_task_retrieve_start( + MMSTaskRetrieve* retrieve, + MMSConnection* connection) +{ + MMSTask* task = &retrieve->task; + char* dir = mms_task_dir(task); + char* file = NULL; + int fd; + MMS_ASSERT(mms_connection_is_open(connection)); + + /* Cleanup any leftovers */ + mms_task_retrieve_finish_transfer(retrieve); + retrieve->bytes_received = 0; + + /* Create new temporary file */ + fd = mms_create_file(dir, MMS_RETRIEVE_CONF_FILE, &file); + if (fd >= 0) { + /* Set up the transfer */ + retrieve->tx = mms_http_transfer_new(task->config, + connection, "GET", retrieve->uri, fd); + if (retrieve->tx) { + /* Start the transfer */ + SoupMessage* message = retrieve->tx->message; + MMS_DEBUG("%s -> %s", retrieve->uri, file); + soup_message_body_set_accumulate(message->response_body, FALSE); + retrieve->got_chunk_signal_id = g_signal_connect(message, + "got-chunk", G_CALLBACK(mms_task_retrieve_got_chunk), + retrieve); + soup_session_queue_message(retrieve->tx->session, message, + mms_task_retrieve_finished, retrieve); + } + } + + if (retrieve->tx) { + mms_task_set_state(task, MMS_TASK_STATE_TRANSMITTING); + mms_task_retrieve_state(retrieve, MMS_RECEIVE_STATE_RECEIVING); + } else { + retrieve->status_code = SOUP_STATUS_NONE; + mms_task_set_state(task, MMS_TASK_STATE_DONE); + close(fd); + } + + g_free(file); + g_free(dir); +} + +static +void +mms_task_retrieve_transmit( + MMSTask* task, + MMSConnection* connection) +{ + if (task->state != MMS_TASK_STATE_TRANSMITTING) { + mms_task_retrieve_start(MMS_TASK_RETRIEVE(task), connection); + } +} + +static +void +mms_task_retrieve_cancel( + MMSTask* task) +{ + mms_task_retrieve_finish_transfer(MMS_TASK_RETRIEVE(task)); + MMS_TASK_CLASS(mms_task_retrieve_parent_class)->fn_cancel(task); +} + +static +void +mms_task_retrieve_network_unavailable( + MMSTask* task) +{ + mms_task_retrieve_finish_transfer(MMS_TASK_RETRIEVE(task)); + mms_task_set_state(task, MMS_TASK_STATE_SLEEP); +} + +/** + * First stage of deinitialization (release all references). + * May be called more than once in the lifetime of the object. + */ +static +void +mms_task_retrieve_dispose( + GObject* object) +{ + MMSTaskRetrieve* retrieve = MMS_TASK_RETRIEVE(object); + mms_task_retrieve_finish_transfer(retrieve); + G_OBJECT_CLASS(mms_task_retrieve_parent_class)->dispose(object); +} + +/** + * Final stage of deinitialization + */ +static +void +mms_task_retrieve_finalize( + GObject* object) +{ + MMSTaskRetrieve* retrieve = MMS_TASK_RETRIEVE(object); + if (!SOUP_STATUS_IS_SUCCESSFUL(retrieve->status_code)) { + mms_task_retrieve_state(retrieve, MMS_RECEIVE_STATE_DOWNLOAD_ERROR); + } + g_free(retrieve->uri); + g_free(retrieve->transaction_id); + G_OBJECT_CLASS(mms_task_retrieve_parent_class)->finalize(object); +} + +/** + * Per class initializer + */ +static +void +mms_task_retrieve_class_init( + MMSTaskRetrieveClass* klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS(klass); + object_class->dispose = mms_task_retrieve_dispose; + object_class->finalize = mms_task_retrieve_finalize; + klass->fn_run = mms_task_retrieve_run; + klass->fn_cancel = mms_task_retrieve_cancel; + klass->fn_transmit = mms_task_retrieve_transmit; + klass->fn_network_unavailable = mms_task_retrieve_network_unavailable; +} + +/** + * Per instance initializer + */ +static +void +mms_task_retrieve_init( + MMSTaskRetrieve* retrieve) +{ + retrieve->status_code = SOUP_STATUS_CANCELLED; +} + +/* Create MMS retrieve task */ +MMSTask* +mms_task_retrieve_new( + const MMSConfig* config, + MMSHandler* handler, + const char* id, + const char* imsi, + const MMSPdu* pdu) +{ + const time_t now = time(NULL); + + MMS_ASSERT(pdu); + MMS_ASSERT(pdu->type == MMS_MESSAGE_TYPE_NOTIFICATION_IND); + MMS_ASSERT(pdu->transaction_id); + if (pdu->ni.expiry > now) { + MMSTaskRetrieve* retrieve = mms_task_alloc(MMS_TYPE_TASK_RETRIEVE, + config, handler, "Retrieve", id, imsi); + retrieve->task.deadline = pdu->ni.expiry; + retrieve->uri = g_strdup(pdu->ni.location); + retrieve->transaction_id = g_strdup(pdu->transaction_id); + return &retrieve->task; + } else { + MMS_ERR("Message already expired"); + } + return NULL; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_task_upload.c b/mms-lib/src/mms_task_upload.c new file mode 100644 index 0000000..62f9a64 --- /dev/null +++ b/mms-lib/src/mms_task_upload.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_task.h" +#include "mms_connection.h" +#include "mms_file_util.h" +#include "mms_util.h" +#include + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_task_upload_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-task-upload"); + +#define MMS_UPLOAD_MAX_CHUNK (2048) + +/* Class definition */ +typedef MMSTaskClass MMSTaskUploadClass; +typedef struct mms_task_upload { + MMSTask task; + MMSHttpTransfer* tx; + gulong wrote_headers_signal_id; + gulong wrote_chunk_signal_id; + gsize bytes_total; + gsize bytes_sent; + char* file; +} MMSTaskUpload; + +G_DEFINE_TYPE(MMSTaskUpload, mms_task_upload, MMS_TYPE_TASK); +#define MMS_TYPE_TASK_UPLOAD (mms_task_upload_get_type()) +#define MMS_TASK_UPLOAD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_TASK_UPLOAD, MMSTaskUpload)) + +static +void +mms_task_upload_finish_transfer( + MMSTaskUpload* up) +{ + if (up->tx) { + SoupMessage* message = up->tx->message; + g_signal_handler_disconnect(message, up->wrote_headers_signal_id); + g_signal_handler_disconnect(message, up->wrote_chunk_signal_id); + mms_http_transfer_free(up->tx); + up->wrote_headers_signal_id = 0; + up->wrote_chunk_signal_id = 0; + up->tx = NULL; + } +} + +static +void +mms_task_upload_finished( + SoupSession* session, + SoupMessage* msg, + gpointer user_data) +{ + MMSTaskUpload* up = user_data; + MMS_ASSERT(up->tx && (up->tx->session == session)); + if (up->tx && (up->tx->session == session)) { + MMS_TASK_STATE next_state; + MMSTask* task = &up->task; + MMS_DEBUG("Upload status %u", msg->status_code); + if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) { + next_state = MMS_TASK_STATE_DONE; + } else { + /* Will retry if this was an I/O error, otherwise we consider + * it a permanent failure */ + if (SOUP_STATUS_IS_TRANSPORT_ERROR(msg->status_code)) { + next_state = MMS_TASK_STATE_SLEEP; + } else { + next_state = MMS_TASK_STATE_DONE; + MMS_WARN("Upload failure %u", msg->status_code); + } + } + mms_task_set_state(task, next_state); + } else { + MMS_VERBOSE_("ignoring stale completion message"); + } +} + +static +void +mms_task_upload_write_next_chunk( + SoupMessage* msg, + MMSTaskUpload* up) +{ +#if MMS_LOG_VERBOSE + if (up->bytes_sent) MMS_VERBOSE("%u bytes", (guint)up->bytes_sent); +#endif + MMS_ASSERT(up->tx && up->tx->message == msg); + if (up->tx && + up->tx->message == msg && + up->bytes_total > up->bytes_sent) { + int nbytes; + void* chunk; + gsize len = up->bytes_total - up->bytes_sent; + if (len > MMS_UPLOAD_MAX_CHUNK) len = MMS_UPLOAD_MAX_CHUNK; + chunk = g_malloc(len); + nbytes = read(up->tx->fd, chunk, len); + if (nbytes > 0) { + up->bytes_sent += nbytes; + soup_message_body_append_take(msg->request_body, chunk, nbytes); + return; + } + } + soup_message_body_complete(msg->request_body); +} + +static +gboolean +mms_task_upload_start( + MMSTaskUpload* up, + MMSConnection* connection) +{ + int fd; + MMS_ASSERT(mms_connection_is_open(connection)); + mms_task_upload_finish_transfer(up); + up->bytes_sent = 0; + fd = open(up->file, O_RDONLY); + if (fd >= 0) { + struct stat st; + int err = fstat(fd, &st); + if (!err) { + /* Set up the transfer */ + up->bytes_total = st.st_size; + up->tx = mms_http_transfer_new(up->task.config, connection, + "POST", connection->mmsc, fd); + if (up->tx) { + /* Headers */ + SoupMessage* msg = up->tx->message; + soup_message_headers_set_content_type( + msg->request_headers, + MMS_CONTENT_TYPE, NULL); + soup_message_headers_set_content_length( + msg->request_headers, + st.st_size); + + /* Connect the signals */ + up->wrote_headers_signal_id = + g_signal_connect(msg, "wrote_headers", + G_CALLBACK(mms_task_upload_write_next_chunk), up); + up->wrote_chunk_signal_id = + g_signal_connect(msg, "wrote_chunk", + G_CALLBACK(mms_task_upload_write_next_chunk), up); + + /* Start the transfer */ + MMS_DEBUG("%s -> %s (%u bytes)", up->file, + connection->mmsc, (guint)up->bytes_total); + soup_session_queue_message(up->tx->session, msg, + mms_task_upload_finished, up); + return TRUE; + } + } else { + MMS_ERR("Can't stat %s: %s", up->file, strerror(errno)); + } + close(fd); + } else { + MMS_WARN("Failed to open %s: %s", up->file, strerror(errno)); + } + return FALSE; +} + +static +void +mms_task_upload_transmit( + MMSTask* task, + MMSConnection* conn) +{ + if (task->state != MMS_TASK_STATE_TRANSMITTING) { + mms_task_set_state(task, + mms_task_upload_start(MMS_TASK_UPLOAD(task), conn) ? + MMS_TASK_STATE_TRANSMITTING : MMS_TASK_STATE_DONE); + } +} + +static +void +mms_task_upload_run( + MMSTask* task) +{ + mms_task_set_state(task, MMS_TASK_STATE_NEED_CONNECTION); +} + +static +void +mms_task_upload_network_unavailable( + MMSTask* task) +{ + MMSTaskUpload* up = MMS_TASK_UPLOAD(task); + mms_task_upload_finish_transfer(up); + mms_task_set_state(task, MMS_TASK_STATE_SLEEP); +} + +static +void +mms_task_upload_cancel( + MMSTask* task) +{ + mms_task_upload_finish_transfer(MMS_TASK_UPLOAD(task)); + MMS_TASK_CLASS(mms_task_upload_parent_class)->fn_cancel(task); +} + +/** + * First stage of deinitialization (release all references). + * May be called more than once in the lifetime of the object. + */ +static +void +mms_task_upload_dispose( + GObject* object) +{ + MMSTaskUpload* up = MMS_TASK_UPLOAD(object); + mms_task_upload_finish_transfer(up); + G_OBJECT_CLASS(mms_task_upload_parent_class)->dispose(object); +} + +/** + * Final stage of deinitialization + */ +static +void +mms_task_upload_finalize( + GObject* object) +{ + MMSTaskUpload* up = MMS_TASK_UPLOAD(object); + if (!up->task.config->keep_temp_files) { + mms_remove_file_and_dir(up->file); + } + g_free(up->file); + G_OBJECT_CLASS(mms_task_upload_parent_class)->finalize(object); +} + +/** + * Per class initializer + */ +static +void +mms_task_upload_class_init( + MMSTaskUploadClass* klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS(klass); + klass->fn_run = mms_task_upload_run; + klass->fn_transmit = mms_task_upload_transmit; + klass->fn_network_unavailable = mms_task_upload_network_unavailable; + klass->fn_cancel = mms_task_upload_cancel; + object_class->dispose = mms_task_upload_dispose; + object_class->finalize = mms_task_upload_finalize; +} + +/** + * Per instance initializer + */ +static +void +mms_task_upload_init( + MMSTaskUpload* up) +{ +} + +/** + * Create MMS upload task + */ +MMSTask* +mms_task_upload_new( + const MMSConfig* config, + MMSHandler* handler, + const char* name, + const char* id, + const char* imsi, + const char* file) +{ + MMSTaskUpload* up = mms_task_alloc(MMS_TYPE_TASK_UPLOAD, + config, handler, name, id, imsi); + up->file = g_strdup(file); + MMS_ASSERT(g_file_test(up->file, G_FILE_TEST_IS_REGULAR)); + return &up->task; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_util.c b/mms-lib/src/mms_util.c new file mode 100644 index 0000000..bd72dcf --- /dev/null +++ b/mms-lib/src/mms_util.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_util.h" +#include "mms_lib_util.h" +#include "mms_connection.h" +#include "mms_codec.h" + +#ifndef _WIN32 +# include +# include +# include +#endif + +/* Appeared in libsoup somewhere between 2.41.5 and 2.41.90 */ +#ifndef SOUP_SESSION_LOCAL_ADDRESS +# define SOUP_SESSION_LOCAL_ADDRESS "local-address" +#endif + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_util_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-util"); + +/** + * Strips leading spaces and "/TYPE=" suffix from the string. + */ +char* +mms_strip_address_type( + char* address) +{ + if (address) { + char* type = g_strrstr(g_strstrip(address), "/TYPE="); + if (type) *type = 0; + } + return address; +} + +/** + * Splits comma-separated list of addresses into an array of string pointers. + * Strips "/TYPE=" suffix from each address. Caller needs to deallocate the + * returned list with g_strfreev. + */ +char** +mms_split_address_list( + const char* addres_list) +{ + char** list = NULL; + if (addres_list && addres_list[0]) { + int i; + list = g_strsplit(addres_list, ",", 0); + for (i=0; list[i]; i++) { + list[i] = mms_strip_address_type(list[i]); + } + } else { + list = g_new(char*, 1); + list[0] = NULL; + } + return list; +} + +/** + * Allocates and decodes WAP push PDU. Returns NULL if decoding fails. + */ +MMSPdu* +mms_decode_bytes( + GBytes* bytes) +{ + MMSPdu* pdu = NULL; + if (bytes) { + gsize len = 0; + const guint8* data = g_bytes_get_data(bytes, &len); + pdu = g_new0(MMSPdu, 1); + if (!mms_message_decode(data, len, pdu)) { + mms_message_free(pdu); + pdu = NULL; + } + } + return pdu; +} + +/** + * Utility to converts string URI into SoupURI + */ +SoupURI* +mms_parse_http_uri( + const char* raw_uri) +{ + SoupURI* uri = NULL; + if (raw_uri) { + static const char* http = "http://"; + const char* uri_to_parse; + char* tmp_uri = NULL; + if (g_str_has_prefix(raw_uri, http)) { + uri_to_parse = raw_uri; + } else { + uri_to_parse = tmp_uri = g_strconcat(http, raw_uri, NULL); + } + uri = soup_uri_new(uri_to_parse); + if (!uri) { + MMS_ERR("Could not parse %s as a URI", uri_to_parse); + } + g_free(tmp_uri); + } + return uri; +} + +/** + * Sets up new SOUP session + */ +static +SoupSession* +mms_create_http_session( + const MMSConfig* cfg, + MMSConnection* conn) +{ + SoupSession* session = NULL; + + /* Determine address of the MMS interface */ + if (conn->netif && conn->netif[0]) { +#ifndef _WIN32 + struct ifreq ifr; + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd >= 0) { + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, conn->netif, IFNAMSIZ-1); + if (ioctl(fd, SIOCGIFADDR, &ifr) >= 0) { + SoupAddress* local_address = soup_address_new_from_sockaddr( + &ifr.ifr_addr, sizeof(ifr.ifr_addr)); +# if MMS_LOG_DEBUG + char buf[128]; + int af = ifr.ifr_addr.sa_family; + buf[0] = 0; + if (af == AF_INET) { + struct sockaddr_in* addr = (void*)&ifr.ifr_addr; + inet_ntop(af, &addr->sin_addr, buf, sizeof(buf)); + } else if (af == AF_INET6) { + struct sockaddr_in6* addr = (void*)&ifr.ifr_addr; + inet_ntop(af, &addr->sin6_addr, buf, sizeof(buf)); + } else { + snprintf(buf, sizeof(buf), "
", af); + } + buf[sizeof(buf)-1] = 0; + MMS_DEBUG("MMS interface address %s", buf); +# endif /* MMS_LOG_DEBUG */ + MMS_ASSERT(local_address); + session = soup_session_async_new_with_options( + SOUP_SESSION_LOCAL_ADDRESS, local_address, + NULL); + g_object_unref(local_address); + } else { + MMS_ERR("Failed to query IP address of %s: %s", + conn->netif, strerror(errno)); + } + close(fd); + } +#endif /* _WIN32 */ + } else { + MMS_WARN("MMS interface is unknown"); + } + + if (!session) { + /* No local address so bind to any interface */ + session = soup_session_async_new(); + } + + if (conn->mmsproxy && conn->mmsproxy[0]) { + SoupURI* proxy_uri = mms_parse_http_uri(conn->mmsproxy); + if (proxy_uri) { + MMS_DEBUG("MMS proxy %s", conn->mmsproxy); + g_object_set(session, SOUP_SESSION_PROXY_URI, proxy_uri, NULL); + soup_uri_free(proxy_uri); + } + } + + if (cfg->user_agent) { + g_object_set(session, SOUP_SESSION_USER_AGENT, cfg->user_agent, NULL); + } + + return session; +} + +/** + * Create HTTP transfer context. + */ +MMSHttpTransfer* +mms_http_transfer_new( + const MMSConfig* config, + MMSConnection* connection, + const char* method, + const char* uri, + int fd) +{ + SoupURI* soup_uri = soup_uri_new(uri); + if (soup_uri) { + MMSHttpTransfer* tx = g_new(MMSHttpTransfer, 1); + tx->session = mms_create_http_session(config, connection); + tx->message = soup_message_new_from_uri(method, soup_uri); + tx->connection = mms_connection_ref(connection); + tx->fd = fd; + soup_uri_free(soup_uri); + soup_message_set_flags(tx->message, + SOUP_MESSAGE_NO_REDIRECT | + SOUP_MESSAGE_NEW_CONNECTION); + /* We shouldn't need this extra reference but otherwise + * SoupMessage gets deallocated too early. Not sure why. */ + g_object_ref(tx->message); + return tx; + } + return NULL; +} + +/** + * Deallocates HTTP transfer context created by mms_http_transfer_new() + */ +void +mms_http_transfer_free( + MMSHttpTransfer* tx) +{ + if (tx) { + soup_session_abort(tx->session); + g_object_unref(tx->session); + g_object_unref(tx->message); + mms_connection_unref(tx->connection); + close(tx->fd); + g_free(tx); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/src/mms_util.h b/mms-lib/src/mms_util.h new file mode 100644 index 0000000..ae9a3fa --- /dev/null +++ b/mms-lib/src/mms_util.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_UTIL_H +#define JOLLA_MMS_UTIL_H + +#include "mms_lib_types.h" +#include + +typedef struct mms_http_transfer { + MMSConnection* connection; + SoupSession* session; + SoupMessage* message; + int fd; +} MMSHttpTransfer; + +char* +mms_strip_address_type( + char* address); + +char** +mms_split_address_list( + const char* addres_list); + +MMSPdu* +mms_decode_bytes( + GBytes* bytes); + +SoupURI* +mms_parse_http_uri( + const char* raw_uri); + +MMSHttpTransfer* +mms_http_transfer_new( + const MMSConfig* config, + MMSConnection* connection, + const char* method, + const char* uri, + int fd); + +void +mms_http_transfer_free( + MMSHttpTransfer* tx); + +/* NULL-resistant variant of g_strstrip */ +G_INLINE_FUNC char* mms_strip(char* str) + { return str ? g_strstrip(str) : NULL; } + +#endif /* JOLLA_MMS_UTIL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/Makefile b/mms-lib/test/Makefile new file mode 100644 index 0000000..2506b5c --- /dev/null +++ b/mms-lib/test/Makefile @@ -0,0 +1,8 @@ +# -*- Mode: makefile-gmake -*- + +all: +%: + @$(MAKE) -C mms_codec $* + @$(MAKE) -C read_report $* + @$(MAKE) -C retrieve $* + @$(MAKE) -C retrieve_cancel $* diff --git a/mms-lib/test/common/Makefile b/mms-lib/test/common/Makefile new file mode 100644 index 0000000..99d10f9 --- /dev/null +++ b/mms-lib/test/common/Makefile @@ -0,0 +1,163 @@ +# -*- Mode: makefile-gmake -*- + +.PHONY: all debug release clean test test_banner +.PHONY: mms_lib_debug_lib mms_lib_release_lib + +# +# Real test makefile defines EXE and SRC and includes this one. +# + +ifndef SRC +${error SRC not defined} +endif + +ifndef EXE +${error EXE not defined} +endif + +# +# Required packages +# +LIB_PKGS = libwspcodec libsoup-2.4 glib-2.0 +PKGS = $(LIB_PKGS) + +# +# Default target +# + +all: debug release + +# +# Directories +# + +SRC_DIR = . +COMMON_DIR = ../common +BUILD_DIR = build +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Code coverage +# + +ifndef GCOV +GCOV = 0 +endif + +ifneq ($(GCOV),0) +BASE_FLAGS += --coverage +endif + +# +# mms-lib +# + +MMS_LIB_LIB = libmms.a +MMS_LIB_DIR = ../.. +MMS_LIB_BUILD_DIR = $(MMS_LIB_DIR)/build +MMS_LIB_DEBUG_LIB = $(MMS_LIB_BUILD_DIR)/debug/$(MMS_LIB_LIB) +MMS_LIB_RELEASE_LIB = $(MMS_LIB_BUILD_DIR)/release/$(MMS_LIB_LIB) +MMS_LIB_MAKE = $(MAKE) --no-print-directory -C $(MMS_LIB_DIR) GCOV=$(GCOV) + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +DEBUG_FLAGS = $(BASE_FLAGS) -g +RELEASE_FLAGS = $(BASE_FLAGS) -O2 +DEBUG_DEFS = -DDEBUG +RELEASE_DEFS = +WARNINGS = -Wall +LIBS = $(shell pkg-config --libs $(LIB_PKGS)) +CFLAGS = $(shell pkg-config --cflags $(PKGS)) -I$(MMS_LIB_DIR)/include \ + -I$(MMS_LIB_DIR)/src -I$(COMMON_DIR) -MMD + +DEBUG_CFLAGS = $(WARNINGS) $(DEBUG_FLAGS) $(DEBUG_DEFS) $(CFLAGS) +RELEASE_CFLAGS = $(WARNINGS) $(RELEASE_FLAGS) $(RELEASE_DEFS) $(CFLAGS) +DEBUG_LIBS = $(MMS_LIB_DEBUG_LIB) $(LIBS) +RELEASE_LIBS = $(MMS_LIB_RELEASE_LIB) $(LIBS) + +# +# Files +# + +DEBUG_OBJS = \ + $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) \ + $(COMMON_SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = \ + $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) \ + $(COMMON_SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEBUG_DEPS = mms_lib_debug_lib +RELEASE_DEPS = mms_lib_release_lib +DEBUG_EXE_DEPS = $(MMS_LIB_DEBUG_LIB) $(DEBUG_BUILD_DIR) +RELEASE_EXE_DEPS = $(MMS_LIB_RELEASE_LIB) $(RELEASE_BUILD_DIR) +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +# +# Rules +# + +DEBUG_EXE = $(DEBUG_BUILD_DIR)/$(EXE) +RELEASE_EXE = $(RELEASE_BUILD_DIR)/$(EXE) + +debug: $(DEBUG_DEPS) $(DEBUG_EXE) + +release: $(RELEASE_DEPS) $(RELEASE_EXE) + +clean: + $(MAKE) -w -C $(MMS_LIB_DIR) clean + rm -fr $(BUILD_DIR) $(SRC_DIR)/*~ + +test_banner: + @echo "===========" $(EXE) "=========== " + +test: test_banner debug + @$(DEBUG_EXE) + +mms_lib_debug_lib: + @$(MMS_LIB_MAKE) debug + +mms_lib_release_lib: + @$(MMS_LIB_MAKE) release + +$(MMS_LIB_DEBUG_LIB): mms_lib_debug_lib + +$(MMS_LIB_RELEASE_LIB): mms_lib_release_lib + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o : $(COMMON_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(COMMON_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_EXE): $(DEBUG_EXE_DEPS) $(DEBUG_OBJS) + $(LD) $(DEBUG_FLAGS) $(DEBUG_OBJS) $(DEBUG_LIBS) -o $@ + +$(RELEASE_EXE): $(RELEASE_EXE_DEPS) $(RELEASE_OBJS) + $(LD) $(RELEASE_FLAGS) $(RELEASE_OBJS) $(RELEASE_LIBS) -o $@ + strip $@ diff --git a/mms-lib/test/common/test_connection.c b/mms-lib/test/common/test_connection.c new file mode 100644 index 0000000..0828e67 --- /dev/null +++ b/mms-lib/test/common/test_connection.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "test_connection.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_connection_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-connection-test"); + +typedef MMSConnectionClass MMSConnectionTestClass; +typedef MMSConnection MMSConnectionTest; + +G_DEFINE_TYPE(MMSConnectionTest, mms_connection_test, MMS_TYPE_CONNECTION); +#define MMS_TYPE_CONNECTION_TEST (mms_connection_test_get_type()) +#define MMS_CONNECTION_TEST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_CONNECTION_TEST, MMSConnectionTest)) + +static +gboolean +mms_connection_test_set_state( + MMSConnection* test, + MMS_CONNECTION_STATE state) +{ + if (test->state != state && test->state != MMS_CONNECTION_STATE_CLOSED) { + test->state = state; + if (test->delegate && + test->delegate->fn_connection_state_changed) { + test->delegate->fn_connection_state_changed(test->delegate, test); + } + } + return TRUE; +} + +static +gboolean +test_connection_test_open( + void* param) +{ + MMSConnectionTest* test = param; + mms_connection_test_set_state(test, test->netif ? + MMS_CONNECTION_STATE_OPEN : MMS_CONNECTION_ERROR_FAILED); + mms_connection_unref(test); + return FALSE; +} + +MMSConnection* +mms_connection_test_new( + const char* imsi, + unsigned short port) +{ + MMSConnectionTest* test = g_object_new(MMS_TYPE_CONNECTION_TEST, NULL); + test->imsi = g_strdup(imsi); + test->mmsc = g_strdup("http://mmsc"); + if (port) { + test->mmsproxy = g_strdup_printf("127.0.0.1:%hu", port); + test->netif = g_strdup("lo"); + } + test->state = MMS_CONNECTION_STATE_OPENING; + g_idle_add(test_connection_test_open, mms_connection_ref(test)); + return test; +} + +static +void +mms_connection_test_close( + MMSConnection* test) +{ + mms_connection_test_set_state(test, MMS_CONNECTION_STATE_CLOSED); +} + +/** + * Per class initializer + */ +static +void +mms_connection_test_class_init( + MMSConnectionTestClass* klass) +{ + klass->fn_close = mms_connection_test_close; +} + +/** + * Per instance initializer + */ +static +void +mms_connection_test_init( + MMSConnectionTest* conn) +{ + MMS_VERBOSE_("%p", conn); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/common/test_connection.h b/mms-lib/test/common/test_connection.h new file mode 100644 index 0000000..3c432a6 --- /dev/null +++ b/mms-lib/test/common/test_connection.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef TEST_CONNECTION_H +#define TEST_CONNECTION_H + +#include "mms_connection.h" + +MMSConnection* +mms_connection_test_new( + const char* imsi, + unsigned short port); + +#endif /* TEST_CONNECTION_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/common/test_connman.c b/mms-lib/test/common/test_connman.c new file mode 100644 index 0000000..c4212ad --- /dev/null +++ b/mms-lib/test/common/test_connman.c @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "test_connman.h" +#include "test_connection.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_connman_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-connman-test"); + +typedef MMSConnManClass MMSConnManTestClass; +typedef struct mms_connman_test { + MMSConnMan cm; + MMSConnection* connection; + unsigned short port; +} MMSConnManTest; + +G_DEFINE_TYPE(MMSConnManTest, mms_connman_test, MMS_TYPE_CONNMAN); +#define MMS_TYPE_CONNMAN_TEST (mms_connman_test_get_type()) +#define MMS_CONNMAN_TEST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_CONNMAN_TEST, MMSConnManTest)) + +void +mms_connman_test_set_port( + MMSConnMan* cm, + unsigned short port) +{ + MMSConnManTest* test = MMS_CONNMAN_TEST(cm); + test->port = port; +} + +void +mms_connman_test_close_connection( + MMSConnMan* cm) +{ + MMSConnManTest* test = MMS_CONNMAN_TEST(cm); + if (test->connection) { + MMS_DEBUG("Closing connection..."); + mms_connection_close(test->connection); + mms_connection_unref(test->connection); + test->connection = NULL; + } +} + +static +MMSConnection* +mms_connman_test_open_connection( + MMSConnMan* cm, + const char* imsi, + gboolean user_request) +{ + MMSConnManTest* test = MMS_CONNMAN_TEST(cm); + mms_connman_test_close_connection(cm); + if (test->port) { + test->connection = mms_connection_test_new(imsi, test->port); + return mms_connection_ref(test->connection); + } else { + return NULL; + } +} + +static +void +mms_connman_test_dispose( + GObject* object) +{ + MMSConnManTest* test = MMS_CONNMAN_TEST(object); + mms_connman_test_close_connection(&test->cm); +} + +static +void +mms_connman_test_class_init( + MMSConnManTestClass* klass) +{ + klass->fn_open_connection = mms_connman_test_open_connection; + G_OBJECT_CLASS(klass)->dispose = mms_connman_test_dispose; +} + +static +void +mms_connman_test_init( + MMSConnManTest* cm) +{ +} + +MMSConnMan* +mms_connman_test_new() +{ + return g_object_new(MMS_TYPE_CONNMAN_TEST,NULL); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/common/test_connman.h b/mms-lib/test/common/test_connman.h new file mode 100644 index 0000000..b2e3103 --- /dev/null +++ b/mms-lib/test/common/test_connman.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef TEST_CONNMAN_H +#define TEST_CONNMAN_H + +#include "mms_connman.h" + +MMSConnMan* +mms_connman_test_new(void); + +void +mms_connman_test_set_port( + MMSConnMan* cm, + unsigned short port); + +void +mms_connman_test_close_connection( + MMSConnMan* cm); + +#endif /* TEST_CONNMAN_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/common/test_handler.c b/mms-lib/test/common/test_handler.c new file mode 100644 index 0000000..9bbc602 --- /dev/null +++ b/mms-lib/test/common/test_handler.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "test_handler.h" +#include "mms_dispatcher.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_handler_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-handler-test"); + +/* Class definition */ +typedef MMSHandlerClass MMSHandlerTestClass; +typedef struct mms_handler_test { + MMSHandler handler; + unsigned int last_id; + GHashTable* recs; + MMSDispatcher* dispatcher; +} MMSHandlerTest; + +typedef struct mms_handler_record { + char* id; + char* imsi; + MMSMessage* msg; + GBytes* data; + MMS_RECEIVE_STATE receive_state; + guint receive_message_id; + MMSDispatcher* dispatcher; +} MMSHandlerRecord; + +G_DEFINE_TYPE(MMSHandlerTest, mms_handler_test, MMS_TYPE_HANDLER); +#define MMS_TYPE_HANDLER_TEST (mms_handler_test_get_type()) +#define MMS_HANDLER_TEST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MMS_TYPE_HANDLER_TEST, MMSHandlerTest)) + +MMS_RECEIVE_STATE +mms_handler_test_receive_state( + MMSHandler* handler, + const char* id) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(handler); + MMSHandlerRecord* rec = NULL; + if (id) { + rec = g_hash_table_lookup(test->recs, id); + } else if (g_hash_table_size(test->recs) == 1) { + GList* values = g_hash_table_get_values(test->recs); + rec = g_list_first(values)->data; + g_list_free(values); + } + return rec ? rec->receive_state : MMS_RECEIVE_STATE_INVALID; +} + +static +void +mms_handler_test_receive_pending_check( + gpointer key, + gpointer value, + gpointer user_data) +{ + MMSHandlerRecord* rec = value; + gboolean* pending = user_data; + if (rec->receive_message_id) *pending = TRUE; +} + +gboolean +mms_handler_test_receive_pending( + MMSHandler* handler, + const char* id) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(handler); + MMSHandlerRecord* rec = NULL; + if (id) { + rec = g_hash_table_lookup(test->recs, id); + return rec && rec->receive_message_id; + } else { + gboolean pending = FALSE; + g_hash_table_foreach(test->recs, + mms_handler_test_receive_pending_check, &pending); + return pending; + } +} + +static +void +mms_handler_test_hash_remove_record( + gpointer data) +{ + MMSHandlerRecord* rec = data; + if (rec->receive_message_id) { + g_source_remove(rec->receive_message_id); + rec->receive_message_id = 0; + } + mms_dispatcher_unref(rec->dispatcher); + mms_message_unref(rec->msg); + g_bytes_unref(rec->data); + g_free(rec->imsi); + g_free(rec->id); + g_free(rec); +} + +static +gboolean +mms_handler_test_receive( + gpointer data) +{ + MMSHandlerRecord* rec = data; + MMSDispatcher* disp = rec->dispatcher; + MMS_ASSERT(rec->receive_message_id); + MMS_ASSERT(rec->dispatcher); + MMS_DEBUG("Initiating receive of message %s", rec->id); + rec->receive_message_id = 0; + rec->dispatcher = NULL; + mms_dispatcher_receive_message(disp, rec->id, rec->imsi, TRUE, rec->data); + mms_dispatcher_start(disp); + mms_dispatcher_unref(disp); + return FALSE; +} + +static +char* +mms_handler_test_message_notify( + MMSHandler* handler, + const char* imsi, + const char* from, + const char* subj, + time_t expiry, + GBytes* data) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(handler); + MMSHandlerRecord* rec = g_new0(MMSHandlerRecord, 1); + unsigned int rec_id = (++test->last_id); + rec->id = g_strdup_printf("%u", rec_id); + rec->imsi = g_strdup(imsi); + rec->receive_state = MMS_RECEIVE_STATE_INVALID; + rec->data = g_bytes_ref(data); + MMS_DEBUG("Push %s imsi=%s from=%s subj=%s", rec->id, imsi, from, subj); + g_hash_table_replace(test->recs, rec->id, rec); + if (test->dispatcher) { + rec->receive_message_id = g_idle_add(mms_handler_test_receive, rec); + rec->dispatcher = mms_dispatcher_ref(test->dispatcher); + return g_strdup(""); + } else { + return g_strdup(rec->id); + } +} + +static +gboolean +mms_handler_test_message_received( + MMSHandler* handler, + MMSMessage* msg) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(handler); + MMSHandlerRecord* rec = g_hash_table_lookup(test->recs, msg->id); + MMS_DEBUG("Message %s from=%s subj=%s", msg->id, msg->from, msg->subject); + MMS_ASSERT(rec); + if (rec) { + mms_message_unref(rec->msg); + rec->msg = mms_message_ref(msg); + return TRUE; + } else { + return FALSE; + } +} + +static +gboolean +mms_handler_test_message_receive_state_changed( + MMSHandler* handler, + const char* id, + MMS_RECEIVE_STATE state) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(handler); + MMSHandlerRecord* rec = g_hash_table_lookup(test->recs, id); + if (rec) { + rec->receive_state = state; + MMS_DEBUG("Message %s state %d", id, state); + return TRUE; + } else { + MMS_ERR("Invalid message id %s", id); + return FALSE; + } +} + +void +mms_handler_test_finalize( + GObject* object) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(object); + mms_dispatcher_unref(test->dispatcher); + g_hash_table_remove_all(test->recs); + g_hash_table_unref(test->recs); + G_OBJECT_CLASS(mms_handler_test_parent_class)->finalize(object); +} + +static +void +mms_handler_test_class_init( + MMSHandlerTestClass* klass) +{ + G_OBJECT_CLASS(klass)->finalize = mms_handler_test_finalize; + klass->fn_message_notify = mms_handler_test_message_notify; + klass->fn_message_received = mms_handler_test_message_received; + klass->fn_message_receive_state_changed = + mms_handler_test_message_receive_state_changed; +} + +static +void +mms_handler_test_init( + MMSHandlerTest* test) +{ + test->recs = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, mms_handler_test_hash_remove_record); +} + +MMSHandler* +mms_handler_test_new() +{ + return g_object_new(MMS_TYPE_HANDLER_TEST, NULL); +} + +void +mms_handler_test_defer_receive( + MMSHandler* handler, + MMSDispatcher* dispatcher) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(handler); + mms_dispatcher_unref(test->dispatcher); + test->dispatcher = mms_dispatcher_ref(dispatcher); +} + +void +mms_handler_test_reset( + MMSHandler* handler) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(handler); + g_hash_table_remove_all(test->recs); + mms_dispatcher_unref(test->dispatcher); + test->dispatcher = NULL; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/common/test_handler.h b/mms-lib/test/common/test_handler.h new file mode 100644 index 0000000..c0e0541 --- /dev/null +++ b/mms-lib/test/common/test_handler.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef TEST_HANDLER_H +#define TEST_HANDLER_H + +#include "mms_handler.h" + +MMSHandler* +mms_handler_test_new(void); + +MMS_RECEIVE_STATE +mms_handler_test_receive_state( + MMSHandler* handler, + const char* id); + +gboolean +mms_handler_test_receive_pending( + MMSHandler* handler, + const char* id); + +void +mms_handler_test_defer_receive( + MMSHandler* handler, + MMSDispatcher* dispatcher); + +void +mms_handler_test_reset( + MMSHandler* handler); + +#endif /* TEST_HANDLER_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/common/test_http.c b/mms-lib/test/common/test_http.c new file mode 100644 index 0000000..b76f70c --- /dev/null +++ b/mms-lib/test/common/test_http.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "test_http.h" + +#include "mms_log.h" + +#include + +/* A single HTTP transaction */ +struct test_http { + gint ref_count; + SoupServer* server; + GMappedFile* resp_file; + GBytes* req_bytes; + int resp_status; + char* resp_content_type; +}; + +static +void +test_http_callback( + SoupServer* server, + SoupMessage* msg, + const char* path, + GHashTable* query, + SoupClientContext* context, + gpointer data) +{ + char* uri = soup_uri_to_string(soup_message_get_uri (msg), FALSE); + MMS_VERBOSE("%s %s HTTP/1.%d", msg->method, uri, + soup_message_get_http_version(msg)); + g_free(uri); + if (msg->method == SOUP_METHOD_CONNECT) { + soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED); + } else { + TestHttp* http = data; + if (msg->request_body->length) { + SoupBuffer* request = soup_message_body_flatten(msg->request_body); + if (http->req_bytes) g_bytes_unref(http->req_bytes); + http->req_bytes = g_bytes_new_with_free_func(request->data, + request->length, (GDestroyNotify)soup_buffer_free, request); + } + soup_message_set_status(msg, http->resp_status); + soup_message_headers_set_content_type(msg->response_headers, + http->resp_content_type ? http->resp_content_type : "text/plain", + NULL); + soup_message_headers_append(msg->response_headers, + "Accept-Ranges", "bytes"); + soup_message_headers_append(msg->response_headers, + "Connection", "close"); + if (http->resp_file) { + soup_message_headers_set_content_length(msg->response_headers, + g_mapped_file_get_length(http->resp_file)); + soup_message_body_append(msg->response_body, SOUP_MEMORY_TEMPORARY, + g_mapped_file_get_contents(http->resp_file), + g_mapped_file_get_length(http->resp_file)); + } else { + soup_message_headers_set_content_length(msg->response_headers, 0); + } + } + soup_message_body_complete(msg->request_body); +} + +guint +test_http_get_port( + TestHttp* http) +{ + return soup_server_get_port(http->server); +} + +GBytes* +test_http_get_post_data( + TestHttp* http) +{ + return http->req_bytes; +} + +void +test_http_close( + TestHttp* http) +{ + soup_server_quit(http->server); +} + +TestHttp* +test_http_new( + GMappedFile* resp_file, + const char* resp_content_type, + int resp_status) +{ + TestHttp* http = g_new0(TestHttp, 1); + http->ref_count = 1; + if (resp_file) { + http->resp_file = g_mapped_file_ref(resp_file); + http->resp_content_type = g_strdup(resp_content_type); + } + http->resp_status = resp_status; + http->server = g_object_new(SOUP_TYPE_SERVER, NULL); + MMS_DEBUG("Listening on port %hu", soup_server_get_port(http->server)); + soup_server_add_handler(http->server, NULL, test_http_callback, http, NULL); + soup_server_run_async(http->server); + return http; +} + +TestHttp* +test_http_ref( + TestHttp* http) +{ + if (http) { + MMS_ASSERT(http->ref_count > 0); + g_atomic_int_inc(&http->ref_count); + } + return http; +} + +void +test_http_unref( + TestHttp* http) +{ + if (http) { + MMS_ASSERT(http->ref_count > 0); + if (g_atomic_int_dec_and_test(&http->ref_count)) { + test_http_close(http); + if (http->resp_file) g_mapped_file_unref(http->resp_file); + if (http->req_bytes) g_bytes_unref(http->req_bytes); + g_object_unref(http->server); + g_free(http->resp_content_type); + g_free(http); + } + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/common/test_http.h b/mms-lib/test/common/test_http.h new file mode 100644 index 0000000..c0af890 --- /dev/null +++ b/mms-lib/test/common/test_http.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef TEST_HTTP_H +#define TEST_HTTP_H + +#include + +/* Local HTTP server for emulating MMSC */ +typedef struct test_http TestHttp; + +TestHttp* +test_http_new( + GMappedFile* get_file, + const char* resp_content_type, + int resp_status); + +TestHttp* +test_http_ref( + TestHttp* http); + +void +test_http_unref( + TestHttp* http); + +guint +test_http_get_port( + TestHttp* http); + +GBytes* +test_http_get_post_data( + TestHttp* http); + +void +test_http_close( + TestHttp* http); + +#endif /* TEST_CONNMAN_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/coverage/run b/mms-lib/test/coverage/run new file mode 100755 index 0000000..610d668 --- /dev/null +++ b/mms-lib/test/coverage/run @@ -0,0 +1,38 @@ +#!/bin/bash +# +# This script requires lcov to be installed +# + +TESTS="mms_codec read_report retrieve retrieve_cancel" +FLAVOR="release" + +pushd `dirname $0` > /dev/null +COV_DIR="$PWD" +pushd .. > /dev/null +TEST_DIR="$PWD" +pushd .. > /dev/null +BASE_DIR="$PWD" +popd > /dev/null +popd > /dev/null +popd > /dev/null + +for t in $TESTS ; do + make -C "$TEST_DIR/$t" clean +done + +for t in $TESTS ; do + make GCOV=1 -C "$TEST_DIR/$t" $FLAVOR || exit 1 +done + +for t in $TESTS ; do + pushd "$TEST_DIR/$t" + build/$FLAVOR/test_$t || exit 1 + popd +done + +FULL_COV="$COV_DIR/full.gcov" +MMSLIB_COV="$COV_DIR/mms-lib.gcov" +rm -f "$FULL_COV" "$MMSLIB_COV" +lcov -c -d "$BASE_DIR/build/$FLAVOR" -b "$BASE_DIR" -o "$FULL_COV" || exit 1 +lcov -e "$FULL_COV" "$BASE_DIR/src/*" -o "$MMSLIB_COV" || exit 1 +genhtml "$MMSLIB_COV" --output-directory "$COV_DIR/results" || exit 1 diff --git a/mms-lib/test/mms_codec/Makefile b/mms-lib/test/mms_codec/Makefile new file mode 100644 index 0000000..b528ac9 --- /dev/null +++ b/mms-lib/test/mms_codec/Makefile @@ -0,0 +1,6 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_mms_codec +SRC = test_mms_codec.c + +include ../common/Makefile diff --git a/mms-lib/test/mms_codec/data/m-delivery.ind b/mms-lib/test/mms_codec/data/m-delivery.ind new file mode 100644 index 0000000..a933447 Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-delivery.ind differ diff --git a/mms-lib/test/mms_codec/data/m-notification_1.0.ind b/mms-lib/test/mms_codec/data/m-notification_1.0.ind new file mode 100644 index 0000000..ce88f7e Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-notification_1.0.ind differ diff --git a/mms-lib/test/mms_codec/data/m-notification_1.1.ind b/mms-lib/test/mms_codec/data/m-notification_1.1.ind new file mode 100644 index 0000000..f9bcc7d Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-notification_1.1.ind differ diff --git a/mms-lib/test/mms_codec/data/m-notification_1.2.ind b/mms-lib/test/mms_codec/data/m-notification_1.2.ind new file mode 100644 index 0000000..91799ac Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-notification_1.2.ind differ diff --git a/mms-lib/test/mms_codec/data/m-notifyresp.ind b/mms-lib/test/mms_codec/data/m-notifyresp.ind new file mode 100644 index 0000000..33f2edb Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-notifyresp.ind differ diff --git a/mms-lib/test/mms_codec/data/m-read-orig.ind b/mms-lib/test/mms_codec/data/m-read-orig.ind new file mode 100644 index 0000000..dd3fce7 Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-read-orig.ind differ diff --git a/mms-lib/test/mms_codec/data/m-read-rec.ind b/mms-lib/test/mms_codec/data/m-read-rec.ind new file mode 100644 index 0000000..d8558d1 Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-read-rec.ind differ diff --git a/mms-lib/test/mms_codec/data/m-retrieve_1.0.conf b/mms-lib/test/mms_codec/data/m-retrieve_1.0.conf new file mode 100644 index 0000000..b66d78e Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-retrieve_1.0.conf differ diff --git a/mms-lib/test/mms_codec/data/m-retrieve_1.1.conf b/mms-lib/test/mms_codec/data/m-retrieve_1.1.conf new file mode 100644 index 0000000..aabaaf4 Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-retrieve_1.1.conf differ diff --git a/mms-lib/test/mms_codec/data/m-retrieve_1.2.conf b/mms-lib/test/mms_codec/data/m-retrieve_1.2.conf new file mode 100644 index 0000000..5f81591 Binary files /dev/null and b/mms-lib/test/mms_codec/data/m-retrieve_1.2.conf differ diff --git a/mms-lib/test/mms_codec/test_mms_codec.c b/mms-lib/test/mms_codec/test_mms_codec.c new file mode 100644 index 0000000..e795cff --- /dev/null +++ b/mms-lib/test/mms_codec/test_mms_codec.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_log.h" +#include "mms_codec.h" + +static +gboolean +test_parse_mms_pdu( + GBytes* bytes, + struct mms_message* msg) +{ + gsize len = 0; + const guint8* data = g_bytes_get_data(bytes, &len); + return mms_message_decode(data, len, msg); +} + +static +gboolean +test_file( + const char* file, + gboolean (*parse)(GBytes* bytes, struct mms_message* msg)) +{ + GError* error = NULL; + GMappedFile* map = g_mapped_file_new(file, FALSE, &error); + if (map) { + struct mms_message* msg = g_new0(struct mms_message, 1); + const void* data = g_mapped_file_get_contents(map); + const gsize length = g_mapped_file_get_length(map); + GBytes* bytes = g_bytes_new_static(data, length); + gboolean ok = parse(bytes, msg); + g_bytes_unref(bytes); + g_mapped_file_unref(map); + mms_message_free(msg); + if (ok) { + MMS_INFO("OK: %s", file); + return TRUE; + } + MMS_ERR("Failed to decode %s", file); + } else { + MMS_ERR("%s", error->message); + g_error_free(error); + } + return FALSE; +} + +static +gboolean +test_files( + const char* files[], + int count, + gboolean (*parse)(GBytes* bytes, struct mms_message* msg)) +{ + int i; + gboolean ok = TRUE; + for (i=0; i +#include + +#define RET_OK (0) +#define RET_ERR (1) +#define RET_TIMEOUT (2) + +typedef struct test { + MMSDispatcherDelegate delegate; + MMSConnMan* cm; + MMSHandler* handler; + MMSDispatcher* disp; + GMainLoop* loop; + guint timeout_id; + TestHttp* http; + int ret; +} Test; + +static +void +test_done( + MMSDispatcherDelegate* delegate, + MMSDispatcher* dispatcher) +{ + Test* test = MMS_CAST(delegate,Test,delegate); + if (test->ret == RET_OK) { + const void* resp_data = NULL; + gsize resp_len = 0; + GBytes* reply = test_http_get_post_data(test->http); + if (reply) resp_data = g_bytes_get_data(reply, &resp_len); + if (resp_len > 0) { + MMSPdu* pdu = g_new0(MMSPdu, 1); + if (mms_message_decode(resp_data, resp_len, pdu)) { + if (pdu->type != MMS_MESSAGE_TYPE_READ_REC_IND) { + test->ret = RET_ERR; + MMS_ERR("Unexpected PDU type %u", pdu->type); + } + } else { + test->ret = RET_ERR; + MMS_ERR("Can't decode PDU"); + } + mms_message_free(pdu); + } + } + MMS_INFO("%s", (test->ret == RET_OK) ? "OK" : "FAILED"); + g_main_loop_quit(test->loop); +} + +static +gboolean +test_timeout( + gpointer data) +{ + Test* test = data; + test->timeout_id = 0; + test->ret = RET_TIMEOUT; + MMS_INFO("TIMEOUT"); + if (test->http) test_http_close(test->http); + mms_connman_test_close_connection(test->cm); + mms_dispatcher_cancel(test->disp, NULL); + return FALSE; +} + +static +void +test_init( + Test* test, + const MMSConfig* config) +{ + test->cm = mms_connman_test_new(); + test->handler = mms_handler_test_new(); + test->disp = mms_dispatcher_new(config, test->cm, test->handler); + test->loop = g_main_loop_new(NULL, FALSE); + test->timeout_id = g_timeout_add_seconds(10, test_timeout, test); + test->delegate.fn_done = test_done; + mms_dispatcher_set_delegate(test->disp, &test->delegate); + test->http = test_http_new(NULL, NULL, SOUP_STATUS_OK); + mms_connman_test_set_port(test->cm, test_http_get_port(test->http)); + test->ret = RET_ERR; +} + +static +void +test_finalize( + Test* test) +{ + if (test->timeout_id) { + g_source_remove(test->timeout_id); + test->timeout_id = 0; + } + test_http_close(test->http); + test_http_unref(test->http); + mms_connman_test_close_connection(test->cm); + mms_connman_unref(test->cm); + mms_handler_unref(test->handler); + mms_dispatcher_unref(test->disp); + g_main_loop_unref(test->loop); +} + +static +int +test_read_report( + const MMSConfig* config) +{ + Test test; + test_init(&test, config); + if (mms_dispatcher_send_read_report(test.disp, "1", "IMSI", + "MessageID", "+358501111111", MMS_READ_STATUS_READ)) { + if (mms_dispatcher_start(test.disp)) { + test.ret = RET_OK; + g_main_loop_run(test.loop); + } else { + MMS_INFO("FAILED"); + } + } else { + MMS_INFO("FAILED"); + } + test_finalize(&test); + return test.ret; +} + +int main(int argc, char* argv[]) +{ + int ret; + MMSConfig config; + char* tmpd = g_mkdtemp(g_strdup("/tmp/test_retrieve_XXXXXX")); + mms_lib_init(); + mms_lib_default_config(&config); + config.idle_secs = 0; + config.root_dir = tmpd; + mms_log_default.name = "test_read_report"; + if (argc > 1 && !strcmp(argv[1], "-v")) { + mms_log_default.level = MMS_LOGLEVEL_VERBOSE; + } else { + mms_log_default.level = MMS_LOGLEVEL_INFO; + mms_util_log.level = + mms_task_decode_log.level = + mms_task_retrieve_log.level = + mms_task_notification_log.level = MMS_LOGLEVEL_NONE; + mms_log_stdout_timestamp = FALSE; + } + MMS_VERBOSE("Temporary directory %s", tmpd); + ret = test_read_report(&config); + remove(tmpd); + g_free(tmpd); + return ret; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/retrieve/Makefile b/mms-lib/test/retrieve/Makefile new file mode 100644 index 0000000..4c05429 --- /dev/null +++ b/mms-lib/test/retrieve/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_retrieve +SRC = test_retrieve.c +COMMON_SRC = test_connection.c test_connman.c test_handler.c test_http.c + +include ../common/Makefile diff --git a/mms-lib/test/retrieve/data/DeliveryInd/m-delivery.ind b/mms-lib/test/retrieve/data/DeliveryInd/m-delivery.ind new file mode 100644 index 0000000..a933447 Binary files /dev/null and b/mms-lib/test/retrieve/data/DeliveryInd/m-delivery.ind differ diff --git a/mms-lib/test/retrieve/data/Expired/m-notification.ind b/mms-lib/test/retrieve/data/Expired/m-notification.ind new file mode 100644 index 0000000..4555cbf Binary files /dev/null and b/mms-lib/test/retrieve/data/Expired/m-notification.ind differ diff --git a/mms-lib/test/retrieve/data/GarbagePush/garbage b/mms-lib/test/retrieve/data/GarbagePush/garbage new file mode 100644 index 0000000..e41b2a5 --- /dev/null +++ b/mms-lib/test/retrieve/data/GarbagePush/garbage @@ -0,0 +1 @@ +This is not a valid WAP push PDU diff --git a/mms-lib/test/retrieve/data/GarbageRetrieve/garbage b/mms-lib/test/retrieve/data/GarbageRetrieve/garbage new file mode 100644 index 0000000..b5185eb --- /dev/null +++ b/mms-lib/test/retrieve/data/GarbageRetrieve/garbage @@ -0,0 +1 @@ +This is not a valid m-Retrieve.conf PDU diff --git a/mms-lib/test/retrieve/data/GarbageRetrieve/m-notification.ind b/mms-lib/test/retrieve/data/GarbageRetrieve/m-notification.ind new file mode 100644 index 0000000..ce88f7e Binary files /dev/null and b/mms-lib/test/retrieve/data/GarbageRetrieve/m-notification.ind differ diff --git a/mms-lib/test/retrieve/data/NotAllowed/m-notification.ind b/mms-lib/test/retrieve/data/NotAllowed/m-notification.ind new file mode 100644 index 0000000..91799ac Binary files /dev/null and b/mms-lib/test/retrieve/data/NotAllowed/m-notification.ind differ diff --git a/mms-lib/test/retrieve/data/NotAllowed/not-allowed.html b/mms-lib/test/retrieve/data/NotAllowed/not-allowed.html new file mode 100644 index 0000000..adc45b9 --- /dev/null +++ b/mms-lib/test/retrieve/data/NotAllowed/not-allowed.html @@ -0,0 +1,18 @@ + + + + +Restricted request + +
+
+

HTTP Error 403: The service you requested is restricted

+
+

The service you requested is restricted and not available to your browser

+The restriction can be based on your IP-address, hostname, browser software, +time-of-day or other variables. Most likely you requested a service that was +made available to a restricted subnet. +
+
+ diff --git a/mms-lib/test/retrieve/data/NotFound/m-notification.ind b/mms-lib/test/retrieve/data/NotFound/m-notification.ind new file mode 100644 index 0000000..820e689 Binary files /dev/null and b/mms-lib/test/retrieve/data/NotFound/m-notification.ind differ diff --git a/mms-lib/test/retrieve/data/NotFound/not-found.html b/mms-lib/test/retrieve/data/NotFound/not-found.html new file mode 100644 index 0000000..6ed1c75 --- /dev/null +++ b/mms-lib/test/retrieve/data/NotFound/not-found.html @@ -0,0 +1,15 @@ + + + + +Document not found + +
+
+

HTTP Error 404: The requested document was not found

+
+

The document you requested does not exist at this site

+
+
+ diff --git a/mms-lib/test/retrieve/data/ReadOrigInd/m-read-orig.ind b/mms-lib/test/retrieve/data/ReadOrigInd/m-read-orig.ind new file mode 100644 index 0000000..dd3fce7 Binary files /dev/null and b/mms-lib/test/retrieve/data/ReadOrigInd/m-read-orig.ind differ diff --git a/mms-lib/test/retrieve/data/SoonExpired/m-notification.ind b/mms-lib/test/retrieve/data/SoonExpired/m-notification.ind new file mode 100644 index 0000000..3be1e75 Binary files /dev/null and b/mms-lib/test/retrieve/data/SoonExpired/m-notification.ind differ diff --git a/mms-lib/test/retrieve/data/Success/m-acknowledge.ind b/mms-lib/test/retrieve/data/Success/m-acknowledge.ind new file mode 100644 index 0000000..961578c Binary files /dev/null and b/mms-lib/test/retrieve/data/Success/m-acknowledge.ind differ diff --git a/mms-lib/test/retrieve/data/Success/m-notification.ind b/mms-lib/test/retrieve/data/Success/m-notification.ind new file mode 100644 index 0000000..820e689 Binary files /dev/null and b/mms-lib/test/retrieve/data/Success/m-notification.ind differ diff --git a/mms-lib/test/retrieve/data/Success/m-retrieve.conf b/mms-lib/test/retrieve/data/Success/m-retrieve.conf new file mode 100644 index 0000000..20271e7 Binary files /dev/null and b/mms-lib/test/retrieve/data/Success/m-retrieve.conf differ diff --git a/mms-lib/test/retrieve/data/Success/parts/0 b/mms-lib/test/retrieve/data/Success/parts/0 new file mode 100644 index 0000000..cf85a6a --- /dev/null +++ b/mms-lib/test/retrieve/data/Success/parts/0 @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mms-lib/test/retrieve/data/Success/parts/131200181.jpg b/mms-lib/test/retrieve/data/Success/parts/131200181.jpg new file mode 100644 index 0000000..fe09b04 Binary files /dev/null and b/mms-lib/test/retrieve/data/Success/parts/131200181.jpg differ diff --git a/mms-lib/test/retrieve/data/Success/parts/140100041.jpg b/mms-lib/test/retrieve/data/Success/parts/140100041.jpg new file mode 100644 index 0000000..da06184 Binary files /dev/null and b/mms-lib/test/retrieve/data/Success/parts/140100041.jpg differ diff --git a/mms-lib/test/retrieve/data/Success/parts/text_0011.txt b/mms-lib/test/retrieve/data/Success/parts/text_0011.txt new file mode 100644 index 0000000..52a8d4e --- /dev/null +++ b/mms-lib/test/retrieve/data/Success/parts/text_0011.txt @@ -0,0 +1 @@ +Вид с крыши \ No newline at end of file diff --git a/mms-lib/test/retrieve/data/Success/parts/text_0021.txt b/mms-lib/test/retrieve/data/Success/parts/text_0021.txt new file mode 100644 index 0000000..9f5d07e --- /dev/null +++ b/mms-lib/test/retrieve/data/Success/parts/text_0021.txt @@ -0,0 +1 @@ +Дворцовый мост \ No newline at end of file diff --git a/mms-lib/test/retrieve/data/UnsupportedPush/unsupported b/mms-lib/test/retrieve/data/UnsupportedPush/unsupported new file mode 100644 index 0000000..33f2edb Binary files /dev/null and b/mms-lib/test/retrieve/data/UnsupportedPush/unsupported differ diff --git a/mms-lib/test/retrieve/test_retrieve.c b/mms-lib/test/retrieve/test_retrieve.c new file mode 100644 index 0000000..ce02ae2 --- /dev/null +++ b/mms-lib/test/retrieve/test_retrieve.c @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "test_connman.h" +#include "test_handler.h" +#include "test_http.h" + +#include "mms_log.h" +#include "mms_codec.h" +#include "mms_lib_log.h" +#include "mms_lib_util.h" +#include "mms_dispatcher.h" + +#include +#include + +#define RET_OK (0) +#define RET_ERR (1) +#define RET_TIMEOUT (2) + +#define MMS_MESSAGE_TYPE_NONE (0) + +#define DATA_DIR "data/" + +typedef struct test_desc { + const char* name; + const char* dir; + const char* ni_file; + const char* rc_file; + unsigned int status; + const char* content_type; + MMS_RECEIVE_STATE expected_state; + enum mms_message_type reply_msg; + int flags; + +#define TEST_PUSH_HANDLING_FAILURE_OK (0x01) +#define TEST_DEFER_RECEIVE (0x02) + +} TestDesc; + +typedef struct test { + const TestDesc* desc; + MMSDispatcherDelegate delegate; + MMSConnMan* cm; + MMSHandler* handler; + MMSDispatcher* disp; + GMappedFile* notification_ind; + GMappedFile* retrieve_conf; + GMainLoop* loop; + guint timeout_id; + TestHttp* http; + int ret; +} Test; + +static const TestDesc retrieve_tests[] = { + { + "Success", + NULL, + "m-notification.ind", + "m-retrieve.conf", + SOUP_STATUS_OK, + MMS_CONTENT_TYPE, + MMS_RECEIVE_STATE_DECODING, + MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, + 0 + },{ + "DeferSuccess", + "Success", + "m-notification.ind", + "m-retrieve.conf", + SOUP_STATUS_OK, + MMS_CONTENT_TYPE, + MMS_RECEIVE_STATE_DECODING, + MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, + TEST_DEFER_RECEIVE + },{ + "Expired", + NULL, + "m-notification.ind", + NULL, + SOUP_STATUS_NOT_FOUND, + NULL, + MMS_RECEIVE_STATE_DOWNLOAD_ERROR, + MMS_MESSAGE_TYPE_NONE, + 0 + },{ + "SoonExpired", + NULL, + "m-notification.ind", + NULL, + SOUP_STATUS_TRY_AGAIN, + NULL, + MMS_RECEIVE_STATE_DOWNLOAD_ERROR, + MMS_MESSAGE_TYPE_NONE, + 0 + },{ + "NotAllowed", + NULL, + "m-notification.ind", + "not-allowed.html", + SOUP_STATUS_BAD_REQUEST, + "text/html", + MMS_RECEIVE_STATE_DOWNLOAD_ERROR, + MMS_MESSAGE_TYPE_NONE, + 0 + },{ + "NotFound", + NULL, + "m-notification.ind", + "not-found.html", + SOUP_STATUS_NOT_FOUND, + "text/html", + MMS_RECEIVE_STATE_DOWNLOAD_ERROR, + MMS_MESSAGE_TYPE_NONE, + 0 + },{ + "GarbageRetrieve", + NULL, + "m-notification.ind", + "garbage", + SOUP_STATUS_OK, + MMS_CONTENT_TYPE, + MMS_RECEIVE_STATE_DECODING_ERROR, + MMS_MESSAGE_TYPE_NOTIFYRESP_IND, + 0 + },{ + "GarbagePush", + NULL, + "garbage", + NULL, + 0, + NULL, + MMS_RECEIVE_STATE_INVALID, + MMS_MESSAGE_TYPE_NONE, + TEST_PUSH_HANDLING_FAILURE_OK + },{ + "UnsupportedPush", + NULL, + "unsupported", + NULL, + 0, + NULL, + MMS_RECEIVE_STATE_INVALID, + MMS_MESSAGE_TYPE_NONE, + TEST_PUSH_HANDLING_FAILURE_OK + },{ + "ReadOrigInd", + NULL, + "m-read-orig.ind", + NULL, + 0, + NULL, + MMS_RECEIVE_STATE_INVALID, + MMS_MESSAGE_TYPE_NONE, + 0 + },{ + "DeliveryInd", + NULL, + "m-delivery.ind", + NULL, + 0, + NULL, + MMS_RECEIVE_STATE_INVALID, + MMS_MESSAGE_TYPE_NONE, + 0 + } +}; + +static +void +test_finish( + Test* test) +{ + const char* name = test->desc->name; + if (test->ret == RET_OK) { + MMS_RECEIVE_STATE state; + state = mms_handler_test_receive_state(test->handler, NULL); + if (state != test->desc->expected_state) { + test->ret = RET_ERR; + MMS_ERR("Test %s state %d, expected %d", name, state, + test->desc->expected_state); + } else { + const void* resp_data = NULL; + gsize resp_len = 0; + GBytes* reply = test_http_get_post_data(test->http); + if (reply) resp_data = g_bytes_get_data(reply, &resp_len); + if (resp_len > 0) { + if (test->desc->reply_msg) { + MMSPdu* pdu = g_new0(MMSPdu, 1); + if (mms_message_decode(resp_data, resp_len, pdu)) { + if (pdu->type != test->desc->reply_msg) { + test->ret = RET_ERR; + MMS_ERR("Test %s reply %u, expected %u", name, + pdu->type, test->desc->reply_msg); + } + } else { + test->ret = RET_ERR; + MMS_ERR("Test %s can't decode reply message", name); + } + mms_message_free(pdu); + } else { + test->ret = RET_ERR; + MMS_ERR("Test %s expects no reply", name); + } + } else if (test->desc->reply_msg) { + test->ret = RET_ERR; + MMS_ERR("Test %s expects reply", name); + } + } + } + MMS_INFO("Test %s %s", name, (test->ret == RET_OK) ? "OK" : "FAILED"); + mms_handler_test_reset(test->handler); + g_main_loop_quit(test->loop); +} + +static +void +test_done( + MMSDispatcherDelegate* delegate, + MMSDispatcher* dispatcher) +{ + Test* test = MMS_CAST(delegate,Test,delegate); + if (!mms_handler_test_receive_pending(test->handler, NULL)) { + test_finish(test); + } +} + +static +gboolean +test_timeout( + gpointer data) +{ + Test* test = data; + test->timeout_id = 0; + test->ret = RET_TIMEOUT; + MMS_INFO("Test %s TIMEOUT", test->desc->name); + if (test->http) test_http_close(test->http); + mms_connman_test_close_connection(test->cm); + mms_dispatcher_cancel(test->disp, NULL); + return FALSE; +} + +static +gboolean +test_init( + Test* test, + const MMSConfig* config, + const TestDesc* desc) +{ + gboolean ok = FALSE; + GError* error = NULL; + const char* dir = desc->dir ? desc->dir : desc->name; + char* ni = g_strconcat(DATA_DIR, dir, "/", desc->ni_file, NULL); + char* rc = desc->rc_file ? g_strconcat(DATA_DIR, dir, "/", + desc->rc_file, NULL) : NULL; + memset(test, 0, sizeof(*test)); + test->notification_ind = g_mapped_file_new(ni, FALSE, &error); + if (test->notification_ind) { + if (rc) test->retrieve_conf = g_mapped_file_new(rc, FALSE, &error); + if (test->retrieve_conf || !rc) { + g_mapped_file_ref(test->notification_ind); + test->desc = desc; + test->cm = mms_connman_test_new(); + test->handler = mms_handler_test_new(); + test->disp = mms_dispatcher_new(config, test->cm, test->handler); + test->loop = g_main_loop_new(NULL, FALSE); + test->timeout_id = g_timeout_add_seconds(10, test_timeout, test); + test->delegate.fn_done = test_done; + mms_dispatcher_set_delegate(test->disp, &test->delegate); + test->http = test_http_new(test->retrieve_conf, + test->desc->content_type, test->desc->status); + mms_connman_test_set_port(test->cm, test_http_get_port(test->http)); + if (desc->flags & TEST_DEFER_RECEIVE) { + mms_handler_test_defer_receive(test->handler, test->disp); + } + test->ret = RET_ERR; + ok = TRUE; + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + g_mapped_file_unref(test->notification_ind); + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + g_free(ni); + g_free(rc); + return ok; +} + +static +void +test_finalize( + Test* test) +{ + if (test->timeout_id) { + g_source_remove(test->timeout_id); + test->timeout_id = 0; + } + if (test->http) { + test_http_close(test->http); + test_http_unref(test->http); + } + mms_connman_test_close_connection(test->cm); + mms_connman_unref(test->cm); + mms_handler_unref(test->handler); + mms_dispatcher_unref(test->disp); + g_main_loop_unref(test->loop); + g_mapped_file_unref(test->notification_ind); + if (test->retrieve_conf) g_mapped_file_unref(test->retrieve_conf); +} + +static +int +test_retrieve_once( + const MMSConfig* config, + const TestDesc* desc) +{ + Test test; + if (test_init(&test, config, desc)) { + GBytes* push = g_bytes_new_static( + g_mapped_file_get_contents(test.notification_ind), + g_mapped_file_get_length(test.notification_ind)); + if (mms_dispatcher_handle_push(test.disp, "TestConnection", push)) { + if (mms_dispatcher_start(test.disp)) { + test.ret = RET_OK; + g_main_loop_run(test.loop); + } else { + MMS_INFO("Test %s FAILED", desc->name); + } + } else { + if (desc->flags & TEST_PUSH_HANDLING_FAILURE_OK) { + MMS_INFO("Test %s OK", desc->name); + test.ret = RET_OK; + } else { + MMS_INFO("Test %s FAILED", desc->name); + } + } + g_bytes_unref(push); + test_finalize(&test); + return test.ret; + } else { + return RET_ERR; + } +} + +static +int +test_retrieve( + const MMSConfig* config, + const char* name) +{ + int i, ret; + if (name) { + const TestDesc* found = NULL; + for (i=0, ret = RET_ERR; iname, name)) { + ret = test_retrieve_once(config, test); + found = test; + break; + } + } + if (!found) MMS_ERR("No such test: %s", name); + } else { + for (i=0, ret = RET_OK; i 1 && !strcmp(argv[1], "-v")) { + mms_log_default.level = MMS_LOGLEVEL_VERBOSE; + memmove(argv + 1, argv + 2, (argc-2)*sizeof(argv[0])); + argc--; + } else { + mms_log_default.level = MMS_LOGLEVEL_INFO; + mms_util_log.level = + mms_task_decode_log.level = + mms_task_retrieve_log.level = + mms_task_notification_log.level = MMS_LOGLEVEL_NONE; + mms_log_stdout_timestamp = FALSE; + } + + if (argc == 2 && argv[1][0] != '-') { + test_name = argv[1]; + } + + if (argc == 1 || test_name) { + int ret; + char* tmpd = g_mkdtemp(g_strdup("/tmp/test_retrieve_XXXXXX")); + MMS_VERBOSE("Temporary directory %s", tmpd); + config.root_dir = tmpd; + config.idle_secs = 0; + ret = test_retrieve(&config, test_name); + remove(tmpd); + g_free(tmpd); + return ret; + } else { + printf("Usage: test_retrieve [-v] [TEST]\n"); + return RET_ERR; + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/retrieve_cancel/Makefile b/mms-lib/test/retrieve_cancel/Makefile new file mode 100644 index 0000000..c41b0b1 --- /dev/null +++ b/mms-lib/test/retrieve_cancel/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake -*- + +EXE = test_retrieve_cancel +SRC = test_retrieve_cancel.c test_connman.c test_handler.c +COMMON_SRC = test_connection.c + +include ../common/Makefile diff --git a/mms-lib/test/retrieve_cancel/test_connman.c b/mms-lib/test/retrieve_cancel/test_connman.c new file mode 100644 index 0000000..adc46bd --- /dev/null +++ b/mms-lib/test/retrieve_cancel/test_connman.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "test_connman.h" +#include "test_connection.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_connman_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-connman-test"); + +typedef MMSConnManClass MMSConnManTestClass; +typedef struct mms_connman_test { + MMSConnMan cm; + mms_connman_connection_requested_cb connection_requested_cb; + void* connection_requested_param; + gboolean fail; +} MMSConnManTest; + +G_DEFINE_TYPE(MMSConnManTest, mms_connman_test, MMS_TYPE_CONNMAN); +#define MMS_TYPE_CONNMAN_TEST (mms_connman_test_get_type()) +#define MMS_CONNMAN_TEST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_CONNMAN_TEST, MMSConnManTest)) + +void +mms_connman_set_connection_requested_cb( + MMSConnMan* cm, + mms_connman_connection_requested_cb cb, + void* param) +{ + MMSConnManTest* test = MMS_CONNMAN_TEST(cm); + test->connection_requested_cb = cb; + test->connection_requested_param = param; +} + +MMSConnection* +mms_connman_test_open_connection( + MMSConnMan* cm, + const char* imsi, + gboolean user_request) +{ + MMSConnManTest* test = MMS_CONNMAN_TEST(cm); + if (test->fail) { + if (test->connection_requested_cb) { + test->connection_requested_cb(cm, + test->connection_requested_param); + } + return NULL; + } else { + test->fail = TRUE; + return mms_connection_test_new(imsi, 0); + } +} + +static +void +mms_connman_test_class_init( + MMSConnManTestClass* klass) +{ + klass->fn_open_connection = mms_connman_test_open_connection; +} + +static +void +mms_connman_test_init( + MMSConnManTest* cm) +{ +} + +MMSConnMan* +mms_connman_test_new() +{ + return g_object_new(MMS_TYPE_CONNMAN_TEST,NULL); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/retrieve_cancel/test_connman.h b/mms-lib/test/retrieve_cancel/test_connman.h new file mode 100644 index 0000000..33a6508 --- /dev/null +++ b/mms-lib/test/retrieve_cancel/test_connman.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef TEST_CONNMAN_H +#define TEST_CONNMAN_H + +#include "mms_connman.h" + +MMSConnMan* +mms_connman_test_new(void); + +typedef +void +(*mms_connman_connection_requested_cb)( + MMSConnMan* cm, + void* param); + +void +mms_connman_set_connection_requested_cb( + MMSConnMan* cm, + mms_connman_connection_requested_cb cb, + void* param); + +#endif /* TEST_CONNMAN_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/retrieve_cancel/test_handler.c b/mms-lib/test/retrieve_cancel/test_handler.c new file mode 100644 index 0000000..4ab8c42 --- /dev/null +++ b/mms-lib/test/retrieve_cancel/test_handler.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "test_handler.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_handler_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-handler-test"); + +/* Class definition */ +typedef MMSHandlerClass MMSHandlerTestClass; +typedef struct mms_handler_test { + MMSHandler handler; + mms_handler_message_id_cb message_id_cb; + void* message_id_param; +} MMSHandlerTest; + +G_DEFINE_TYPE(MMSHandlerTest, mms_handler_test, MMS_TYPE_HANDLER); +#define MMS_TYPE_HANDLER_TEST (mms_handler_test_get_type()) +#define MMS_HANDLER_TEST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), \ + MMS_TYPE_HANDLER_TEST, MMSHandlerTest)) + +static +char* +mms_handler_test_message_notify( + MMSHandler* handler, + const char* imsi, + const char* from, + const char* subject, + time_t expiry, + GBytes* push) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(handler); + return test->message_id_cb ? + test->message_id_cb(handler, test->message_id_param) : + NULL; +} + +static +gboolean +mms_handler_test_message_received( + MMSHandler* handler, + MMSMessage* msg) +{ + MMS_DEBUG("Message %s received", msg->id); + return TRUE; +} + +static +gboolean +mms_handler_test_message_receive_state_changed( + MMSHandler* handler, + const char* id, + MMS_RECEIVE_STATE state) +{ + MMS_DEBUG("Message %s state %d", id, state); + return TRUE; +} + +static +void +mms_handler_test_class_init( + MMSHandlerTestClass* klass) +{ + klass->fn_message_notify = mms_handler_test_message_notify; + klass->fn_message_received = mms_handler_test_message_received; + klass->fn_message_receive_state_changed = + mms_handler_test_message_receive_state_changed; +} + +static +void +mms_handler_test_init( + MMSHandlerTest* test) +{ +} + +MMSHandler* +mms_handler_test_new() +{ + return g_object_new(MMS_TYPE_HANDLER_TEST, NULL); +} + +void +mms_handler_set_message_id_cb( + MMSHandler* handler, + mms_handler_message_id_cb cb, + void* param) +{ + MMSHandlerTest* test = MMS_HANDLER_TEST(handler); + test->message_id_cb = cb; + test->message_id_param = param; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/retrieve_cancel/test_handler.h b/mms-lib/test/retrieve_cancel/test_handler.h new file mode 100644 index 0000000..d81f802 --- /dev/null +++ b/mms-lib/test/retrieve_cancel/test_handler.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef TEST_HANDLER_H +#define TEST_HANDLER_H + +#include "mms_handler.h" + +MMSHandler* +mms_handler_test_new(void); + +typedef +char* +(*mms_handler_message_id_cb)( + MMSHandler* handler, + void* param); + +void +mms_handler_set_message_id_cb( + MMSHandler* handler, + mms_handler_message_id_cb cb, + void* param); + +#endif /* TEST_HANDLER_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-lib/test/retrieve_cancel/test_retrieve_cancel.c b/mms-lib/test/retrieve_cancel/test_retrieve_cancel.c new file mode 100644 index 0000000..5ef27e8 --- /dev/null +++ b/mms-lib/test/retrieve_cancel/test_retrieve_cancel.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "test_connman.h" +#include "test_handler.h" + +#include "mms_log.h" +#include "mms_lib_util.h" +#include "mms_dispatcher.h" + +#define RET_OK (0) +#define RET_ERR (1) +#define RET_TIMEOUT (2) + +typedef struct test_desc { + const char* name; + const guint8* pdu; + gsize pdusize; + int flags; + +#define TEST_ASYNC_CANCEL (0x01) +#define TEST_ACCEPT_MSG (0x02) + +} TestDesc; + +typedef struct test { + const TestDesc* desc; + MMSDispatcherDelegate delegate; + MMSConnMan* cm; + MMSHandler* handler; + MMSDispatcher* disp; + GBytes* pdu; + GMainLoop* loop; + guint cancel_id; + guint timeout_id; + char* id; + int ret; +} Test; + +static +void +test_done( + MMSDispatcherDelegate* delegate, + MMSDispatcher* dispatcher) +{ + Test* test = MMS_CAST(delegate,Test,delegate); + MMS_INFO("%s test %s", test->desc->name, (test->ret == RET_OK) ? + "OK" : "FAILED"); + g_main_loop_quit(test->loop); +} + +static +gboolean +test_timeout( + gpointer data) +{ + Test* test = data; + test->timeout_id = 0; + test->ret = RET_TIMEOUT; + MMS_INFO("%s test TIMEOUT", test->desc->name); + mms_dispatcher_cancel(test->disp, NULL); + return FALSE; +} + +static +void +test_init( + Test* test, + const MMSConfig* config, + const TestDesc* desc) +{ + memset(test, 0, sizeof(*test)); + test->desc = desc; + test->cm = mms_connman_test_new(); + test->handler = mms_handler_test_new(); + test->disp = mms_dispatcher_new(config, test->cm, test->handler); + test->pdu = g_bytes_new_static(desc->pdu, desc->pdusize); + test->loop = g_main_loop_new(NULL, FALSE); + test->delegate.fn_done = test_done; + mms_dispatcher_set_delegate(test->disp, &test->delegate); + test->timeout_id = g_timeout_add_seconds(10, test_timeout, test); + test->ret = RET_ERR; +} + +static +void +test_finalize( + Test* test) +{ + if (test->timeout_id) { + g_source_remove(test->timeout_id); + test->timeout_id = 0; + } + mms_connman_unref(test->cm); + mms_handler_unref(test->handler); + mms_dispatcher_unref(test->disp); + g_bytes_unref(test->pdu); + g_main_loop_unref(test->loop); + g_free(test->id); +} + +static +char* +test_msg_id( + MMSHandler* handler, + void* param) +{ + Test* test = param; + MMS_ASSERT(!test->id); + if (test->id) { + test->ret = RET_ERR; + g_main_loop_quit(test->loop); + return NULL; + } else if (test->desc->flags & TEST_ACCEPT_MSG) { + return g_strdup(test->id = g_strdup("21285")); + } else { + return NULL; + } +} + +static +gboolean +test_cancel( + void* param) +{ + Test* test = param; + test->cancel_id = 0; + MMS_DEBUG("Asynchronous cancel %s", test->id); + mms_dispatcher_cancel(test->disp, test->id); + return FALSE; +} + +static +void +test_connect( + MMSConnMan* cm, + void* param) +{ + Test* test = param; + if (test->desc->flags & TEST_ASYNC_CANCEL) { + if (!test->cancel_id) { + test->cancel_id = g_idle_add_full(G_PRIORITY_HIGH, + test_cancel, test, NULL); + } + } else { + MMS_DEBUG("Synchronous cancel %s", test->id ? test->id : "all"); + mms_dispatcher_cancel(test->disp, test->id); + } +} + +static +int +test_once( + const MMSConfig* config, + const TestDesc* desc) +{ + Test test; + MMS_VERBOSE(">>>>>>>>>>>>>> %s <<<<<<<<<<<<<<", desc->name); + test_init(&test, config, desc); + if (mms_dispatcher_handle_push(test.disp, "IMSI", test.pdu)) { + mms_connman_set_connection_requested_cb(test.cm, test_connect, &test); + mms_handler_set_message_id_cb(test.handler, test_msg_id, &test); + if (mms_dispatcher_start(test.disp)) { + test.ret = RET_OK; + g_main_loop_run(test.loop); + } + } + test_finalize(&test); + return test.ret; +} + +static +int +test( + const MMSConfig* config) +{ + /* + * WSP header: + * application/vnd.wap.mms-message + * MMS headers: + * X-Mms-Message-Type: M-Notification.ind + * X-Mms-Transaction-Id: Ad0b9pXNC + * X-Mms-MMS-Version: 1.2 + * From: +358540000000/TYPE=PLMN + * X-Mms-Delivery-Report: No + * X-Mms-Message-Class: Personal + * X-Mms-Message-Size: 137105 + * X-Mms-Expiry: +259199 sec + * X-Mms-Content-Location: http://mmsc42:10021/mmsc/4_2?Ad0b9pXNC + */ + static const guint8 plus_259199_sec[] = { + 0x8c,0x82,0x98,0x41,0x64,0x30,0x62,0x39,0x70,0x58,0x4e,0x43, + 0x00,0x8d,0x92,0x89,0x19,0x80,0x2b,0x33,0x35,0x38,0x35,0x34, + 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x2f,0x54,0x59,0x50,0x45, + 0x3d,0x50,0x4c,0x4d,0x4e,0x00,0x86,0x81,0x8a,0x80,0x8e,0x03, + 0x02,0x17,0x91,0x88,0x05,0x81,0x03,0x03,0xf4,0x7f,0x83,0x68, + 0x74,0x74,0x70,0x3a,0x2f,0x2f,0x6d,0x6d,0x73,0x63,0x34,0x32, + 0x3a,0x31,0x30,0x30,0x32,0x31,0x2f,0x6d,0x6d,0x73,0x63,0x2f, + 0x34,0x5f,0x32,0x3f,0x41,0x64,0x30,0x62,0x39,0x70,0x58,0x4e, + 0x43,0x00 + }; + + /* + * WSP header: + * application/vnd.wap.mms-message + * MMS headers: + * X-Mms-Message-Type: M-Notification.ind + * X-Mms-Transaction-Id: Ad0b9pXNC + * X-Mms-MMS-Version: 1.2 + * From: +358540000000/TYPE=PLMN + * X-Mms-Delivery-Report: No + * X-Mms-Message-Class: Personal + * X-Mms-Message-Size: 137105 + * X-Mms-Expiry: +1 sec + * X-Mms-Content-Location: http://mmsc42:10021/mmsc/4_2?Ad0b9pXNC + */ + static const guint8 plus_1_sec[] = { + 0x8c,0x82,0x98,0x41,0x64,0x30,0x62,0x39,0x70,0x58,0x4e,0x43, + 0x00,0x8d,0x92,0x89,0x19,0x80,0x2b,0x33,0x35,0x38,0x35,0x34, + 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x2f,0x54,0x59,0x50,0x45, + 0x3d,0x50,0x4c,0x4d,0x4e,0x00,0x86,0x81,0x8a,0x80,0x8e,0x03, + 0x02,0x17,0x91,0x88,0x03,0x81,0x01,0x01,0x83,0x68,0x74,0x74, + 0x70,0x3a,0x2f,0x2f,0x6d,0x6d,0x73,0x63,0x34,0x32,0x3a,0x31, + 0x30,0x30,0x32,0x31,0x2f,0x6d,0x6d,0x73,0x63,0x2f,0x34,0x5f, + 0x32,0x3f,0x41,0x64,0x30,0x62,0x39,0x70,0x58,0x4e,0x43,0x00 + }; + + static const TestDesc tests[] = { + { + "AsyncCancel", plus_259199_sec, sizeof(plus_259199_sec), + TEST_ASYNC_CANCEL | TEST_ACCEPT_MSG + },{ + "SyncCancel", plus_259199_sec, sizeof(plus_259199_sec), + TEST_ACCEPT_MSG + },{ + "NoHandler", plus_1_sec, sizeof(plus_1_sec), 0 + } + }; + + int i, ret = RET_OK; + for (i=0; i 1 && !strcmp(argv[1], "-v")) { + mms_log_stdout_timestamp = TRUE; + mms_log_default.level = MMS_LOGLEVEL_VERBOSE; + } else { + mms_log_stdout_timestamp = FALSE; + mms_log_default.level = MMS_LOGLEVEL_INFO; + } + return test(&config); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/Makefile b/mms-ofono/Makefile new file mode 100644 index 0000000..acce7d1 --- /dev/null +++ b/mms-ofono/Makefile @@ -0,0 +1,127 @@ +# -*- Mode: makefile -*- + +.PHONY: clean all debug release + +# Required packages +PKGS = glib-2.0 gio-2.0 gio-unix-2.0 + +# +# Default target +# + +all: debug release + +# +# Sources +# + +SRC = \ + mms_ofono_manager.c \ + mms_ofono_modem.c \ + mms_ofono_context.c \ + mms_ofono_connection.c \ + mms_ofono_connman.c +GEN_SRC = \ + org.ofono.Modem.c \ + org.ofono.Manager.c \ + org.ofono.SimManager.c \ + org.ofono.ConnectionManager.c \ + org.ofono.ConnectionContext.c + +# +# Directories +# + +SRC_DIR = src +INCLUDE_DIR = include +BUILD_DIR = build +GEN_DIR = $(BUILD_DIR) +SPEC_DIR = spec +MMS_LIB_INCLUDE = ../mms-lib/include +DEBUG_BUILD_DIR = $(BUILD_DIR)/debug +RELEASE_BUILD_DIR = $(BUILD_DIR)/release + +# +# Tools and flags +# + +CC = $(CROSS_COMPILE)gcc +LD = $(CC) +WARN = -Wall +ARFLAGS = rc +INCLUDES = -I$(SRC_DIR) -I$(INCLUDE_DIR) -I$(MMS_LIB_INCLUDE) -I$(GEN_DIR) -I. +CFLAGS = $(INCLUDES) $(shell pkg-config --cflags $(PKGS)) -MMD +DEBUG_FLAGS = -g +RELEASE_FLAGS = -O2 +DEBUG_DEFS = -DDEBUG +RELEASE_DEFS = +DEBUG_CFLAGS = $(DEBUG_FLAGS) $(DEBUG_DEFS) $(CFLAGS) +RELEASE_CFLAGS = $(RELEASE_FLAGS) $(RELEASE_DEFS) $(CFLAGS) + +# +# Files +# + +.PRECIOUS: $(GEN_SRC:%=$(GEN_DIR)/%) + +DEBUG_OBJS = \ + $(GEN_SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(DEBUG_BUILD_DIR)/%.o) +RELEASE_OBJS = \ + $(GEN_SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) \ + $(SRC:%.c=$(RELEASE_BUILD_DIR)/%.o) + +# +# Dependencies +# + +DEPS = $(DEBUG_OBJS:%.o=%.d) $(RELEASE_OBJS:%.o=%.d) +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(strip $(DEPS)),) +-include $(DEPS) +endif +endif + +# +# Rules +# +LIB = libmms-ofono.a +DEBUG_LIB = $(DEBUG_BUILD_DIR)/$(LIB) +RELEASE_LIB = $(RELEASE_BUILD_DIR)/$(LIB) + +debug: $(DEBUG_LIB) + +release: $(RELEASE_LIB) + +clean: + rm -fr $(BUILD_DIR) *~ $(SRC_DIR)/*~ $(INCLUDE_DIR)/*~ + +$(GEN_DIR): + mkdir -p $@ + +$(DEBUG_BUILD_DIR): + mkdir -p $@ + +$(RELEASE_BUILD_DIR): + mkdir -p $@ + +$(GEN_DIR)/%.c: $(SPEC_DIR)/%.xml + gdbus-codegen --generate-c-code $(@:%.c=%) $< + +$(DEBUG_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(SRC_DIR)/%.c + $(CC) -c $(WARN) $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(DEBUG_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(RELEASE_BUILD_DIR)/%.o : $(GEN_DIR)/%.c + $(CC) -c $(RELEASE_CFLAGS) -MT"$@" -MF"$(@:%.o=%.d)" $< -o $@ + +$(DEBUG_LIB): $(DEBUG_BUILD_DIR) $(DEBUG_OBJS) + $(AR) $(ARFLAGS) $@ $(DEBUG_OBJS) + +$(RELEASE_LIB): $(RELEASE_BUILD_DIR) $(RELEASE_OBJS) + $(AR) $(ARFLAGS) $@ $(RELEASE_OBJS) diff --git a/mms-ofono/include/mms_ofono_connman.h b/mms-ofono/include/mms_ofono_connman.h new file mode 100644 index 0000000..7788e81 --- /dev/null +++ b/mms-ofono/include/mms_ofono_connman.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_CONNMAN_OFONO_H +#define JOLLA_MMS_CONNMAN_OFONO_H + +#include +#include "mms_connman.h" + +MMSConnMan* +mms_connman_ofono_new(void); + +#endif /* JOLLA_MMS_CONNMAN_OFONO_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/include/mms_ofono_log.h b/mms-ofono/include/mms_ofono_log.h new file mode 100644 index 0000000..f7030d4 --- /dev/null +++ b/mms-ofono/include/mms_ofono_log.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_OFONO_LOG_H +#define JOLLA_MMS_OFONO_LOG_H + +#include "mms_log.h" + +#define MMS_OFONO_LOG_MODULES(log) \ + log(mms_ofono_modem_log)\ + log(mms_ofono_manager_log)\ + log(mms_ofono_context_log) + +MMS_OFONO_LOG_MODULES(MMS_LOG_MODULE_DECL) + +#endif /* JOLLA_MMS_OFONO_LOG_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/mms-ofono.pro b/mms-ofono/mms-ofono.pro new file mode 100644 index 0000000..d1e4002 --- /dev/null +++ b/mms-ofono/mms-ofono.pro @@ -0,0 +1,130 @@ +TEMPLATE = lib +CONFIG += staticlib +CONFIG -= qt +CONFIG += link_pkgconfig +PKGCONFIG += glib-2.0 gio-2.0 gio-unix-2.0 +DBUS_SPEC_DIR = $$_PRO_FILE_PWD_/spec +INCLUDEPATH += include +INCLUDEPATH += ../mms-lib/include +QMAKE_CFLAGS += -Wno-unused + +CONFIG(debug, debug|release) { + DEFINES += DEBUG + DESTDIR = $$_PRO_FILE_PWD_/build/debug +} else { + DESTDIR = $$_PRO_FILE_PWD_/build/release +} + +SOURCES += \ + src/mms_ofono_connection.c \ + src/mms_ofono_connman.c \ + src/mms_ofono_context.c \ + src/mms_ofono_manager.c \ + src/mms_ofono_modem.c + +HEADERS += \ + src/mms_ofono_connection.h \ + src/mms_ofono_context.h \ + src/mms_ofono_manager.h \ + src/mms_ofono_modem.h \ + src/mms_ofono_names.h \ + src/mms_ofono_types.h + +HEADERS += \ + include/mms_ofono_connman.h \ + include/mms_ofono_log.h + +# org.ofono.Manager +MANAGER_XML = $$DBUS_SPEC_DIR/org.ofono.Manager.xml +MANAGER_GENERATE = gdbus-codegen --generate-c-code \ + org.ofono.Manager $$MANAGER_XML +MANAGER_H = org.ofono.Manager.h +org_ofono_Manager_h.input = MANAGER_XML +org_ofono_Manager_h.output = $$MANAGER_H +org_ofono_Manager_h.commands = $$MANAGER_GENERATE +org_ofono_Manager_h.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_Manager_h + +MANAGER_C = org.ofono.Manager.c +org_ofono_Manager_c.input = MANAGER_XML +org_ofono_Manager_c.output = $$MANAGER_C +org_ofono_Manager_c.commands = $$MANAGER_GENERATE +org_ofono_Manager_c.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_Manager_c +GENERATED_SOURCES += $$MANAGER_C + +# org.ofono.ConnectionManager +CONNECTION_MANAGER_XML = $$DBUS_SPEC_DIR/org.ofono.ConnectionManager.xml +CONNECTION_MANAGER_GENERATE = gdbus-codegen --generate-c-code \ + org.ofono.ConnectionManager $$CONNECTION_MANAGER_XML +CONNECTION_MANAGER_H = org.ofono.ConnectionManager.h +org_ofono_ConnectionManager_h.input = CONNECTION_MANAGER_XML +org_ofono_ConnectionManager_h.output = $$CONNECTION_MANAGER_H +org_ofono_ConnectionManager_h.commands = $$CONNECTION_MANAGER_GENERATE +org_ofono_ConnectionManager_h.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_ConnectionManager_h + +CONNECTION_MANAGER_C = org.ofono.ConnectionManager.c +org_ofono_ConnectionManager_c.input = CONNECTION_MANAGER_XML +org_ofono_ConnectionManager_c.output = $$CONNECTION_MANAGER_C +org_ofono_ConnectionManager_c.commands = $$CONNECTION_MANAGER_GENERATE +org_ofono_ConnectionManager_c.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_ConnectionManager_c +GENERATED_SOURCES += $$CONNECTION_MANAGER_C + +# org.ofono.ConnectionContext +CONNECTION_CONTEXT_XML = $$DBUS_SPEC_DIR/org.ofono.ConnectionContext.xml +CONNECTION_CONTEXT_GENERATE = gdbus-codegen --generate-c-code \ + org.ofono.ConnectionContext $$CONNECTION_CONTEXT_XML +CONNECTION_CONTEXT_H = org.ofono.ConnectionContext.h +org_ofono_ConnectionContext_h.input = CONNECTION_CONTEXT_XML +org_ofono_ConnectionContext_h.output = $$CONNECTION_CONTEXT_H +org_ofono_ConnectionContext_h.commands = $$CONNECTION_CONTEXT_GENERATE +org_ofono_ConnectionContext_h.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_ConnectionContext_h + +CONNECTION_CONTEXT_C = org.ofono.ConnectionContext.c +org_ofono_ConnectionContext_c.input = CONNECTION_CONTEXT_XML +org_ofono_ConnectionContext_c.output = $$CONNECTION_CONTEXT_C +org_ofono_ConnectionContext_c.commands = $$CONNECTION_CONTEXT_GENERATE +org_ofono_ConnectionContext_c.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_ConnectionContext_c +GENERATED_SOURCES += $$CONNECTION_CONTEXT_C + +# org.ofono.SimManager +SIM_MANAGER_XML = $$DBUS_SPEC_DIR/org.ofono.SimManager.xml +SIM_MANAGER_GENERATE = gdbus-codegen --generate-c-code \ + org.ofono.SimManager $$SIM_MANAGER_XML +SIM_MANAGER_H = org.ofono.SimManager.h +org_ofono_SimManager_h.input = SIM_MANAGER_XML +org_ofono_SimManager_h.output = $$SIM_MANAGER_H +org_ofono_SimManager_h.commands = $$SIM_MANAGER_GENERATE +org_ofono_SimManager_h.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_SimManager_h + +SIM_MANAGER_C = org.ofono.SimManager.c +org_ofono_SimManager_c.input = SIM_MANAGER_XML +org_ofono_SimManager_c.output = $$SIM_MANAGER_C +org_ofono_SimManager_c.commands = $$SIM_MANAGER_GENERATE +org_ofono_SimManager_c.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_SimManager_c +GENERATED_SOURCES += $$SIM_MANAGER_C + +# org.ofono.Modem +MODEM_XML = $$DBUS_SPEC_DIR/org.ofono.Modem.xml +MODEM_GENERATE = gdbus-codegen --generate-c-code \ + org.ofono.Modem $$MODEM_XML +MODEM_H = org.ofono.Modem.h +org_ofono_Modem_h.input = MODEM_XML +org_ofono_Modem_h.output = $$MODEM_H +org_ofono_Modem_h.commands = $$MODEM_GENERATE +org_ofono_Modem_h.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_Modem_h + +MODEM_C = org.ofono.Modem.c +org_ofono_Modem_c.input = MODEM_XML +org_ofono_Modem_c.output = $$MODEM_C +org_ofono_Modem_c.commands = $$MODEM_GENERATE +org_ofono_Modem_c.CONFIG = no_link +QMAKE_EXTRA_COMPILERS += org_ofono_Modem_c +GENERATED_SOURCES += $$MODEM_C diff --git a/mms-ofono/spec/org.ofono.ConnectionContext.xml b/mms-ofono/spec/org.ofono.ConnectionContext.xml new file mode 100644 index 0000000..a0cc069 --- /dev/null +++ b/mms-ofono/spec/org.ofono.ConnectionContext.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/mms-ofono/spec/org.ofono.ConnectionManager.xml b/mms-ofono/spec/org.ofono.ConnectionManager.xml new file mode 100644 index 0000000..6c20187 --- /dev/null +++ b/mms-ofono/spec/org.ofono.ConnectionManager.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mms-ofono/spec/org.ofono.Manager.xml b/mms-ofono/spec/org.ofono.Manager.xml new file mode 100644 index 0000000..de11b3e --- /dev/null +++ b/mms-ofono/spec/org.ofono.Manager.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/mms-ofono/spec/org.ofono.MessageManager.xml b/mms-ofono/spec/org.ofono.MessageManager.xml new file mode 100644 index 0000000..23d2d05 --- /dev/null +++ b/mms-ofono/spec/org.ofono.MessageManager.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mms-ofono/spec/org.ofono.Modem.xml b/mms-ofono/spec/org.ofono.Modem.xml new file mode 100644 index 0000000..80be1a9 --- /dev/null +++ b/mms-ofono/spec/org.ofono.Modem.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/mms-ofono/spec/org.ofono.NetworkRegistration.xml b/mms-ofono/spec/org.ofono.NetworkRegistration.xml new file mode 100644 index 0000000..69dd7ec --- /dev/null +++ b/mms-ofono/spec/org.ofono.NetworkRegistration.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/mms-ofono/spec/org.ofono.SimManager.xml b/mms-ofono/spec/org.ofono.SimManager.xml new file mode 100644 index 0000000..23fc87b --- /dev/null +++ b/mms-ofono/spec/org.ofono.SimManager.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mms-ofono/src/mms_ofono_connection.c b/mms-ofono/src/mms_ofono_connection.c new file mode 100644 index 0000000..b050f31 --- /dev/null +++ b/mms-ofono/src/mms_ofono_connection.c @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_ofono_connection.h" +#include "mms_ofono_context.h" +#include "mms_ofono_modem.h" +#include "mms_ofono_names.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_connection_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-ofono-connection"); + +/* Generated headers */ +#include "org.ofono.ConnectionContext.h" + +typedef MMSConnectionClass MMSOfonoConnectionClass; + +G_DEFINE_TYPE(MMSOfonoConnection, mms_ofono_connection, MMS_TYPE_CONNECTION); +#define MMS_TYPE_OFONO_CONNECTION (mms_ofono_connection_get_type()) +#define MMS_OFONO_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_OFONO_CONNECTION, MMSOfonoConnection)) + +MMSConnection* +mms_ofono_connection( + MMSOfonoConnection* ofono) +{ + return &ofono->connection; +} + +void +mms_ofono_connection_cancel( + MMSOfonoConnection* ofono) +{ + if (ofono->connection.state <= MMS_CONNECTION_STATE_OPENING) { + mms_ofono_connection_set_state(ofono, MMS_CONNECTION_STATE_FAILED); + } else { + mms_ofono_connection_set_state(ofono, MMS_CONNECTION_STATE_CLOSED); + } +} + +gboolean +mms_ofono_connection_set_state( + MMSOfonoConnection* ofono, + MMS_CONNECTION_STATE state) +{ + if (ofono->connection.state != state) { + if (ofono->connection.state == MMS_CONNECTION_STATE_FAILED || + ofono->connection.state == MMS_CONNECTION_STATE_CLOSED) { + /* These are terminal states, can't change those */ + return FALSE; + } else if (ofono->connection.state > state) { + /* Can't move back to a previous state */ + return FALSE; + } + if (state == MMS_CONNECTION_STATE_FAILED || + state == MMS_CONNECTION_STATE_CLOSED) { + /* Stop listening for property changes */ + if (ofono->property_change_signal_id) { + g_signal_handler_disconnect(ofono->proxy, + ofono->property_change_signal_id); + ofono->property_change_signal_id = 0; + } + } + ofono->connection.state = state; + if (ofono->connection.delegate && + ofono->connection.delegate->fn_connection_state_changed) { + ofono->connection.delegate->fn_connection_state_changed( + ofono->connection.delegate, &ofono->connection); + } + } + return TRUE; +} + +static +void +mms_ofono_connection_property_changed( + OrgOfonoConnectionContext* proxy, + const char* key, + GVariant* variant, + MMSOfonoConnection* ofono) +{ + MMSConnection* conn = &ofono->connection; + MMS_ASSERT(proxy == ofono->proxy); + GVariant* value = g_variant_get_variant(variant); + MMS_VERBOSE("%p %s %s", conn, conn->imsi, key); + if (!strcmp(key, OFONO_CONTEXT_PROPERTY_ACTIVE)) { + gboolean active = g_variant_get_boolean(value); + if (active) { + MMS_DEBUG("Connection %s opened", conn->imsi); + mms_ofono_connection_set_state(ofono, MMS_CONNECTION_STATE_OPEN); + } else { + mms_ofono_connection_set_state(ofono, MMS_CONNECTION_STATE_CLOSED); + } + } else if (!strcmp(key, OFONO_CONTEXT_PROPERTY_SETTINGS)) { + GVariant* interfaceValue = g_variant_lookup_value(value, + OFONO_CONTEXT_SETTING_INTERFACE, G_VARIANT_TYPE_STRING); + g_free(conn->netif); + if (interfaceValue) { + conn->netif = g_strdup(g_variant_get_string(interfaceValue, NULL)); + MMS_DEBUG("Interface: %s", conn->netif); + g_variant_unref(interfaceValue); + } else { + conn->netif = NULL; + } + } else if (!strcmp(key, OFONO_CONTEXT_PROPERTY_MMS_PROXY)) { + g_free(conn->mmsproxy); + conn->mmsproxy = g_strdup(g_variant_get_string(value, NULL)); + MMS_DEBUG("MessageProxy: %s", conn->mmsproxy); + } else if (!strcmp(key, OFONO_CONTEXT_PROPERTY_MMS_CENTER)) { + g_free(conn->mmsc); + conn->mmsc = g_strdup(g_variant_get_string(value, NULL)); + MMS_DEBUG("MessageCenter: %s", conn->mmsc); + } else { + MMS_ASSERT(strcmp(key, OFONO_CONTEXT_PROPERTY_TYPE)); + } + g_variant_unref(value); +} + +MMSOfonoConnection* +mms_ofono_connection_new( + MMSOfonoContext* context, + gboolean user_request) +{ + GError* error = NULL; + GVariant* properties = NULL; + if (org_ofono_connection_context_call_get_properties_sync(context->proxy, + &properties, NULL, &error)) { + GVariant* value; + MMSOfonoConnection* ofono; + MMSConnection* conn; + + ofono = g_object_new(MMS_TYPE_OFONO_CONNECTION, NULL); + conn = &ofono->connection; + + conn->user_connection = user_request; + g_object_ref(ofono->proxy = context->proxy); + ofono->context = context; + ofono->connection.state = context->active ? + MMS_CONNECTION_STATE_OPEN : MMS_CONNECTION_STATE_OPENING; + + value = g_variant_lookup_value(properties, + OFONO_CONTEXT_PROPERTY_ACTIVE, G_VARIANT_TYPE_BOOLEAN); + if (value) { + if (g_variant_get_boolean(value)) { + ofono->connection.state = MMS_CONNECTION_STATE_OPEN; + } + g_variant_unref(value); + } + value = g_variant_lookup_value(properties, + OFONO_CONTEXT_PROPERTY_MMS_PROXY, G_VARIANT_TYPE_STRING); + if (value) { + conn->mmsproxy = g_strdup(g_variant_get_string(value, NULL)); + MMS_DEBUG("MessageProxy: %s", conn->mmsproxy); + g_variant_unref(value); + } + value = g_variant_lookup_value(properties, + OFONO_CONTEXT_PROPERTY_MMS_CENTER, G_VARIANT_TYPE_STRING); + if (value) { + conn->mmsc = g_strdup(g_variant_get_string(value, NULL)); + MMS_DEBUG("MessageCenter: %s", conn->mmsc); + g_variant_unref(value); + } + value = g_variant_lookup_value(properties, + OFONO_CONTEXT_PROPERTY_SETTINGS, G_VARIANT_TYPE_VARDICT); + if (value) { + GVariant* netif = g_variant_lookup_value(value, + OFONO_CONTEXT_SETTING_INTERFACE, G_VARIANT_TYPE_STRING); + if (netif) { + conn->netif = g_strdup(g_variant_get_string(netif, NULL)); + MMS_DEBUG("Interface: %s", conn->netif); + g_variant_unref(netif); + } + g_variant_unref(value); + } + + /* Listen for property changes */ + ofono->property_change_signal_id = g_signal_connect( + ofono->proxy, "property-changed", + G_CALLBACK(mms_ofono_connection_property_changed), + conn); + conn->imsi = g_strdup(context->modem->imsi); + g_variant_unref(properties); + return ofono; + } else { + MMS_ERR("Error getting connection properties: %s", MMS_ERRMSG(error)); + if (error) g_error_free(error); + return NULL; + } +} + +MMSOfonoConnection* +mms_ofono_connection_ref( + MMSOfonoConnection* ofono) +{ + if (ofono) mms_connection_ref(&ofono->connection); + return ofono; +} + +void +mms_ofono_connection_unref( + MMSOfonoConnection* ofono) +{ + if (ofono) mms_connection_unref(&ofono->connection); +} + +static +void +mms_ofono_connection_close( + MMSConnection* connection) +{ + MMSOfonoConnection* ofono = MMS_OFONO_CONNECTION(connection); + if (ofono->context) mms_ofono_context_set_active(ofono->context, FALSE); +} + +/** + * First stage of deinitialization (release all references). + * May be called more than once in the lifetime of the object. + */ +static +void +mms_ofono_connection_dispose( + GObject* object) +{ + MMSOfonoConnection* ofono = MMS_OFONO_CONNECTION(object); + if (ofono->property_change_signal_id) { + g_signal_handler_disconnect(ofono->proxy, + ofono->property_change_signal_id); + ofono->property_change_signal_id = 0; + } + if (ofono->proxy) { + g_object_unref(ofono->proxy); + ofono->proxy = NULL; + } + G_OBJECT_CLASS(mms_ofono_connection_parent_class)->dispose(object); +} + +/** + * Per class initializer + */ +static +void +mms_ofono_connection_class_init( + MMSOfonoConnectionClass* klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS(klass); + klass->fn_close = mms_ofono_connection_close; + object_class->dispose = mms_ofono_connection_dispose; +} + +/** + * Per instance initializer + */ +static +void +mms_ofono_connection_init( + MMSOfonoConnection* conn) +{ + MMS_VERBOSE_("%p", conn); +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_connection.h b/mms-ofono/src/mms_ofono_connection.h new file mode 100644 index 0000000..d30b1c2 --- /dev/null +++ b/mms-ofono/src/mms_ofono_connection.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_CONNECTION_OFONO_H +#define JOLLA_MMS_CONNECTION_OFONO_H + +#include "mms_connection.h" +#include "mms_ofono_types.h" + +struct mms_ofono_connection { + MMSConnection connection; + MMSOfonoContext* context; + struct _OrgOfonoConnectionContext* proxy; + gulong property_change_signal_id; +}; + +MMSOfonoConnection* +mms_ofono_connection_new( + MMSOfonoContext* context, + gboolean user_request); + +MMSOfonoConnection* +mms_ofono_connection_ref( + MMSOfonoConnection* connection); + +void +mms_ofono_connection_unref( + MMSOfonoConnection* connection); + +void +mms_ofono_connection_cancel( + MMSOfonoConnection* connection); + +gboolean +mms_ofono_connection_set_state( + MMSOfonoConnection* connection, + MMS_CONNECTION_STATE state); + +#endif /* JOLLA_MMS_CONNECTION_OFONO_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_connman.c b/mms-ofono/src/mms_ofono_connman.c new file mode 100644 index 0000000..6651ba7 --- /dev/null +++ b/mms-ofono/src/mms_ofono_connman.c @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_ofono_connman.h" +#include "mms_ofono_connection.h" +#include "mms_ofono_manager.h" +#include "mms_ofono_modem.h" +#include "mms_ofono_names.h" +#include "mms_connection.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_connman_log +#include "mms_lib_log.h" +MMS_LOG_MODULE_DEFINE("mms-ofono-connman"); + +typedef MMSConnManClass MMSOfonoConnManClass; +typedef struct mms_ofono_connman { + GObject cm; + guint ofono_watch_id; + GDBusConnection* bus; + MMSOfonoManager* man; +} MMSOfonoConnMan; + +G_DEFINE_TYPE(MMSOfonoConnMan, mms_ofono_connman, MMS_TYPE_CONNMAN); +#define MMS_TYPE_OFONO_CONNMAN (mms_ofono_connman_get_type()) +#define MMS_OFONO_CONNMAN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),\ + MMS_TYPE_OFONO_CONNMAN, MMSOfonoConnMan)) + +/** + * Creates a new connection or returns the reference to an aready active one. + * The caller must release the reference. + */ +static +MMSConnection* +mms_ofono_connman_open_connection( + MMSConnMan* cm, + const char* imsi, + gboolean user_request) +{ + MMSOfonoConnMan* ofono = MMS_OFONO_CONNMAN(cm); + MMSOfonoModem* modem = mms_ofono_manager_modem_for_imsi(ofono->man, imsi); + if (modem) { + MMSOfonoContext* mms = modem->mms_context; + if (mms) { + if (!mms->connection) { + mms->connection = mms_ofono_connection_new(mms, user_request); + } + if (mms->connection && !mms->active) { + mms_ofono_context_set_active(mms, TRUE); + } + return mms_connection_ref(&mms->connection->connection); + } + } else { + MMS_DEBUG("SIM %s is not avialable", imsi); + } + return NULL; +} + +static +void +mms_connman_ofono_appeared( + GDBusConnection* bus, + const gchar* name, + const gchar* owner, + gpointer self) +{ + MMSOfonoConnMan* ofono = self; + MMS_DEBUG("Name '%s' is owned by %s", name, owner); + MMS_ASSERT(!ofono->man); + mms_ofono_manager_free(ofono->man); + ofono->man = mms_ofono_manager_new(bus); +} + +static +void +mms_connman_ofono_vanished( + GDBusConnection* bus, + const gchar* name, + gpointer self) +{ + MMSOfonoConnMan* ofono = self; + MMS_DEBUG("Name '%s' has disappeared", name); + mms_ofono_manager_free(ofono->man); + ofono->man = NULL; +} + +/** + * Creates oFono connection manager + */ +MMSConnMan* +mms_connman_ofono_new() +{ + GError* error = NULL; + GDBusConnection* bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); + if (bus) { + MMSOfonoConnMan* ofono = g_object_new(MMS_TYPE_OFONO_CONNMAN, NULL); + ofono->bus = bus; + ofono->ofono_watch_id = g_bus_watch_name_on_connection(bus, + OFONO_SERVICE, G_BUS_NAME_WATCHER_FLAGS_NONE, + mms_connman_ofono_appeared, mms_connman_ofono_vanished, + ofono, NULL); + MMS_ASSERT(ofono->ofono_watch_id); + return &ofono->cm; + } else { + MMS_ERR("Failed to connect to system bus: %s", MMS_ERRMSG(error)); + g_error_free(error); + return NULL; + } +} + +/** + * First stage of deinitialization (release all references). + * May be called more than once in the lifetime of the object. + */ +static +void +mms_ofono_connman_dispose( + GObject* object) +{ + MMSOfonoConnMan* ofono = MMS_OFONO_CONNMAN(object); + MMS_VERBOSE_(""); + if (ofono->ofono_watch_id) { + g_bus_unwatch_name(ofono->ofono_watch_id); + ofono->ofono_watch_id = 0; + } + if (ofono->man) { + mms_ofono_manager_free(ofono->man); + ofono->man = NULL; + } + if (ofono->bus) { + g_object_unref(ofono->bus); + ofono->bus = NULL; + } + G_OBJECT_CLASS(mms_ofono_connman_parent_class)->dispose(object); +} + +/** + * Per class initializer + */ +static +void +mms_ofono_connman_class_init( + MMSOfonoConnManClass* klass) +{ + klass->fn_open_connection = mms_ofono_connman_open_connection; + G_OBJECT_CLASS(klass)->dispose = mms_ofono_connman_dispose; +} + +/** + * Per instance initializer + */ +static +void +mms_ofono_connman_init( + MMSOfonoConnMan* cm) +{ +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_context.c b/mms-ofono/src/mms_ofono_context.c new file mode 100644 index 0000000..5166428 --- /dev/null +++ b/mms-ofono/src/mms_ofono_context.c @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_ofono_context.h" +#include "mms_ofono_modem.h" +#include "mms_ofono_connection.h" +#include "mms_ofono_names.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_ofono_context_log +#include "mms_ofono_log.h" +MMS_LOG_MODULE_DEFINE("mms-ofono-context"); + +/* Generated headers */ +#include "org.ofono.ConnectionContext.h" + +static +void +mms_ofono_context_property_changed( + OrgOfonoConnectionContext* proxy, + const char* key, + GVariant* variant, + MMSOfonoContext* context) +{ + MMS_ASSERT(proxy == context->proxy); + if (!strcmp(key, OFONO_CONTEXT_PROPERTY_ACTIVE)) { + GVariant* value = g_variant_get_variant(variant); + context->active = g_variant_get_boolean(value); + MMS_VERBOSE_("%p %s = %d", context, key, context->active); + g_variant_unref(value); + if (context->active) { + if (context->connection && !mms_ofono_connection_set_state( + context->connection, MMS_CONNECTION_STATE_OPEN)) { + /* Connection is in a wrong state? */ + context->connection->context = NULL; + mms_ofono_connection_unref(context->connection); + context->connection = NULL; + } + if (!context->connection) { + context->connection = mms_ofono_connection_new(context, FALSE); + } + } else if (context->connection) { + context->connection->context = NULL; + mms_ofono_connection_unref(context->connection); + context->connection = NULL; + } + } else { + MMS_VERBOSE_("%p %s", context, key); + MMS_ASSERT(strcmp(key, OFONO_CONTEXT_PROPERTY_TYPE)); + } +} + +static +void +mms_ofono_context_set_active_done( + GObject* proxy, + GAsyncResult* result, + gpointer user_data) +{ + GError* error = NULL; + MMSOfonoContext* context = user_data; + gboolean ok = org_ofono_connection_context_call_set_property_finish( + ORG_OFONO_CONNECTION_CONTEXT(proxy), result, &error); + + if (!ok) { + MMSOfonoConnection* ofono = context->connection; + if (ofono && ofono->connection.state == MMS_CONNECTION_STATE_OPENING) { + /* Connection failed to open, fire state change event and drop + * our reference to it */ + context->connection = NULL; + ofono->context = NULL; + MMS_ERR("Connection %s failed: %s", ofono->connection.imsi, + MMS_ERRMSG(error)); + + mms_ofono_connection_set_state(ofono, MMS_CONNECTION_STATE_FAILED); + mms_ofono_connection_unref(ofono); + } + } + + if (error) g_error_free(error); +} + +void +mms_ofono_context_set_active( + MMSOfonoContext* context, + gboolean active) +{ + GCancellable* cancel; + GAsyncReadyCallback cb; + gpointer data; + if (active) { + MMS_DEBUG("Opening connection %s", context->modem->imsi); + if (context->set_active_cancel) { + g_cancellable_cancel(context->set_active_cancel); + g_object_unref(context->set_active_cancel); + } + cancel = context->set_active_cancel = g_cancellable_new(); + cb = mms_ofono_context_set_active_done; + data = context; + } else { + MMS_DEBUG("Closing connection %s", context->modem->imsi); + cancel = NULL; + cb = NULL; + data = NULL; + if (context->connection) { + MMSOfonoConnection* ofono = context->connection; + context->connection = NULL; + ofono->context = NULL; + mms_ofono_connection_set_state(ofono,MMS_CONNECTION_STATE_CLOSED); + mms_ofono_connection_unref(ofono); + } + } + org_ofono_connection_context_call_set_property(context->proxy, + OFONO_CONTEXT_PROPERTY_ACTIVE, g_variant_new_variant( + g_variant_new_boolean(active)), cancel, cb, data); +} + +MMSOfonoContext* +mms_ofono_context_new( + MMSOfonoModem* modem, + const char* path, + GVariant* properties) +{ + GError* error = NULL; + OrgOfonoConnectionContext* proxy; + proxy = org_ofono_connection_context_proxy_new_sync(modem->bus, + G_DBUS_PROXY_FLAGS_NONE, OFONO_SERVICE, path, NULL, &error); + if (proxy) { + MMSOfonoContext* context = g_new0(MMSOfonoContext, 1); + GVariant* value = g_variant_lookup_value( + properties, OFONO_CONTEXT_PROPERTY_ACTIVE, + G_VARIANT_TYPE_BOOLEAN); + if (value) { + context->active = g_variant_get_boolean(value); + g_variant_unref(value); + } + context->path = g_strdup(path); + context->proxy = proxy; + context->modem = modem; + + /* Subscribe for PropertyChanged notifications */ + context->property_change_signal_id = g_signal_connect( + proxy, "property-changed", + G_CALLBACK(mms_ofono_context_property_changed), + context); + + return context; + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + return NULL; + } +} + +void +mms_ofono_context_free( + MMSOfonoContext* context) +{ + if (context) { + if (context->connection) { + context->connection->context = NULL; + mms_ofono_connection_cancel(context->connection); + mms_ofono_connection_unref(context->connection); + } + if (context->set_active_cancel) { + g_cancellable_cancel(context->set_active_cancel); + g_object_unref(context->set_active_cancel); + } + if (context->proxy) { + g_signal_handler_disconnect(context->proxy, + context->property_change_signal_id); + g_object_unref(context->proxy); + } + g_free(context->path); + g_free(context); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_context.h b/mms-ofono/src/mms_ofono_context.h new file mode 100644 index 0000000..be527af --- /dev/null +++ b/mms-ofono/src/mms_ofono_context.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_OFONO_CONTEXT_H +#define JOLLA_MMS_OFONO_CONTEXT_H + +#include "mms_ofono_types.h" + +struct mms_ofono_context { + MMSOfonoModem* modem; + char* path; + gboolean active; + struct _OrgOfonoConnectionContext* proxy; + gulong property_change_signal_id; + MMSOfonoConnection* connection; + GCancellable* set_active_cancel; +}; + +MMSOfonoContext* +mms_ofono_context_new( + MMSOfonoModem* modem, + const char* path, + GVariant* properties); + +void +mms_ofono_context_set_active( + MMSOfonoContext* context, + gboolean active); + +void +mms_ofono_context_free( + MMSOfonoContext* context); + +#endif /* JOLLA_MMS_OFONO_CONTEXT_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_manager.c b/mms-ofono/src/mms_ofono_manager.c new file mode 100644 index 0000000..697ef3f --- /dev/null +++ b/mms-ofono/src/mms_ofono_manager.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_ofono_manager.h" +#include "mms_ofono_modem.h" +#include "mms_ofono_names.h" +#include "mms_ofono_names.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_ofono_manager_log +#include "mms_ofono_log.h" +MMS_LOG_MODULE_DEFINE("mms-ofono-manager"); + +/* Generated headers */ +#include "org.ofono.Manager.h" + +struct mms_ofono_manager { + GDBusConnection* bus; + GHashTable* modems; + OrgOfonoManager* proxy; + gulong modem_added_signal_id; + gulong modem_removed_signal_id; +}; + +static +void +mms_ofono_manager_set_modems( + MMSOfonoManager* ofono, + GVariant* modems) +{ + GVariantIter iter; + GVariant* child; + MMS_DEBUG("%u modem(s) found", (guint)g_variant_n_children(modems)); + g_hash_table_remove_all(ofono->modems); + + for (g_variant_iter_init(&iter, modems); + (child = g_variant_iter_next_value(&iter)) != NULL; + g_variant_unref(child)) { + + MMSOfonoModem* modem; + const char* path = NULL; + GVariant* properties = NULL; + + g_variant_get(child, "(&o@a{sv})", &path, &properties); + MMS_ASSERT(path); + MMS_ASSERT(properties); + + modem = mms_ofono_modem_new(ofono->bus, path, properties); + if (modem) g_hash_table_replace(ofono->modems, modem->path, modem); + g_variant_unref(properties); + } +} + +static +void +mms_ofono_manager_modem_added( + OrgOfonoManager* proxy, + const char* path, + GVariant* properties, + MMSOfonoManager* ofono) +{ + MMSOfonoModem* modem; + MMS_VERBOSE_("%p %s", ofono, path); + MMS_ASSERT(proxy == ofono->proxy); + g_hash_table_remove(ofono->modems, path); + modem = mms_ofono_modem_new(ofono->bus, path, properties); + if (modem) g_hash_table_replace(ofono->modems, modem->path, modem); +} + +static +void +mms_ofono_manager_modem_removed( + OrgOfonoManager* proxy, + const char* path, + MMSOfonoManager* ofono) +{ + MMS_VERBOSE_("%p %s", ofono, path); + MMS_ASSERT(proxy == ofono->proxy); + g_hash_table_remove(ofono->modems, path); +} + +static +void +mms_ofono_manager_hash_remove_modem( + gpointer data) +{ + mms_ofono_modem_free(data); +} + +MMSOfonoManager* +mms_ofono_manager_new( + GDBusConnection* bus) +{ + GError* error = NULL; + OrgOfonoManager* proxy = org_ofono_manager_proxy_new_sync(bus, + G_DBUS_PROXY_FLAGS_NONE, OFONO_SERVICE, "/", NULL, &error); + if (proxy) { + GVariant* modems = NULL; + if (org_ofono_manager_call_get_modems_sync(proxy, &modems, + NULL, &error)) { + + MMSOfonoManager* ofono = g_new0(MMSOfonoManager, 1); + ofono->proxy = proxy; + g_object_ref(ofono->bus = bus); + ofono->modems = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, mms_ofono_manager_hash_remove_modem); + + /* Subscribe for ModemAdded/Removed notifications */ + ofono->modem_added_signal_id = g_signal_connect( + proxy, "modem-added", + G_CALLBACK(mms_ofono_manager_modem_added), + ofono); + ofono->modem_removed_signal_id = g_signal_connect( + proxy, "modem-removed", + G_CALLBACK(mms_ofono_manager_modem_removed), + ofono); + + mms_ofono_manager_set_modems(ofono, modems); + g_variant_unref(modems); + return ofono; + + } else { + MMS_ERR("Can't get list of modems: %s", MMS_ERRMSG(error)); + g_error_free(error); + } + g_object_unref(proxy); + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + return NULL; +} + +static +gboolean +mms_ofono_manager_modem_imsi_find_cb( + gpointer key, + gpointer value, + gpointer user_data) +{ + MMSOfonoModem* modem = value; + const char* imsi = user_data; + MMS_ASSERT(imsi); + return modem->imsi && !strcmp(modem->imsi, imsi); +} + +MMSOfonoModem* +mms_ofono_manager_modem_for_imsi( + MMSOfonoManager* ofono, + const char* imsi) +{ + return ofono ? g_hash_table_find(ofono->modems, + mms_ofono_manager_modem_imsi_find_cb, (void*)imsi) : NULL; +} + +void +mms_ofono_manager_free( + MMSOfonoManager* ofono) +{ + if (ofono) { + if (ofono->proxy) { + g_signal_handler_disconnect(ofono->proxy, + ofono->modem_added_signal_id); + g_signal_handler_disconnect(ofono->proxy, + ofono->modem_removed_signal_id); + g_object_unref(ofono->proxy); + } + g_hash_table_destroy(ofono->modems); + g_object_unref(ofono->bus); + g_free(ofono); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_manager.h b/mms-ofono/src/mms_ofono_manager.h new file mode 100644 index 0000000..76e40ae --- /dev/null +++ b/mms-ofono/src/mms_ofono_manager.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_OFONO_MANAGER_H +#define JOLLA_MMS_OFONO_MANAGER_H + +#include "mms_ofono_types.h" + +MMSOfonoManager* +mms_ofono_manager_new( + GDBusConnection* bus); + +void +mms_ofono_manager_free( + MMSOfonoManager* ofono); + +MMSOfonoModem* +mms_ofono_manager_modem_for_imsi( + MMSOfonoManager* ofono, + const char* imsi); + +#endif /* JOLLA_MMS_OFONO_MANAGER_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_modem.c b/mms-ofono/src/mms_ofono_modem.c new file mode 100644 index 0000000..95bef9a --- /dev/null +++ b/mms-ofono/src/mms_ofono_modem.c @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#include "mms_ofono_modem.h" +#include "mms_ofono_names.h" + +/* Logging */ +#define MMS_LOG_MODULE_NAME mms_ofono_modem_log +#include "mms_ofono_log.h" +MMS_LOG_MODULE_DEFINE("mms-ofono-modem"); + +/* Generated headers */ +#include "org.ofono.Modem.h" +#include "org.ofono.SimManager.h" +#include "org.ofono.ConnectionManager.h" + +typedef struct mms_context_info { + char* path; + GVariant* properties; +} MMSContextInfo; + +static +MMSContextInfo* +mms_context_info_new( + const char* path, + GVariant* properties) +{ + MMSContextInfo* info = g_new(MMSContextInfo, 1); + info->path = g_strdup(path); + g_variant_ref(info->properties = properties); + return info; +} + +void +mms_context_info_free( + MMSContextInfo* info) +{ + if (info) { + g_variant_unref(info->properties); + g_free(info->path); + g_free(info); + } +} + +static +void +mms_ofono_modem_disconnect_sim_proxy( + MMSOfonoModem* modem) +{ + if (modem->sim_proxy) { + g_signal_handler_disconnect(modem->sim_proxy, + modem->sim_property_change_signal_id); + g_object_unref(modem->sim_proxy); + modem->sim_proxy = NULL; + } +} + +static +void +mms_ofono_modem_disconnect_gprs_proxy( + MMSOfonoModem* modem) +{ + if (modem->gprs_proxy) { + g_signal_handler_disconnect(modem->gprs_proxy, + modem->gprs_context_added_signal_id); + g_signal_handler_disconnect(modem->gprs_proxy, + modem->gprs_context_removed_signal_id); + g_object_unref(modem->gprs_proxy); + modem->gprs_proxy = NULL; + } +} + +static +MMSContextInfo* +mms_ofono_modem_find_mms_context( + OrgOfonoConnectionManager* proxy) +{ + GError* error = NULL; + MMSContextInfo* mms_context = NULL; + GVariant* contexts = NULL; + if (org_ofono_connection_manager_call_get_contexts_sync(proxy, + &contexts, NULL, &error)) { + GVariantIter iter; + GVariant* child; + MMS_VERBOSE(" %d context(s)", (guint)g_variant_n_children(contexts)); + for (g_variant_iter_init(&iter, contexts); + !mms_context && (child = g_variant_iter_next_value(&iter)); + g_variant_unref(child)) { + + const char* path = NULL; + GVariant* properties = NULL; + g_variant_get(child, "(&o@a{sv})", &path, &properties); + if (properties) { + GVariant* value = g_variant_lookup_value(properties, + OFONO_CONTEXT_PROPERTY_TYPE, G_VARIANT_TYPE_STRING); + if (value) { + const char* type = g_variant_get_string(value, NULL); + if (path && type && !strcmp(type, OFONO_CONTEXT_TYPE_MMS)) { + mms_context = mms_context_info_new(path, properties); + } + g_variant_unref(value); + } + g_variant_unref(properties); + } + } + g_variant_unref(contexts); + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + return mms_context; +} + +static +void +mms_ofono_modem_gprs_context_added( + OrgOfonoConnectionManager* proxy, + const char* path, + GVariant* properties, + MMSOfonoModem* modem) +{ + GVariant* value = g_variant_lookup_value(properties, + OFONO_CONTEXT_PROPERTY_TYPE, G_VARIANT_TYPE_STRING); + const char* type = g_variant_get_string(value, NULL); + MMS_VERBOSE_("%p %s", modem, path); + MMS_ASSERT(proxy == modem->gprs_proxy); + if (type && !strcmp(type, OFONO_CONTEXT_TYPE_MMS)) { + MMS_DEBUG("MMS context %s created", path); + } + g_variant_unref(value); +} + +static +void +mms_ofono_modem_gprs_context_removed( + OrgOfonoConnectionManager* proxy, + const char* path, + MMSOfonoModem* modem) +{ + MMS_VERBOSE_("%p %s", modem, path); + MMS_ASSERT(proxy == modem->gprs_proxy); + if (modem->mms_context && g_strcmp0(modem->mms_context->path, path)) { + MMS_DEBUG("MMS context %s removed", path); + } +} + +static +char* +mms_ofono_modem_query_imsi( + OrgOfonoSimManager* proxy) +{ + char* imsi = NULL; + GError* error = NULL; + GVariant* properties = NULL; + if (org_ofono_sim_manager_call_get_properties_sync(proxy, &properties, + NULL, &error)) { + GVariant* imsi_value = g_variant_lookup_value(properties, + OFONO_SIM_PROPERTY_SUBSCRIBER_IDENTITY, G_VARIANT_TYPE_STRING); + if (imsi_value) { + imsi = g_strdup(g_variant_get_string(imsi_value, NULL)); + g_variant_unref(imsi_value); + } + g_variant_unref(properties); + } else { + MMS_ERR("%s", MMS_ERRMSG(error)); + g_error_free(error); + } + return imsi; +} + +static +void +mms_ofono_modem_sim_property_changed( + OrgOfonoSimManager* proxy, + const char* key, + GVariant* value, + MMSOfonoModem* modem) +{ + MMS_VERBOSE_("%p %s", modem, key); + MMS_ASSERT(proxy == modem->sim_proxy); + if (!strcmp(key, OFONO_SIM_PROPERTY_SUBSCRIBER_IDENTITY)) { + GVariant* variant = g_variant_get_variant(value); + g_free(modem->imsi); + modem->imsi = g_strdup(g_variant_get_string(variant, NULL)); + g_variant_unref(variant); + MMS_VERBOSE("IMSI: %s", modem->imsi); + } +} + +static +void +mms_ofono_modem_scan_interfaces( + MMSOfonoModem* m, + GVariant* ifs) +{ + GError* error = NULL; + gboolean sim_interface = FALSE; + gboolean gprs_interface = FALSE; + + if (ifs) { + GVariantIter iter; + GVariant* child; + for (g_variant_iter_init(&iter, ifs); + (child = g_variant_iter_next_value(&iter)) && + (!sim_interface || !gprs_interface); + g_variant_unref(child)) { + const char* ifname = NULL; + g_variant_get(child, "&s", &ifname); + if (ifname) { + if (!strcmp(ifname, OFONO_SIM_INTERFACE)) { + MMS_VERBOSE(" Found %s", ifname); + sim_interface = TRUE; + } else if (!strcmp(ifname, OFONO_GPRS_INTERFACE)) { + MMS_VERBOSE(" Found %s", ifname); + gprs_interface = TRUE; + } + } + } + } + + /* org.ofono.SimManager */ + if (m->imsi) { + g_free(m->imsi); + m->imsi = NULL; + } + if (sim_interface) { + if (!m->sim_proxy) { + m->sim_proxy = org_ofono_sim_manager_proxy_new_sync(m->bus, + G_DBUS_PROXY_FLAGS_NONE, OFONO_SERVICE, m->path, NULL, &error); + if (m->sim_proxy) { + /* Subscribe for PropertyChanged notifications */ + m->sim_property_change_signal_id = g_signal_connect( + m->sim_proxy, "property-changed", + G_CALLBACK(mms_ofono_modem_sim_property_changed), + m); + } else { + MMS_ERR("SimManager %s: %s", m->path, MMS_ERRMSG(error)); + g_error_free(error); + } + } + if (m->sim_proxy) { + m->imsi = mms_ofono_modem_query_imsi(m->sim_proxy); + MMS_VERBOSE("IMSI: %s", m->imsi ? m->imsi : ""); + } + } else if (m->sim_proxy) { + mms_ofono_modem_disconnect_sim_proxy(m); + } + + /* org.ofono.ConnectionManager */ + if (gprs_interface) { + MMSContextInfo* context_info = NULL; + if (!m->gprs_proxy) { + m->gprs_proxy = org_ofono_connection_manager_proxy_new_sync(m->bus, + G_DBUS_PROXY_FLAGS_NONE, OFONO_SERVICE, m->path, NULL, &error); + if (m->gprs_proxy) { + /* Subscribe for ContextAdded/Removed notifications */ + m->gprs_context_added_signal_id = g_signal_connect( + m->gprs_proxy, "context-added", + G_CALLBACK(mms_ofono_modem_gprs_context_added), + m); + m->gprs_context_removed_signal_id = g_signal_connect( + m->gprs_proxy, "context-removed", + G_CALLBACK(mms_ofono_modem_gprs_context_removed), + m); + } else { + MMS_ERR("ConnectionManager %s: %s", m->path, MMS_ERRMSG(error)); + g_error_free(error); + } + } + if (m->gprs_proxy) { + context_info = mms_ofono_modem_find_mms_context(m->gprs_proxy); + } + if (context_info) { + MMS_VERBOSE("MMS context: %s", context_info->path); + if (m->mms_context && + !g_strcmp0(context_info->path, m->mms_context->path)) { + mms_ofono_context_free(m->mms_context); + m->mms_context = NULL; + } + if (!m->mms_context) { + m->mms_context = mms_ofono_context_new(m, context_info->path, + context_info->properties); + } + mms_context_info_free(context_info); + } else { + MMS_VERBOSE("No MMS context"); + if (m->mms_context) { + mms_ofono_context_free(m->mms_context); + m->mms_context = NULL; + } + } + } else if (m->gprs_proxy) { + mms_ofono_modem_disconnect_gprs_proxy(m); + if (m->mms_context) { + mms_ofono_context_free(m->mms_context); + m->mms_context = NULL; + } + } +} + +static +void +mms_ofono_modem_property_changed( + OrgOfonoModem* proxy, + const char* key, + GVariant* variant, + MMSOfonoModem* modem) +{ + MMS_VERBOSE_("%p %s", modem, key); + MMS_ASSERT(proxy == modem->proxy); + if (!strcmp(key, OFONO_MODEM_PROPERTY_INTERFACES)) { + GVariant* value = g_variant_get_variant(variant); + mms_ofono_modem_scan_interfaces(modem, value); + g_variant_unref(value); + } +} + +MMSOfonoModem* +mms_ofono_modem_new( + GDBusConnection* bus, + const char* path, + GVariant* properties) +{ + GError* error = NULL; + MMSOfonoModem* modem = NULL; + OrgOfonoModem* proxy = org_ofono_modem_proxy_new_sync(bus, + G_DBUS_PROXY_FLAGS_NONE, OFONO_SERVICE, path, NULL, &error); + if (proxy) { + GVariant* interfaces = g_variant_lookup_value(properties, + OFONO_MODEM_PROPERTY_INTERFACES, G_VARIANT_TYPE_STRING_ARRAY); + modem = g_new0(MMSOfonoModem, 1); + MMS_DEBUG("Modem path '%s'", path); + MMS_VERBOSE_("%p '%s'", modem, path); + modem->path = g_strdup(path); + modem->proxy = proxy; + g_object_ref(modem->bus = bus); + + /* Check what we currently have */ + mms_ofono_modem_scan_interfaces(modem, interfaces); + g_variant_unref(interfaces); + + /* Register to receive PropertyChanged notifications */ + modem->property_change_signal_id = g_signal_connect( + proxy, "property-changed", + G_CALLBACK(mms_ofono_modem_property_changed), + modem); + } else { + MMS_ERR("%s: %s", path, MMS_ERRMSG(error)); + g_error_free(error); + } + return modem; +} + +void +mms_ofono_modem_free( + MMSOfonoModem* modem) +{ + if (modem) { + MMS_VERBOSE_("%p '%s'", modem, modem->path); + mms_ofono_modem_disconnect_sim_proxy(modem); + mms_ofono_modem_disconnect_gprs_proxy(modem); + mms_ofono_context_free(modem->mms_context); + if (modem->proxy) { + g_signal_handler_disconnect(modem->proxy, + modem->property_change_signal_id); + g_object_unref(modem->proxy); + } + g_object_unref(modem->bus); + g_free(modem->path); + g_free(modem->imsi); + g_free(modem); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_modem.h b/mms-ofono/src/mms_ofono_modem.h new file mode 100644 index 0000000..516e6ad --- /dev/null +++ b/mms-ofono/src/mms_ofono_modem.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_OFONO_MODEM_H +#define JOLLA_MMS_OFONO_MODEM_H + +#include "mms_ofono_context.h" + +struct mms_ofono_modem { + GDBusConnection* bus; + char* path; + + struct _OrgOfonoModem* proxy; + gulong property_change_signal_id; + + struct _OrgOfonoSimManager* sim_proxy; + gulong sim_property_change_signal_id; + char* imsi; + + struct _OrgOfonoConnectionManager* gprs_proxy; + gulong gprs_context_added_signal_id; + gulong gprs_context_removed_signal_id; + + MMSOfonoContext* mms_context; +}; + +MMSOfonoModem* +mms_ofono_modem_new( + GDBusConnection* bus, + const char* path, + GVariant* properties); + +void +mms_ofono_modem_free( + MMSOfonoModem* modem); + +#endif /* JOLLA_MMS_OFONO_MODEM_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_names.h b/mms-ofono/src/mms_ofono_names.h new file mode 100644 index 0000000..11a510c --- /dev/null +++ b/mms-ofono/src/mms_ofono_names.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_OFONO_NAMES_H +#define JOLLA_MMS_OFONO_NAMES_H + +#define OFONO_SERVICE "org.ofono" + +#define OFONO_MANAGER_INTERFACE OFONO_SERVICE ".Manager" +#define OFONO_MODEM_INTERFACE OFONO_SERVICE ".Modem" +#define OFONO_SIM_INTERFACE OFONO_SERVICE ".SimManager" +#define OFONO_GPRS_INTERFACE OFONO_SERVICE ".ConnectionManager" +#define OFONO_CONTEXT_INTERFACE OFONO_SERVICE ".ConnectionContext" + +#define OFONO_MODEM_PROPERTY_INTERFACES "Interfaces" +#define OFONO_SIM_PROPERTY_SUBSCRIBER_IDENTITY "SubscriberIdentity" + +#define OFONO_CONTEXT_PROPERTY_ACTIVE "Active" +#define OFONO_CONTEXT_PROPERTY_MMS_PROXY "MessageProxy" +#define OFONO_CONTEXT_PROPERTY_MMS_CENTER "MessageCenter" + +#define OFONO_CONTEXT_PROPERTY_SETTINGS "Settings" +#define OFONO_CONTEXT_SETTING_INTERFACE "Interface" + +#define OFONO_CONTEXT_PROPERTY_TYPE "Type" +#define OFONO_CONTEXT_TYPE_MMS "mms" + +#endif /* JOLLA_MMS_OFONO_NAMES_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/mms-ofono/src/mms_ofono_types.h b/mms-ofono/src/mms_ofono_types.h new file mode 100644 index 0000000..6627a77 --- /dev/null +++ b/mms-ofono/src/mms_ofono_types.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013-2014 Jolla Ltd. + * + * 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. + * + */ + +#ifndef JOLLA_MMS_OFONO_TYPES_H +#define JOLLA_MMS_OFONO_TYPES_H + +#include +#include "mms_lib_util.h" + +typedef struct mms_ofono_manager MMSOfonoManager; +typedef struct mms_ofono_modem MMSOfonoModem; +typedef struct mms_ofono_context MMSOfonoContext; +typedef struct mms_ofono_connection MMSOfonoConnection; + +#endif /* JOLLA_MMS_OFONO_TYPES_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/rpm/mms-engine.spec b/rpm/mms-engine.spec new file mode 100644 index 0000000..45c723a --- /dev/null +++ b/rpm/mms-engine.spec @@ -0,0 +1,53 @@ +Name: mms-engine +Summary: MMS engine +Version: 1.1.0 +Release: 1 +Group: Communications/Telephony and IM +License: GPLv2 +URL: https://github.com/nemomobile/mms-engine +Source0: %{name}-%{version}.tar.bz2 +Requires: dbus +Requires: ofono + +BuildRequires: python +BuildRequires: pkgconfig(glib-2.0) >= 2.32 +BuildRequires: pkgconfig(libsoup-2.4) >= 2.38 +BuildRequires: pkgconfig(libwspcodec) >= 2.0 + +%define src mms-engine +%define exe mms-engine +%define dbusname org.nemomobile.MmsEngine +%define dbusconfig %{_datadir}/dbus-1/system-services +%define dbuspolicy %{_sysconfdir}/dbus-1/system.d + +# Activation method: +%define pushconfig %{_sysconfdir}/ofono/push_forwarder.d +#define pushconfig {_sysconfdir}/push-agent +#Requires: push-agent >= 1.1 + +%description +MMS engine + +%prep +%setup -q -n %{name}-%{version} + +%build +make -C %{src} release + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}%{_sbindir} +mkdir -p %{buildroot}%{dbusconfig} +mkdir -p %{buildroot}%{dbuspolicy} +mkdir -p %{buildroot}%{pushconfig} +cp %{src}/build/release/%{exe} %{buildroot}%{_sbindir}/ +cp %{src}/%{dbusname}.service %{buildroot}%{dbusconfig}/ +cp %{src}/%{dbusname}.dbus.conf %{buildroot}%{dbuspolicy}/%{dbusname}.conf +cp %{src}/%{dbusname}.push.conf %{buildroot}%{pushconfig}/%{dbusname}.conf + +%files +%defattr(-,root,root,-) +%config %{dbuspolicy}/%{dbusname}.conf +%config %{pushconfig}/%{dbusname}.conf +%{dbusconfig}/%{dbusname}.service +%{_sbindir}/%{exe}