util: Improvements of string helpers

Improvements include:
- test cases for every string helper
- a new `printHex` helper to output hex conversion directly to std::ostream
- new `toHex` and `printHex` helpers that accept Buffer as input parameter
- a new `fromHex` helper to convert the supplied string to shared_ptr<const Buffer>
- replaced uses of CryptoPP routines with `toHex` and `fromHex` helpers where applicable

Change-Id: I092c9fa8fd21c413b53ea5624b82f769287bb42c
Refs: #3006
diff --git a/src/name-component.cpp b/src/name-component.cpp
index 576fac4..dbe9998 100644
--- a/src/name-component.cpp
+++ b/src/name-component.cpp
@@ -102,16 +102,10 @@
                   "(expected sha256 in hex encoding)");
 
     try {
-      std::string value;
-      CryptoPP::StringSource(reinterpret_cast<const uint8_t*>(trimmedString.c_str()) +
-                               getSha256DigestUriPrefix().size(),
-                             trimmedString.size () - getSha256DigestUriPrefix().size(), true,
-                             new CryptoPP::HexDecoder(new CryptoPP::StringSink(value)));
-
-      return fromImplicitSha256Digest(reinterpret_cast<const uint8_t*>(value.c_str()),
-                                      value.size());
+      trimmedString.erase(0, getSha256DigestUriPrefix().size());
+      return fromImplicitSha256Digest(fromHex(trimmedString));
     }
-    catch (CryptoPP::Exception& e) {
+    catch (StringHelperError& e) {
       throw Error("Cannot convert to a ImplicitSha256DigestComponent (invalid hex encoding)");
     }
   }
@@ -139,8 +133,7 @@
   if (type() == tlv::ImplicitSha256DigestComponent) {
     result << getSha256DigestUriPrefix();
 
-    CryptoPP::StringSource(value(), value_size(), true,
-                           new CryptoPP::HexEncoder(new CryptoPP::FileSink(result), false));
+    printHex(result, value(), value_size(), false);
   }
   else {
     const uint8_t* value = this->value();
diff --git a/src/util/digest.cpp b/src/util/digest.cpp
index 664df2b..d5bb312 100644
--- a/src/util/digest.cpp
+++ b/src/util/digest.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "digest.hpp"
+#include "string-helper.hpp"
 #include <sstream>
 
 namespace ndn {
@@ -158,11 +159,8 @@
 std::ostream&
 operator<<(std::ostream& os, Digest<Hash>& digest)
 {
-  using namespace CryptoPP;
-
-  std::string output;
   ConstBufferPtr buffer = digest.computeDigest();
-  StringSource(buffer->buf(), buffer->size(), true, new HexEncoder(new FileSink(os)));
+  printHex(os, buffer->buf(), buffer->size());
 
   return os;
 }
diff --git a/src/util/string-helper.cpp b/src/util/string-helper.cpp
new file mode 100644
index 0000000..fc0e1a1
--- /dev/null
+++ b/src/util/string-helper.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2015 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "string-helper.hpp"
+#include "../encoding/buffer-stream.hpp"
+#include "../security/cryptopp.hpp"
+
+#include <sstream>
+#include <iomanip>
+
+#include <boost/algorithm/string/trim.hpp>
+
+namespace ndn {
+
+void
+printHex(std::ostream& os, const uint8_t* buffer, size_t length, bool isUpperCase/* = true*/)
+{
+  if (buffer == nullptr || length == 0)
+    return;
+
+  auto newFlags = std::ios::hex;
+  if (isUpperCase) {
+    newFlags |= std::ios::uppercase;
+  }
+  auto oldFlags = os.flags(newFlags);
+  auto oldFill = os.fill('0');
+  for (size_t i = 0; i < length; ++i) {
+    os << std::setw(2) << static_cast<unsigned int>(buffer[i]);
+  }
+  os.fill(oldFill);
+  os.flags(oldFlags);
+}
+
+void
+printHex(std::ostream& os, const Buffer& buffer, bool isUpperCase/* = true*/)
+{
+  return printHex(os, buffer.buf(), buffer.size(), isUpperCase);
+}
+
+std::string
+toHex(const uint8_t* buffer, size_t length, bool isUpperCase/* = true*/)
+{
+  if (buffer == nullptr || length == 0)
+    return "";
+
+  std::ostringstream result;
+  printHex(result, buffer, length, isUpperCase);
+  return result.str();
+}
+
+std::string
+toHex(const Buffer& buffer, bool isUpperCase/* = true*/)
+{
+  return toHex(buffer.buf(), buffer.size(), isUpperCase);
+}
+
+int
+fromHexChar(uint8_t c)
+{
+  if (c >= '0' && c <= '9')
+    return c - '0';
+  else if (c >= 'A' && c <= 'F')
+    return c - 'A' + 0xA;
+  else if (c >= 'a' && c <= 'f')
+    return c - 'a' + 0xA;
+  else
+    return -1;
+}
+
+shared_ptr<const Buffer>
+fromHex(const std::string& hexString)
+{
+  if (hexString.size() % 2 != 0) {
+    throw StringHelperError("Invalid number of characters in the supplied hex string");
+  }
+
+  using namespace CryptoPP;
+
+  OBufferStream os;
+  StringSource(hexString, true, new HexDecoder(new FileSink(os)));
+  shared_ptr<const Buffer> buffer = os.buf();
+
+  if (buffer->size() * 2 != hexString.size()) {
+    throw StringHelperError("The supplied hex string contains non-hex characters");
+  }
+
+  return buffer;
+}
+
+void
+trimLeft(std::string& str)
+{
+  boost::algorithm::trim_left(str);
+}
+
+void
+trimRight(std::string& str)
+{
+  boost::algorithm::trim_right(str);
+}
+
+void
+trim(std::string& str)
+{
+  boost::algorithm::trim(str);
+}
+
+std::string
+unescape(const std::string& str)
+{
+  std::ostringstream result;
+
+  for (size_t i = 0; i < str.size(); ++i) {
+    if (str[i] == '%' && i + 2 < str.size()) {
+      int hi = fromHexChar(str[i + 1]);
+      int lo = fromHexChar(str[i + 2]);
+
+      if (hi < 0 || lo < 0)
+        // Invalid hex characters, so just keep the escaped string.
+        result << str[i] << str[i + 1] << str[i + 2];
+      else
+        result << static_cast<char>((hi << 4) | lo);
+
+      // Skip ahead past the escaped value.
+      i += 2;
+    }
+    else
+      // Just copy through.
+      result << str[i];
+  }
+
+  return result.str();
+}
+
+} // namespace ndn
diff --git a/src/util/string-helper.hpp b/src/util/string-helper.hpp
index f9f37e4..e0ba8a6 100644
--- a/src/util/string-helper.hpp
+++ b/src/util/string-helper.hpp
@@ -17,131 +17,126 @@
  * <http://www.gnu.org/licenses/>.
  *
  * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
- *
- * @author Jeff Thompson <jefft0@remap.ucla.edu>
  */
 
 #ifndef NDN_STRING_HELPER_HPP
 #define NDN_STRING_HELPER_HPP
 
-#include <string>
-#include <sstream>
+#include "../common.hpp"
+#include "../encoding/buffer.hpp"
 
 namespace ndn {
 
-static const char* WHITESPACE_CHARS = " \n\r\t";
+class StringHelperError : public std::invalid_argument
+{
+public:
+  explicit
+  StringHelperError(const std::string& what)
+    : std::invalid_argument(what)
+  {
+  }
+};
+
+/**
+ * @brief Output the hex representation of the bytes in array to the output stream @p os
+ *
+ * @param os Output stream
+ * @param buffer The array of bytes
+ * @param length Size of the array
+ * @param isUpperCase if true (default) output use uppercase for hex values
+ *
+ * @example
+ *     printHex(std::cout, "Hello, World!") outputs "48656C6C6F2C20776F726C6421"
+ *     printHex(std::cout, "Hello, World!", false) outputs "48656c6c6f2c20776f726c6421"
+ *
+ * Each octet is always represented as two hex characters ("00" for octet==0).
+ *
+ * The output string is a continuous sequence of hex characters without any whitespace separators.
+ */
+void
+printHex(std::ostream& os, const uint8_t* buffer, size_t length, bool isUpperCase = true);
+
+/**
+ * @brief Output the hex representation of the bytes in the @p buffer to the output stream @p os
+ *
+ * @param os Output stream
+ * @param buffer The array of bytes
+ * @param isUpperCase if true (default) output use uppercase for hex values
+ */
+void
+printHex(std::ostream& os, const Buffer& buffer, bool isUpperCase = true);
 
 /**
  * @brief Return the hex representation of the bytes in array
  *
- * @param array The array of bytes
- * @param arraySize Size of the array
+ * @param buffer The array of bytes
+ * @param length Size of the array
+ * @param isUpperCase if true (default) output use uppercase for hex values
+ *
+ * @example
+ *     toHex("Hello, World!") == "48656C6C6F2C20776F726C6421"
+ *     toHex("Hello, World!", false) == "48656c6c6f2c20776f726c6421"
+ *
+ * Each octet is always represented as two hex characters ("00" for octet==0).
+ *
+ * The output string is a continuous sequence of hex characters without any whitespace separators.
  */
-inline std::string
-toHex(const uint8_t* array, size_t arraySize)
-{
-  if (array == 0 || arraySize == 0)
-    return "";
+std::string
+toHex(const uint8_t* buffer, size_t length, bool isUpperCase = true);
 
-  std::ostringstream result;
-  result.flags(std::ios::hex | std::ios::uppercase);
-  for (size_t i = 0; i < arraySize; ++i) {
-    uint8_t x = array[i];
-    if (x < 16)
-      result << '0';
-    result << (unsigned int)x;
-  }
+/**
+ * @brief Return the hex representation of the bytes in the @p buffer to the output stream @p os
+ *
+ * @param buffer The array of bytes
+ * @param isUpperCase if true (default) output use uppercase for hex values
+ */
+std::string
+toHex(const Buffer& buffer, bool isUpperCase = true);
 
-  return result.str();
-}
+/**
+ * @brief Convert the hex string to buffer
+ * @param hexString sequence of pairs of hex numbers (lower and upper case can be mixed)
+ *        without any whitespace separators (e.g., "48656C6C6F2C20776F726C6421")
+ * @throw StringHelperError if input is invalid
+ */
+shared_ptr<const Buffer>
+fromHex(const std::string& hexString);
 
 /**
  * @brief Modify str in place to erase whitespace on the left
  */
-inline void
-trimLeft(std::string& str)
-{
-  size_t found = str.find_first_not_of(WHITESPACE_CHARS);
-  if (found != std::string::npos) {
-    if (found > 0)
-      str.erase(0, found);
-  }
-  else
-    // All whitespace
-    str.clear();
-}
+void
+trimLeft(std::string& str);
 
 /**
  * @brief Modify str in place to erase whitespace on the right
  */
-inline void
-trimRight(std::string& str)
-{
-  size_t found = str.find_last_not_of(WHITESPACE_CHARS);
-  if (found != std::string::npos) {
-    if (found + 1 < str.size())
-      str.erase(found + 1);
-  }
-  else
-    // All whitespace
-    str.clear();
-}
+void
+trimRight(std::string& str);
 
 /**
  * @brief Modify str in place to erase whitespace on the left and right
  */
-inline void
-trim(std::string& str)
-{
-  trimLeft(str);
-  trimRight(str);
-}
+void
+trim(std::string& str);
 
 /**
  * @brief Convert the hex character to an integer from 0 to 15, or -1 if not a hex character
  */
-inline int
-fromHexChar(uint8_t c)
-{
-  if (c >= '0' && c <= '9')
-    return (int)c - (int)'0';
-  else if (c >= 'A' && c <= 'F')
-    return (int)c - (int)'A' + 10;
-  else if (c >= 'a' && c <= 'f')
-    return (int)c - (int)'a' + 10;
-  else
-    return -1;
-}
+int
+fromHexChar(uint8_t c);
 
 /**
- * @brief Return a copy of str, converting each escaped "%XX" to the char value
+ * @brief Decode a percent-encoded string
+ * @see RFC 3986 section 2
+ *
+ * When % is not followed by two hex characters, the output is not transformed.
+ *
+ * @example unescape("hello%20world") == "hello world"
+ * @example unescape("hello%20world%FooBar") == "hello world%FooBar"
  */
-inline std::string
-unescape(const std::string& str)
-{
-  std::ostringstream result;
-
-  for (size_t i = 0; i < str.size(); ++i) {
-    if (str[i] == '%' && i + 2 < str.size()) {
-      int hi = fromHexChar(str[i + 1]);
-      int lo = fromHexChar(str[i + 2]);
-
-      if (hi < 0 || lo < 0)
-        // Invalid hex characters, so just keep the escaped string.
-        result << str[i] << str[i + 1] << str[i + 2];
-      else
-        result << (uint8_t)(16 * hi + lo);
-
-      // Skip ahead past the escaped value.
-      i += 2;
-    }
-    else
-      // Just copy through.
-      result << str[i];
-  }
-
-  return result.str();
-}
+std::string
+unescape(const std::string& str);
 
 } // namespace ndn
 
diff --git a/tests/unit-tests/security/digest-sha256.t.cpp b/tests/unit-tests/security/digest-sha256.t.cpp
index ebf50c7..ffc6908 100644
--- a/tests/unit-tests/security/digest-sha256.t.cpp
+++ b/tests/unit-tests/security/digest-sha256.t.cpp
@@ -22,7 +22,8 @@
 #include "security/digest-sha256.hpp"
 #include "security/key-chain.hpp"
 #include "security/validator.hpp"
-#include "security/cryptopp.hpp"
+#include "util/string-helper.hpp"
+
 #include "identity-management-fixture.hpp"
 #include "boost-test.hpp"
 
@@ -39,10 +40,8 @@
 
   char content[6] = "1234\n";
   ConstBufferPtr buf = crypto::sha256(reinterpret_cast<uint8_t*>(content), 5);
-  std::string result;
-  StringSource(buf->buf(), buf->size(), true, new HexEncoder(new StringSink(result), false));
 
-  BOOST_CHECK_EQUAL(SHA256_RESULT, result);
+  BOOST_CHECK_EQUAL(SHA256_RESULT, toHex(buf->buf(), buf->size(), false));
 }
 
 BOOST_AUTO_TEST_CASE(DataSignature)
diff --git a/tests/unit-tests/security/pib-data-fixture.cpp b/tests/unit-tests/security/pib-data-fixture.cpp
index a42c0e3..dfb5304 100644
--- a/tests/unit-tests/security/pib-data-fixture.cpp
+++ b/tests/unit-tests/security/pib-data-fixture.cpp
@@ -79,10 +79,7 @@
  *   {
  *     using namespace CryptoPP;
  *
- *     std::stringstream ss;
- *     StringSource(buf, size, true, new HexEncoder(new FileSink(ss)));
- *
- *     std::string hex = ss.str();
+ *     std::string hex = toHex(buf, size);
  *
  *     for (int i = 0; i < hex.size(); i++) {
  *       if (i % 40 == 0)
diff --git a/tests/unit-tests/util/digest.t.cpp b/tests/unit-tests/util/digest.t.cpp
index 531868f..e1b3278 100644
--- a/tests/unit-tests/util/digest.t.cpp
+++ b/tests/unit-tests/util/digest.t.cpp
@@ -21,6 +21,7 @@
 
 #include "util/digest.hpp"
 #include "util/crypto.hpp"
+#include "util/string-helper.hpp"
 
 #include "boost-test.hpp"
 
@@ -215,15 +216,12 @@
 
 BOOST_AUTO_TEST_CASE(Print)
 {
-  using namespace CryptoPP;
-
   uint8_t origin[32] = {0x94, 0xEE, 0x05, 0x93, 0x35, 0xE5, 0x87, 0xE5,
                         0x01, 0xCC, 0x4B, 0xF9, 0x06, 0x13, 0xE0, 0x81,
                         0x4F, 0x00, 0xA7, 0xB0, 0x8B, 0xC7, 0xC6, 0x48,
                         0xFD, 0x86, 0x5A, 0x2A, 0xF6, 0xA2, 0x2C, 0xC2};
 
-  std::string hexString;
-  StringSource(origin, 32, true, new HexEncoder(new StringSink(hexString)));
+  std::string hexString = toHex(origin, 32);
 
   std::string str("TEST");
   Sha256 digest;
@@ -234,7 +232,6 @@
 
   BOOST_CHECK_EQUAL(os.str(), hexString);
   BOOST_CHECK_EQUAL(digest.toString(), hexString);
-
 }
 
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/tests/unit-tests/util/string-helper.t.cpp b/tests/unit-tests/util/string-helper.t.cpp
new file mode 100644
index 0000000..82c30d7
--- /dev/null
+++ b/tests/unit-tests/util/string-helper.t.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2015 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "util/string-helper.hpp"
+
+#include "boost-test.hpp"
+#include <iostream>
+
+namespace ndn {
+namespace util {
+namespace test {
+
+BOOST_AUTO_TEST_SUITE(UtilStringHelper)
+
+BOOST_AUTO_TEST_CASE(ToHex)
+{
+  std::string test = "Hello, world!";
+  BOOST_CHECK_EQUAL(toHex(reinterpret_cast<const uint8_t*>(test.data()), test.size()),
+                    "48656C6C6F2C20776F726C6421");
+
+  BOOST_CHECK_EQUAL(toHex(reinterpret_cast<const uint8_t*>(test.data()), test.size(), false),
+                    "48656c6c6f2c20776f726c6421");
+
+  BOOST_CHECK_EQUAL(toHex(nullptr, 0), "");
+
+  Buffer buffer(test.data(), test.size());
+  BOOST_CHECK_EQUAL(toHex(buffer, false),  "48656c6c6f2c20776f726c6421");
+}
+
+BOOST_AUTO_TEST_CASE(FromHexChar)
+{
+  // for (int ch = 0; ch <= std::numeric_limits<uint8_t>::max(); ++ch) {
+  //   std::cout << "{0x" << std::hex << ch << ", "
+  //             << std::dec << fromHexChar(static_cast<char>(ch)) << "}, ";
+  //   if (ch % 8 == 7)
+  //     std::cout << std::endl;
+  // }
+  std::vector<std::pair<unsigned char, int>> hexMap{
+    {0x0, -1}, {0x1, -1}, {0x2, -1}, {0x3, -1}, {0x4, -1}, {0x5, -1}, {0x6, -1}, {0x7, -1},
+    {0x8, -1}, {0x9, -1}, {0xa, -1}, {0xb, -1}, {0xc, -1}, {0xd, -1}, {0xe, -1}, {0xf, -1},
+    {0x10, -1}, {0x11, -1}, {0x12, -1}, {0x13, -1}, {0x14, -1}, {0x15, -1}, {0x16, -1}, {0x17, -1},
+    {0x18, -1}, {0x19, -1}, {0x1a, -1}, {0x1b, -1}, {0x1c, -1}, {0x1d, -1}, {0x1e, -1}, {0x1f, -1},
+    {0x20, -1}, {0x21, -1}, {0x22, -1}, {0x23, -1}, {0x24, -1}, {0x25, -1}, {0x26, -1}, {0x27, -1},
+    {0x28, -1}, {0x29, -1}, {0x2a, -1}, {0x2b, -1}, {0x2c, -1}, {0x2d, -1}, {0x2e, -1}, {0x2f, -1},
+    {0x30, 0}, {0x31, 1}, {0x32, 2}, {0x33, 3}, {0x34, 4}, {0x35, 5}, {0x36, 6}, {0x37, 7},
+    {0x38, 8}, {0x39, 9}, {0x3a, -1}, {0x3b, -1}, {0x3c, -1}, {0x3d, -1}, {0x3e, -1}, {0x3f, -1},
+    {0x40, -1}, {0x41, 10}, {0x42, 11}, {0x43, 12}, {0x44, 13}, {0x45, 14}, {0x46, 15}, {0x47, -1},
+    {0x48, -1}, {0x49, -1}, {0x4a, -1}, {0x4b, -1}, {0x4c, -1}, {0x4d, -1}, {0x4e, -1}, {0x4f, -1},
+    {0x50, -1}, {0x51, -1}, {0x52, -1}, {0x53, -1}, {0x54, -1}, {0x55, -1}, {0x56, -1}, {0x57, -1},
+    {0x58, -1}, {0x59, -1}, {0x5a, -1}, {0x5b, -1}, {0x5c, -1}, {0x5d, -1}, {0x5e, -1}, {0x5f, -1},
+    {0x60, -1}, {0x61, 10}, {0x62, 11}, {0x63, 12}, {0x64, 13}, {0x65, 14}, {0x66, 15}, {0x67, -1},
+    {0x68, -1}, {0x69, -1}, {0x6a, -1}, {0x6b, -1}, {0x6c, -1}, {0x6d, -1}, {0x6e, -1}, {0x6f, -1},
+    {0x70, -1}, {0x71, -1}, {0x72, -1}, {0x73, -1}, {0x74, -1}, {0x75, -1}, {0x76, -1}, {0x77, -1},
+    {0x78, -1}, {0x79, -1}, {0x7a, -1}, {0x7b, -1}, {0x7c, -1}, {0x7d, -1}, {0x7e, -1}, {0x7f, -1},
+    {0x80, -1}, {0x81, -1}, {0x82, -1}, {0x83, -1}, {0x84, -1}, {0x85, -1}, {0x86, -1}, {0x87, -1},
+    {0x88, -1}, {0x89, -1}, {0x8a, -1}, {0x8b, -1}, {0x8c, -1}, {0x8d, -1}, {0x8e, -1}, {0x8f, -1},
+    {0x90, -1}, {0x91, -1}, {0x92, -1}, {0x93, -1}, {0x94, -1}, {0x95, -1}, {0x96, -1}, {0x97, -1},
+    {0x98, -1}, {0x99, -1}, {0x9a, -1}, {0x9b, -1}, {0x9c, -1}, {0x9d, -1}, {0x9e, -1}, {0x9f, -1},
+    {0xa0, -1}, {0xa1, -1}, {0xa2, -1}, {0xa3, -1}, {0xa4, -1}, {0xa5, -1}, {0xa6, -1}, {0xa7, -1},
+    {0xa8, -1}, {0xa9, -1}, {0xaa, -1}, {0xab, -1}, {0xac, -1}, {0xad, -1}, {0xae, -1}, {0xaf, -1},
+    {0xb0, -1}, {0xb1, -1}, {0xb2, -1}, {0xb3, -1}, {0xb4, -1}, {0xb5, -1}, {0xb6, -1}, {0xb7, -1},
+    {0xb8, -1}, {0xb9, -1}, {0xba, -1}, {0xbb, -1}, {0xbc, -1}, {0xbd, -1}, {0xbe, -1}, {0xbf, -1},
+    {0xc0, -1}, {0xc1, -1}, {0xc2, -1}, {0xc3, -1}, {0xc4, -1}, {0xc5, -1}, {0xc6, -1}, {0xc7, -1},
+    {0xc8, -1}, {0xc9, -1}, {0xca, -1}, {0xcb, -1}, {0xcc, -1}, {0xcd, -1}, {0xce, -1}, {0xcf, -1},
+    {0xd0, -1}, {0xd1, -1}, {0xd2, -1}, {0xd3, -1}, {0xd4, -1}, {0xd5, -1}, {0xd6, -1}, {0xd7, -1},
+    {0xd8, -1}, {0xd9, -1}, {0xda, -1}, {0xdb, -1}, {0xdc, -1}, {0xdd, -1}, {0xde, -1}, {0xdf, -1},
+    {0xe0, -1}, {0xe1, -1}, {0xe2, -1}, {0xe3, -1}, {0xe4, -1}, {0xe5, -1}, {0xe6, -1}, {0xe7, -1},
+    {0xe8, -1}, {0xe9, -1}, {0xea, -1}, {0xeb, -1}, {0xec, -1}, {0xed, -1}, {0xee, -1}, {0xef, -1},
+    {0xf0, -1}, {0xf1, -1}, {0xf2, -1}, {0xf3, -1}, {0xf4, -1}, {0xf5, -1}, {0xf6, -1}, {0xf7, -1},
+    {0xf8, -1}, {0xf9, -1}, {0xfa, -1}, {0xfb, -1}, {0xfc, -1}, {0xfd, -1}, {0xfe, -1}, {0xff, -1}
+  };
+  for (const auto& item : hexMap) {
+    BOOST_CHECK_EQUAL(fromHexChar(static_cast<char>(item.first)), item.second);
+  }
+}
+
+BOOST_AUTO_TEST_CASE(FromHex)
+{
+  BOOST_CHECK_NO_THROW(fromHex("48656c6c6f2c20776f726c6421"));
+  BOOST_CHECK(*fromHex("48656c6c6f2c20776f726c6421") ==
+    (std::vector<uint8_t>{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21}));
+
+  BOOST_CHECK_NO_THROW(fromHex("012a3Bc4defAB5CdEF"));
+  BOOST_CHECK(*fromHex("012a3Bc4defAB5CdEF") ==
+    (std::vector<uint8_t>{0x01, 0x2a, 0x3b, 0xc4, 0xde, 0xfa, 0xb5, 0xcd, 0xef}));
+
+  BOOST_CHECK_THROW(fromHex("1"), StringHelperError);
+  BOOST_CHECK_THROW(fromHex("zz"), StringHelperError);
+  BOOST_CHECK_THROW(fromHex("00az"), StringHelperError);
+}
+
+BOOST_AUTO_TEST_CASE(Trim)
+{
+  std::string test1 = "Hello, world!";
+  std::string test2 = "\n \t    Hello, world!\n\r   \t ";
+  std::string test3 = "            \t \n \r          ";
+
+  // TrimLeft
+  std::string test = test1;
+  trimLeft(test);
+  BOOST_CHECK_EQUAL(test, "Hello, world!");
+
+  test = test2;
+  trimLeft(test);
+  BOOST_CHECK_EQUAL(test, "Hello, world!\n\r   \t ");
+
+  test = test3;
+  trimLeft(test);
+  BOOST_CHECK_EQUAL(test, "");
+
+  // TrimRight
+  test = test1;
+  trimRight(test);
+  BOOST_CHECK_EQUAL(test, "Hello, world!");
+
+  test = test2;
+  trimRight(test);
+  BOOST_CHECK_EQUAL(test, "\n \t    Hello, world!");
+
+  test = test3;
+  trimRight(test);
+  BOOST_CHECK_EQUAL(test, "");
+
+  // Trim
+  test = test1;
+  trim(test);
+  BOOST_CHECK_EQUAL(test, "Hello, world!");
+
+  test = test2;
+  trim(test);
+  BOOST_CHECK_EQUAL(test, "Hello, world!");
+
+  test = test3;
+  trim(test);
+  BOOST_CHECK_EQUAL(test, "");
+}
+
+BOOST_AUTO_TEST_CASE(Unescape)
+{
+  std::string test1 = "Hello%01, world!%AA  ";
+  std::string test2 = "Invalid escape %ZZ (not a hex value)";
+  std::string test3 = "Invalid escape %a (should be two hex symbols)";
+  std::string test4 = "Invalid escape %a";
+
+  BOOST_CHECK_EQUAL(unescape(test1), "Hello\x01, world!\xAA  ");
+
+  BOOST_CHECK_EQUAL(unescape(test2), "Invalid escape %ZZ (not a hex value)");
+  BOOST_CHECK_EQUAL(unescape(test3), "Invalid escape %a (should be two hex symbols)");
+  BOOST_CHECK_EQUAL(unescape(test4), "Invalid escape %a");
+
+  BOOST_CHECK_EQUAL(unescape("%01%2a%3B%c4%de%fA%B5%Cd%EF"),
+                    "\x01\x2a\x3b\xc4\xde\xfa\xb5\xcd\xef");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // namespace test
+} // namespace util
+} // namespace ndn