You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2017/08/30 14:24:54 UTC

[trafficserver] branch 7.1.x updated (71fdc75 -> 319e219)

This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a change to branch 7.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git.


    from 71fdc75  Updated changelog
     new e798dfe  Unit tests for AWS Signature Version 4
     new 319e219  FD leaks when ep.start() failed or cancelled in acceptEvent or con.connect() failed

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 iocore/net/SSLNetVConnection.cc                    |   1 -
 iocore/net/UnixNetVConnection.cc                   |   8 +-
 plugins/s3_auth/Makefile.inc                       |   9 +
 plugins/s3_auth/aws_auth_v4.h                      | 105 +--
 .../s3_auth/{aws_auth_v4.h => aws_auth_v4_wrap.h}  |  90 +--
 .../s3_auth/unit-tests/main.cpp                    |   6 +-
 plugins/s3_auth/unit-tests/test_aws_auth_v4.cc     | 861 +++++++++++++++++++++
 plugins/s3_auth/unit-tests/test_aws_auth_v4.h      | 145 ++++
 8 files changed, 1034 insertions(+), 191 deletions(-)
 copy plugins/s3_auth/{aws_auth_v4.h => aws_auth_v4_wrap.h} (53%)
 copy iocore/eventsystem/test_P_Buffer.cc => plugins/s3_auth/unit-tests/main.cpp (88%)
 create mode 100644 plugins/s3_auth/unit-tests/test_aws_auth_v4.cc
 create mode 100644 plugins/s3_auth/unit-tests/test_aws_auth_v4.h

-- 
To stop receiving notification emails like this one, please contact
['"commits@trafficserver.apache.org" <co...@trafficserver.apache.org>'].

[trafficserver] 02/02: FD leaks when ep.start() failed or cancelled in acceptEvent or con.connect() failed

Posted by zw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch 7.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 319e219f6b98f331e6cd0e6ee302da770a150213
Author: Oknet Xu <xu...@skyguard.com.cn>
AuthorDate: Wed Aug 30 18:32:24 2017 +0800

    FD leaks when ep.start() failed or cancelled in acceptEvent or con.connect() failed
    
    (cherry picked from commit 9ded874c82de732d2c2d42c2b633d7e94adcb2e7)
    
     Conflicts:
    	iocore/net/UnixNetVConnection.cc
---
 iocore/net/SSLNetVConnection.cc  | 1 -
 iocore/net/UnixNetVConnection.cc | 8 +++++---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 53d7012..9b389a2 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -892,7 +892,6 @@ SSLNetVConnection::free(EThread *t)
 
   closed = 0;
   options.reset();
-  con.close();
 
   ink_assert(con.fd == NO_FD);
 
diff --git a/iocore/net/UnixNetVConnection.cc b/iocore/net/UnixNetVConnection.cc
index c914f6c..122cd42 100644
--- a/iocore/net/UnixNetVConnection.cc
+++ b/iocore/net/UnixNetVConnection.cc
@@ -94,7 +94,6 @@ close_UnixNetVConnection(UnixNetVConnection *vc, EThread *t)
   NetHandler *nh = vc->nh;
   vc->cancel_OOB();
   vc->ep.stop();
-  vc->con.close();
 
   ink_release_assert(vc->thread == t);
 
@@ -1152,14 +1151,14 @@ UnixNetVConnection::acceptEvent(int event, Event *e)
 
   SET_HANDLER((NetVConnHandler)&UnixNetVConnection::mainEvent);
 
-  nh                 = get_NetHandler(thread);
   PollDescriptor *pd = get_PollDescriptor(thread);
   if (ep.start(pd, this, EVENTIO_READ | EVENTIO_WRITE) < 0) {
     Debug("iocore_net", "acceptEvent : failed EventIO::start");
-    close_UnixNetVConnection(this, e->ethread);
+    free(t);
     return EVENT_DONE;
   }
 
+  nh = get_NetHandler(thread);
   set_inactivity_timeout(0);
   nh->open_list.enqueue(this);
 
@@ -1394,6 +1393,9 @@ void
 UnixNetVConnection::free(EThread *t)
 {
   ink_release_assert(t == this_ethread());
+
+  // close socket fd
+  con.close();
   // clear variables for reuse
   this->mutex.clear();
   action_.mutex.clear();

-- 
To stop receiving notification emails like this one, please contact
"commits@trafficserver.apache.org" <co...@trafficserver.apache.org>.

[trafficserver] 01/02: Unit tests for AWS Signature Version 4

Posted by zw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch 7.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit e798dfea32938f8c2bee716b1a34890a85ad15e2
Author: Gancho Tenev <gt...@gmail.com>
AuthorDate: Tue Apr 25 13:02:44 2017 -0700

    Unit tests for AWS Signature Version 4
    
    (cherry picked from commit 3bcc7ce051c391b1833fbb4f9343b45a96ae19ff)
---
 plugins/s3_auth/Makefile.inc                       |   9 +
 plugins/s3_auth/aws_auth_v4.h                      | 105 +--
 .../s3_auth/{aws_auth_v4.h => aws_auth_v4_wrap.h}  |  90 +--
 plugins/s3_auth/unit-tests/main.cpp                |  25 +
 plugins/s3_auth/unit-tests/test_aws_auth_v4.cc     | 861 +++++++++++++++++++++
 plugins/s3_auth/unit-tests/test_aws_auth_v4.h      | 145 ++++
 6 files changed, 1051 insertions(+), 184 deletions(-)

diff --git a/plugins/s3_auth/Makefile.inc b/plugins/s3_auth/Makefile.inc
index 7865d5e..83af007 100644
--- a/plugins/s3_auth/Makefile.inc
+++ b/plugins/s3_auth/Makefile.inc
@@ -16,3 +16,12 @@
 
 pkglib_LTLIBRARIES += s3_auth/s3_auth.la
 s3_auth_s3_auth_la_SOURCES = s3_auth/s3_auth.cc s3_auth/aws_auth_v4.cc
+
+check_PROGRAMS +=  test_s3auth
+
+test_s3auth_CPPFLAGS = $(AM_CPPFLAGS) -I$(abs_top_srcdir)/tests/include -DAWS_AUTH_V4_UNIT_TEST
+test_s3auth_LDADD = $(OPENSSL_LIBS)
+test_s3auth_SOURCES = \
+    s3_auth/unit-tests/main.cpp \
+    s3_auth/unit-tests/test_aws_auth_v4.cc \
+    s3_auth/aws_auth_v4.cc
diff --git a/plugins/s3_auth/aws_auth_v4.h b/plugins/s3_auth/aws_auth_v4.h
index 1959ddf..94d2a52 100644
--- a/plugins/s3_auth/aws_auth_v4.h
+++ b/plugins/s3_auth/aws_auth_v4.h
@@ -52,106 +52,11 @@ public:
   virtual HeaderIterator headerEnd()         = 0;
 };
 
-/* Define a header iterator to be used in the plugin using ATS API */
-class HeaderIterator
-{
-public:
-  HeaderIterator() : _bufp(nullptr), _hdrs(TS_NULL_MLOC), _field(TS_NULL_MLOC) {}
-  HeaderIterator(TSMBuffer bufp, TSMLoc hdrs, TSMLoc field) : _bufp(bufp), _hdrs(hdrs), _field(field) {}
-  HeaderIterator(const HeaderIterator &it)
-  {
-    _bufp  = it._bufp;
-    _hdrs  = it._hdrs;
-    _field = it._field;
-  }
-  ~HeaderIterator() {}
-  HeaderIterator &
-  operator=(HeaderIterator &it)
-  {
-    _bufp  = it._bufp;
-    _hdrs  = it._hdrs;
-    _field = it._field;
-    return *this;
-  }
-  HeaderIterator &operator++()
-  {
-    /* @todo this is said to be slow in the API call comments, do something better here */
-    TSMLoc next = TSMimeHdrFieldNext(_bufp, _hdrs, _field);
-    TSHandleMLocRelease(_bufp, _hdrs, _field);
-    _field = next;
-    return *this;
-  }
-  HeaderIterator operator++(int)
-  {
-    HeaderIterator tmp(*this);
-    operator++();
-    return tmp;
-  }
-  bool
-  operator!=(const HeaderIterator &it)
-  {
-    return _bufp != it._bufp || _hdrs != it._hdrs || _field != it._field;
-  }
-  bool
-  operator==(const HeaderIterator &it)
-  {
-    return _bufp == it._bufp && _hdrs == it._hdrs && _field == it._field;
-  }
-  const char *
-  getName(int *len)
-  {
-    return TSMimeHdrFieldNameGet(_bufp, _hdrs, _field, len);
-  }
-  const char *
-  getValue(int *len)
-  {
-    return TSMimeHdrFieldValueStringGet(_bufp, _hdrs, _field, -1, len);
-  }
-  TSMBuffer _bufp;
-  TSMLoc _hdrs;
-  TSMLoc _field;
-};
-
-/* Define a API to be used in the plugin using ATS API */
-class TsApi : public TsInterface
-{
-public:
-  TsApi(TSMBuffer bufp, TSMLoc hdrs, TSMLoc url) : _bufp(bufp), _hdrs(hdrs), _url(url) {}
-  ~TsApi() {}
-  const char *
-  getMethod(int *len)
-  {
-    return TSHttpHdrMethodGet(_bufp, _hdrs, len);
-  }
-  const char *
-  getHost(int *len)
-  {
-    return TSHttpHdrHostGet(_bufp, _hdrs, len);
-  }
-  const char *
-  getPath(int *len)
-  {
-    return TSUrlPathGet(_bufp, _url, len);
-  }
-  const char *
-  getQuery(int *len)
-  {
-    return TSUrlHttpQueryGet(_bufp, _url, len);
-  }
-  HeaderIterator
-  headerBegin()
-  {
-    return HeaderIterator(_bufp, _hdrs, TSMimeHdrFieldGet(_bufp, _hdrs, 0));
-  }
-  HeaderIterator
-  headerEnd()
-  {
-    return HeaderIterator(_bufp, _hdrs, TS_NULL_MLOC);
-  }
-  TSMBuffer _bufp;
-  TSMLoc _hdrs;
-  TSMLoc _url;
-};
+#ifdef AWS_AUTH_V4_UNIT_TEST
+#include "unit-tests/test_aws_auth_v4.h"
+#else
+#include "aws_auth_v4_wrap.h"
+#endif
 
 /* S3 auth v4 utility API */
 
diff --git a/plugins/s3_auth/aws_auth_v4.h b/plugins/s3_auth/aws_auth_v4_wrap.h
similarity index 53%
copy from plugins/s3_auth/aws_auth_v4.h
copy to plugins/s3_auth/aws_auth_v4_wrap.h
index 1959ddf..b14f9c6 100644
--- a/plugins/s3_auth/aws_auth_v4.h
+++ b/plugins/s3_auth/aws_auth_v4_wrap.h
@@ -17,40 +17,13 @@
 */
 
 /**
- * @file aws_auth_v4.h
- * @brief AWS Auth v4 signing utility.
- * @see aws_auth_v4.cc
+ * @file aws_auth_v4_ts.h
+ * @brief TS API adaptor and header iterator using the TS API which are swapped with mocks during testing.
+ * @see aws_auth_v4.h
  */
 
-#ifndef PLUGINS_S3_AUTH_AWS_AUTH_V4_CC_
-#define PLUGINS_S3_AUTH_AWS_AUTH_V4_CC_
-
-#include <algorithm> /* transform() */
-#include <cstddef>   /* soze_t */
-#include <string>    /* std::string */
-#include <sstream>   /* std::stringstream */
-#include <map>       /* std::map */
-#include <set>       /* std::set */
-
-#include <ts/ts.h>
-
-typedef std::string String;
-typedef std::set<std::string> StringSet;
-typedef std::map<std::string, std::string> StringMap;
-
-class HeaderIterator;
-
-class TsInterface
-{
-public:
-  virtual ~TsInterface(){};
-  virtual const char *getMethod(int *length) = 0;
-  virtual const char *getHost(int *length)   = 0;
-  virtual const char *getPath(int *length)   = 0;
-  virtual const char *getQuery(int *length)  = 0;
-  virtual HeaderIterator headerBegin()       = 0;
-  virtual HeaderIterator headerEnd()         = 0;
-};
+#ifndef PLUGINS_S3_AUTH_AWS_AUTH_V4_WRAP_H_
+#define PLUGINS_S3_AUTH_AWS_AUTH_V4_WRAP_H_
 
 /* Define a header iterator to be used in the plugin using ATS API */
 class HeaderIterator
@@ -153,55 +126,4 @@ public:
   TSMLoc _url;
 };
 
-/* S3 auth v4 utility API */
-
-static const String X_AMZ_CONTENT_SHA256 = "x-amz-content-sha256";
-static const String X_AMX_DATE           = "x-amz-date";
-static const String X_AMZ                = "x-amz-";
-static const String CONTENT_TYPE         = "content-type";
-static const String HOST                 = "host";
-
-String trimWhiteSpaces(const String &s);
-
-template <typename ContainerType>
-void
-commaSeparateString(ContainerType &ss, const String &input, bool trim = true, bool lowerCase = true)
-{
-  std::istringstream istr(input);
-  String token;
-
-  while (std::getline(istr, token, ',')) {
-    token = trim ? trimWhiteSpaces(token) : token;
-    if (lowerCase) {
-      std::transform(token.begin(), token.end(), token.begin(), ::tolower);
-    }
-    ss.insert(ss.end(), token);
-  }
-}
-
-class AwsAuthV4
-{
-public:
-  AwsAuthV4(TsInterface &api, time_t *now, bool signPayload, const char *awsAccessKeyId, size_t awsAccessKeyIdLen,
-            const char *awsSecretAccessKey, size_t awsSecretAccessKeyLen, const char *awsService, size_t awsServiceLen,
-            const StringSet &includedHeaders, const StringSet &excludedHeaders, const StringMap &regionMap);
-  const char *getDateTime(size_t *dateTimeLen);
-  String getPayloadHash();
-  String getAuthorizationHeader();
-
-private:
-  TsInterface &_api;
-  char _dateTime[sizeof "20170428T010203Z"];
-  bool _signPayload               = false;
-  const char *_awsAccessKeyId     = nullptr;
-  size_t _awsAccessKeyIdLen       = 0;
-  const char *_awsSecretAccessKey = nullptr;
-  size_t _awsSecretAccessKeyLen   = 0;
-  const char *_awsService         = nullptr;
-  size_t _awsServiceLen           = 0;
-
-  const StringSet &_includedHeaders;
-  const StringSet &_excludedHeaders;
-  const StringMap &_regionMap;
-};
-#endif /* PLUGINS_S3_AUTH_AWS_AUTH_V4_CC_ */
+#endif /* PLUGINS_S3_AUTH_AWS_AUTH_V4_WRAP_H_ */
diff --git a/plugins/s3_auth/unit-tests/main.cpp b/plugins/s3_auth/unit-tests/main.cpp
new file mode 100644
index 0000000..6aed3a6
--- /dev/null
+++ b/plugins/s3_auth/unit-tests/main.cpp
@@ -0,0 +1,25 @@
+/** @file
+
+  This file used for catch based tests. It is the main() stub.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#define CATCH_CONFIG_MAIN
+#include "catch.hpp"
diff --git a/plugins/s3_auth/unit-tests/test_aws_auth_v4.cc b/plugins/s3_auth/unit-tests/test_aws_auth_v4.cc
new file mode 100644
index 0000000..4589aca
--- /dev/null
+++ b/plugins/s3_auth/unit-tests/test_aws_auth_v4.cc
@@ -0,0 +1,861 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+/**
+ * @file aws_auth_v4_test.cc
+ * @brief Unit tests for functions implementing S3 auth version 4
+ */
+
+#include <string.h>
+#include <openssl/hmac.h>   /* EVP_MAX_MD_SIZE */
+#include <catch.hpp>        /* catch unit-test framework */
+#include "../aws_auth_v4.h" /* S3 auth v4 utility */
+
+/* uriEncode() ***************************************************************************************************************** */
+
+TEST_CASE("uriEncode(): encode empty input", "[AWS][auth][utility]")
+{
+  String in("");
+  String encoded = uriEncode(in, /* isObjectName */ false);
+  CHECK(0 == encoded.length()); /* 0 encoded because of the invalid input */
+}
+
+TEST_CASE("uriEncode(): encode unreserved chars", "[s3_auth]")
+{
+  const String in = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                    "abcdefghijklmnopqrstuvwxyz"
+                    "0123456789"
+                    "-._~";
+  String encoded = uriEncode(in, /* isObjectName */ false);
+
+  CHECK(in.length() == encoded.length());
+  CHECK_FALSE(encoded.compare("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                              "abcdefghijklmnopqrstuvwxyz"
+                              "0123456789"
+                              "-._~"));
+}
+
+TEST_CASE("uriEncode(): encode reserved chars in a name which is not object name", "[AWS][auth][utility]")
+{
+  const String in = " /!\"#$%&'()*+,:;<=>?@[\\]^`{|}"; /* some printable but reserved chars */
+  String encoded  = uriEncode(in, /* isObjectName */ false);
+
+  CHECK(3 * in.length() == encoded.length()); /* size of "%NN" = 3 */
+  CHECK_FALSE(encoded.compare("%20%2F%21%22%23%24%25%26%27%28%29%2A%2B%2C%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%60%7B%7C%7D"));
+}
+
+TEST_CASE("uriEncode(): encode reserved chars in an object name", "[AWS][auth][utility]")
+{
+  const String in = " /!\"#$%&'()*+,:;<=>?@[\\]^`{|}"; /* some printable but reserved chars */
+  String encoded  = uriEncode(in, /* isObjectName */ true);
+
+  CHECK(3 * in.length() - 2 == encoded.length()); /* size of "%NN" = 3, '/' is not encoded */
+  CHECK_FALSE(encoded.compare("%20/%21%22%23%24%25%26%27%28%29%2A%2B%2C%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%60%7B%7C%7D"));
+}
+
+/* base16Encode() ************************************************************************************************************** */
+
+TEST_CASE("base16Encode(): base16 encode empty string", "[utility]")
+{
+  const char *in = nullptr;
+  size_t inLen   = 0;
+  String encoded = base16Encode(in, inLen);
+
+  CHECK(0 == encoded.length());
+}
+
+TEST_CASE("base16Encode(): base16 encode RFC4648 test vectors", "[utility]")
+{
+  /* use the test vectors from RFC4648: https://tools.ietf.org/html/rfc4648#section-10 (just convert to lower case) */
+  const char *bench[] = {"",       "",     "f",        "66",    "fo",         "666f",   "foo",
+                         "666f6f", "foob", "666f6f62", "fooba", "666f6f6261", "foobar", "666f6f626172"};
+
+  for (size_t i = 0; i < sizeof(bench) / sizeof(char *); i += 2) {
+    const char *in = bench[i];
+    size_t inLen   = strlen(in);
+    String encoded = base16Encode(in, inLen);
+
+    CHECK(inLen * 2 == encoded.length());
+    CHECK_FALSE(encoded.compare(bench[i + 1]));
+  }
+}
+
+/* trimWhiteSpaces() ******************************************************************************************************** */
+
+TEST_CASE("trimWhiteSpaces(): trim invalid arguments, check pointers", "[utility]")
+{
+  const char *in = nullptr;
+  size_t inLen   = 0;
+  size_t outLen  = 0;
+
+  const char *start = trimWhiteSpaces(in, inLen, outLen);
+
+  CHECK(in == start);
+}
+
+TEST_CASE("trimWhiteSpaces(): trim empty input, check pointers", "[utility]")
+{
+  const char *in = "";
+  size_t inLen   = 0;
+  size_t outLen  = 0;
+
+  const char *start = trimWhiteSpaces(in, inLen, outLen);
+
+  CHECK(in == start);
+}
+
+TEST_CASE("trimWhiteSpaces(): trim nothing to trim, check pointers", "[utility]")
+{
+  const char in[] = "Important Message";
+  size_t inLen    = strlen(in);
+  size_t newLen   = 0;
+
+  const char *start = trimWhiteSpaces(in, inLen, newLen);
+
+  CHECK(in == start);
+  CHECK(inLen == newLen);
+}
+
+TEST_CASE("trimWhiteSpaces(): trim beginning, check pointers", "[utility]")
+{
+  const char in[] = " \t\nImportant Message";
+  size_t inLen    = strlen(in);
+  size_t newLen   = 0;
+
+  const char *start = trimWhiteSpaces(in, inLen, newLen);
+
+  CHECK(in + 3 == start);
+  CHECK(inLen - 3 == newLen);
+}
+
+TEST_CASE("trimWhiteSpaces(): trim end, check pointers", "[utility]")
+{
+  const char in[] = "Important Message \t\n";
+  size_t inLen    = strlen(in);
+  size_t newLen   = 0;
+
+  const char *start = trimWhiteSpaces(in, inLen, newLen);
+
+  CHECK(in == start);
+  CHECK(inLen - 3 == newLen);
+}
+
+TEST_CASE("trimWhiteSpaces(): trim both ends, check pointers", "[utility]")
+{
+  const char in[] = "\v\t\n Important Message \t\n";
+  size_t inLen    = strlen(in);
+  size_t newLen   = 0;
+
+  const char *start = trimWhiteSpaces(in, inLen, newLen);
+
+  CHECK(in + 4 == start);
+  CHECK(inLen - 7 == newLen);
+}
+
+TEST_CASE("trimWhiteSpaces(): trim both, check string", "[utility]")
+{
+  String in      = "\v\t\n Important Message \t\n";
+  String trimmed = trimWhiteSpaces(in);
+
+  CHECK_FALSE(trimmed.compare("Important Message"));
+  CHECK(in.length() - 7 == trimmed.length());
+}
+
+TEST_CASE("trimWhiteSpaces(): trim right, check string", "[utility]")
+{
+  String in      = "Important Message \t\n";
+  String trimmed = trimWhiteSpaces(in);
+
+  CHECK_FALSE(trimmed.compare("Important Message"));
+  CHECK(in.length() - 3 == trimmed.length());
+}
+
+TEST_CASE("trimWhiteSpaces(): trim left, check string", "[utility]")
+{
+  String in      = "\v\t\n Important Message";
+  String trimmed = trimWhiteSpaces(in);
+
+  CHECK_FALSE(trimmed.compare("Important Message"));
+  CHECK(in.length() - 4 == trimmed.length());
+}
+
+TEST_CASE("trimWhiteSpaces(): trim empty, check string", "[utility]")
+{
+  String in      = "\v\t\n  \t\n";
+  String trimmed = trimWhiteSpaces(in);
+
+  CHECK(trimmed.empty());
+  CHECK(0 == trimmed.length());
+}
+
+/* AWS Regions ***************************************************************************************************** */
+
+TEST_CASE("AWSRegions: get region empty input", "[AWS][auth][utility]")
+{
+  const char *host = "";
+  String s         = getRegion(defaultDefaultRegionMap, host, strlen(host));
+  CHECK_FALSE(s.compare("us-east-1"));
+}
+
+TEST_CASE("AWSRegions: get region by providing no bucket name", "[AWS][auth][utility]")
+{
+  const char *host = "s3.eu-west-2.amazonaws.com";
+  String s         = getRegion(defaultDefaultRegionMap, host, strlen(host));
+  CHECK_FALSE(s.compare("eu-west-2"));
+}
+
+TEST_CASE("AWSRegions: get region by providing bucket name having single label", "[AWS][auth][utility]")
+{
+  const char *host = "label1.label2.s3.eu-west-2.amazonaws.com";
+  String s         = getRegion(defaultDefaultRegionMap, host, strlen(host));
+  CHECK_FALSE(s.compare("eu-west-2"));
+}
+
+TEST_CASE("AWSRegions: get region by providing bucket name having multiple labels", "[AWS][auth][utility]")
+{
+  const char *host = "label1.label2.s3.eu-west-2.amazonaws.com";
+  String s         = getRegion(defaultDefaultRegionMap, host, strlen(host));
+  CHECK_FALSE(s.compare("eu-west-2"));
+}
+
+TEST_CASE("AWSRegions: get region by providing bucket name having single label not matching any entry point",
+          "[AWS][auth][utility]")
+{
+  const char *host = "THIS_NEVER_MATCHES.eu-west-2.amazonaws.com";
+  String s         = getRegion(defaultDefaultRegionMap, host, strlen(host));
+  CHECK_FALSE(s.compare("us-east-1"));
+}
+
+TEST_CASE("AWSRegions: get region by providing bucket name having multiple labels not matching any entry point",
+          "[AWS][auth][utility]")
+{
+  const char *host = "label1.label2.THIS_NEVER_MATCHES.eu-west-2.amazonaws.com";
+  String s         = getRegion(defaultDefaultRegionMap, host, strlen(host));
+  CHECK_FALSE(s.compare("us-east-1"));
+}
+
+/* AWS spec tests/example ****************************************************************************************** */
+
+/* Test from docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
+ * User id, secret and time */
+const char *awsSecretAccessKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
+const char *awsAccessKeyId     = "AKIAIOSFODNN7EXAMPLE";
+const char *awsService         = "s3";
+
+void
+ValidateBench(TsInterface &api, bool signPayload, time_t *now, const char *bench[], const StringSet &includedHeaders,
+              const StringSet &excludedHeaders)
+{
+  /* Test the main entry point for calculation of the Authorization header content */
+  AwsAuthV4 util(api, now, signPayload, awsAccessKeyId, strlen(awsAccessKeyId), awsSecretAccessKey, strlen(awsSecretAccessKey),
+                 awsService, strlen(awsService), includedHeaders, excludedHeaders, defaultDefaultRegionMap);
+  String authorizationHeader = util.getAuthorizationHeader();
+  CHECK_FALSE(authorizationHeader.compare(bench[0]));
+
+  /* Test payload hash */
+  String payloadHash = util.getPayloadHash();
+  CHECK_FALSE(payloadHash.compare(bench[5]));
+
+  /* Test the date time header content */
+  size_t dateLen   = 0;
+  const char *date = util.getDateTime(&dateLen);
+  CHECK_FALSE(String(date, dateLen).compare(bench[2]));
+
+  /* Now test particular test points to pinpoint problems easier in case of regression */
+
+  /* test the canonization of the request */
+  String signedHeaders;
+  String canonicalReq = getCanonicalRequestSha256Hash(api, signPayload, includedHeaders, excludedHeaders, signedHeaders);
+  CHECK_FALSE(canonicalReq.compare(bench[1]));
+  CHECK_FALSE(signedHeaders.compare(bench[6]));
+
+  /* Test the formating of the date and time */
+  char dateTime[sizeof("20170428T010203Z")];
+  size_t dateTimeLen = getIso8601Time(now, dateTime, sizeof(dateTime));
+  CHECK_FALSE(String(dateTime, dateTimeLen).compare(bench[2]));
+
+  /* Test the region name */
+  int hostLen      = 0;
+  const char *host = api.getHost(&hostLen);
+  String awsRegion = getRegion(defaultDefaultRegionMap, host, hostLen);
+
+  /* Test string to sign calculation */
+  String stringToSign = getStringToSign(host, hostLen, dateTime, dateTimeLen, awsRegion.c_str(), awsRegion.length(), awsService,
+                                        strlen(awsService), canonicalReq.c_str(), canonicalReq.length());
+  CHECK_FALSE(stringToSign.compare(bench[3]));
+
+  /* Test the signature calculation */
+  char signature[EVP_MAX_MD_SIZE];
+  size_t signatureLen =
+    getSignature(awsSecretAccessKey, strlen(awsSecretAccessKey), awsRegion.c_str(), awsRegion.length(), awsService,
+                 strlen(awsService), dateTime, 8, stringToSign.c_str(), stringToSign.length(), signature, EVP_MAX_MD_SIZE);
+  String base16Signature = base16Encode(signature, signatureLen);
+  CHECK_FALSE(base16Signature.compare(bench[4]));
+}
+
+/**
+ * Test from docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
+ * Example: GET Object
+ */
+TEST_CASE("AWSAuthSpecByExample: GET Object", "[AWS][auth][SpecByExample]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("test.txt");
+  api._query.assign("");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["Range"]                = "bytes=0-9";
+  api._headers["x-amz-content-sha256"] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+
+  const char *bench[] = {
+    /* Authorization Header */
+    "AWS4-HMAC-SHA256 "
+    "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,"
+    "SignedHeaders=host;range;x-amz-content-sha256;x-amz-date,"
+    "Signature=f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41",
+    /* Canonical Request sha256 */
+    "7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972",
+    /* Date and time*/
+    "20130524T000000Z",
+    /* String to sign */
+    "AWS4-HMAC-SHA256\n"
+    "20130524T000000Z\n"
+    "20130524/us-east-1/s3/aws4_request\n"
+    "7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972",
+    /* Signature */
+    "f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41",
+    /* Payload hash */
+    "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+    /* Signed Headers */
+    "host;range;x-amz-content-sha256;x-amz-date",
+  };
+
+  ValidateBench(api, /*signePayload */ true, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders);
+}
+
+/**
+ * Test from docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
+ * Example: GET Bucket Lifecycle
+ */
+TEST_CASE("AWSAuthSpecByExample: GET Bucket Lifecycle", "[AWS][auth][SpecByExample]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("lifecycle");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["x-amz-content-sha256"] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+
+  const char *bench[] = {
+    /* Authorization Header */
+    "AWS4-HMAC-SHA256 "
+    "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,"
+    "SignedHeaders=host;x-amz-content-sha256;x-amz-date,"
+    "Signature=fea454ca298b7da1c68078a5d1bdbfbbe0d65c699e0f91ac7a200a0136783543",
+    /* Canonical Request sha256 */
+    "9766c798316ff2757b517bc739a67f6213b4ab36dd5da2f94eaebf79c77395ca",
+    /* Date and time*/
+    "20130524T000000Z",
+    /* String to sign */
+    "AWS4-HMAC-SHA256\n"
+    "20130524T000000Z\n"
+    "20130524/us-east-1/s3/aws4_request\n"
+    "9766c798316ff2757b517bc739a67f6213b4ab36dd5da2f94eaebf79c77395ca",
+    /* Signature */
+    "fea454ca298b7da1c68078a5d1bdbfbbe0d65c699e0f91ac7a200a0136783543",
+    /* Payload hash */
+    "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+    /* Signed Headers */
+    "host;x-amz-content-sha256;x-amz-date",
+  };
+
+  ValidateBench(api, /*signePayload */ true, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders);
+}
+
+/**
+ * Test from docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
+ * Example: Get Bucket (List Objects)
+ */
+TEST_CASE("AWSAuthSpecByExample: Get Bucket List Objects", "[AWS][auth][SpecByExample]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["x-amz-content-sha256"] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+
+  const char *bench[] = {
+    /* Authorization Header */
+    "AWS4-HMAC-SHA256 "
+    "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,"
+    "SignedHeaders=host;x-amz-content-sha256;x-amz-date,"
+    "Signature=34b48302e7b5fa45bde8084f4b7868a86f0a534bc59db6670ed5711ef69dc6f7",
+    /* Canonical Request sha256 */
+    "df57d21db20da04d7fa30298dd4488ba3a2b47ca3a489c74750e0f1e7df1b9b7",
+    /* Date and time*/
+    "20130524T000000Z",
+    /* String to sign */
+    "AWS4-HMAC-SHA256\n"
+    "20130524T000000Z\n"
+    "20130524/us-east-1/s3/aws4_request\n"
+    "df57d21db20da04d7fa30298dd4488ba3a2b47ca3a489c74750e0f1e7df1b9b7",
+    /* Signature */
+    "34b48302e7b5fa45bde8084f4b7868a86f0a534bc59db6670ed5711ef69dc6f7",
+    /* Payload hash */
+    "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+    /* Signed Headers */
+    "host;x-amz-content-sha256;x-amz-date",
+  };
+
+  ValidateBench(api, /*signePayload */ true, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders);
+}
+
+/**
+ * Test based on docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
+ * but this time don't sign the payload to test "UNSIGNED-PAYLOAD" feature.
+ * Example: Get Bucket (List Objects)
+ */
+TEST_CASE("AWSAuthSpecByExample: GET Bucket List Objects, unsigned pay-load", "[AWS][auth][SpecByExample]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+
+  const char *bench[] = {
+    /* Authorization Header */
+    "AWS4-HMAC-SHA256 "
+    "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,"
+    "SignedHeaders=host;x-amz-content-sha256;x-amz-date,"
+    "Signature=b1a076428fa68c2c42202ee5a5718b8207f725e451e2157d6b1c393e01fc2e68",
+    /* Canonical Request sha256 */
+    "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc",
+    /* Date and time*/
+    "20130524T000000Z",
+    /* String to sign */
+    "AWS4-HMAC-SHA256\n"
+    "20130524T000000Z\n"
+    "20130524/us-east-1/s3/aws4_request\n"
+    "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc",
+    /* Signature */
+    "b1a076428fa68c2c42202ee5a5718b8207f725e451e2157d6b1c393e01fc2e68",
+    /* Payload hash */
+    "UNSIGNED-PAYLOAD",
+    /* Signed Headers */
+    "host;x-amz-content-sha256;x-amz-date",
+  };
+
+  ValidateBench(api, /*signePayload */ false, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders);
+}
+
+/**
+ * Test based on docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
+ * but this time don't sign the payload to test "UNSIGNED-PAYLOAD" feature and
+ * have extra headers to be excluded from the signature (internal and changing headers)
+ * Example: Get Bucket (List Objects)
+ */
+TEST_CASE("AWSAuthSpecByExample: GET Bucket List Objects, unsigned pay-load, exclude internal and changing headers", "[AWS][auth]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+  api._headers["@internal"]            = "internal value";
+  api._headers["x-forwarded-for"]      = "192.168.7.1";
+  api._headers["via"] = "http/1.1 tcp ipv4 ats_dev[7e67ac60-c204-450d-90be-a426dd3b569f] (ApacheTrafficServer/7.2.0)";
+
+  const char *bench[] = {
+    /* Authorization Header */
+    "AWS4-HMAC-SHA256 "
+    "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,"
+    "SignedHeaders=host;x-amz-content-sha256;x-amz-date,"
+    "Signature=b1a076428fa68c2c42202ee5a5718b8207f725e451e2157d6b1c393e01fc2e68",
+    /* Canonical Request sha256 */
+    "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc",
+    /* Date and time*/
+    "20130524T000000Z",
+    /* String to sign */
+    "AWS4-HMAC-SHA256\n"
+    "20130524T000000Z\n"
+    "20130524/us-east-1/s3/aws4_request\n"
+    "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc",
+    /* Signature */
+    "b1a076428fa68c2c42202ee5a5718b8207f725e451e2157d6b1c393e01fc2e68",
+    /* Payload hash */
+    "UNSIGNED-PAYLOAD",
+    /* Signed Headers */
+    "host;x-amz-content-sha256;x-amz-date",
+  };
+
+  ValidateBench(api, /*signePayload */ false, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders);
+}
+
+/* Utility parameters related tests ******************************************************************************** */
+
+void
+ValidateBenchCanonicalRequest(TsInterface &api, bool signPayload, time_t *now, const char *bench[],
+                              const StringSet &includedHeaders, const StringSet &excludedHeaders)
+{
+  /* Test the main entry point for calculation of the Authorization header content */
+  AwsAuthV4 util(api, now, signPayload, awsAccessKeyId, strlen(awsAccessKeyId), awsSecretAccessKey, strlen(awsSecretAccessKey),
+                 awsService, strlen(awsService), includedHeaders, excludedHeaders, defaultDefaultRegionMap);
+
+  /* test the canonization of the request */
+  String signedHeaders;
+  String canonicalReq = getCanonicalRequestSha256Hash(api, signPayload, includedHeaders, excludedHeaders, signedHeaders);
+  CHECK_FALSE(signedHeaders.compare(bench[0]));
+  CHECK_FALSE(canonicalReq.compare(bench[1]));
+}
+
+TEST_CASE("S3AuthV4UtilParams: include all headers by default", "[AWS][auth][utility]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["Content-Type"]         = "gzip";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+  api._headers["HeaderA"]              = "HeaderAValue";
+  api._headers["HeaderB"]              = "HeaderBValue";
+  api._headers["HeaderC"]              = "HeaderCValue";
+  api._headers["HeaderD"]              = "HeaderDValue";
+  api._headers["HeaderE"]              = "HeaderEValue";
+  api._headers["HeaderF"]              = "HeaderFValue";
+
+  StringSet include = defaultIncludeHeaders;
+  StringSet exclude = defaultExcludeHeaders;
+
+  const char *bench[] = {
+    /* Signed Headers */
+    "content-type;headera;headerb;headerc;headerd;headere;headerf;host;x-amz-content-sha256;x-amz-date",
+    /* Canonical Request sha256 */
+    "819a275bbd601fd6f6ba39190ee8299d34fcb0f5e0a4c0d8017c35e79a026579",
+  };
+
+  ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude);
+}
+
+TEST_CASE("S3AuthV4UtilParams: include all headers explicit", "[AWS][auth][SpecByExample]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["Content-Type"]         = "gzip";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+  api._headers["HeaderA"]              = "HeaderAValue";
+  api._headers["HeaderB"]              = "HeaderBValue";
+  api._headers["HeaderC"]              = "HeaderCValue";
+  api._headers["HeaderD"]              = "HeaderDValue";
+  api._headers["HeaderE"]              = "HeaderEValue";
+  api._headers["HeaderF"]              = "HeaderFValue";
+
+  StringSet include;
+  commaSeparateString<StringSet>(include, "HeaderA,HeaderB,HeaderC,HeaderD,HeaderE,HeaderF");
+  StringSet exclude = defaultExcludeHeaders;
+
+  const char *bench[] = {
+    /* Signed Headers */
+    "content-type;headera;headerb;headerc;headerd;headere;headerf;host;x-amz-content-sha256;x-amz-date",
+    /* Canonical Request sha256 */
+    "819a275bbd601fd6f6ba39190ee8299d34fcb0f5e0a4c0d8017c35e79a026579",
+  };
+
+  ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude);
+}
+
+TEST_CASE("S3AuthV4UtilParams: exclude all headers explicit", "[AWS][auth][utility]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["Content-Type"]         = "gzip";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+  api._headers["HeaderA"]              = "HeaderAValue";
+  api._headers["HeaderB"]              = "HeaderBValue";
+  api._headers["HeaderC"]              = "HeaderCValue";
+  api._headers["HeaderD"]              = "HeaderDValue";
+  api._headers["HeaderE"]              = "HeaderEValue";
+  api._headers["HeaderF"]              = "HeaderFValue";
+
+  StringSet include = defaultIncludeHeaders;
+  StringSet exclude;
+  commaSeparateString<StringSet>(exclude, "HeaderA,HeaderB,HeaderC,HeaderD,HeaderE,HeaderF");
+
+  const char *bench[] = {
+    /* Signed Headers */
+    "content-type;host;x-amz-content-sha256;x-amz-date",
+    /* Canonical Request sha256 */
+    "ef3088997c69bc860e0bb36f97a8335f38863339e7fd01f2cd17b5391da575fb",
+  };
+
+  ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude);
+}
+
+TEST_CASE("S3AuthV4UtilParams: include/exclude non overlapping headers", "[AWS][auth][utility]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["Content-Type"]         = "gzip";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+  api._headers["HeaderA"]              = "HeaderAValue";
+  api._headers["HeaderB"]              = "HeaderBValue";
+  api._headers["HeaderC"]              = "HeaderCValue";
+  api._headers["HeaderD"]              = "HeaderDValue";
+  api._headers["HeaderE"]              = "HeaderEValue";
+  api._headers["HeaderF"]              = "HeaderFValue";
+
+  StringSet include, exclude;
+  commaSeparateString<StringSet>(include, "HeaderA,HeaderB,HeaderC");
+  commaSeparateString<StringSet>(exclude, "HeaderD,HeaderE,HeaderF");
+
+  const char *bench[] = {
+    /* Signed Headers */
+    "content-type;headera;headerb;headerc;host;x-amz-content-sha256;x-amz-date",
+    /* Canonical Request sha256 */
+    "c1c7fb808eefdb712192efeed168fdecef0f8d95e8df5a2569d127068c425209",
+  };
+
+  ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude);
+}
+
+TEST_CASE("S3AuthV4UtilParams: include/exclude overlapping headers", "[AWS][auth][utility]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["Content-Type"]         = "gzip";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+  api._headers["HeaderA"]              = "HeaderAValue";
+  api._headers["HeaderB"]              = "HeaderBValue";
+  api._headers["HeaderC"]              = "HeaderCValue";
+  api._headers["HeaderD"]              = "HeaderDValue";
+  api._headers["HeaderE"]              = "HeaderEValue";
+  api._headers["HeaderF"]              = "HeaderFValue";
+
+  StringSet include, exclude;
+  commaSeparateString<StringSet>(include, "HeaderA,HeaderB,HeaderC");
+  commaSeparateString<StringSet>(exclude, "HeaderC,HeaderD,HeaderE,HeaderF");
+
+  const char *bench[] = {
+    /* Signed Headers */
+    "content-type;headera;headerb;host;x-amz-content-sha256;x-amz-date",
+    /* Canonical Request sha256 */
+    "0ac0bd67e304b3c25ec51f01b86c824f7439cdb0a5bc16acdebab73f34e12a57",
+  };
+
+  ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude);
+}
+
+TEST_CASE("S3AuthV4UtilParams: include/exclude overlapping headers missing include", "[AWS][auth][utility]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["Content-Type"]         = "gzip";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+  api._headers["HeaderA"]              = "HeaderAValue";
+  api._headers["HeaderB"]              = "HeaderBValue";
+  api._headers["HeaderC"]              = "HeaderCValue";
+  api._headers["HeaderD"]              = "HeaderDValue";
+  api._headers["HeaderE"]              = "HeaderEValue";
+  api._headers["HeaderF"]              = "HeaderFValue";
+
+  StringSet include, exclude;
+  commaSeparateString<StringSet>(include, "HeaderA,HeaderC");
+  commaSeparateString<StringSet>(exclude, "HeaderC,HeaderD,HeaderE,HeaderF");
+
+  const char *bench[] = {
+    /* Signed Headers */
+    "content-type;headera;host;x-amz-content-sha256;x-amz-date",
+    /* Canonical Request sha256 */
+    "5b5bef63c923fed685230feb91d8059fe8d56c80d21ba6922ee335ff3fcc45bf",
+  };
+
+  ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude);
+}
+
+TEST_CASE("S3AuthV4UtilParams: include/exclude overlapping headers missing exclude", "[AWS][auth][utility]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["Content-Type"]         = "gzip";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+  api._headers["HeaderA"]              = "HeaderAValue";
+  api._headers["HeaderB"]              = "HeaderBValue";
+  api._headers["HeaderC"]              = "HeaderCValue";
+  api._headers["HeaderD"]              = "HeaderDValue";
+  api._headers["HeaderE"]              = "HeaderEValue";
+  api._headers["HeaderF"]              = "HeaderFValue";
+
+  StringSet include, exclude;
+  commaSeparateString<StringSet>(include, "HeaderA,HeaderB,HeaderC");
+  commaSeparateString<StringSet>(exclude, "HeaderC,HeaderD,HeaderF");
+
+  const char *bench[] = {
+    /* Signed Headers */
+    "content-type;headera;headerb;host;x-amz-content-sha256;x-amz-date",
+    /* Canonical Request sha256 */
+    "0ac0bd67e304b3c25ec51f01b86c824f7439cdb0a5bc16acdebab73f34e12a57",
+  };
+
+  ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude);
+}
+
+/*
+ * Mandatory headers Host, x-amz-* and Content-Type will must be included even if the user asked to exclude them.
+ */
+TEST_CASE("S3AuthV4UtilParams: include content type", "[AWS][auth][utility]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["Content-Type"]         = "gzip";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+
+  StringSet include = defaultIncludeHeaders;
+  StringSet exclude;
+  commaSeparateString<StringSet>(exclude, "Content-Type,x-amz-content-sha256,x-amz-date");
+
+  const char *bench[] = {
+    /* Signed Headers */
+    "content-type;host;x-amz-content-sha256;x-amz-date",
+    /* Canonical Request sha256 */
+    "ef3088997c69bc860e0bb36f97a8335f38863339e7fd01f2cd17b5391da575fb",
+  };
+
+  ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude);
+}
+
+/*
+ * Mandatory headers Host, x-amz-* and Content-Type will must be included even if the user asked to exclude them.
+ * Content-type should not be signed if missing from the HTTP request.
+ */
+TEST_CASE("S3AuthV4UtilParams: include missing content type", "[AWS][auth][utility]")
+{
+  time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */
+
+  /* Define the HTTP request elements */
+  MockTsInterface api;
+  api._method.assign("GET");
+  api._host.assign("examplebucket.s3.amazonaws.com");
+  api._path.assign("");
+  api._query.assign("max-keys=2&prefix=J");
+  api._headers["Host"]                 = "examplebucket.s3.amazonaws.com";
+  api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD";
+  api._headers["x-amz-date"]           = "20130524T000000Z";
+
+  StringSet include = defaultIncludeHeaders;
+  StringSet exclude;
+  commaSeparateString<StringSet>(exclude, "Content-Type,x-amz-content-sha256,x-amz-date");
+
+  const char *bench[] = {
+    /* Signed Headers */
+    "host;x-amz-content-sha256;x-amz-date",
+    /* Canonical Request sha256 */
+    "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc",
+  };
+
+  ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude);
+}
diff --git a/plugins/s3_auth/unit-tests/test_aws_auth_v4.h b/plugins/s3_auth/unit-tests/test_aws_auth_v4.h
new file mode 100644
index 0000000..e7889c5
--- /dev/null
+++ b/plugins/s3_auth/unit-tests/test_aws_auth_v4.h
@@ -0,0 +1,145 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+/**
+ * @file test_aws_auth_v4.h
+ * @brief TS API mock and and mock header iterator used for unit testing.
+ * @see test_aws_auth_v4.cc
+ */
+
+#ifndef PLUGINS_S3_AUTH_UNIT_TESTS_TEST_AWS_AUTH_V4_H_
+#define PLUGINS_S3_AUTH_UNIT_TESTS_TEST_AWS_AUTH_V4_H_
+
+#include <string> /* std::string */
+
+/* Define a header iterator to be used in unit tests */
+class HeaderIterator
+{
+public:
+  HeaderIterator(const StringMap::iterator &it) { _it = it; }
+  HeaderIterator(const HeaderIterator &i) { _it = i._it; }
+  ~HeaderIterator() {}
+  HeaderIterator &
+  operator=(HeaderIterator &i)
+  {
+    _it = i._it;
+    return *this;
+  }
+  HeaderIterator &operator++()
+  {
+    _it++;
+    return *this;
+  }
+  HeaderIterator operator++(int)
+  {
+    HeaderIterator tmp(*this);
+    operator++();
+    return tmp;
+  }
+  bool
+  operator!=(const HeaderIterator &it)
+  {
+    return _it != it._it;
+  }
+  const char *
+  getName(int *len)
+  {
+    *len = _it->first.length();
+    return _it->first.c_str();
+  }
+  const char *
+  getValue(int *len)
+  {
+    *len = _it->second.length();
+    return _it->second.c_str();
+  }
+  StringMap::iterator _it;
+};
+
+/* Define a mock API to be used in unit-tests */
+class MockTsInterface : public TsInterface
+{
+public:
+  const char *
+  getMethod(int *length)
+  {
+    *length = _method.length();
+    return _method.c_str();
+  }
+  const char *
+  getHost(int *length)
+  {
+    *length = _host.length();
+    return _host.c_str();
+  }
+  const char *
+  getPath(int *length)
+  {
+    *length = _path.length();
+    return _path.c_str();
+  }
+  const char *
+  getQuery(int *length)
+  {
+    *length = _query.length();
+    return _query.c_str();
+  }
+  HeaderIterator
+  headerBegin()
+  {
+    return HeaderIterator(_headers.begin());
+  }
+  HeaderIterator
+  headerEnd()
+  {
+    return HeaderIterator(_headers.end());
+  }
+
+  String _method;
+  String _host;
+  String _path;
+  String _query;
+  StringMap _headers;
+};
+
+/* Expose the following methods only to the unit tests */
+String base16Encode(const char *in, size_t inLen);
+String uriEncode(const String in, bool isObjectName = false);
+String lowercase(const char *in, size_t inLen);
+const char *trimWhiteSpaces(const char *in, size_t inLen, size_t &newLen);
+
+String getCanonicalRequestSha256Hash(TsInterface &api, bool signPayload, const StringSet &includeHeaders,
+                                     const StringSet &excludeHeaders, String &signedHeaders);
+String getStringToSign(TsInterface &api, const char *dateTime, size_t dateTimeLen, const char *canonicalRequestSha256Hash,
+                       size_t canonicalRequestSha256HashLen);
+String getStringToSign(const char *host, size_t hostLen, const char *dateTime, size_t dateTimeLen, const char *region,
+                       size_t regionLen, const char *service, size_t serviceLen, const char *canonicalRequestSha256Hash,
+                       size_t canonicalRequestSha256HashLen);
+String getRegion(const StringMap &regionMap, const char *host, size_t hostLen);
+size_t hmacSha256(const char *secret, size_t secretLen, const char *msg, size_t msgLen, char *hmac, size_t hmacLen);
+
+size_t getSignature(const char *awsSecret, size_t awsSecretLen, const char *awsRegion, size_t awsRegionLen, const char *awsService,
+                    size_t awsServiceLen, const char *dateTime, size_t dateTimeLen, const char *stringToSign,
+                    size_t stringToSignLen, char *base16Signature, size_t base16SignatureLen);
+size_t getIso8601Time(time_t *now, char *dateTime, size_t dateTimeLen);
+
+extern const StringMap defaultDefaultRegionMap;
+extern const StringSet defaultExcludeHeaders;
+extern const StringSet defaultIncludeHeaders;
+
+#endif /* PLUGINS_S3_AUTH_UNIT_TESTS_TEST_AWS_AUTH_V4_H_ */

-- 
To stop receiving notification emails like this one, please contact
"commits@trafficserver.apache.org" <co...@trafficserver.apache.org>.