/* * Copyright (C) 2013-2017 Jolla Ltd. * Contact: Slava Monich * * 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 "test_transfer_list.h" #include "test_util.h" #include "mms_codec.h" #include "mms_file_util.h" #include "mms_lib_log.h" #include "mms_lib_util.h" #include "mms_settings.h" #include "mms_dispatcher.h" #include #include #include #define RET_OK (0) #define RET_ERR (1) #define RET_TIMEOUT (2) #define MMS_MESSAGE_TYPE_NONE (0) #define DATA_DIR "data/" #define LOCALHOST "127.0.0.1" #define TEST_TIMEOUT (10) /* seconds */ typedef struct test_part_desc { const char* content_type; const char* content_id; const char* file_name; } TestPartDesc; 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; const char* attic_file; MMS_RECEIVE_STATE expected_state; enum mms_message_type reply_msg; const TestPartDesc* parts; unsigned int nparts; const char* proxy; int flags; #define TEST_PUSH_HANDLING_FAILURE_OK (0x01) #define TEST_DEFER_RECEIVE (0x02) #define TEST_REJECT_RECEIVE (0x04) #define TEST_CONNECTION_FAILURE (0x08) #define TEST_OFFLINE (0x10) #define TEST_CANCEL_RECEIVED (0x20) } TestDesc; typedef struct test { const TestDesc* desc; const MMSConfig* config; MMSDispatcherDelegate delegate; MMSConnMan* cm; MMSHandler* handler; MMSDispatcher* disp; GMappedFile* notification_ind; GMappedFile* retrieve_conf; GMainLoop* loop; guint timeout_id; gulong msgreceived_id; TestHttp* http; int ret; } Test; static const TestPartDesc retrieve_success1_parts [] = { { "application/smil;charset=utf-8", "<0>", "0.smil" }, { "text/plain;charset=utf-8", "", "text_0011.txt" }, { "image/jpeg", "<131200181.jpg>", "131200181.jpg" }, { "text/plain;charset=utf-8", "", "text_0021.txt" }, { "image/jpeg", "<140100041.jpg>", "140100041.jpg" } }; static const TestPartDesc retrieve_success2_parts [] = { { "image/jpeg", "<2014_03_.jpg>", "2014_03_.jpg" }, { "text/plain;charset=utf-8", "", "Test_mms.txt" }, { "application/smil;charset=utf-8", "<332047400>", "332047400" }, }; static const TestPartDesc retrieve_success3_parts [] = { { "application/smil;charset=utf-8", "<0>", "0" }, { "image/jpeg", "<1>", "1.jpg" }, { "text/plain;charset=utf-8", "<2>", "text_001.txt" } }; static const TestPartDesc retrieve_transfer_encoding_parts [] = { { "application/smil;charset=utf-8", "", "smil" }, { "image/jpeg", "<1.jpg>", "1.jpg" }, { "text/plain;charset=utf-8", "<2.txt>", "2.txt" } }; static const TestPartDesc retrieve_invalid_subject [] = { { "application/smil;charset=us-ascii", "", "smil.smi" }, { "image/jpeg", "<1>", "1" } }; #define TEST_PARTS(parts) parts, G_N_ELEMENTS(parts) #define TEST_PARTS_NONE NULL, 0 static const TestDesc retrieve_tests[] = { { "Success1", NULL, "m-notification.ind", "m-retrieve.conf", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DECODING, MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, TEST_PARTS(retrieve_success1_parts), "0127.000.000.001", 0 },{ "Success2", /* Generated by Nokia C6 (Symbian "Belle") */ NULL, "m-notification.ind", "m-retrieve.conf", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DECODING, MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, TEST_PARTS(retrieve_success2_parts), "127.0.0.001", 0 },{ "Success3", /* Generated by Nokia N9 */ NULL, "m-notification.ind", "m-retrieve.conf", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DECODING, MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, TEST_PARTS(retrieve_success3_parts), LOCALHOST, 0 },{ "QuotedPrintable", /* quoted-printable transfer encoding for text */ NULL, "m-notification.ind", "m-retrieve.conf", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DECODING, MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, TEST_PARTS(retrieve_transfer_encoding_parts), LOCALHOST, 0 },{ "Base64", /* Base64 transfer encoding for text */ NULL, "m-notification.ind", "m-retrieve.conf", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DECODING, MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, TEST_PARTS(retrieve_transfer_encoding_parts), LOCALHOST, 0 },{ "InvalidSubject", NULL, "m-notification.ind", "m-retrieve.conf", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DECODING, MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, TEST_PARTS(retrieve_invalid_subject), LOCALHOST, 0 },{ "DeferSuccess", "Success1", "m-notification.ind", "m-retrieve.conf", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DECODING, MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, TEST_PARTS(retrieve_success1_parts), LOCALHOST, TEST_DEFER_RECEIVE },{ "CancelReceive", "Success1", "m-notification.ind", "m-retrieve.conf", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DECODING, MMS_MESSAGE_TYPE_ACKNOWLEDGE_IND, TEST_PARTS(retrieve_success1_parts), LOCALHOST, TEST_DEFER_RECEIVE | TEST_CANCEL_RECEIVED },{ "Expired1", NULL, "m-notification.ind", NULL, SOUP_STATUS_NOT_FOUND, NULL, NULL, MMS_RECEIVE_STATE_DOWNLOAD_ERROR, MMS_MESSAGE_TYPE_NONE, TEST_PARTS_NONE, LOCALHOST, 0 },{ "Expired2", NULL, "m-notification.ind", NULL, SOUP_STATUS_OK, NULL, NULL, MMS_RECEIVE_STATE_INVALID, MMS_MESSAGE_TYPE_NOTIFYRESP_IND, TEST_PARTS_NONE, LOCALHOST, TEST_REJECT_RECEIVE },{ "SoonExpired", NULL, "m-notification.ind", NULL, SOUP_STATUS_TRY_AGAIN, NULL, NULL, MMS_RECEIVE_STATE_DOWNLOAD_ERROR, MMS_MESSAGE_TYPE_NONE, TEST_PARTS_NONE, LOCALHOST, 0 },{ "Offline", NULL, "m-notification.ind", NULL, 0, NULL, NULL, MMS_RECEIVE_STATE_DOWNLOAD_ERROR, MMS_MESSAGE_TYPE_NONE, TEST_PARTS_NONE, LOCALHOST, TEST_OFFLINE },{ "Timeout", NULL, "m-notification.ind", NULL, 0, NULL, NULL, MMS_RECEIVE_STATE_DOWNLOAD_ERROR, MMS_MESSAGE_TYPE_NONE, TEST_PARTS_NONE, LOCALHOST, TEST_CONNECTION_FAILURE },{ "NotAllowed", NULL, "m-notification.ind", "not-allowed.html", SOUP_STATUS_BAD_REQUEST, "text/html", NULL, MMS_RECEIVE_STATE_DOWNLOAD_ERROR, MMS_MESSAGE_TYPE_NONE, TEST_PARTS_NONE, LOCALHOST, 0 },{ "NotFound", NULL, "m-notification.ind", "not-found.html", SOUP_STATUS_NOT_FOUND, "text/html", NULL, MMS_RECEIVE_STATE_DOWNLOAD_ERROR, MMS_MESSAGE_TYPE_NONE, TEST_PARTS_NONE, LOCALHOST, 0 },{ "MessageNotFound", NULL, "m-notification.ind", "m-retrieve.conf", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DOWNLOAD_ERROR, MMS_MESSAGE_TYPE_NONE, TEST_PARTS_NONE, LOCALHOST, 0 },{ "GarbageRetrieve", NULL, "m-notification.ind", "garbage", SOUP_STATUS_OK, MMS_CONTENT_TYPE, NULL, MMS_RECEIVE_STATE_DECODING_ERROR, MMS_MESSAGE_TYPE_NOTIFYRESP_IND, TEST_PARTS_NONE, LOCALHOST, 0 },{ "GarbagePush", NULL, "garbage", NULL, 0, NULL, "000/push.pdu", MMS_RECEIVE_STATE_INVALID, MMS_MESSAGE_TYPE_NONE, TEST_PARTS_NONE, LOCALHOST, TEST_PUSH_HANDLING_FAILURE_OK },{ "UnsupportedPush", NULL, "unsupported", NULL, 0, NULL, "000/push.pdu", MMS_RECEIVE_STATE_INVALID, MMS_MESSAGE_TYPE_NONE, TEST_PARTS_NONE, LOCALHOST, TEST_PUSH_HANDLING_FAILURE_OK } }; static gboolean test_files_equal( const char* path1, const char* path2) { gboolean equal = FALSE; if (path1 && path2) { GError* error = NULL; GMappedFile* f1 = g_mapped_file_new(path1, FALSE, &error); if (f1) { GMappedFile* f2 = g_mapped_file_new(path2, FALSE, &error); if (f2) { const gsize size = g_mapped_file_get_length(f1); if (g_mapped_file_get_length(f2) == size) { const void* data1 = g_mapped_file_get_contents(f1); const void* data2 = g_mapped_file_get_contents(f2); equal = !memcmp(data1, data2, size); } g_mapped_file_unref(f2); } g_mapped_file_unref(f1); } } if (!equal) { GERR("%s is not identical to %s", path1, path2); } return equal; } static int test_validate_parts( Test* test) { const TestDesc* desc = test->desc; MMSMessage* msg = mms_handler_test_get_received_message(test->handler,NULL); if (!desc->nparts && (!msg || !g_slist_length(msg->parts))) { return RET_OK; } else { const char* dir = desc->dir ? desc->dir : desc->name; const unsigned int nparts = g_slist_length(msg->parts); if (desc->nparts == nparts) { const TestPartDesc* expect = test->desc->parts; GSList* list = msg->parts; while (list) { const MMSMessagePart* part = list->data; char* sample = g_strconcat(DATA_DIR, dir, "/parts/", expect->file_name, NULL); const gboolean match = test_files_equal(part->file, sample); const char* fname; G_GNUC_BEGIN_IGNORE_DEPRECATIONS; fname = g_basename(part->file); G_GNUC_END_IGNORE_DEPRECATIONS; g_free(sample); if (!match) { /* Message message is printed by test_files_equal */ return RET_ERR; } if (strcmp(expect->content_type, part->content_type)) { GERR("Content type mismatch: expected %s, got %s", expect->content_type, part->content_type); return RET_ERR; } if (strcmp(expect->file_name, fname)) { GERR("File name mismatch: expected %s, got %s", expect->file_name, fname); return RET_ERR; } if (!part->content_id) { GERR("Missing content id"); return RET_ERR; } if (strcmp(expect->content_id, part->content_id)) { GERR("Content-ID mismatch: expected %s, got %s", expect->content_id, part->content_id); return RET_ERR; } list = list->next; expect++; } return RET_OK; } GERR("%u parts expected, got %u", desc->nparts, nparts); return RET_ERR; } } static void test_finish( Test* test) { const TestDesc* desc = test->desc; const char* name = desc->name; if (test->ret == RET_OK) { MMS_RECEIVE_STATE state; state = mms_handler_test_receive_state(test->handler, NULL); if (state != desc->expected_state) { test->ret = RET_ERR; GERR("%s state %d, expected %d", name, state, 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 (desc->reply_msg) { MMSPdu* pdu = g_new0(MMSPdu, 1); if (mms_message_decode(resp_data, resp_len, pdu)) { if (pdu->type == desc->reply_msg) { test->ret = test_validate_parts(test); } else { test->ret = RET_ERR; GERR("%s reply %u, expected %u", name, pdu->type, desc->reply_msg); } } else { test->ret = RET_ERR; GERR("%s can't decode reply message", name); } mms_message_free(pdu); } else { test->ret = RET_ERR; GERR("%s expects no reply", name); } } else if (desc->reply_msg) { test->ret = RET_ERR; GERR("%s expects reply", name); } } } if (test->ret == RET_OK && desc->attic_file) { const char* dir = desc->dir ? desc->dir : desc->name; char* f1 = g_strconcat(DATA_DIR, dir, "/", desc->ni_file, NULL); char* f2 = g_strconcat(test->config->root_dir, "/" MMS_ATTIC_DIR "/", desc->attic_file, NULL); if (test_files_equal(f1, f2)) { char* dir = g_path_get_dirname(f2); remove(f2); rmdir(dir); g_free(dir); } else { test->ret = RET_ERR; } g_free(f1); g_free(f2); } GINFO("%s: %s", (test->ret == RET_OK) ? "OK" : "FAILED", name); mms_handler_test_reset(test->handler); g_signal_handler_disconnect(test->handler, test->msgreceived_id); g_main_loop_quit(test->loop); } static void test_done( MMSDispatcherDelegate* delegate, MMSDispatcher* dispatcher) { Test* test = G_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; GINFO("%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_notify( MMSHandler* handler, const char* imsi, const char* from, const char* subject, time_t expiry, GBytes* data, void* param) { Test* test = param; return !(test->desc->flags & TEST_REJECT_RECEIVE); } typedef struct test_cancel_msg { MMSDispatcher* disp; MMSMessage* msg; } TestCancelMsg; static gboolean test_cancel_msg_proc( void* param) { TestCancelMsg* cancel = param; GVERBOSE("Cancelling message %s", cancel->msg->id); mms_dispatcher_cancel(cancel->disp, cancel->msg->id); mms_dispatcher_unref(cancel->disp); mms_message_unref(cancel->msg); g_free(cancel); return FALSE; } static void test_msgreceived( MMSHandler* handler, MMSMessage* msg, void* param) { Test* test = param; if (test->desc->flags & TEST_CANCEL_RECEIVED) { TestCancelMsg* cancel = g_new(TestCancelMsg,1); cancel->disp = mms_dispatcher_ref(test->disp); cancel->msg = mms_message_ref(msg); g_idle_add_full(G_PRIORITY_HIGH, test_cancel_msg_proc, cancel, NULL); } } static gboolean test_init( Test* test, const MMSConfig* config, const TestDesc* desc, gboolean debug) { 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; GDEBUG(">>>>>>>>>> %s <<<<<<<<<<", desc->name); memset(test, 0, sizeof(*test)); test->config = config; 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) { MMSSettings* settings = mms_settings_default_new(config); MMSTransferList* transfers = mms_transfer_list_test_new(); 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(settings, test->cm, test->handler, transfers); test->loop = g_main_loop_new(NULL, FALSE); if (!debug) { test->timeout_id = g_timeout_add_seconds(TEST_TIMEOUT, test_timeout, test); } test->delegate.fn_done = test_done; mms_dispatcher_set_delegate(test->disp, &test->delegate); if (!(desc->flags & TEST_CONNECTION_FAILURE)) { test->http = test_http_new(test->retrieve_conf, test->desc->content_type, test->desc->status); mms_connman_test_set_proxy(test->cm, desc->proxy, test_http_get_port(test->http)); } if (desc->flags & TEST_OFFLINE) { mms_connman_test_set_offline(test->cm, TRUE); } if (desc->flags & TEST_DEFER_RECEIVE) { mms_handler_test_defer_receive(test->handler, test->disp); } mms_handler_test_set_prenotify_fn(test->handler, test_notify, test); test->msgreceived_id = mms_handler_test_add_msgreceived_fn(test->handler, test_msgreceived, test); mms_transfer_list_unref(transfers); mms_settings_unref(settings); test->ret = RET_ERR; ok = TRUE; } else { GERR("%s", GERRMSG(error)); g_error_free(error); } g_mapped_file_unref(test->notification_ind); } else { GERR("%s", GERRMSG(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, gboolean debug) { Test test; if (test_init(&test, config, desc, debug)) { GError* error = NULL; 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, &error)) { if (mms_dispatcher_start(test.disp)) { test.ret = RET_OK; g_main_loop_run(test.loop); } else { GINFO("%s FAILED", desc->name); } } else { g_error_free(error); if (desc->flags & TEST_PUSH_HANDLING_FAILURE_OK) { test.ret = RET_OK; test_finish(&test); } else { GINFO("%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, gboolean debug) { int i, ret; if (name) { const TestDesc* found = NULL; for (i=0, ret = RET_ERR; iname, name)) { ret = test_retrieve_once(config, test, debug); found = test; break; } } if (!found) GERR("No such test: %s", name); } else { for (i=0, ret = RET_OK; i