twister

HTTP fault injector for testing
Log | Files | Refs | README | LICENSE

commit 1f2e546e3647ca38d20796c2f275766c3fa03e38
parent c5156b4054f0f376fb7be15b57a95b4db015c2a1
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Fri,  9 Mar 2018 20:50:40 +0100

add command to modify responses.

Diffstat:
Msrc/include/taler_twister_service.h | 20++++++++++++++++++++
Msrc/test/test_twister.sh | 20++++++++++----------
Msrc/twister/taler-twister-service.c | 184++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/twister/taler-twister.c | 45+++++++++++++++++++++++++++++++++++++++++++++
Msrc/twister/twister.h | 33+++++++++++++++++++++++++++++++++
Msrc/twister/twister_api.c | 42++++++++++++++++++++++++++++++++++++++++++
6 files changed, 310 insertions(+), 34 deletions(-)

diff --git a/src/include/taler_twister_service.h b/src/include/taler_twister_service.h @@ -76,6 +76,26 @@ struct TALER_TWISTER_Operation; /** + * Change the response field pointed by @a modify_path with + * @a modify_value. + * + * @param h twister instance to control + * @param modify_path object-like notation path to the object to + * modify + * @param modify_value value to use for @a modify_path + * @param cb callback to call once twister gets this instruction. + * @param cb_cls closure for @a cb_callback + * + * @return operation handle. + */ +struct TALER_TWISTER_Operation * +TALER_TWISTER_modify_path (struct TALER_TWISTER_Handle *h, + const char *path, + const char *value, + GNUNET_SCHEDULER_TaskCallback cb, + void *cb_cls); + +/** * Change the next response code to @a new_rc. * * @param h twister instance to control diff --git a/src/test/test_twister.sh b/src/test/test_twister.sh @@ -34,19 +34,19 @@ if ! test '{"hello":[]}' = "$emptied_body"; then fi # set field -#taler-twister -c ./test_twister.conf \ -# --setfield "hello" \ -# --value "fake" -#modfield_body=$(curl -s ${TWISTER_URL}) +taler-twister -c ./test_twister.conf \ + --modobject "hello" \ + --value "fake" +modobject_body=$(curl -s ${TWISTER_URL}) + +if ! test '{"hello":"fake"}' = "$modobject_body"; then + printf "Response body (%s) has not been modified as expected\n" \ + "$modobject_body" + exit 1 +fi # shutdown twister and webserver kill $web_server_pid kill $twister_service_pid -#if ! test '{"hello":"fake"}' = "$modfield_body"; then -# printf "Response body (%s) has not been modified as expected\n" \ -# "$modfield_body" -# exit 1 -#fi - exit 0 diff --git a/src/twister/taler-twister-service.c b/src/twister/taler-twister-service.c @@ -239,7 +239,18 @@ static unsigned int hack_response_code; /** * Will point to a JSON object to delete */ -static char delete_path[TWISTER_PATH_LENGTH]; +static char delete_path[TWISTER_PATH_LENGTH] = { '\0' }; + +/** + * Will point to a JSON object to modify + */ +static char modify_path[TWISTER_PATH_LENGTH] = { '\0' }; + +/** + * New value. + */ +static char modify_value[TWISTER_VALUE_LENGTH]; + /** * Will point to the path if it has to be deleted @@ -839,7 +850,6 @@ walk_response_object (const char *path, char *token; char *last_token; char *path_dup; - unsigned int index; if (NULL == (parsed_response = json_loadb (hr->io_buf, hr->io_len, JSON_DECODE_ANY, &error))) @@ -848,39 +858,37 @@ walk_response_object (const char *path, return NULL; } - path_dup = GNUNET_strdup (delete_path); + GNUNET_asprintf (&path_dup, + ".%s", /* Make sure path starts with dot. */ + path); + last_token = strrchr (path_dup, '.') + 1; /* Give first nondelim char. */ token = strtok (path_dup, "."); element = parsed_response; - do + + while (last_token != token) { - /* user gave path == "."; returning the body untouched. - * another option is to return an empty body. */ + TALER_LOG_DEBUG ("token/last_token: %s@%p / %s@%p\n", + token, token, + last_token, last_token); if (NULL == token) - { - TALER_LOG_WARNING ("Whole body won't be deleted" - ", token: %s/%p, last_token: %s/%p\n", - token, token, - last_token, last_token); - - json_decref (parsed_response); - GNUNET_free (path_dup); - return NULL; - } + return NULL; // path was ".", refuse to process it. + if (NULL != (cur = json_object_get (element, token))) { element = cur; + token = strtok (NULL, "."); continue; } - index = (unsigned int) strtoul (token, - NULL, - 10); + if (NULL != (cur = json_array_get (element, - index))) + (unsigned int) strtoul + (token, NULL, 10)))) { element = cur; + token = strtok (NULL, "."); continue; } TALER_LOG_WARNING ("Path token '%s' not found\n", @@ -889,7 +897,6 @@ walk_response_object (const char *path, return NULL; } - while (last_token != (token = strtok (NULL, "."))); *target = last_token; *parent = element; @@ -897,7 +904,103 @@ walk_response_object (const char *path, return parsed_response; } +/** + * TODO. + */ +static unsigned int +modify_object (struct MHD_Connection *con, + struct HttpRequest *hr) +{ + + char *target; + char *mod_body; + unsigned long long n; + unsigned int ret_modify; + json_t *parent; + json_t *parsed_response; + json_t *new_value; + json_error_t error; + + if (NULL == (parsed_response = walk_response_object + (modify_path, &parent, &target, hr))) + { + return MHD_queue_response (con, + hr->response_code, + hr->response); + } + /* At this point, the parent and the target are pointed to. */ + + /* The new value has uncertain type; we try until find someting, + * or error. */ + + if (NULL != (new_value = json_loadb (modify_value, + strlen (modify_value), + JSON_DECODE_ANY, + &error))) + goto ret_modbody; + if (NULL != (new_value = json_string (modify_value))) + goto ret_modbody; + + if (ULONG_MAX > (n = strtoull (modify_value, + NULL, + 10))) + { + if (NULL != (new_value = json_integer ((json_int_t) n))) + goto ret_modbody; + } + + TALER_LOG_ERROR ("Unvalid new value given: %s\n", + modify_value); + /* Resetting operation too. */ + modify_path[0] = '\0'; + + return MHD_queue_response (con, + hr->response_code, + hr->response); + ret_modbody: + + ret_modify = -1; + if (json_is_object (parent)) + ret_modify = json_object_set_new (parent, + target, + new_value); + if (json_is_array (parent)) + ret_modify = json_array_set_new + (parent, + (unsigned int) strtoul (target, + NULL, + 10), + new_value); + if (-1 == ret_modify) + { + TALER_LOG_WARNING ("Could not replace '%s'\n", target); + json_decref (parsed_response); + return MHD_queue_response (con, + hr->response_code, + hr->response); + } + + mod_body = json_dumps (parsed_response, JSON_COMPACT); + hr->mod_response = MHD_create_response_from_buffer + (strlen (mod_body), + mod_body, + MHD_RESPMEM_MUST_COPY); + + struct HttpResponseHeader *header; + for (header = hr->header_head; + NULL != header; + header = header->next) + GNUNET_break + (MHD_YES == MHD_add_response_header (hr->mod_response, + header->type, + header->value)); + json_decref (parsed_response); + modify_path[0] = '\0'; + return MHD_queue_response (con, + hr->response_code, + hr->mod_response); +} /** * Delete object within the proxied response. @@ -952,8 +1055,7 @@ delete_object (struct MHD_Connection *con, (strlen (mod_body), mod_body, MHD_RESPMEM_MUST_COPY); - json_decref (parsed_response); - delete_path[0] = '\0'; + struct HttpResponseHeader *header; for (header = hr->header_head; NULL != header; @@ -962,6 +1064,8 @@ delete_object (struct MHD_Connection *con, (MHD_YES == MHD_add_response_header (hr->mod_response, header->type, header->value)); + json_decref (parsed_response); + delete_path[0] = '\0'; return MHD_queue_response (con, hr->response_code, hr->mod_response); @@ -1244,8 +1348,15 @@ create_response (void *cls, return delete_object (con, hr); } + if ('\0' != modify_path[0]) + { + TALER_LOG_DEBUG ("Will modify path: %s to value %s\n", + modify_path, + modify_value); + + return modify_object (con, hr); + } - /* response might have been modified. */ return MHD_queue_response (con, hr->response_code, hr->response); @@ -1671,6 +1782,25 @@ send_acknowledgement (struct GNUNET_SERVICE_Client *c) * @param src received message */ static void +handle_modify_path (void *cls, + const struct TWISTER_ModifyPath *src) +{ + struct GNUNET_SERVICE_Client *c = cls; + + strcpy (modify_path, src->path); + strcpy (modify_value, src->value); + + send_acknowledgement (c); +} + + +/** + * Control handler for deleting JSON objects + * + * @param cls message queue for sending replies + * @param src received message + */ +static void handle_delete_path (void *cls, const struct TWISTER_DeletePath *src) { @@ -1713,10 +1843,16 @@ GNUNET_SERVICE_MAIN struct TWISTER_SetResponseCode, NULL), + GNUNET_MQ_hd_fixed_size (modify_path, + TWISTER_MESSAGE_TYPE_MODIFY_PATH, + struct TWISTER_ModifyPath, + NULL), + GNUNET_MQ_hd_fixed_size (delete_path, TWISTER_MESSAGE_TYPE_DELETE_PATH, struct TWISTER_DeletePath, NULL), + GNUNET_MQ_handler_end ()); diff --git a/src/twister/taler-twister.c b/src/twister/taler-twister.c @@ -53,6 +53,16 @@ static unsigned int hack_response_code; static char *delete_path; /** + * Path to the object to modify. + */ +static char *modify_path; + +/** + * New value for the object pointed by `modify_path`. + */ +static char *modify_value; + +/** * This option is used to check whether the twister can accept * connections over the unix domain socket interface. Used when * launching it to see if everything (?) is okay. @@ -149,6 +159,24 @@ run (void *cls, NULL)) ) num_ops++; + if (NULL != modify_path) + { + if (NULL == modify_value) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "New value not given, give -V|--value also\n"); + return; + } + + if (NULL != TALER_TWISTER_modify_path + (tth, + modify_path, + modify_value, + &handle_acknowledgement, + NULL)) + num_ops++; + } + if (0 == num_ops) { fprintf (stderr, "No valid hacks specified!\n"); @@ -170,6 +198,23 @@ main (int argc, struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_string + ('m', + "modobject", + "PATH", + gettext_noop + ("Modify object pointed by PATH, require --value.\n"), + &modify_path), + + GNUNET_GETOPT_option_string + ('V', + "value", + "VALUE", + gettext_noop + ("Make VALUE the new value of the field" + " pointed by PATH (--modfield).\n"), + &modify_value), + + GNUNET_GETOPT_option_string ('d', "deleteobject", "PATH", diff --git a/src/twister/twister.h b/src/twister/twister.h @@ -34,12 +34,40 @@ */ #define TWISTER_PATH_LENGTH 100 +/** + * Max lenght for JSON values to use in place of the originals. + */ +#define TWISTER_VALUE_LENGTH 100 + #define TWISTER_MESSAGE_TYPE_ACKNOWLEDGEMENT 1 #define TWISTER_MESSAGE_TYPE_SET_RESPONSE_CODE 2 #define TWISTER_MESSAGE_TYPE_DELETE_PATH 3 +#define TWISTER_MESSAGE_TYPE_MODIFY_PATH 4 + + +GNUNET_NETWORK_STRUCT_BEGIN +struct TWISTER_ModifyPath +{ + /** + * Type: #TWISTER_MESSAGE_TYPE_DELETE_PATH + */ + struct GNUNET_MessageHeader header; + + /** + * Path to the object to modify. + */ + char path[TWISTER_PATH_LENGTH]; + + /** + * New value to use. + */ + char value[TWISTER_VALUE_LENGTH]; +}; +GNUNET_NETWORK_STRUCT_END + GNUNET_NETWORK_STRUCT_BEGIN @@ -62,6 +90,11 @@ struct TWISTER_DeletePath */ char path[TWISTER_PATH_LENGTH]; + /** + * New value to use in place of the original. + */ + char value[TWISTER_VALUE_LENGTH]; + }; GNUNET_NETWORK_STRUCT_END diff --git a/src/twister/twister_api.c b/src/twister/twister_api.c @@ -243,6 +243,48 @@ TALER_TWISTER_delete_path } /** + * Change the response field pointed by @a modify_path with + * @a modify_value. + * + * @param h twister instance to control + * @param modify_path object-like notation path to the object to + * modify + * @param modify_value value to use for @a modify_path + * @param cb callback to call once twister gets this instruction. + * @param cb_cls closure for @a cb_callback + * + * @return operation handle. + */ +struct TALER_TWISTER_Operation * +TALER_TWISTER_modify_path (struct TALER_TWISTER_Handle *h, + const char *path, + const char *value, + GNUNET_SCHEDULER_TaskCallback cb, + void *cb_cls) +{ + struct TALER_TWISTER_Operation *op; + struct GNUNET_MQ_Envelope *env; + struct TWISTER_ModifyPath *src; + + op = GNUNET_new (struct TALER_TWISTER_Operation); + op->h = h; + op->cb = cb; + op->cb_cls = cb_cls; + GNUNET_CONTAINER_DLL_insert_tail (h->op_head, + h->op_tail, + op); + /* Prepare *env*elope. */ + env = GNUNET_MQ_msg + (src, TWISTER_MESSAGE_TYPE_MODIFY_PATH); + /* Put data into the envelope. */ + strcpy (src->path, path); + strcpy (src->value, value); + /* Send message. */ + GNUNET_MQ_send (h->mq, env); + return op; +} + +/** * Change the next response code to @a new_rc. * * @param h twister instance to control