From e5b4d04593b8f123aae3b0b9630742b64261f9d4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Petr=20P=C3=ADsa=C5=99?= Date: Thu, 31 Dec 2015 11:52:53 +0100 Subject: [PATCH] Implement GetDataBoxActivityStatus as isds_get_box_state_history() Although the specification states second precision and obligatory elements, we express the times as timeval structure to keep the libisds API uniform. Be ware the times are not pointers to timeval. Because specification guarantees the times are always defined, we do not use pointers to timeval to ease applications from useless checking for NULL. --- TODO | 2 - src/isds.c | 339 +++++++++++++++++++++++++++--- src/isds.h | 22 ++ src/unix.c | 1 + test/simline/Makefile.am | 2 + test/simline/isds_get_box_state_history.c | 304 +++++++++++++++++++++++++++ test/simline/service.c | 326 ++++++++++++++++++++++++++++ test/simline/services.h | 21 ++ test/simline/system.h | 11 + {src => test/simline}/unix.c | 43 ++-- test/simline/win32.c | 27 +++ 11 files changed, 1056 insertions(+), 42 deletions(-) create mode 100644 test/simline/isds_get_box_state_history.c copy {src => test/simline}/unix.c (62%) diff --git a/TODO b/TODO index d31c513..2372ed7 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,3 @@ -* Implement news from 2015-11-09 and previous - GetDataBoxActivityStatus * Test Re-signISDSDocument service Testing instance seems refusing any input as not-a-message * Implement news from 2012-07-29 diff --git a/src/isds.c b/src/isds.c index 98a1b39..a10a9a7 100644 --- a/src/isds.c +++ b/src/isds.c @@ -2287,11 +2287,11 @@ static isds_error timeval2timestring(const struct timeval *time, #endif /* HAVE_LIBCURL */ -/* Convert UTF-8 ISO 8601 date-time @string to struct timeval. +/* Convert UTF-8 ISO 8601 date-time @string to static struct timeval. * It respects microseconds too. Microseconds are rounded half up. - * In case of error, @time will be freed. */ -static isds_error timestring2timeval(const xmlChar *string, - struct timeval **time) { + * In case of error, @time will be undefined. */ +static isds_error timestring2static_timeval(const xmlChar *string, + struct timeval *time) { struct tm broken; char *offset, *delim, *endptr; const int subsecond_resolution = 6; @@ -2306,18 +2306,11 @@ static isds_error timestring2timeval(const xmlChar *string, if (!time) return IE_INVAL; if (!string) { - zfree(*time); return IE_INVAL; } memset(&broken, 0, sizeof(broken)); - - if (!*time) { - *time = calloc(1, sizeof(**time)); - if (!*time) return IE_NOMEM; - } else { - memset(*time, 0, sizeof(**time)); - } + memset(time, 0, sizeof(*time)); /* xsd:date is ISO 8601 string, thus ASCII */ @@ -2329,7 +2322,6 @@ static isds_error timestring2timeval(const xmlChar *string, &broken.tm_year, &broken.tm_mon, &broken.tm_mday, &broken.tm_hour, &broken.tm_min, &broken.tm_sec, &i)) < 6) { - zfree(*time); return IE_DATE; } @@ -2341,7 +2333,6 @@ static isds_error timestring2timeval(const xmlChar *string, /* Parse date and time without subseconds and offset */ offset = strptime((char*)string, "%Y-%m-%dT%T", &broken); if (!offset) { - zfree(*time); return IE_DATE; } #endif @@ -2372,7 +2363,6 @@ static isds_error timestring2timeval(const xmlChar *string, long_number = strtol(subseconds, &endptr, 10); if (*endptr != '\0' || long_number == LONG_MIN || long_number == LONG_MAX) { - zfree(*time); return IE_DATE; } /* POSIX sys_time.h(0p) defines tv_usec timeval member as su_seconds_t @@ -2380,18 +2370,17 @@ static isds_error timestring2timeval(const xmlChar *string, * microseconds" and "the type shall be a signed integer capable of * storing values at least in the range [-1, 1000000]. */ if (long_number < -1 || long_number >= 1000000) { - zfree(*time); return IE_DATE; } - (*time)->tv_usec = long_number; + time->tv_usec = long_number; /* Round the subseconds */ if (round_up) { - if (999999 == (*time)->tv_usec) { - (*time)->tv_usec = 0; + if (999999 == time->tv_usec) { + time->tv_usec = 0; broken.tm_sec++; } else { - (*time)->tv_usec++; + time->tv_usec++; } } @@ -2411,7 +2400,6 @@ static isds_error timestring2timeval(const xmlChar *string, * colon separator */ if (offset && (*offset == '-' || *offset == '+')) { if (2 != sscanf(offset + 1, "%2d:%2d", &offset_hours, &offset_minutes)) { - zfree(*time); return IE_DATE; } if (*offset == '+') { @@ -2424,9 +2412,8 @@ static isds_error timestring2timeval(const xmlChar *string, } /* Convert to time_t */ - (*time)->tv_sec = _isds_timegm(&broken); - if ((*time)->tv_sec == (time_t) -1) { - zfree(*time); + time->tv_sec = _isds_timegm(&broken); + if (time->tv_sec == (time_t) -1) { return IE_DATE; } @@ -2434,6 +2421,35 @@ static isds_error timestring2timeval(const xmlChar *string, } +/* Convert UTF-8 ISO 8601 date-time @string to reallocated struct timeval. + * It respects microseconds too. Microseconds are rounded half up. + * In case of error, @time will be freed. */ +static isds_error timestring2timeval(const xmlChar *string, + struct timeval **time) { + isds_error error; + + if (!time) return IE_INVAL; + if (!string) { + zfree(*time); + return IE_INVAL; + } + + if (!*time) { + *time = calloc(1, sizeof(**time)); + if (!*time) return IE_NOMEM; + } else { + memset(*time, 0, sizeof(**time)); + } + + error = timestring2static_timeval(string, *time); + if (error) { + zfree(*time); + } + + return error; +} + + /* Convert unsigned int into isds_message_status. * @context is session context * @number is pointer to number value. NULL will be treated as invalid value. @@ -8149,6 +8165,281 @@ leave: } +#if HAVE_LIBCURL +/* Convert XSD:tdbPeriod XML tree into structure + * @context is ISDS context. + * @period is automatically reallocated found box status period structure. + * @xpath_ctx is XPath context with current node as element of + * XSD:tDbPeriod type. + * In case of error @period will be freed. */ +static isds_error extract_Period(struct isds_ctx *context, + struct isds_box_state_period **period, xmlXPathContextPtr xpath_ctx) { + isds_error err = IE_SUCCESS; + xmlXPathObjectPtr result = NULL; + char *string = NULL; + long int *dbState_ptr; + + if (NULL == context) return IE_INVALID_CONTEXT; + if (NULL == period) return IE_INVAL; + isds_box_state_period_free(period); + if (!xpath_ctx) return IE_INVAL; + + + *period = calloc(1, sizeof(**period)); + if (NULL == *period) { + err = IE_NOMEM; + goto leave; + } + + /* Extract data */ + EXTRACT_STRING("isds:PeriodFrom", string); + if (NULL == string) { + err = IE_XML; + isds_log_message(context, + _("Could not find PeriodFrom element value")); + goto leave; + } + err = timestring2static_timeval((xmlChar *) string, + &((*period)->from)); + if (err) { + char *string_locale = _isds_utf82locale(string); + if (err == IE_DATE) err = IE_ISDS; + isds_printf_message(context, + _("Could not convert PeriodFrom as ISO time: %s"), + string_locale); + free(string_locale); + goto leave; + } + zfree(string); + + EXTRACT_STRING("isds:PeriodTo", string); + if (NULL == string) { + err = IE_XML; + isds_log_message(context, + _("Could not find PeriodTo element value")); + goto leave; + } + err = timestring2static_timeval((xmlChar *) string, + &((*period)->to)); + if (err) { + char *string_locale = _isds_utf82locale(string); + if (err == IE_DATE) err = IE_ISDS; + isds_printf_message(context, + _("Could not convert PeriodTo as ISO time: %s"), + string_locale); + free(string_locale); + goto leave; + } + zfree(string); + + dbState_ptr = &((*period)->dbState); + EXTRACT_LONGINT("isds:DbState", dbState_ptr, 1); + +leave: + if (err) isds_box_state_period_free(period); + free(string); + xmlXPathFreeObject(result); + return err; +} +#endif /* HAVE_LIBCURL */ + + +/* Get history of box state changes. + * @context is ISDS session context. + * @box_id is UTF-8 encoded sender box identifier as zero terminated string. + * @from_time is first second of history to return in @history. Server ignores + * subseconds. NULL means time of creating the box. + * @to_time is last second of history to return in @history. Server ignores + * subseconds. It's valid to have the @from_time equaled to the @to_time. The + * interval is closed from both ends. NULL means now. + * @history outputs auto-reallocated list of pointers to struct + * isds_box_state_period. Each item describes a continues time when the box + * was in one state. The state is 1 for accessible box. Otherwise the box + * is inaccessible (priviledged users will get exact box state as enumerated + * in isds_DbState, other users 0). + * @return: + * IE_SUCCESS if the history has been obtained correctly, + * or other appropriate error. Please note that server allows to retrieve + * the history only to some users. */ +isds_error isds_get_box_state_history(struct isds_ctx *context, + const char *box_id, + const struct timeval *from_time, const struct timeval *to_time, + struct isds_list **history) { + isds_error err = IE_SUCCESS; +#if HAVE_LIBCURL + char *box_id_locale = NULL; + xmlNodePtr request = NULL, node; + xmlNsPtr isds_ns = NULL; + xmlChar *string = NULL; + + xmlDocPtr response = NULL; + xmlXPathContextPtr xpath_ctx = NULL; + xmlXPathObjectPtr result = NULL; +#endif + + if (!context) return IE_INVALID_CONTEXT; + zfree(context->long_message); + + /* Free output argument */ + isds_list_free(history); + +#if HAVE_LIBCURL + /* Check if connection is established */ + if (NULL == context->curl) return IE_CONNECTION_CLOSED; + + /* ??? XML schema allows empty box ID, textual documentation + * requries the value. */ + /* Allow undefined box_id */ + if (NULL != box_id) { + box_id_locale = _isds_utf82locale((char*)box_id); + if (NULL == box_id_locale) { + err = IE_NOMEM; + goto leave; + } + } + + /* Build request */ + request = xmlNewNode(NULL, BAD_CAST "GetDataBoxActivityStatus"); + if (NULL == request) { + isds_printf_message(context, + _("Could not build GetDataBoxActivityStatus request " + "for %s box"), + box_id_locale); + err = IE_ERROR; + goto leave; + } + isds_ns = xmlNewNs(request, BAD_CAST ISDS_NS, NULL); + if(!isds_ns) { + isds_log_message(context, _("Could not create ISDS name space")); + err = IE_ERROR; + goto leave; + } + xmlSetNs(request, isds_ns); + + /* Add mandatory XSD:tIdDbInput child */ + INSERT_STRING(request, BAD_CAST "dbID", box_id); + /* Add times elements only when defined */ + /* ???: XML schema requires the values, textual documentation does not. */ + if (from_time) { + err = timeval2timestring(from_time, &string); + if (err) { + isds_log_message(context, + _("Could not convert `from_time' argument to ISO time " + "string")); + goto leave; + } + INSERT_STRING(request, "baFrom", string); + zfree(string); + } + if (to_time) { + err = timeval2timestring(to_time, &string); + if (err) { + isds_log_message(context, + _("Could not convert `to_time' argument to ISO time " + "string")); + goto leave; + } + INSERT_STRING(request, "baTo", string); + zfree(string); + } + + /* Send request and check response*/ + err = send_destroy_request_check_response(context, + SERVICE_DB_SEARCH, BAD_CAST "GetDataBoxActivityStatus", + &request, &response, NULL, NULL); + if (err) goto leave; + + + /* Extract data */ + /* Set context to the root */ + xpath_ctx = xmlXPathNewContext(response); + if (!xpath_ctx) { + err = IE_ERROR; + goto leave; + } + if (_isds_register_namespaces(xpath_ctx, MESSAGE_NS_UNSIGNED)) { + err = IE_ERROR; + goto leave; + } + result = xmlXPathEvalExpression(BAD_CAST "/isds:GetDataBoxActivityStatusResponse", + xpath_ctx); + if (!result) { + err = IE_ERROR; + goto leave; + } + if (xmlXPathNodeSetIsEmpty(result->nodesetval)) { + isds_log_message(context, _("Missing GetDataBoxActivityStatusResponse element")); + err = IE_ISDS; + goto leave; + } + if (result->nodesetval->nodeNr > 1) { + isds_log_message(context, _("Multiple GetDataBoxActivityStatusResponse element")); + err = IE_ISDS; + goto leave; + } + xpath_ctx->node = result->nodesetval->nodeTab[0]; + xmlXPathFreeObject(result); result = NULL; + + /* Ignore dbID, it's the same as the input argument. */ + + /* Extract records */ + if (NULL == history) goto leave; + result = xmlXPathEvalExpression(BAD_CAST "isds:Periods/isds:Period", + xpath_ctx); + if (!result) { + err = IE_ERROR; + goto leave; + } + if (!xmlXPathNodeSetIsEmpty(result->nodesetval)) { + struct isds_list *prev_item = NULL; + + /* Iterate over all records */ + for (int i = 0; i < result->nodesetval->nodeNr; i++) { + struct isds_list *item; + + /* Prepare structure */ + item = calloc(1, sizeof(*item)); + if (!item) { + err = IE_NOMEM; + goto leave; + } + item->destructor = (void(*)(void**))isds_box_state_period_free; + if (i == 0) *history = item; + else prev_item->next = item; + prev_item = item; + + /* Extract it */ + xpath_ctx->node = result->nodesetval->nodeTab[i]; + err = extract_Period(context, + (struct isds_box_state_period **) (&item->data), + xpath_ctx); + if (err) goto leave; + } + } + +leave: + if (!err) { + isds_log(ILF_ISDS, ILL_DEBUG, + _("GetDataBoxActivityStatus request for %s box " + "processed by server successfully.\n"), box_id_locale); + } + if (err) { + isds_list_free(history); + } + + free(box_id_locale); + xmlXPathFreeObject(result); + xmlXPathFreeContext(xpath_ctx); + xmlFreeDoc(response); + +#else /* not HAVE_LIBCURL */ + err = IE_NOTSUP; +#endif + + return err; +} + + /* Get list of permissions to send commercial messages. * @context is ISDS session context. * @box_id is UTF-8 encoded sender box identifier as zero terminated string diff --git a/src/isds.h b/src/isds.h index 631d333..7cc885c 100644 --- a/src/isds.h +++ b/src/isds.h @@ -1332,6 +1332,28 @@ isds_error isds_find_box_by_fulltext(struct isds_ctx *context, isds_error isds_CheckDataBox(struct isds_ctx *context, const char *box_id, long int *box_status); +/* Get history of box state changes. + * @context is ISDS session context. + * @box_id is UTF-8 encoded sender box identifier as zero terminated string. + * @from_time is first second of history to return in @history. Server ignores + * subseconds. NULL means time of creating the box. + * @to_time is last second of history to return in @history. Server ignores + * subseconds. It's valid to have the @from_time equaled to the @to_time. The + * interval is closed from both ends. NULL means now. + * @history outputs auto-reallocated list of pointers to struct + * isds_box_state_period. Each item describes a continues time when the box + * was in one state. The state is 1 for accessible box. Otherwise the box + * is inaccessible (priviledged users will get exact box state as enumerated + * in isds_DbState, other users 0). + * @return: + * IE_SUCCESS if the history has been obtained correctly, + * or other appropriate error. Please note that server allows to retrieve + * the history only to some users. */ +isds_error isds_get_box_state_history(struct isds_ctx *context, + const char *box_id, + const struct timeval *from_time, const struct timeval *to_time, + struct isds_list **history); + /* Get list of permissions to send commercial messages. * @context is ISDS session context. * @box_id is UTF-8 encoded sender box identifier as zero terminated string diff --git a/src/unix.c b/src/unix.c index 9e10f69..63a6f7b 100644 --- a/src/unix.c +++ b/src/unix.c @@ -1,5 +1,6 @@ #include "isds_priv.h" #include +#include /* for getenv */ #include #include #include "isds.h" diff --git a/test/simline/Makefile.am b/test/simline/Makefile.am index fd3424c..cae7322 100644 --- a/test/simline/Makefile.am +++ b/test/simline/Makefile.am @@ -18,6 +18,7 @@ if BUILD_CURL TESTS += basic_authentication hotp_authentication totp_authentication \ certificate_user_password_authentication \ isds_change_password isds_get_commercial_credit \ + isds_get_box_state_history \ isds_find_box_by_fulltext isds_FindDataBox isds_FindPersonalDataBox \ hotp_isds_change_password totp_isds_change_password \ isds_delete_message_from_storage isds_ping isds_resign_message @@ -52,6 +53,7 @@ certificate_user_password_authentication_SOURCES = \ certificate_user_password_authentication.c $(common) isds_change_password_SOURCES = isds_change_password.c $(common) isds_get_commercial_credit_SOURCES = isds_get_commercial_credit.c $(common) +isds_get_box_state_history_SOURCES = isds_get_box_state_history.c $(common) isds_find_box_by_fulltext_SOURCES = isds_find_box_by_fulltext.c $(common) isds_FindDataBox_SOURCES = isds_FindDataBox.c $(common) \ test_DbOwnerInfo.c test_DbOwnerInfo.h diff --git a/test/simline/isds_get_box_state_history.c b/test/simline/isds_get_box_state_history.c new file mode 100644 index 0000000..4845a97 --- /dev/null +++ b/test/simline/isds_get_box_state_history.c @@ -0,0 +1,304 @@ +#ifndef _POSIX_SOURCE +#define _POSIX_SOURCE /* For getaddrinfo(3) */ +#endif + +#ifndef _BSD_SOURCE +#define _BSD_SOURCE /* For NI_MAXHOST up to glibc-2.19 */ +#endif +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE /* For NI_MAXHOST since glibc-2.20 */ +#endif + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 /* For unsetenv(3) */ +#endif + +#include "../test.h" +#include "test_DbOwnerInfo.h" +#include "server.h" +#include "isds.h" + +static const char *username = "Doug1as$"; +static const char *password = "42aA#bc8"; + + +static int test_login(const isds_error error, struct isds_ctx *context, + const char *url, const char *username, const char *password, + const struct isds_pki_credentials *pki_credentials, + struct isds_otp *otp) { + isds_error err; + + err = isds_login(context, url, username, password, pki_credentials, otp); + if (error != err) + FAIL_TEST("Wrong return code: expected=%s, returned=%s (%s)", + isds_strerror(error), isds_strerror(err), + isds_long_message(context)); + + PASS_TEST; +} + +/* Compare isds_box_state_period. + * @return 0 if equaled, 1 otherwise. */ +static int compare_isds_box_state_period( + const struct isds_box_state_period *expected_result, + const struct isds_box_state_period *result) { + + TEST_POINTER_DUPLICITY(expected_result, result); + if (NULL == expected_result) + return 0; + + TEST_TIMEVALPTR_DUPLICITY(&expected_result->from, &result->from); + TEST_TIMEVALPTR_DUPLICITY(&expected_result->to, &result->to); + TEST_INT_DUPLICITY(expected_result->dbState, result->dbState); + + return 0; +} + +/* Compare list of isds_box_state_period structures. + * @return 0 if equaled, 1 otherwise and set failure reason. */ +static int compare_isds_box_state_period_lists(const struct isds_list *expected_list, + const struct isds_list *list) { + const struct isds_list *expected_item, *item; + int i; + + for (i = 1, expected_item = expected_list, item = list; + NULL != expected_item && NULL != item; + i++, expected_item = expected_item->next, item = item->next) { + if (compare_isds_box_state_period( + (struct isds_box_state_period *)expected_item->data, + (struct isds_box_state_period *)item->data)) + return 1; + } + if (NULL != expected_item && NULL == item) + FAIL_TEST("Result list is missing %d. item", i); + if (NULL == expected_item && NULL != item) + FAIL_TEST("Result list has superfluous %d. item", i); + + return 0; +} + +static int test_isds_get_box_state_history(const isds_error expected_error, + struct isds_ctx *context, + const char *dbID, const struct timeval *from, const struct timeval *to, + const struct isds_list *expected_results) { + isds_error error; + struct isds_list *results; + TEST_CALLOC(results); + + error = isds_get_box_state_history(context, dbID, from, to, &results); + TEST_DESTRUCTOR((void(*)(void*))isds_list_free, &results); + + if (expected_error != error) { + FAIL_TEST("Wrong return code: expected=%s, returned=%s (%s)", + isds_strerror(expected_error), isds_strerror(error), + isds_long_message(context)); + } + + if (IE_SUCCESS != error) { + TEST_POINTER_IS_NULL(results); + PASS_TEST; + } + + if (compare_isds_box_state_period_lists(expected_results, results)) + return 1; + + + PASS_TEST; +} + +int main(void) { + int error; + pid_t server_process; + struct isds_ctx *context = NULL; + + INIT_TEST("isds_get_box_state_history"); + + if (unsetenv("http_proxy")) { + ABORT_UNIT("Could not remove http_proxy variable from environment\n"); + } + if (isds_init()) { + isds_cleanup(); + ABORT_UNIT("isds_init() failed\n"); + } + context = isds_ctx_create(); + if (!context) { + isds_cleanup(); + ABORT_UNIT("isds_ctx_create() failed\n"); + } + + { + /* Full response with two results */ + char *url = NULL; + + char *input_dbID = "A123456"; + struct timeval input_from = { + .tv_sec = 5, + .tv_usec = 0 + }; + struct timeval input_to = { + .tv_sec = 6, + .tv_usec = 0 + }; + + struct isds_box_state_period period1 = { + .from = { + .tv_sec = 1, + .tv_usec = 0 + }, + .to = { + .tv_sec = 2, + .tv_usec = 0 + }, + .dbState = 1 + }; + + struct isds_box_state_period period2 = { + .from = { + .tv_sec = 3, + .tv_usec = 0 + }, + .to = { + .tv_sec = 4, + .tv_usec = 0 + }, + .dbState = 2 + }; + struct isds_list results2 = { + .next = NULL, + .data = &period2, + .destructor = NULL + }; + struct isds_list results = { + .next = &results2, + .data = &period1, + .destructor = NULL + }; + + struct server_box_state_period server_period1 = { + .from = &period1.from, + .to = &period1.to, + .dbState = period1.dbState + }; + struct server_box_state_period server_period2 = { + .from = &period2.from, + .to = &period2.to, + .dbState = period2.dbState + }; + struct server_list server_results2 = { + .next = NULL, + .data = &server_period2, + .destructor = NULL + }; + struct server_list server_results = { + .next = &server_results2, + .data = &server_period1, + .destructor = NULL + }; + + const struct arguments_DS_df_GetDataBoxActivityStatus + service_arguments = { + .status_code = "0000", + .status_message = "Ok.", + .box_id = input_dbID, + .from = &input_from, + .to = &input_to, + .result_box_id = "B123456", + .results_exists = 0, + .results = &server_results + }; + const struct service_configuration services[] = { + { SERVICE_DS_Dz_DummyOperation, NULL }, + { SERVICE_DS_df_GetDataBoxActivityStatus, &service_arguments }, + { SERVICE_END, NULL } + }; + const struct arguments_basic_authentication server_arguments = { + .username = username, + .password = password, + .isds_deviations = 1, + .services = services + }; + error = start_server(&server_process, &url, + server_basic_authentication, &server_arguments, NULL); + if (error == -1) { + isds_ctx_free(&context); + isds_cleanup(); + ABORT_UNIT(server_error); + } + TEST("login", test_login, IE_SUCCESS, + context, url, username, password, NULL, NULL); + free(url); + + TEST("All data", test_isds_get_box_state_history, IE_SUCCESS, + context, input_dbID, &input_from, &input_to, &results); + + isds_logout(context); + if (stop_server(server_process)) { + isds_ctx_free(&context); + isds_cleanup(); + ABORT_UNIT(server_error); + } + } + + { + /* Some error */ + char *url = NULL; + + char *input_dbID = "A123456"; + struct timeval input_from = { + .tv_sec = 5, + .tv_usec = 0 + }; + struct timeval input_to = { + .tv_sec = 6, + .tv_usec = 0 + }; + + const struct arguments_DS_df_GetDataBoxActivityStatus + service_arguments = { + .status_code = "0002", + .status_message = "No such box", + .box_id = input_dbID, + .from = &input_from, + .to = &input_to, + .result_box_id = NULL, + .results_exists = 0, + .results = NULL + }; + const struct service_configuration services[] = { + { SERVICE_DS_Dz_DummyOperation, NULL }, + { SERVICE_DS_df_GetDataBoxActivityStatus, &service_arguments }, + { SERVICE_END, NULL } + }; + const struct arguments_basic_authentication server_arguments = { + .username = username, + .password = password, + .isds_deviations = 1, + .services = services + }; + error = start_server(&server_process, &url, + server_basic_authentication, &server_arguments, NULL); + if (error == -1) { + isds_ctx_free(&context); + isds_cleanup(); + ABORT_UNIT(server_error); + } + TEST("login", test_login, IE_SUCCESS, + context, url, username, password, NULL, NULL); + free(url); + + TEST("An error", test_isds_get_box_state_history, IE_ISDS, + context, input_dbID, &input_from, &input_to, NULL); + + isds_logout(context); + if (stop_server(server_process)) { + isds_ctx_free(&context); + isds_cleanup(); + ABORT_UNIT(server_error); + } + } + + + isds_ctx_free(&context); + isds_cleanup(); + SUM_TEST(); +} diff --git a/test/simline/service.c b/test/simline/service.c index 4942203..533bed5 100644 --- a/test/simline/service.c +++ b/test/simline/service.c @@ -7,6 +7,7 @@ #include #include /* For intmax_t */ #include /* For PRIdMAX */ +#include /* for isdigit() */ #include #include #include @@ -47,6 +48,161 @@ struct service { const void *arguments); }; + +/* Convert UTF-8 ISO 8601 date-time @string to struct timeval. + * It respects microseconds too. Microseconds are rounded half up. + * In case of error, @time will be freed. */ +static http_error timestring2timeval(const char *string, + struct timeval **time) { + struct tm broken; + char *offset, *delim, *endptr; + const int subsecond_resolution = 6; + char subseconds[subsecond_resolution + 1]; + _Bool round_up = 0; + int offset_hours, offset_minutes; + int i; + long int long_number; +#ifdef _WIN32 + int tmp; +#endif + + if (!time) return HTTP_ERROR_SERVER; + if (!string) { + free(*time); + *time = NULL; + return HTTP_ERROR_CLIENT; + } + + memset(&broken, 0, sizeof(broken)); + + if (!*time) { + *time = calloc(1, sizeof(**time)); + if (!*time) return HTTP_ERROR_SERVER; + } else { + memset(*time, 0, sizeof(**time)); + } + + + /* xsd:date is ISO 8601 string, thus ASCII */ + /*TODO: negative year */ + +#ifdef _WIN32 + i = 0; + if ((tmp = sscanf((const char*)string, "%d-%d-%dT%d:%d:%d%n", + &broken.tm_year, &broken.tm_mon, &broken.tm_mday, + &broken.tm_hour, &broken.tm_min, &broken.tm_sec, + &i)) < 6) { + free(*time); + *time = NULL; + return HTTP_ERROR_CLIENT; + } + + broken.tm_year -= 1900; + broken.tm_mon--; + broken.tm_isdst = -1; + offset = (char*)string + i; +#else + /* Parse date and time without subseconds and offset */ + offset = strptime((char*)string, "%Y-%m-%dT%T", &broken); + if (!offset) { + free(*time); + *time = NULL; + return HTTP_ERROR_CLIENT; + } +#endif + + /* Get subseconds */ + if (*offset == '.' ) { + offset++; + + /* Copy first 6 digits, pad it with zeros. + * Current server implementation uses only millisecond resolution. */ + /* TODO: isdigit() is locale sensitive */ + for (i = 0; + i < subsecond_resolution && isdigit(*offset); + i++, offset++) { + subseconds[i] = *offset; + } + if (subsecond_resolution == i && isdigit(*offset)) { + /* Check 7th digit for rounding */ + if (*offset >= '5') round_up = 1; + offset++; + } + for (; i < subsecond_resolution; i++) { + subseconds[i] = '0'; + } + subseconds[subsecond_resolution] = '\0'; + + /* Convert it into integer */ + long_number = strtol(subseconds, &endptr, 10); + if (*endptr != '\0' || long_number == LONG_MIN || + long_number == LONG_MAX) { + free(*time); + *time = NULL; + return HTTP_ERROR_SERVER; + } + /* POSIX sys_time.h(0p) defines tv_usec timeval member as su_seconds_t + * type. sys_types.h(0p) defines su_seconds_t as "used for time in + * microseconds" and "the type shall be a signed integer capable of + * storing values at least in the range [-1, 1000000]. */ + if (long_number < -1 || long_number >= 1000000) { + free(*time); + *time = NULL; + return HTTP_ERROR_CLIENT; + } + (*time)->tv_usec = long_number; + + /* Round the subseconds */ + if (round_up) { + if (999999 == (*time)->tv_usec) { + (*time)->tv_usec = 0; + broken.tm_sec++; + } else { + (*time)->tv_usec++; + } + } + + /* move to the zone offset delimiter or signal NULL*/ + delim = strchr(offset, '-'); + if (!delim) + delim = strchr(offset, '+'); + if (!delim) + delim = strchr(offset, 'Z'); + offset = delim; + } + + /* Get zone offset */ + /* ISO allows zone offset string only: "" | "Z" | ("+"|"-" ":") + * "" equals to "Z" and it means UTC zone. */ + /* One can not use strptime(, "%z",) becase it's RFC E-MAIL format without + * colon separator */ + if (offset && (*offset == '-' || *offset == '+')) { + if (2 != sscanf(offset + 1, "%2d:%2d", &offset_hours, &offset_minutes)) { + free(*time); + *time = NULL; + return HTTP_ERROR_CLIENT; + } + if (*offset == '+') { + broken.tm_hour -= offset_hours; + broken.tm_min -= offset_minutes; + } else { + broken.tm_hour += offset_hours; + broken.tm_min += offset_minutes; + } + } + + /* Convert to time_t */ + (*time)->tv_sec = _isds_timegm(&broken); + if ((*time)->tv_sec == (time_t) -1) { + free(*time); + *time = NULL; + return HTTP_ERROR_CLIENT; + } + + return HTTP_ERROR_SUCCESS; +} + + /* Following EXTRACT_* macros expect @xpath_ctx, @error, @message, * and leave label. */ #define ELEMENT_EXISTS(element, allow_multiple) { \ @@ -375,6 +531,17 @@ static int datecmp(const struct tm *a, const struct tm *b) { } +/* Compare times represented by pointer to struct timeval. + * @return 0 if equalued, non-0 otherwise. */ +static int timecmp(const struct timeval *a, const struct timeval *b) { + if (NULL == a && b == NULL) return 0; + if ((NULL == a && b != NULL) || (NULL != a && b == NULL)) return 1; + if (a->tv_sec != b->tv_sec) return 1; + if (a->tv_usec != b->tv_usec) return 1; + return 0; +} + + /* Checks an @element_name's value is an @expected_value string. * @code is a static output ISDS error code * @error_message is a reallocated output ISDS error message @@ -660,6 +827,78 @@ leave: } +/* Checks an @element_name's value is an @expected_value time. + * @code is a static output ISDS error code + * @error_message is an reallocated output ISDS error message + * @xpath_ctx is a current XPath context + * @element_name is name of a element to check + * @must_exist is true if the @element_name must exist even if @expected_value + * is NULL. + * @expected_value is an expected boolean value + * @return HTTP_ERROR_SUCCESS if the @element_name element's value is + * @expected_value. HTTP_ERROR_CLIENT if not equaled, HTTP_ERROR_SERVER if an + * internal error occured. */ +static http_error element_equals_time(const char **code, char **message, + xmlXPathContextPtr xpath_ctx, const char *element_name, + _Bool must_exist, const struct timeval *expected_value) { + http_error error = HTTP_ERROR_SUCCESS; + char *string = NULL; + struct timeval *value = NULL; + + if (must_exist) { + error = element_exists(code, message, xpath_ctx, element_name, 0); + if (HTTP_ERROR_SUCCESS != error) + goto leave; + } + + error = extract_string(code, message, xpath_ctx, element_name, &string); + if (HTTP_ERROR_SUCCESS != error) + goto leave; + + if (NULL != expected_value) { + if (NULL == string) { + *code = "9999"; + test_asprintf(message, "Empty %s element", element_name); + error = HTTP_ERROR_CLIENT; + goto leave; + } + error = timestring2timeval(string, &value); + if (error) { + if (error == HTTP_ERROR_CLIENT) { \ + test_asprintf(message, "%s value is not a valid time: %s", + element_name, string); + } + goto leave; + } + if (timecmp(expected_value, value)) { + *code = "9999"; + test_asprintf(message, "Unexpected %s element value: " + "expected=%ds:%" PRIdMAX "us, got=%ds:%" PRIdMAX "us", + element_name, + expected_value->tv_sec, (intmax_t)expected_value->tv_usec, + value->tv_sec, (intmax_t)value->tv_usec); + error = HTTP_ERROR_CLIENT; + goto leave; + } + } else { + if (NULL != string && *string != '\0') { + *code = "9999"; + test_asprintf(message, + "Unexpected %s element value: " + "expected empty value, got=`%s'", + element_name, string); + error = HTTP_ERROR_CLIENT; + goto leave; + } + } + +leave: + free(string); + free(value); + return error; +} + + /* Insert dmStatus or similar subtree * @parent is element to insert to * @dm is true for dmStatus, otherwise dbStatus @@ -1384,6 +1623,90 @@ leave: } +/* Insert list of period results as XSD:tdbPeriodsArray XML tree. + * @isds_response is XML node with the response + * @results is list of struct server_box_state_period *. + * @create_empty_root is true to create Periods element even if @results is + * empty. */ +static http_error insert_tdbPeriodsArray(xmlNodePtr isds_response, + const struct server_list *results, _Bool create_empty_root) { + http_error error = HTTP_ERROR_SUCCESS; + xmlNodePtr root, entry; + + if (NULL == isds_response) return HTTP_ERROR_SERVER; + + if (NULL != results || create_empty_root) + INSERT_ELEMENT(root, isds_response, "Periods"); + + if (NULL == results) return HTTP_ERROR_SUCCESS; + + for (const struct server_list *item = results; NULL != item; + item = item->next) { + const struct server_box_state_period *result = + (struct server_box_state_period *)item->data; + + INSERT_ELEMENT(entry, root, "Period"); + if (NULL == result) continue; + + INSERT_TIMEVALPTR(entry, "PeriodFrom", result->from); + INSERT_TIMEVALPTR(entry, "PeriodTo", result->to); + INSERT_LONGINTPTR(entry, "DbState", &(result->dbState)); + } + +leave: + return error; +} + + +/* Implement GetDataBoxActivityStatus. + * @arguments is pointer to struct arguments_DS_df_GetDataBoxActivityStatus */ +static http_error service_GetDataBoxActivityStatus( + xmlXPathContextPtr xpath_ctx, + xmlNodePtr isds_response, + const void *arguments) { + http_error error = HTTP_ERROR_SUCCESS; + const char *code = "9999"; + char *message = NULL; + const struct arguments_DS_df_GetDataBoxActivityStatus *configuration = + (const struct arguments_DS_df_GetDataBoxActivityStatus *)arguments; + + if (NULL == configuration || NULL == configuration->status_code || + NULL == configuration->status_message) { + error = HTTP_ERROR_SERVER; + goto leave; + } + + /* Check request */ + error = element_equals_string(&code, &message, xpath_ctx, + "isds:dbID", 1, configuration->box_id); + /* ??? XML schema and textual documentation does not agree on obligatority + * of the isds:baFrom and isds:baTo value or presence. */ + error = element_equals_time(&code, &message, xpath_ctx, + "isds:baFrom", 1, configuration->from); + error = element_equals_time(&code, &message, xpath_ctx, + "isds:baTo", 1, configuration->to); + if (error) goto leave; + + /* Build response */ + if ((error = insert_tdbPeriodsArray(isds_response, configuration->results, + configuration->results_exists))) { + goto leave; + } + + code = configuration->status_code; + message = strdup(configuration->status_message); + +leave: + if (HTTP_ERROR_SERVER != error) { + http_error next_error = insert_isds_status(isds_response, 0, + BAD_CAST code, BAD_CAST message, NULL); + if (HTTP_ERROR_SUCCESS != next_error) error = next_error; + } + free(message); + return error; +} + + /* Implement ISDSSearch2. * @arguments is pointer to struct arguments_DS_df_ISDSSearch2 */ static http_error service_ISDSSearch2( @@ -1730,6 +2053,9 @@ static struct service services[] = { { SERVICE_DS_df_FindPersonalDataBox, "DS/df", BAD_CAST ISDS_NS, BAD_CAST "FindPersonalDataBox", service_FindPersonalDataBox }, + { SERVICE_DS_df_GetDataBoxActivityStatus, + "DS/df", BAD_CAST ISDS_NS, BAD_CAST "GetDataBoxActivityStatus", + service_GetDataBoxActivityStatus }, { SERVICE_DS_df_ISDSSearch2, "DS/df", BAD_CAST ISDS_NS, BAD_CAST "ISDSSearch2", service_ISDSSearch2 }, diff --git a/test/simline/services.h b/test/simline/services.h index 1f4b7a0..743cb00 100644 --- a/test/simline/services.h +++ b/test/simline/services.h @@ -12,6 +12,7 @@ typedef enum { SERVICE_DS_df_DataBoxCreditInfo, SERVICE_DS_df_FindDataBox, SERVICE_DS_df_FindPersonalDataBox, + SERVICE_DS_df_GetDataBoxActivityStatus, SERVICE_DS_df_ISDSSearch2, SERVICE_DS_DsManage_ChangeISDSPassword, SERVICE_DS_Dx_EraseMessage, @@ -139,6 +140,13 @@ struct server_owner_info { _Bool *dbOpenAddressing; }; +/* Box state period */ +struct server_box_state_period { + struct timeval *from; + struct timeval *to; + long int dbState; +}; + /* General linked list */ struct server_list { struct server_list *next; /* Next list item, @@ -180,6 +188,19 @@ struct arguments_DS_df_FindPersonalDataBox { dbResults */ }; +struct arguments_DS_df_GetDataBoxActivityStatus { + const char *status_code; + const char *status_message; + const char *box_id; /* Input */ + const struct timeval *from; /* Input */ + const struct timeval *to; /* Input */ + const char *result_box_id; /* Output */ + const _Bool results_exists; /* Return Periods element */ + const struct server_list *results; /* Return list of + struct server_box_state_period * as + Periods */ +}; + struct arguments_DS_df_ISDSSearch2 { const char *status_code; const char *status_message; diff --git a/test/simline/system.h b/test/simline/system.h index a0ba6a8..221e45b 100644 --- a/test/simline/system.h +++ b/test/simline/system.h @@ -9,5 +9,16 @@ #include "unix.h" #endif +#include /* for struvt tm */ +#include /* for time_t */ + http_error _server_datestring2tm(const char *string, struct tm *time); + +/* Convert UTC broken time to time_t. + * @broken_utc it time in UTC in broken format. Despite its content is not + * touched, it'sw not-const because underlying POSIX function has non-const + * signature. + * @return (time_t) -1 in case of error */ +time_t _isds_timegm(struct tm *broken_utc); + #endif diff --git a/src/unix.c b/test/simline/unix.c similarity index 62% copy from src/unix.c copy to test/simline/unix.c index 9e10f69..cb214e6 100644 --- a/src/unix.c +++ b/test/simline/unix.c @@ -1,35 +1,46 @@ -#include "isds_priv.h" -#include -#include +#ifndef _XOPEN_SOURCE +/* >= 500: strdup(3) from string.h, strptime(3) from time.h */ +/* >= 600: setenv(3) */ +#define _XOPEN_SOURCE 600 +#endif +#include "../test-tools.h" +#include /* for getenv(), abort() */ +#include /* for strdup() */ +#include /* for stderr */ #include -#include "isds.h" -#include "utils.h" +#include "http.h" + +/* PANIC macro aborts current process without any clean up. + * Use it as last resort fatal error solution */ +#define PANIC(message) { \ + if (stderr != NULL ) fprintf(stderr, \ + "SERVER PANIC (%s:%d): %s\n", __FILE__, __LINE__, (message)); \ + abort(); \ +} static char *tz_orig; /* Copy of original TZ variable */ -#if HAVE_LIBCURL /* Convert UTF-8 @string representation of ISO 8601 date to @time. * XXX: Not all ISO formats are supported */ -_hidden isds_error _isds_datestring2tm(const xmlChar *string, struct tm *time) { +_hidden http_error _server_datestring2tm(const char *string, struct tm *time) { char *offset; - if (!string || !time) return IE_INVAL; + if (!string || !time) return HTTP_ERROR_SERVER; /* xsd:date is ISO 8601 string, thus ASCII */ - offset = strptime((char*)string, "%Y-%m-%d", time); + offset = strptime(string, "%Y-%m-%d", time); if (offset && *offset == '\0') - return IE_SUCCESS; + return HTTP_ERROR_SUCCESS; - offset = strptime((char*)string, "%Y%m%d", time); + offset = strptime(string, "%Y%m%d", time); if (offset && *offset == '\0') - return IE_SUCCESS; + return HTTP_ERROR_SUCCESS; - offset = strptime((char*)string, "%Y-%j", time); + offset = strptime(string, "%Y-%j", time); if (offset && *offset == '\0') - return IE_SUCCESS; + return HTTP_ERROR_SUCCESS; - return IE_NOTSUP; + return HTTP_ERROR_SERVER; } -#endif /* Switches time zone to UTC. * XXX: This is not reentrant and not thread-safe */ diff --git a/test/simline/win32.c b/test/simline/win32.c index 8abe78b..6219768 100644 --- a/test/simline/win32.c +++ b/test/simline/win32.c @@ -85,3 +85,30 @@ http_error _server_datestring2tm(const char *string, struct tm *time) { time->tm_year -= 1900; return HTTP_ERROR_SUCCESS; } + + +/* Convert UTC broken time to time_t. + * @broken_utc it time in UTC in broken format. Despite its content is not + * touched, it'sw not-const because underlying POSIX function has non-const + * signature. + * @return (time_t) -1 in case of error */ +_hidden time_t _isds_timegm(struct tm *broken_utc) { + time_t ret; + time_t diff; + struct tm broken, *tmp; + + ret = time(0); + tmp = gmtime(&ret); + + if (!tmp) { + return (time_t)-1; + } + + tmp->tm_isdst = broken_utc->tm_isdst; + diff = ret - mktime(tmp); + memcpy(&broken, broken_utc, sizeof(struct tm)); + broken.tm_isdst = tmp->tm_isdst; /* handle broken_utc->tm_isdst < 0 */ + ret = mktime(&broken) + diff; + return ret; +} + -- 2.11.4.GIT