summaryrefslogtreecommitdiffstats
path: root/test/s3comms.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/s3comms.c')
-rw-r--r--test/s3comms.c2643
1 files changed, 2643 insertions, 0 deletions
diff --git a/test/s3comms.c b/test/s3comms.c
new file mode 100644
index 0000000..239e3ed
--- /dev/null
+++ b/test/s3comms.c
@@ -0,0 +1,2643 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Copyright by The HDF Group. *
+ * All rights reserved. *
+ * *
+ * This file is part of HDF5. The full HDF5 copyright notice, including *
+ * terms governing use, modification, and redistribution, is contained in *
+ * the COPYING file, which can be found at the root of the source code *
+ * distribution tree, or in https://www.hdfgroup.org/licenses. *
+ * If you do not have access to either file, you may request a copy from *
+ * help@hdfgroup.org. *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/*
+ * Read-Only S3 Virtual File Driver (VFD)
+ *
+ * Purpose: Unit tests for the S3 Communications (s3comms) module.
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-11
+ */
+
+#include "h5test.h"
+#include "H5FDs3comms.h"
+#include "H5MMprivate.h" /* memory management */
+
+#ifdef H5_HAVE_ROS3_VFD
+
+/*****************************************************************************
+ *
+ * FILE-LOCAL TESTING MACROS
+ *
+ * Purpose:
+ *
+ * 1) Upon test failure, goto-jump to single-location teardown in test
+ * function. E.g., `error:` (consistency with HDF corpus) or
+ * `failed:` (reflects purpose).
+ * >>> using "error", in part because `H5E_BEGIN_TRY` expects it.
+ * 2) Increase clarity and reduce overhead found with `TEST_ERROR`.
+ * e.g., "if(somefunction(arg, arg2) < 0) TEST_ERROR:"
+ * requires reading of entire line to know whether this if/call is
+ * part of the test setup, test operation, or a test unto itself.
+ * 3) Provide testing macros with optional user-supplied failure message;
+ * if not supplied (NULL), generate comparison output in the spirit of
+ * test-driven development. E.g., "expected 5 but was -3"
+ * User messages clarify test's purpose in code, encouraging description
+ * without relying on comments.
+ * 4) Configurable expected-actual order in generated comparison strings.
+ * Some prefer `VERIFY(expected, actual)`, others
+ * `VERIFY(actual, expected)`. Provide preprocessor ifdef switch
+ * to satifsy both parties, assuming one paradigm per test file.
+ * (One could #undef and redefine the flag through the file as desired,
+ * but _why_.)
+ * Provided as courtesy, per consideration for inclusion in the library
+ * proper.
+ *
+ * Macros:
+ *
+ * JSVERIFY_EXP_ACT - ifdef flag, configures comparison order
+ * FAIL_IF() - check condition
+ * FAIL_UNLESS() - check _not_ condition
+ * JSVERIFY() - long-int equality check; prints reason/comparison
+ * JSVERIFY_NOT() - long-int inequality check; prints
+ * JSVERIFY_STR() - string equality check; prints
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-24
+ *
+ *****************************************************************************/
+
+/*----------------------------------------------------------------------------
+ *
+ * ifdef flag: JSVERIFY_EXP_ACT
+ *
+ * JSVERIFY macros accept arguments as (EXPECTED, ACTUAL[, reason])
+ * default, if this is undefined, is (ACTUAL, EXPECTED[, reason])
+ *
+ *----------------------------------------------------------------------------
+ */
+#define JSVERIFY_EXP_ACT 1L
+
+/*----------------------------------------------------------------------------
+ *
+ * Macro: JSFAILED_AT()
+ *
+ * Purpose:
+ *
+ * Preface a test failure by printing "*FAILED*" and location to stdout
+ * Similar to `H5_FAILED(); AT();` from h5test.h
+ *
+ * *FAILED* at somefile.c:12 in function_name()...
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-24
+ *
+ *----------------------------------------------------------------------------
+ */
+#define JSFAILED_AT() \
+ { \
+ HDprintf("*FAILED* at %s:%d in %s()...\n", __FILE__, __LINE__, FUNC); \
+ }
+
+/*----------------------------------------------------------------------------
+ *
+ * Macro: FAIL_IF()
+ *
+ * Purpose:
+ *
+ * Make tests more accessible and less cluttered than
+ * `if (thing == otherthing()) TEST_ERROR`
+ * paradigm.
+ *
+ * The following lines are roughly equivalent:
+ *
+ * `if (myfunc() < 0) TEST_ERROR;` (as seen elsewhere in HDF tests)
+ * `FAIL_IF(myfunc() < 0)`
+ *
+ * Prints a generic "FAILED AT" line to stdout and jumps to `error`,
+ * similar to `TEST_ERROR` in h5test.h
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-23
+ *
+ *----------------------------------------------------------------------------
+ */
+#define FAIL_IF(condition) \
+ if (condition) { \
+ JSFAILED_AT() \
+ goto error; \
+ }
+
+/*----------------------------------------------------------------------------
+ *
+ * Macro: FAIL_UNLESS()
+ *
+ * Purpose:
+ *
+ * TEST_ERROR wrapper to reduce cognitive overhead from "negative tests",
+ * e.g., "a != b".
+ *
+ * Opposite of FAIL_IF; fails if the given condition is _not_ true.
+ *
+ * `FAIL_IF( 5 != my_op() )`
+ * is equivalent to
+ * `FAIL_UNLESS( 5 == my_op() )`
+ * However, `JSVERIFY(5, my_op(), "bad return")` may be even clearer.
+ * (see JSVERIFY)
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-24
+ *
+ *----------------------------------------------------------------------------
+ */
+#define FAIL_UNLESS(condition) \
+ if (!(condition)) { \
+ JSFAILED_AT() \
+ goto error; \
+ }
+
+/*----------------------------------------------------------------------------
+ *
+ * Macro: JSERR_LONG()
+ *
+ * Purpose:
+ *
+ * Print an failure message for long-int arguments.
+ * ERROR-AT printed first.
+ * If `reason` is given, it is printed on own line and newlined after
+ * else, prints "expected/actual" aligned on own lines.
+ *
+ * *FAILED* at myfile.c:488 in somefunc()...
+ * forest must be made of trees.
+ *
+ * or
+ *
+ * *FAILED* at myfile.c:488 in somefunc()...
+ * ! Expected 425
+ * ! Actual 3
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-24
+ *
+ *----------------------------------------------------------------------------
+ */
+#define JSERR_LONG(expected, actual, reason) \
+ { \
+ JSFAILED_AT() \
+ if (reason != NULL) { \
+ HDprintf("%s\n", (reason)); \
+ } \
+ else { \
+ HDprintf(" ! Expected %ld\n ! Actual %ld\n", (long)(expected), (long)(actual)); \
+ } \
+ }
+
+/*----------------------------------------------------------------------------
+ *
+ * Macro: JSERR_STR()
+ *
+ * Purpose:
+ *
+ * Print an failure message for string arguments.
+ * ERROR-AT printed first.
+ * If `reason` is given, it is printed on own line and newlined after
+ * else, prints "expected/actual" aligned on own lines.
+ *
+ * *FAILED* at myfile.c:421 in myfunc()...
+ * Blue and Red strings don't match!
+ *
+ * or
+ *
+ * *FAILED* at myfile.c:421 in myfunc()...
+ * !!! Expected:
+ * this is my expected
+ * string
+ * !!! Actual:
+ * not what I expected at all
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-24
+ *
+ *----------------------------------------------------------------------------
+ */
+#define JSERR_STR(expected, actual, reason) \
+ { \
+ JSFAILED_AT() \
+ if ((reason) != NULL) { \
+ HDprintf("%s\n", (reason)); \
+ } \
+ else { \
+ HDprintf("!!! Expected:\n%s\n!!!Actual:\n%s\n", (expected), (actual)); \
+ } \
+ }
+
+#ifdef JSVERIFY_EXP_ACT
+/* VERIFY rountines with paramter order (<expected>, <actual> [, <msg> ])
+ */
+
+/*----------------------------------------------------------------------------
+ *
+ * Macro: JSVERIFY()
+ *
+ * Purpose:
+ *
+ * Verify that two long integers are equal.
+ * If unequal, print failure message
+ * (with `reason`, if not NULL; expected/actual if NULL)
+ * and jump to `error` at end of function
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-24
+ *
+ *----------------------------------------------------------------------------
+ */
+#define JSVERIFY(expected, actual, reason) \
+ if ((long)(actual) != (long)(expected)) { \
+ JSERR_LONG((expected), (actual), (reason)) \
+ goto error; \
+ } /* JSVERIFY */
+
+#if 0 /* UNUSED */
+
+/*----------------------------------------------------------------------------
+ *
+ * Macro: JSVERIFY_NOT()
+ *
+ * Purpose:
+ *
+ * Verify that two long integers are _not_ equal.
+ * If equal, print failure message
+ * (with `reason`, if not NULL; expected/actual if NULL)
+ * and jump to `error` at end of function
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-24
+ *
+ *----------------------------------------------------------------------------
+ */
+#define JSVERIFY_NOT(expected, actual, reason) \
+ if ((long)(actual) == (long)(expected)) { \
+ JSERR_LONG((expected), (actual), (reason)) \
+ goto error; \
+ } /* JSVERIFY_NOT */
+#endif /* JSVERIFY_NOT unused */
+
+/*----------------------------------------------------------------------------
+ *
+ * Macro: JSVERIFY_STR()
+ *
+ * Purpose:
+ *
+ * Verify that two strings are equal.
+ * If unequal, print failure message
+ * (with `reason`, if not NULL; expected/actual if NULL)
+ * and jump to `error` at end of function
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-24
+ *
+ *----------------------------------------------------------------------------
+ */
+#define JSVERIFY_STR(expected, actual, reason) \
+ if (strcmp((actual), (expected)) != 0) { \
+ JSERR_STR((expected), (actual), (reason)); \
+ goto error; \
+ } /* JSVERIFY_STR */
+
+#else
+/* JSVERIFY_EXP_ACT not defined
+ *
+ * Repeats macros above, but with actual/expected parameters reversed.
+ */
+
+/*----------------------------------------------------------------------------
+ * Macro: JSVERIFY()
+ * See: JSVERIFY documentation above.
+ * Programmer: Jacob Smith
+ * 2017-10-14
+ *----------------------------------------------------------------------------
+ */
+#define JSVERIFY(actual, expected, reason) \
+ if ((long)(actual) != (long)(expected)) { \
+ JSERR_LONG((expected), (actual), (reason)); \
+ goto error; \
+ } /* JSVERIFY */
+
+#if 0 /* UNUSED */
+
+/*----------------------------------------------------------------------------
+ * Macro: JSVERIFY_NOT()
+ * See: JSVERIFY_NOT documentation above.
+ * Programmer: Jacob Smith
+ * 2017-10-14
+ *----------------------------------------------------------------------------
+ */
+#define JSVERIFY_NOT(actual, expected, reason) \
+ if ((long)(actual) == (long)(expected)) { \
+ JSERR_LONG((expected), (actual), (reason)) \
+ goto error; \
+ } /* JSVERIFY_NOT */
+#endif /* JSVERIFY_NOT unused */
+
+/*----------------------------------------------------------------------------
+ * Macro: JSVERIFY_STR()
+ * See: JSVERIFY_STR documentation above.
+ * Programmer: Jacob Smith
+ * 2017-10-14
+ *----------------------------------------------------------------------------
+ */
+#define JSVERIFY_STR(actual, expected, reason) \
+ if (strcmp((actual), (expected)) != 0) { \
+ JSERR_STR((expected), (actual), (reason)); \
+ goto error; \
+ } /* JSVERIFY_STR */
+
+#endif /* ifdef/else JSVERIFY_EXP_ACT */
+
+#define S3_TEST_PROFILE_NAME "ros3_vfd_test"
+
+#define S3_TEST_RESOURCE_TEXT_RESTRICTED "t8.shakespeare.txt"
+#define S3_TEST_RESOURCE_TEXT_PUBLIC "Poe_Raven.txt"
+#define S3_TEST_RESOURCE_MISSING "missing.csv"
+
+#define S3_TEST_RUN_TIMEOUT 0 /* run tests that might hang */
+#define S3_TEST_MAX_URL_SIZE 256 /* char array size */
+
+/* Global variables for aws test profile.
+ * An attempt is made to read ~/.aws/credentials and ~/.aws/config upon test
+ * startup -- if unable to open either file or cannot load region, id, and key,
+ * tests connecting with S3 will not be run
+ */
+static int s3_test_credentials_loaded = 0;
+static char s3_test_aws_region[16] = "";
+static char s3_test_aws_access_key_id[64] = "";
+static char s3_test_aws_secret_access_key[128] = "";
+static char s3_test_bucket_url[S3_TEST_MAX_URL_SIZE] = "";
+static hbool_t s3_test_bucket_defined = FALSE;
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_macro_format_credential()
+ *
+ * Purpose:
+ *
+ * Demonstrate that the macro `S3COMMS_FORMAT_CREDENTIAL`
+ * performs as expected.
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-19
+ *
+ *----------------------------------------------------------------------------
+ */
+static herr_t
+test_macro_format_credential(void)
+{
+ /************************
+ * test-local variables *
+ ************************/
+
+ char dest[256];
+ const char access[] = "AKIAIOSFODNN7EXAMPLE";
+ const char date[] = "20130524";
+ const char region[] = "us-east-1";
+ const char service[] = "s3";
+ const char expected[] = "AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request";
+
+ TESTING("test_macro_format_credential");
+
+ FAIL_IF(S3COMMS_MAX_CREDENTIAL_SIZE < S3COMMS_FORMAT_CREDENTIAL(dest, access, date, region, service))
+
+ JSVERIFY_STR(expected, dest, NULL)
+
+ PASSED();
+ return 0;
+
+error:
+ return -1;
+
+} /* end test_macro_format_credential() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_aws_canonical_request()
+ *
+ * Purpose:
+ *
+ * Demonstrate the construction of a Canoncial Request (and Signed Headers)
+ *
+ * Elided / not yet implemented:
+ * Query strings
+ * request "body"
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-04
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_aws_canonical_request(void)
+{
+ /*************************
+ * test-local structures *
+ *************************/
+
+ struct header {
+ const char *name;
+ const char *value;
+ };
+
+ struct testcase {
+ const char * exp_request;
+ const char * exp_headers;
+ const char * verb;
+ const char * resource;
+ unsigned int listsize;
+ struct header list[5];
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ struct testcase cases[] = {
+ {
+ "GET\n/some/"
+ "path.file\n\nhost:somebucket.someserver.somedomain\nrange:bytes=150-244\n\nhost;"
+ "range\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "host;range",
+ "GET",
+ "/some/path.file",
+ 2,
+ {
+ {"Range", "bytes=150-244"},
+ {"Host", "somebucket.someserver.somedomain"},
+ },
+ },
+ {"HEAD\n/bucketpath/"
+ "myfile.dat\n\nhost:place.domain\nx-amz-content-sha256:"
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-date:"
+ "19411207T150803Z\n\nhost;x-amz-content-sha256;x-amz-"
+ "date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "host;x-amz-content-sha256;x-amz-date",
+ "HEAD",
+ "/bucketpath/myfile.dat",
+ 3,
+ {
+ {"x-amz-content-sha256", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
+ {"host", "place.domain"},
+ {"x-amz-date", "19411207T150803Z"},
+ }},
+ {
+ "PUT\n/\n\n\n\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "",
+ "PUT",
+ "/",
+ 0,
+ {
+ {"", ""},
+ }, /* unused; satisfies compiler */
+ },
+ }; /* struct testcase cases[] */
+ struct testcase *C = NULL;
+ char cr_dest[512]; /* canonical request */
+ hrb_t * hrb = NULL; /* http request buffer object */
+ unsigned int i = 0; /* looping/indexing */
+ unsigned int j = 0; /* looping/indexing */
+ hrb_node_t * node = NULL; /* http headers list pointer */
+ unsigned int n_cases = 3;
+ char sh_dest[64]; /* signed headers */
+
+ TESTING("test_aws_canonical_request");
+
+ for (i = 0; i < n_cases; i++) {
+ /* pre-test bookkeeping
+ */
+ C = &cases[i];
+ for (j = 0; j < 256; j++) {
+ cr_dest[j] = 0;
+ } /* zero request buffer */
+ for (j = 0; j < 64; j++) {
+ sh_dest[j] = 0;
+ } /* zero headers buffer */
+
+ /* create HTTP request object with given verb, resource/path
+ */
+ hrb = H5FD_s3comms_hrb_init_request(C->verb, C->resource, "HTTP/1.1");
+ HDassert(hrb->body == NULL);
+
+ /* Create headers list from test case input
+ */
+ for (j = 0; j < C->listsize; j++) {
+ FAIL_IF(FAIL == H5FD_s3comms_hrb_node_set(&node, C->list[j].name, C->list[j].value));
+ }
+
+ hrb->first_header = node;
+
+ /* test
+ */
+ JSVERIFY(SUCCEED, H5FD_s3comms_aws_canonical_request(cr_dest, 512, sh_dest, 64, hrb),
+ " unable to compose canonical request")
+ JSVERIFY_STR(C->exp_headers, sh_dest, NULL)
+ JSVERIFY_STR(C->exp_request, cr_dest, NULL)
+
+ /* tear-down
+ */
+ while (node != NULL) {
+ FAIL_IF(FAIL == H5FD_s3comms_hrb_node_set(&node, node->name, NULL));
+ }
+ HDassert(NULL == node);
+ FAIL_IF(FAIL == H5FD_s3comms_hrb_destroy(&hrb));
+ HDassert(NULL == hrb);
+
+ } /* for each test case */
+
+ /***************
+ * ERROR CASES *
+ ***************/
+
+ /* malformed hrb and/or node-list
+ */
+ JSVERIFY(FAIL, H5FD_s3comms_aws_canonical_request(cr_dest, 20, sh_dest, 20, NULL),
+ "http request object cannot be null")
+
+ hrb = H5FD_s3comms_hrb_init_request("GET", "/", "HTTP/1.1");
+ JSVERIFY(FAIL, H5FD_s3comms_aws_canonical_request(NULL, 20, sh_dest, 20, hrb),
+ "canonical request destination cannot be NULL")
+
+ JSVERIFY(FAIL, H5FD_s3comms_aws_canonical_request(cr_dest, 20, NULL, 20, hrb),
+ "signed headers destination cannot be null")
+
+ FAIL_IF(FAIL == H5FD_s3comms_hrb_destroy(&hrb))
+ HDassert(NULL == hrb);
+
+ PASSED();
+ return 0;
+
+error:
+
+ if (node != NULL) {
+ while (node != NULL)
+ (void)H5FD_s3comms_hrb_node_set(&node, node->name, NULL);
+ HDassert(node == NULL);
+ }
+ if (hrb != NULL) {
+ (void)H5FD_s3comms_hrb_destroy(&hrb);
+ }
+
+ return -1;
+
+} /* end test_aws_canonical_request() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_bytes_to_hex
+ *
+ * Purpose:
+ *
+ * Define and verify behavior of `H5FD_s3comms_bytes_to_hex()`.
+ *
+ * Return:
+ *
+ * Success: 0
+ * Failure: -1
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-14
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_bytes_to_hex(void)
+{
+ /*************************
+ * test-local structures *
+ *************************/
+
+ struct testcase {
+ const char exp[17]; /* in size * 2 + 1 for null terminator */
+ const unsigned char in[8];
+ size_t size;
+ hbool_t lower;
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ struct testcase cases[] = {
+ {
+ "52F3000C9A",
+ {82, 243, 0, 12, 154},
+ 5,
+ FALSE,
+ },
+ {
+ "009a0cf3005200", /* lowercase alphas */
+ {0, 154, 12, 243, 0, 82, 0},
+ 7,
+ TRUE,
+ },
+ {
+ "", {17, 63, 26, 56}, 0, FALSE, /* irrelevant */
+ },
+ };
+ int i = 0;
+ int n_cases = 3;
+ char out[17];
+ int out_off = 0;
+
+ TESTING("bytes-to-hex");
+
+ for (i = 0; i < n_cases; i++) {
+ for (out_off = 0; out_off < 17; out_off++) {
+ out[out_off] = 0;
+ }
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_bytes_to_hex(out, cases[i].in, cases[i].size, cases[i].lower), NULL)
+
+ JSVERIFY_STR(cases[i].exp, out, NULL)
+ }
+
+ /* dest cannot be null
+ */
+ JSVERIFY(FAIL, H5FD_s3comms_bytes_to_hex(NULL, (const unsigned char *)"nada", 5, FALSE),
+ "destination cannot be null")
+
+ PASSED();
+ return 0;
+
+error:
+ return -1;
+
+} /* end test_bytes_to_hex() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_hrb_init_request()
+ *
+ * Purpose:
+ *
+ * Define and verify behavior of `H5FD_s3comms_hrb_init_request()`
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-20
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_hrb_init_request(void)
+{
+ /*********************
+ * test-local macros *
+ *********************/
+
+ /*************************
+ * test-local structures *
+ *************************/
+
+ struct testcase {
+ const char msg[64];
+ const char *verb;
+ const char *resource;
+ const char *exp_res;
+ const char *version;
+ hbool_t ret_null;
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ struct testcase cases[] = {
+ {
+ "get HTTP request just as we provided",
+ "GET",
+ "/path/to/some/file",
+ "/path/to/some/file",
+ "HTTP/1.1",
+ FALSE,
+ },
+ {
+ "null verb substitues to GET",
+ NULL,
+ "/MYPATH/MYFILE.tiff",
+ "/MYPATH/MYFILE.tiff",
+ "HTTP/1.1",
+ FALSE,
+ },
+ {
+ "demonstrate non-GET verb",
+ "HEAD",
+ "/MYPATH/MYFILE.tiff",
+ "/MYPATH/MYFILE.tiff",
+ "HTTP/1.1",
+ FALSE,
+ },
+ {
+ "slash prepended to resource path, if necessary",
+ NULL,
+ "MYPATH/MYFILE.tiff",
+ "/MYPATH/MYFILE.tiff",
+ NULL,
+ FALSE,
+ },
+ {
+ "null resource path causes problem",
+ "GET",
+ NULL,
+ NULL,
+ NULL,
+ TRUE,
+ },
+ };
+ struct testcase *C = NULL;
+ unsigned int i = 0;
+ unsigned int ncases = 5;
+ hrb_t * req = NULL;
+
+ TESTING("hrb_init_request");
+
+ for (i = 0; i < ncases; i++) {
+ C = &cases[i];
+ req = H5FD_s3comms_hrb_init_request(C->verb, C->resource, C->version);
+ if (cases[i].ret_null == TRUE) {
+ FAIL_IF(req != NULL);
+ }
+ else {
+ FAIL_IF(req == NULL);
+ JSVERIFY(S3COMMS_HRB_MAGIC, req->magic, NULL)
+ if (C->verb == NULL) {
+ JSVERIFY_STR("GET", req->verb, NULL)
+ }
+ else {
+ JSVERIFY_STR(req->verb, C->verb, NULL)
+ }
+ JSVERIFY_STR("HTTP/1.1", req->version, NULL)
+ JSVERIFY_STR(C->exp_res, req->resource, NULL)
+ FAIL_IF(req->first_header != NULL);
+ FAIL_IF(req->body != NULL);
+ JSVERIFY(0, req->body_len, NULL)
+ JSVERIFY(SUCCEED, H5FD_s3comms_hrb_destroy(&req), "unable to destroy hrb_t")
+ FAIL_IF(NULL != req); /* should annull pointer as well as free */
+ }
+
+ } /* end for each testcase */
+
+ PASSED();
+ return 0;
+
+error:
+ (void)H5FD_s3comms_hrb_destroy(&req);
+
+ return -1;
+
+} /* end test_hrb_init_request() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_hrb_node_set()
+ *
+ * Purpose:
+ *
+ * Test operations on hrb_node_t structure
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-22
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_hrb_node_set(void)
+{
+ /*************************
+ * test-local structures *
+ *************************/
+
+ /* bundle of name/value representing an hrb_node_t
+ */
+ typedef struct node_mock_t {
+ const char *name;
+ const char *value;
+ } node_mock_t;
+
+ /* bundle for a testcase
+ *
+ * `message`
+ * purpose of the testcase
+ *
+ * `delta`
+ * container for name and value strings to pass into node-set function
+ * to to modify the list.
+ *
+ * `returned`
+ * expected return value of node-set function
+ *
+ * `given`
+ * `expected`
+ * string arrays representing the state of the list before and after
+ * modification. The number of strings must be even, with each name
+ * paired to a value. `NULL` terminates the list, with `{NULL}`
+ * representing the empty list.
+ */
+ typedef struct testcase {
+ const char *message;
+ node_mock_t delta;
+ herr_t returned;
+ const char *given[11]; /* name/value pairs in array; NULL sentinel */
+ const char *expected[11];
+ } testcase;
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ testcase cases[] = {
+ {
+ "cannot remove node from null list",
+ {"Host", NULL},
+ FAIL,
+ {NULL},
+ {NULL},
+ },
+ {
+ "cannot create list with NULL field name",
+ {NULL, "somevalue"},
+ FAIL,
+ {NULL},
+ {NULL},
+ },
+ {
+ "create a new list",
+ {"Host", "somevalue"},
+ SUCCEED,
+ {NULL},
+ {
+ "Host",
+ "somevalue",
+ NULL,
+ },
+ },
+ {
+ "insert new node at head list",
+ {"Host", "somevalue"},
+ SUCCEED,
+ {
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ {
+ "Host",
+ "somevalue",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ },
+ {
+ "append new node at list end",
+ {"x-amz-date", "somevalue"},
+ SUCCEED,
+ {
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ {
+ "Range",
+ "bytes=20-40",
+ "x-amz-date",
+ "somevalue",
+ NULL,
+ },
+ },
+ {
+ "insert new node inside list",
+ {"Intermediary", "somevalue"},
+ SUCCEED,
+ {
+ "Host",
+ "somehost",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ {
+ "Host",
+ "somehost",
+ "Intermediary",
+ "somevalue",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ },
+ {
+ "modify node",
+ {"Range", "bytes=40-80"},
+ SUCCEED,
+ {
+ "Host",
+ "somehost",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ {
+ "Host",
+ "somehost",
+ "Range",
+ "bytes=40-80",
+ NULL,
+ },
+ },
+ {
+ "modify node with new case",
+ {"RANGE", "bytes=40-80"},
+ SUCCEED,
+ {
+ "Host",
+ "somehost",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ {
+ "Host",
+ "somehost",
+ "RANGE",
+ "bytes=40-80",
+ NULL,
+ },
+ },
+ {
+ "cannot add node with no name",
+ {NULL, "bytes=40-80"},
+ FAIL,
+ {
+ "Host",
+ "somehost",
+ NULL,
+ },
+ {
+ "Host",
+ "somehost",
+ NULL,
+ },
+ },
+ {
+ "add node with 'empty' name",
+ {"", "bytes=40-80"},
+ SUCCEED,
+ {
+ "Host",
+ "somehost",
+ NULL,
+ },
+ {
+ "",
+ "bytes=40-80",
+ "Host",
+ "somehost",
+ NULL,
+ },
+ },
+ {
+ "remove node from end of list",
+ {"Host", NULL},
+ SUCCEED,
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ "Host",
+ "somehost",
+ NULL,
+ },
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ NULL,
+ },
+ },
+ {
+ "remove node from middle of list",
+ {"Host", NULL},
+ SUCCEED,
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ "Host",
+ "somehost",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ },
+ {
+ "remove node from start of list",
+ {"Date", NULL},
+ SUCCEED,
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ "Host",
+ "somehost",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ {
+ "Host",
+ "somehost",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ },
+ {
+ "remove only node in list",
+ {"Date", NULL},
+ SUCCEED,
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ NULL,
+ },
+ {
+ NULL,
+ },
+ },
+ {
+ "attempt to remove absent node fails",
+ {"Host", NULL},
+ FAIL,
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ },
+ {
+ "removal is case-insensitive",
+ {"hOsT", NULL},
+ SUCCEED,
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ "Host",
+ "somehost",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ {
+ "Date",
+ "Thr, 25 Jan 2018",
+ "Range",
+ "bytes=20-40",
+ NULL,
+ },
+ },
+ };
+ unsigned testcases_count = 16;
+ unsigned test_i = 0;
+ hrb_node_t *list = NULL;
+
+ TESTING("hrb_node_t (test_hrb_node_set)");
+
+ for (test_i = 0; test_i < testcases_count; test_i++) {
+ const hrb_node_t *node = NULL;
+ const testcase * test = &(cases[test_i]);
+ unsigned mock_i = 0;
+
+ /*********
+ * SETUP *
+ *********/
+
+ for (mock_i = 0; test->given[mock_i] != NULL; mock_i += 2) {
+ const char *name = test->given[mock_i];
+ const char *valu = test->given[mock_i + 1];
+
+ FAIL_IF(SUCCEED != H5FD_s3comms_hrb_node_set(&list, name, valu))
+ }
+ /********
+ * TEST *
+ ********/
+
+ /* perform modification on list
+ */
+ JSVERIFY(test->returned, H5FD_s3comms_hrb_node_set(&list, test->delta.name, test->delta.value),
+ test->message)
+
+ /* verify resulting list
+ */
+ node = list;
+ mock_i = 0;
+ while (test->expected[mock_i] != NULL && node != NULL) {
+ const char *name = test->expected[mock_i];
+ const char *valu = test->expected[mock_i + 1];
+
+ JSVERIFY_STR(name, node->name, NULL)
+ JSVERIFY_STR(valu, node->value, NULL)
+
+ mock_i += 2;
+ node = node->next;
+ }
+ FAIL_IF(test->expected[mock_i] != NULL)
+ FAIL_IF(node != NULL)
+
+ /************
+ * TEARDOWN *
+ ************/
+
+ while (list != NULL) {
+ FAIL_IF(SUCCEED != H5FD_s3comms_hrb_node_set(&list, list->name, NULL))
+ }
+ } /* end for each testcase */
+
+ PASSED();
+ return 0;
+
+error:
+ while (list != NULL) {
+ (void)H5FD_s3comms_hrb_node_set(&list, list->name, NULL);
+ }
+
+ return -1;
+
+} /* end test_hrb_node_t() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_HMAC_SHA256()
+ *
+ * Purpose:
+ *
+ * Define and verify behavior of `H5FD_s3comms_HMAC_SHA256()`
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-19
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_HMAC_SHA256(void)
+{
+
+ /*************************
+ * test-local structures *
+ *************************/
+
+ struct testcase {
+ herr_t ret; /* SUCCEED/FAIL expected from call */
+ const unsigned char key[SHA256_DIGEST_LENGTH];
+ size_t key_len;
+ const char * msg;
+ size_t msg_len;
+ const char * exp; /* not used if ret == FAIL */
+ size_t dest_size; /* if 0, `dest` is not malloc'd */
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ struct testcase cases[] = {
+ {
+ SUCCEED,
+ {
+ 0xdb, 0xb8, 0x93, 0xac, 0xc0, 0x10, 0x96, 0x49, 0x18, 0xf1, 0xfd,
+ 0x43, 0x3a, 0xdd, 0x87, 0xc7, 0x0e, 0x8b, 0x0d, 0xb6, 0xbe, 0x30,
+ 0xc1, 0xfb, 0xea, 0xfe, 0xfa, 0x5e, 0xc6, 0xba, 0x83, 0x78,
+ },
+ SHA256_DIGEST_LENGTH,
+ "AWS4-HMAC-SHA256\n20130524T000000Z\n20130524/us-east-1/s3/"
+ "aws4_request\n7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972",
+ HDstrlen("AWS4-HMAC-SHA256\n20130524T000000Z\n20130524/us-east-1/s3/"
+ "aws4_request\n7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972"),
+ "f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41",
+ SHA256_DIGEST_LENGTH * 2 + 1, /* +1 for null terminator */
+ },
+ {
+ SUCCEED,
+ {'J', 'e', 'f', 'e'},
+ 4,
+ "what do ya want for nothing?",
+ 28,
+ "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
+ SHA256_DIGEST_LENGTH * 2 + 1,
+ },
+ {
+ FAIL, "DOESN'T MATTER", 14, "ALSO IRRELEVANT", 15, NULL,
+ 0, /* dest -> null, resulting in immediate error */
+ },
+ };
+ char *dest = NULL;
+ int i = 0;
+ int n_cases = 3;
+
+ TESTING("HMAC_SHA256");
+
+ for (i = 0; i < n_cases; i++) {
+ if (cases[i].dest_size == 0) {
+ dest = NULL;
+ }
+ else {
+ dest = (char *)HDmalloc(sizeof(char) * cases[i].dest_size);
+ HDassert(dest != NULL);
+ }
+
+ JSVERIFY(
+ cases[i].ret,
+ H5FD_s3comms_HMAC_SHA256(cases[i].key, cases[i].key_len, cases[i].msg, cases[i].msg_len, dest),
+ cases[i].msg);
+ if (cases[i].ret == SUCCEED) {
+#ifdef VERBOSE
+ if (0 != strncmp(cases[i].exp, dest, HDstrlen(cases[i].exp))) {
+ /* print out how wrong things are, and then fail
+ */
+ dest = (char *)realloc(dest, cases[i].dest_size + 1);
+ HDassert(dest != NULL);
+ dest[cases[i].dest_size] = 0;
+ HDfprintf(stdout, "ERROR:\n!!! \"%s\"\n != \"%s\"\n", cases[i].exp, dest);
+ TEST_ERROR;
+ }
+#else /* VERBOSE not defined */
+ /* simple pass/fail test
+ */
+ JSVERIFY(0, strncmp(cases[i].exp, dest, HDstrlen(cases[i].exp)), NULL);
+#endif /* VERBOSE */
+ }
+ free(dest);
+ }
+
+ PASSED();
+ return 0;
+
+error:
+ free(dest);
+ return -1;
+
+} /* end test_HMAC_SHA256() */
+
+/*----------------------------------------------------------------------------
+ *
+ * Function: test_nlowercase()
+ *
+ * Purpose:
+ *
+ * Define and verify behavior of `H5FD_s3comms_nlowercase()`
+ *
+ * Programmer: Jacob Smith
+ * 2017-19-18
+ *
+ *----------------------------------------------------------------------------
+ */
+static herr_t
+test_nlowercase(void)
+{
+ /*************************
+ * test-local structures *
+ *************************/
+
+ struct testcase {
+ const char *in;
+ size_t len;
+ const char *exp;
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ /* any character after in exp on or after exp[len] is undefined.
+ * in this test, kept as the null character for simplicity.
+ */
+ struct testcase cases[] = {
+ {
+ "HALlEluJAh",
+ 6,
+ "hallel",
+ },
+ {
+ "all\0 lower",
+ 10,
+ "all\0 lower",
+ },
+ {
+ "to meeeeeee",
+ 0,
+ "",
+ },
+ };
+ char *dest = NULL;
+ int i = 0;
+ int n_cases = 3;
+
+ TESTING("nlowercase");
+
+ for (i = 0; i < n_cases; i++) {
+ dest = (char *)HDmalloc(sizeof(char) * 16);
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_nlowercase(dest, cases[i].in, cases[i].len), cases[i].in)
+ if (cases[i].len > 0) {
+ JSVERIFY(0, strncmp(dest, cases[i].exp, cases[i].len), NULL)
+ }
+ free(dest);
+ } /* end for each testcase */
+
+ JSVERIFY(FAIL, H5FD_s3comms_nlowercase(NULL, cases[0].in, cases[0].len), "null distination should fail")
+
+ PASSED();
+ return 0;
+
+error:
+ free(dest);
+ return -1;
+
+} /* end test_nlowercase() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_parse_url()
+ *
+ * Programmer: Jacob Smith
+ * 2017-11-??
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_parse_url(void)
+{
+ /*********************
+ * test-local macros *
+ *********************/
+
+ /*************************
+ * test-local structures *
+ *************************/
+
+ typedef struct {
+ const char *scheme;
+ const char *host;
+ const char *port;
+ const char *path;
+ const char *query;
+ } const_purl_t;
+
+ struct testcase {
+ const char *url;
+ herr_t exp_ret; /* expected return; */
+ /* if FAIL, `expected` is unused */
+ const_purl_t expected;
+ const char * msg;
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ parsed_url_t * purl = NULL;
+ unsigned int i = 0;
+ unsigned int ncases = 15;
+ struct testcase cases[] = {
+ {
+ NULL,
+ FAIL,
+ {NULL, NULL, NULL, NULL, NULL},
+ "null url",
+ },
+ {
+ "",
+ FAIL,
+ {NULL, NULL, NULL, NULL, NULL},
+ "empty url",
+ },
+ {
+ "ftp://[1000:4000:0002:2010]",
+ SUCCEED,
+ {
+ "ftp",
+ "[1000:4000:0002:2010]",
+ NULL,
+ NULL,
+ NULL,
+ },
+ "IPv6 ftp and empty path (root)",
+ },
+ {
+ "ftp://[1000:4000:0002:2010]:2040",
+ SUCCEED,
+ {
+ "ftp",
+ "[1000:4000:0002:2010]",
+ "2040",
+ NULL,
+ NULL,
+ },
+ "root IPv6 ftp with port",
+ },
+ {
+ "http://some.domain.org:9000/path/to/resource.txt",
+ SUCCEED,
+ {
+ "http",
+ "some.domain.org",
+ "9000",
+ "path/to/resource.txt",
+ NULL,
+ },
+ "without query",
+ },
+ {
+ "https://domain.me:00/file.txt?some_params unchecked",
+ SUCCEED,
+ {
+ "https",
+ "domain.me",
+ "00",
+ "file.txt",
+ "some_params unchecked",
+ },
+ "with query",
+ },
+ {
+ "ftp://domain.com/",
+ SUCCEED,
+ {
+ "ftp",
+ "domain.com",
+ NULL,
+ NULL,
+ NULL,
+ },
+ "explicit root w/out port",
+ },
+ {
+ "ftp://domain.com:1234/",
+ SUCCEED,
+ {
+ "ftp",
+ "domain.com",
+ "1234",
+ NULL,
+ NULL,
+ },
+ "explicit root with port",
+ },
+ {
+ "ftp://domain.com:1234/file?",
+ FAIL,
+ {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ },
+ "empty query is invalid",
+ },
+ {
+ "ftp://:1234/file",
+ FAIL,
+ {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ },
+ "no host",
+ },
+ {
+ "h&r block",
+ FAIL,
+ {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ },
+ "no scheme (bad URL)",
+ },
+ {
+ "http://domain.com?a=b&d=b",
+ SUCCEED,
+ {
+ "http",
+ "domain.com",
+ NULL,
+ NULL,
+ "a=b&d=b",
+ },
+ "QUERY with implict PATH",
+ },
+ {
+ "http://[5]/path?a=b&d=b",
+ SUCCEED,
+ {
+ "http",
+ "[5]",
+ NULL,
+ "path",
+ "a=b&d=b",
+ },
+ "IPv6 extraction is really dumb",
+ },
+ {
+ "http://[1234:5678:0910:1112]:port/path",
+ FAIL,
+ {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ },
+ "non-decimal PORT (port)",
+ },
+ {
+ "http://mydomain.com:01a3/path",
+ FAIL,
+ {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ },
+ "non-decimal PORT (01a3)",
+ },
+ };
+
+ TESTING("url-parsing functionality");
+
+ /*********
+ * TESTS *
+ *********/
+
+ for (i = 0; i < ncases; i++) {
+ HDassert(purl == NULL);
+
+ JSVERIFY(cases[i].exp_ret, H5FD_s3comms_parse_url(cases[i].url, &purl), cases[i].msg)
+
+ if (cases[i].exp_ret == FAIL) {
+ /* on FAIL, `purl` should be untouched--remains NULL */
+ FAIL_UNLESS(purl == NULL)
+ }
+ else {
+ /* on SUCCEED, `purl` should be set */
+ FAIL_IF(purl == NULL)
+
+ if (cases[i].expected.scheme != NULL) {
+ FAIL_IF(NULL == purl->scheme)
+ JSVERIFY_STR(cases[i].expected.scheme, purl->scheme, cases[i].msg)
+ }
+ else {
+ FAIL_UNLESS(NULL == purl->scheme)
+ }
+
+ if (cases[i].expected.host != NULL) {
+ FAIL_IF(NULL == purl->host)
+ JSVERIFY_STR(cases[i].expected.host, purl->host, cases[i].msg)
+ }
+ else {
+ FAIL_UNLESS(NULL == purl->host)
+ }
+
+ if (cases[i].expected.port != NULL) {
+ FAIL_IF(NULL == purl->port)
+ JSVERIFY_STR(cases[i].expected.port, purl->port, cases[i].msg)
+ }
+ else {
+ FAIL_UNLESS(NULL == purl->port)
+ }
+
+ if (cases[i].expected.path != NULL) {
+ FAIL_IF(NULL == purl->path)
+ JSVERIFY_STR(cases[i].expected.path, purl->path, cases[i].msg)
+ }
+ else {
+ FAIL_UNLESS(NULL == purl->path)
+ }
+
+ if (cases[i].expected.query != NULL) {
+ FAIL_IF(NULL == purl->query)
+ JSVERIFY_STR(cases[i].expected.query, purl->query, cases[i].msg)
+ }
+ else {
+ FAIL_UNLESS(NULL == purl->query)
+ }
+ } /* end if parse-url return SUCCEED/FAIL */
+
+ /* per-test cleanup
+ * well-behaved, even if `purl` is NULL
+ */
+ FAIL_IF(FAIL == H5FD_s3comms_free_purl(purl))
+ purl = NULL;
+
+ } /* end for each testcase */
+
+ PASSED();
+ return 0;
+
+error:
+ /***********
+ * cleanup *
+ ***********/
+ (void)H5FD_s3comms_free_purl(purl);
+
+ return -1;
+
+} /* end test_parse_url() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_percent_encode_char()
+ *
+ * Purpose:
+ *
+ * Define and verify behavior of `H5FD_s3comms_percent_encode_char()`
+ *
+ * Return:
+ *
+ * Success: 0
+ * Failure: -1
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-14
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_percent_encode_char(void)
+{
+ /*************************
+ * test-local structures *
+ *************************/
+
+ struct testcase {
+ const char c;
+ const char *exp;
+ size_t exp_len;
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ struct testcase cases[] = {
+ {'$', "%24", 3}, /* u+0024 dollar sign */
+ {' ', "%20", 3}, /* u+0020 space */
+ {'^', "%5E", 3}, /* u+0094 carat */
+ {'/', "%2F", 3}, /* u+002f solidus (forward slash) */
+ /* {??, "%C5%8C", 6},*/ /* u+014c Latin Capital Letter O with Macron */
+ /* Not included because it is multibyte "wide" character that poses */
+ /* issues both in the underlying function and in being written in */
+ /* this file. */
+ /* {'¢', "%C2%A2", 6}, */ /* u+00a2 cent sign */
+ /* above works, but complains about wide character overflow */
+ /* Elide for now, until it is determined (a) unnecessary or */
+ /* (b) requiring signature change to accommodate wide characters */
+ {'\0', "%00", 3}, /* u+0000 null */
+ };
+ char dest[13];
+ size_t dest_len = 0;
+ int i = 0;
+ int n_cases = 5;
+
+ TESTING("percent encode characters");
+
+ for (i = 0; i < n_cases; i++) {
+ JSVERIFY(SUCCEED, H5FD_s3comms_percent_encode_char(dest, (const unsigned char)cases[i].c, &dest_len),
+ NULL)
+ JSVERIFY(cases[i].exp_len, dest_len, NULL)
+ JSVERIFY(0, strncmp(dest, cases[i].exp, dest_len), NULL)
+ JSVERIFY_STR(cases[i].exp, dest, NULL)
+ }
+
+ JSVERIFY(FAIL, H5FD_s3comms_percent_encode_char(NULL, (const unsigned char)'^', &dest_len), NULL)
+
+ PASSED();
+ return 0;
+
+error:
+ return -1;
+} /* end test_percent_encode_char() */
+
+/*---------------------------------------------------------------------------
+ * Function: test_s3r_open()
+ *
+ * Programmer: Jacob Smith 2018-01-24
+ *
+ * Changes: None
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_s3r_get_filesize(void)
+{
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ char url_raven[S3_TEST_MAX_URL_SIZE];
+ s3r_t *handle = NULL;
+
+ TESTING("s3r_get_filesize");
+
+ /* setup -- compose url to target resource
+ */
+ if (FALSE == s3_test_bucket_defined) {
+ SKIPPED();
+ puts(" environment variable HDF5_ROS3_TEST_BUCKET_URL not defined");
+ fflush(stdout);
+ return 0;
+ }
+
+ FAIL_IF(S3_TEST_MAX_URL_SIZE < HDsnprintf(url_raven, S3_TEST_MAX_URL_SIZE, "%s/%s", s3_test_bucket_url,
+ S3_TEST_RESOURCE_TEXT_PUBLIC));
+
+ JSVERIFY(0, H5FD_s3comms_s3r_get_filesize(NULL), "filesize of the null handle should be 0")
+
+ handle = H5FD_s3comms_s3r_open(url_raven, NULL, NULL, NULL);
+ FAIL_IF(handle == NULL)
+
+ JSVERIFY(6464, H5FD_s3comms_s3r_get_filesize(handle), NULL)
+
+ FAIL_IF(SUCCEED != H5FD_s3comms_s3r_close(handle))
+
+ PASSED();
+ return 0;
+
+error:
+ if (handle != NULL)
+ (void)H5FD_s3comms_s3r_close(handle);
+
+ return -1;
+
+} /* end test_s3r_get_filesize() */
+
+/*---------------------------------------------------------------------------
+ * Function: test_s3r_open()
+ *
+ * Programmer: Jacob Smith 2018-01-??
+ *
+ * Changes: None
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_s3r_open(void)
+{
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ char url_missing[S3_TEST_MAX_URL_SIZE];
+ char url_raven[S3_TEST_MAX_URL_SIZE];
+ char url_raven_badport[S3_TEST_MAX_URL_SIZE];
+ char url_shakespeare[S3_TEST_MAX_URL_SIZE];
+ unsigned char signing_key[SHA256_DIGEST_LENGTH];
+ struct tm * now = NULL;
+ char iso8601now[ISO8601_SIZE];
+ s3r_t * handle = NULL;
+ hbool_t curl_ready = FALSE;
+ parsed_url_t *purl = NULL;
+
+ TESTING("s3r_open");
+
+ if (s3_test_credentials_loaded == 0) {
+ SKIPPED();
+ puts(" s3 credentials are not loaded");
+ fflush(stdout);
+ return 0;
+ }
+ if (FALSE == s3_test_bucket_defined) {
+ SKIPPED();
+ puts(" environment variable HDF5_ROS3_TEST_BUCKET_URL not defined");
+ fflush(stdout);
+ return 0;
+ }
+
+ /******************
+ * PRE-TEST SETUP *
+ ******************/
+
+ FAIL_IF(S3_TEST_MAX_URL_SIZE < HDsnprintf(url_shakespeare, S3_TEST_MAX_URL_SIZE, "%s/%s",
+ s3_test_bucket_url, S3_TEST_RESOURCE_TEXT_RESTRICTED));
+
+ FAIL_IF(S3_TEST_MAX_URL_SIZE < HDsnprintf(url_missing, S3_TEST_MAX_URL_SIZE, "%s/%s", s3_test_bucket_url,
+ S3_TEST_RESOURCE_MISSING));
+
+ FAIL_IF(S3_TEST_MAX_URL_SIZE < HDsnprintf(url_raven, S3_TEST_MAX_URL_SIZE, "%s/%s", s3_test_bucket_url,
+ S3_TEST_RESOURCE_TEXT_PUBLIC));
+
+ /* Set given bucket url with invalid/inactive port number for badport.
+ * Note, this sort of micro-management of parsed_url_t is not advised
+ */
+ FAIL_IF(FAIL == H5FD_s3comms_parse_url(s3_test_bucket_url, &purl))
+ if (purl->port == NULL) {
+ purl->port = (char *)H5MM_malloc(sizeof(char) * 5);
+ FAIL_IF(purl->port == NULL);
+ FAIL_IF(5 < HDsnprintf(purl->port, 5, "9000"))
+ }
+ else if (strcmp(purl->port, "9000") != 0) {
+ FAIL_IF(5 < HDsnprintf(purl->port, 5, "9000"))
+ }
+ else {
+ FAIL_IF(5 < HDsnprintf(purl->port, 5, "1234"))
+ }
+ FAIL_IF(S3_TEST_MAX_URL_SIZE < HDsnprintf(url_raven_badport, S3_TEST_MAX_URL_SIZE, "%s://%s:%s/%s",
+ purl->scheme, purl->host, purl->port,
+ S3_TEST_RESOURCE_TEXT_PUBLIC));
+
+ curl_global_init(CURL_GLOBAL_DEFAULT);
+ curl_ready = TRUE;
+
+ now = gmnow();
+ FAIL_IF(now == NULL)
+ FAIL_IF(ISO8601NOW(iso8601now, now) != (ISO8601_SIZE - 1));
+
+ /* It is desired to have means available to verify that signing_key
+ * was set successfully and to an expected value.
+ */
+ FAIL_IF(FAIL == H5FD_s3comms_signing_key(signing_key, (const char *)s3_test_aws_secret_access_key,
+ (const char *)s3_test_aws_region, (const char *)iso8601now));
+
+ /*************************
+ * OPEN NONEXISTENT FILE *
+ *************************/
+
+ /* attempt anonymously
+ */
+ handle = H5FD_s3comms_s3r_open(url_missing, NULL, NULL, NULL);
+ FAIL_IF(handle != NULL);
+
+ /* attempt with authentication
+ */
+ handle =
+ H5FD_s3comms_s3r_open(url_missing, (const char *)s3_test_aws_region,
+ (const char *)s3_test_aws_access_key_id, (const unsigned char *)signing_key);
+ FAIL_IF(handle != NULL);
+
+ /*************************
+ * INACTIVE PORT ON HOST *
+ *************************/
+
+#if S3_TEST_RUN_TIMEOUT
+ HDprintf("Opening on inactive port may hang for a minute; waiting for timeout\n");
+ handle = H5FD_s3comms_s3r_open(url_raven_badport, NULL, NULL, NULL);
+ FAIL_IF(handle != NULL);
+#endif
+
+ /*******************************
+ * INVALID AUTHENTICATION INFO *
+ *******************************/
+
+ /* anonymous access on restricted file
+ */
+ handle = H5FD_s3comms_s3r_open(url_shakespeare, NULL, NULL, NULL);
+ FAIL_IF(handle != NULL);
+
+ /* passed in a bad ID
+ */
+ handle = H5FD_s3comms_s3r_open(url_shakespeare, (const char *)s3_test_aws_region, "I_MADE_UP_MY_ID",
+ (const unsigned char *)signing_key);
+ FAIL_IF(handle != NULL);
+
+ /* using an invalid signing key
+ */
+ handle =
+ H5FD_s3comms_s3r_open(url_shakespeare, (const char *)s3_test_aws_region,
+ (const char *)s3_test_aws_access_key_id, (const unsigned char *)EMPTY_SHA256);
+ FAIL_IF(handle != NULL);
+
+ /*******************************
+ * SUCCESSFUL OPEN (AND CLOSE) *
+ *******************************/
+
+ /* anonymous
+ */
+ handle = H5FD_s3comms_s3r_open(url_raven, NULL, NULL, NULL);
+ FAIL_IF(handle == NULL);
+ JSVERIFY(6464, H5FD_s3comms_s3r_get_filesize(handle), "did not get expected filesize")
+ JSVERIFY(SUCCEED, H5FD_s3comms_s3r_close(handle), "unable to close file")
+ handle = NULL;
+
+ /* using authentication on anonymously-accessible file?
+ */
+ handle =
+ H5FD_s3comms_s3r_open(url_raven, (const char *)s3_test_aws_region,
+ (const char *)s3_test_aws_access_key_id, (const unsigned char *)signing_key);
+ FAIL_IF(handle == NULL);
+ JSVERIFY(6464, H5FD_s3comms_s3r_get_filesize(handle), NULL)
+ JSVERIFY(SUCCEED, H5FD_s3comms_s3r_close(handle), "unable to close file")
+ handle = NULL;
+
+ /* authenticating
+ */
+ handle =
+ H5FD_s3comms_s3r_open(url_shakespeare, (const char *)s3_test_aws_region,
+ (const char *)s3_test_aws_access_key_id, (const unsigned char *)signing_key);
+ FAIL_IF(handle == NULL);
+ JSVERIFY(5458199, H5FD_s3comms_s3r_get_filesize(handle), NULL)
+ JSVERIFY(SUCCEED, H5FD_s3comms_s3r_close(handle), "unable to close file")
+ handle = NULL;
+
+ curl_global_cleanup();
+ curl_ready = FALSE;
+
+ FAIL_IF(FAIL == H5FD_s3comms_free_purl(purl))
+ purl = NULL;
+
+ PASSED();
+ return 0;
+error:
+ /***********
+ * cleanup *
+ ***********/
+
+ if (handle != NULL)
+ H5FD_s3comms_s3r_close(handle);
+ if (purl != NULL)
+ H5FD_s3comms_free_purl(purl);
+ if (curl_ready == TRUE)
+ curl_global_cleanup();
+
+ return -1;
+
+} /* end test_s3r_open() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_s3r_read()
+ *
+ * Purpose:
+ *
+ * Specify and demonstrate the use and life cycle of an S3 Request handle
+ * `s3r_t`, through its related functions.
+ *
+ * H5FD_s3comms_s3r_open
+ * H5FD_s3comms_s3r_getsize << called by open() _only_
+ * H5FD_s3comms_s3r_read << called by getsize(), multiple times working
+ * H5FD_s3comms_s3r_close
+ *
+ * Shows most basic curl interation.
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-06
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_s3r_read(void)
+{
+
+#define S3COMMS_TEST_BUFFER_SIZE 256
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ char url_raven[S3_TEST_MAX_URL_SIZE];
+ char buffer[S3COMMS_TEST_BUFFER_SIZE];
+ s3r_t * handle = NULL;
+ hbool_t curl_ready = FALSE;
+ unsigned int i = 0;
+
+ TESTING("test_s3r_read");
+
+ /*
+ * initial setup
+ */
+ if (FALSE == s3_test_bucket_defined) {
+ SKIPPED();
+ puts(" environment variable HDF5_ROS3_TEST_BUCKET_URL not defined");
+ fflush(stdout);
+ return 0;
+ }
+
+ curl_global_init(CURL_GLOBAL_DEFAULT);
+ curl_ready = TRUE;
+ FAIL_IF(S3_TEST_MAX_URL_SIZE < HDsnprintf(url_raven, S3_TEST_MAX_URL_SIZE, "%s/%s", s3_test_bucket_url,
+ S3_TEST_RESOURCE_TEXT_PUBLIC));
+
+ for (i = 0; i < S3COMMS_TEST_BUFFER_SIZE; i++)
+ buffer[i] = '\0';
+
+ /* open file
+ */
+ handle = H5FD_s3comms_s3r_open(url_raven, NULL, NULL, NULL);
+ FAIL_IF(handle == NULL)
+ JSVERIFY(6464, H5FD_s3comms_s3r_get_filesize(handle), NULL)
+
+ for (i = 0; i < S3COMMS_TEST_BUFFER_SIZE; i++)
+ buffer[i] = '\0';
+
+ /**********************
+ * read start of file *
+ **********************/
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_s3r_read(handle, (haddr_t)0, (size_t)118, buffer), NULL)
+ JSVERIFY_STR("Once upon a midnight dreary, while I pondered, weak and weary,\n"
+ "Over many a quaint and curious volume of forgotten lore",
+ buffer, NULL)
+
+ for (i = 0; i < S3COMMS_TEST_BUFFER_SIZE; i++)
+ buffer[i] = '\0';
+
+ /************************
+ * read arbitrary range *
+ ************************/
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_s3r_read(handle, (haddr_t)2540, (size_t)54, buffer), NULL)
+ JSVERIFY_STR("the grave and stern decorum of the countenance it wore", buffer, NULL)
+
+ for (i = 0; i < S3COMMS_TEST_BUFFER_SIZE; i++)
+ buffer[i] = '\0';
+
+ /**********************
+ * read one character *
+ **********************/
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_s3r_read(handle, (haddr_t)2540, (size_t)1, buffer), NULL)
+ JSVERIFY_STR("t", buffer, NULL)
+
+ for (i = 0; i < S3COMMS_TEST_BUFFER_SIZE; i++)
+ buffer[i] = '\0';
+
+ /***************
+ * read to EoF *
+ ***************/
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_s3r_read(handle, (haddr_t)6370, (size_t)0, buffer), NULL)
+ JSVERIFY(
+ 0,
+ strncmp(
+ buffer,
+ "And my soul from out that shadow that lies floating on the floor\nShall be lifted—nevermore!\n",
+ 94),
+ buffer)
+
+ for (i = 0; i < S3COMMS_TEST_BUFFER_SIZE; i++)
+ buffer[i] = '\0';
+
+ /*****************
+ * read past eof *
+ *****************/
+
+ JSVERIFY(FAIL,
+ H5FD_s3comms_s3r_read(handle, (haddr_t)6400, (size_t)100, /* 6400+100 > 6464 */
+ buffer),
+ NULL)
+ JSVERIFY(0, strcmp("", buffer), NULL)
+
+ /************************
+ * read starts past eof *
+ ************************/
+
+ JSVERIFY(FAIL,
+ H5FD_s3comms_s3r_read(handle, (haddr_t)1200699, /* 1200699 > 6464 */
+ (size_t)100, buffer),
+ NULL)
+ JSVERIFY(0, strcmp("", buffer), NULL)
+
+ /**********************
+ * read starts on eof *
+ **********************/
+
+ JSVERIFY(FAIL, H5FD_s3comms_s3r_read(handle, (haddr_t)6464, (size_t)0, buffer), NULL)
+ JSVERIFY(0, strcmp("", buffer), NULL)
+
+ /*************
+ * TEAR DOWN *
+ *************/
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_s3r_close(handle), "unable to close file")
+ handle = NULL;
+
+ curl_global_cleanup();
+ curl_ready = FALSE;
+
+ PASSED();
+ return 0;
+
+error:
+ /***********
+ * cleanup *
+ ***********/
+
+ if (handle != NULL)
+ H5FD_s3comms_s3r_close(handle);
+
+ if (curl_ready == TRUE)
+ curl_global_cleanup();
+
+ return -1;
+
+#undef S3COMMS_TEST_BUFFER_SIZE
+
+} /* end test_s3r_read() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_signing_key()
+ *
+ * Purpose:
+ *
+ * Define and verify behavior of `H5FD_s3comms_signing_key()`
+ *
+ * More test cases would be a very good idea.
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-18
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_signing_key(void)
+{
+ /*************************
+ * test-local structures *
+ *************************/
+
+ struct testcase {
+ const char * region;
+ const char * secret_key;
+ const char * when;
+ unsigned char exp[SHA256_DIGEST_LENGTH];
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ struct testcase cases[] = {
+ {
+ "us-east-1",
+ "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
+ "20130524T000000Z",
+ {
+ 0xdb, 0xb8, 0x93, 0xac, 0xc0, 0x10, 0x96, 0x49, 0x18, 0xf1, 0xfd,
+ 0x43, 0x3a, 0xdd, 0x87, 0xc7, 0x0e, 0x8b, 0x0d, 0xb6, 0xbe, 0x30,
+ 0xc1, 0xfb, 0xea, 0xfe, 0xfa, 0x5e, 0xc6, 0xba, 0x83, 0x78,
+ },
+ },
+ };
+ int i = 0;
+ unsigned char *key = NULL;
+ int ncases = 1;
+
+ TESTING("signing_key");
+
+ for (i = 0; i < ncases; i++) {
+ key = (unsigned char *)HDmalloc(sizeof(unsigned char) * SHA256_DIGEST_LENGTH);
+ HDassert(key != NULL);
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_signing_key(key, cases[i].secret_key, cases[i].region, cases[i].when),
+ NULL)
+
+ JSVERIFY(0, strncmp((const char *)cases[i].exp, (const char *)key, SHA256_DIGEST_LENGTH),
+ cases[i].exp)
+
+ free(key);
+ key = NULL;
+ }
+
+ /***************
+ * ERROR CASES *
+ ***************/
+
+ key = (unsigned char *)HDmalloc(sizeof(unsigned char) * SHA256_DIGEST_LENGTH);
+ HDassert(key != NULL);
+
+ JSVERIFY(FAIL, H5FD_s3comms_signing_key(NULL, cases[0].secret_key, cases[0].region, cases[0].when),
+ "destination cannot be NULL")
+
+ JSVERIFY(FAIL, H5FD_s3comms_signing_key(key, NULL, cases[0].region, cases[0].when),
+ "secret key cannot be NULL")
+
+ JSVERIFY(FAIL, H5FD_s3comms_signing_key(key, cases[0].secret_key, NULL, cases[0].when),
+ "aws region cannot be NULL")
+
+ JSVERIFY(FAIL, H5FD_s3comms_signing_key(key, cases[0].secret_key, cases[0].region, NULL),
+ "time string cannot be NULL")
+
+ free(key);
+ key = NULL;
+
+ PASSED();
+ return 0;
+
+error:
+ if (key != NULL) {
+ free(key);
+ }
+
+ return -1;
+
+} /* end test_signing_key() */
+
+/*---------------------------------------------------------------------------
+ *
+ * Function: test_tostringtosign()
+ *
+ * Purpose:
+ *
+ * Verify that we can get the "string to sign" from a Canonical Request and
+ * related information.
+ *
+ * Demonstrate failure cases.
+ *
+ * Return:
+ *
+ * Success: 0
+ * Failure: -1
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-13
+ *
+ *---------------------------------------------------------------------------
+ */
+static herr_t
+test_tostringtosign(void)
+{
+ /************************
+ * test-local variables *
+ ************************/
+
+ const char canonreq[] = "GET\n/"
+ "test.txt\n\nhost:examplebucket.s3.amazonaws.com\nrange:bytes=0-9\nx-amz-content-"
+ "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\nx-amz-"
+ "date:20130524T000000Z\n\nhost;range;x-amz-content-sha256;x-amz-"
+ "date\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+ const char iso8601now[] = "20130524T000000Z";
+ const char region[] = "us-east-1";
+ char s2s[512];
+
+ TESTING("s3comms tostringtosign");
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_tostringtosign(s2s, canonreq, iso8601now, region),
+ "unable to create string to sign")
+
+ JSVERIFY_STR("AWS4-HMAC-SHA256\n20130524T000000Z\n20130524/us-east-1/s3/"
+ "aws4_request\n7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972",
+ s2s, NULL)
+
+ JSVERIFY(FAIL, H5FD_s3comms_tostringtosign(s2s, NULL, iso8601now, region),
+ "canonical request string cannot be NULL")
+
+ JSVERIFY(FAIL, H5FD_s3comms_tostringtosign(s2s, canonreq, NULL, region), "time string cannot be NULL")
+
+ JSVERIFY(FAIL, H5FD_s3comms_tostringtosign(s2s, canonreq, iso8601now, NULL), "aws region cannot be NULL")
+
+ PASSED();
+ return 0;
+
+error:
+ return -1;
+
+} /* end test_tostringtosign() */
+
+/*----------------------------------------------------------------------------
+ *
+ * Function: test_trim()
+ *
+ * Purpose:
+ *
+ * Define and verify behavior of `H5FD_s3comms_trim()`.
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-14
+ *
+ *----------------------------------------------------------------------------
+ */
+static herr_t
+test_trim(void)
+{
+ /*************************
+ * test-local structures *
+ *************************/
+
+ struct testcase {
+ const char *in;
+ size_t in_len;
+ const char *exp;
+ size_t exp_len;
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ struct testcase cases[] = {
+ {
+ "block string",
+ 12,
+ "block string",
+ 12,
+ },
+ {
+ " \n\r \t",
+ 6,
+ "",
+ 0,
+ },
+ {
+ " \twhite b4",
+ 10,
+ "white b4",
+ 8,
+ },
+ {
+ "white after\r\n ",
+ 15,
+ "white after",
+ 11,
+ },
+ {
+ " on\nends\t",
+ 9,
+ "on\nends",
+ 7,
+ },
+ };
+ char dest[32];
+ size_t dest_len = 0;
+ int i = 0;
+ int n_cases = 5;
+ char * str = NULL;
+
+ TESTING("s3comms trim");
+
+ for (i = 0; i < n_cases; i++) {
+ HDassert(str == NULL);
+ str = (char *)HDmalloc(sizeof(char) * cases[i].in_len);
+ HDassert(str != NULL);
+ HDstrncpy(str, cases[i].in, cases[i].in_len);
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_trim(dest, str, cases[i].in_len, &dest_len), NULL)
+ JSVERIFY(cases[i].exp_len, dest_len, cases[i].in)
+ if (dest_len > 0) {
+ JSVERIFY(0, strncmp(cases[i].exp, dest, dest_len), cases[i].exp)
+ }
+ free(str);
+ str = NULL;
+ } /* end for each testcase */
+
+ JSVERIFY(SUCCEED, H5FD_s3comms_trim(dest, NULL, 3, &dest_len),
+ "should not fail when trimming a null string");
+ JSVERIFY(0, dest_len, "trimming NULL string writes 0 characters")
+
+ HDassert(str == NULL);
+ str = (char *)HDmalloc(sizeof(char *) * 11);
+ HDassert(str != NULL);
+ memcpy(str, "some text ", 11); /* string with null terminator */
+ JSVERIFY(FAIL, H5FD_s3comms_trim(NULL, str, 10, &dest_len), "destination for trim cannot be NULL");
+ free(str);
+ str = NULL;
+
+ PASSED();
+ return 0;
+
+error:
+ if (str != NULL) {
+ free(str);
+ }
+ return -1;
+
+} /* end test_trim() */
+
+/*----------------------------------------------------------------------------
+ *
+ * Function: test_uriencode()
+ *
+ * Purpose:
+ *
+ * Define and verify behavior of `H5FD_s3comms_uriencode()`.
+ *
+ * Programmer: Jacob Smith
+ * 2017-09-14
+ *
+ *----------------------------------------------------------------------------
+ */
+static herr_t
+test_uriencode(void)
+{
+ /*************************
+ * test-local structures *
+ *************************/
+
+ struct testcase {
+ const char *str;
+ size_t s_len;
+ hbool_t encode_slash;
+ const char *expected;
+ };
+
+ /************************
+ * test-local variables *
+ ************************/
+
+ struct testcase cases[] = {{
+ "/path/to/resource.jpg",
+ 21,
+ FALSE,
+ "/path/to/resource.jpg",
+ },
+ {
+ "/path/to/resource.jpg",
+ 21,
+ TRUE,
+ "%2Fpath%2Fto%2Fresource.jpg",
+ },
+ {
+ "string got_spaa ces",
+ 20,
+ TRUE,
+ "string%20got_spaa%20%20ces",
+ },
+ {
+ "sp ac~es/and-sl ash.encoded",
+ 27,
+ TRUE,
+ "sp%20ac~es%2Fand-sl%20ash.encoded",
+ },
+ {
+ "sp ac~es/and-sl ash.unencoded",
+ 29,
+ FALSE,
+ "sp%20ac~es/and-sl%20ash.unencoded",
+ },
+ {
+ "/path/to/resource.txt",
+ 0,
+ FALSE,
+ "",
+
+ }};
+ char * dest = NULL;
+ size_t dest_written = 0;
+ int i = 0;
+ int ncases = 6;
+ size_t str_len = 0;
+
+ TESTING("s3comms uriencode")
+
+ for (i = 0; i < ncases; i++) {
+ str_len = cases[i].s_len;
+ dest = (char *)HDmalloc(sizeof(char) * str_len * 3 + 1);
+ FAIL_IF(dest == NULL)
+
+ JSVERIFY(SUCCEED,
+ H5FD_s3comms_uriencode(dest, cases[i].str, str_len, cases[i].encode_slash, &dest_written),
+ NULL);
+ JSVERIFY(HDstrlen(cases[i].expected), dest_written, NULL)
+ JSVERIFY(0, strncmp(dest, cases[i].expected, dest_written), cases[i].expected);
+
+ free(dest);
+ dest = NULL;
+ } /* end for each testcase */
+
+ /***************
+ * ERROR CASES *
+ ***************/
+
+ dest = (char *)HDmalloc(sizeof(char) * 15);
+ HDassert(dest != NULL);
+
+ JSVERIFY(FAIL, H5FD_s3comms_uriencode(NULL, "word$", 5, FALSE, &dest_written),
+ "destination cannot be NULL");
+ JSVERIFY(FAIL, H5FD_s3comms_uriencode(dest, NULL, 5, FALSE, &dest_written),
+ "source string cannot be NULL");
+
+ free(dest);
+ dest = NULL;
+
+ PASSED();
+ return 0;
+
+error:
+ if (dest != NULL) {
+ free(dest);
+ }
+ return -1;
+
+} /* end test_uriencode() */
+
+#endif /* H5_HAVE_ROS3_VFD */
+
+/*-------------------------------------------------------------------------
+ * Function: main()
+ *
+ * Purpose:
+ *
+ * Run unit tests for S3 Communications (s3comms).
+ *
+ * Return:
+ *
+ * Success: 0
+ * Failure: 1
+ *
+ * Programmer: Jacob Smith
+ * 2017-10-12
+ *
+ *-------------------------------------------------------------------------
+ */
+int
+main(void)
+{
+#ifdef H5_HAVE_ROS3_VFD
+ int nerrors = 0;
+ const char *bucket_url_env = NULL;
+
+ h5_reset();
+
+#endif /* H5_HAVE_ROS3_VFD */
+
+ HDprintf("Testing S3Communications functionality.\n");
+
+#ifdef H5_HAVE_ROS3_VFD
+
+ /* "clear" profile data strings */
+ s3_test_aws_access_key_id[0] = '\0';
+ s3_test_aws_secret_access_key[0] = '\0';
+ s3_test_aws_region[0] = '\0';
+ s3_test_bucket_url[0] = '\0';
+
+ /* TODO: unit/regression test for H5FD_s3comms_load_aws_profile()
+ * requires a few test files and/or manipulation of default path
+ */
+ /* attempt to load test credentials
+ * if unable, certain tests will be skipped
+ */
+ if (SUCCEED == H5FD_s3comms_load_aws_profile(S3_TEST_PROFILE_NAME, s3_test_aws_access_key_id,
+ s3_test_aws_secret_access_key, s3_test_aws_region)) {
+ s3_test_credentials_loaded = 1;
+ }
+
+ bucket_url_env = HDgetenv("HDF5_ROS3_TEST_BUCKET_URL");
+ if (bucket_url_env == NULL || bucket_url_env[0] == '\0') {
+ HDprintf("WARNING: S3 bucket url is not defined in enviornment "
+ "variable 'HDF5_ROS3_TEST_BUCKET_URL'!\n");
+ }
+ else {
+ HDstrncpy(s3_test_bucket_url, bucket_url_env, S3_TEST_MAX_URL_SIZE);
+ s3_test_bucket_defined = TRUE;
+ }
+
+ /* tests ordered rougly by dependence */
+ nerrors += test_macro_format_credential() < 0 ? 1 : 0;
+ nerrors += test_trim() < 0 ? 1 : 0;
+ nerrors += test_nlowercase() < 0 ? 1 : 0;
+ nerrors += test_uriencode() < 0 ? 1 : 0;
+ nerrors += test_percent_encode_char() < 0 ? 1 : 0;
+ nerrors += test_bytes_to_hex() < 0 ? 1 : 0;
+ nerrors += test_HMAC_SHA256() < 0 ? 1 : 0;
+ nerrors += test_signing_key() < 0 ? 1 : 0;
+ nerrors += test_hrb_node_set() < 0 ? 1 : 0;
+ nerrors += test_hrb_init_request() < 0 ? 1 : 0;
+ nerrors += test_parse_url() < 0 ? 1 : 0;
+ nerrors += test_aws_canonical_request() < 0 ? 1 : 0;
+ nerrors += test_tostringtosign() < 0 ? 1 : 0;
+ nerrors += test_s3r_open() < 0 ? 1 : 0;
+ nerrors += test_s3r_get_filesize() < 0 ? 1 : 0;
+ nerrors += test_s3r_read() < 0 ? 1 : 0;
+
+ if (nerrors) {
+ HDprintf("***** %d S3comms TEST%s FAILED! *****\n", nerrors, nerrors > 1 ? "S" : "");
+ return 1;
+ }
+
+ HDprintf("All S3comms tests passed.\n");
+
+ return 0;
+
+#else
+
+ HDprintf("SKIPPED - read-only S3 VFD not built\n");
+ return EXIT_SUCCESS;
+
+#endif /* H5_HAVE_ROS3_VFD */
+
+} /* end main() */