security: Add base64 and hex encoding/decoding support in transformation

Change-Id: I11d5c8921abcf04411aac38382dc285269f553ef
Refs: #3009
diff --git a/src/security/openssl.hpp b/src/security/detail/openssl.hpp
similarity index 75%
rename from src/security/openssl.hpp
rename to src/security/detail/openssl.hpp
index c753497..09a6f68 100644
--- a/src/security/openssl.hpp
+++ b/src/security/detail/openssl.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2014 Regents of the University of California.
+ * Copyright (c) 2013-2016 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -19,8 +19,8 @@
  * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
  */
 
-#ifndef NDN_SECURITY_OPENSSL_HPP
-#define NDN_SECURITY_OPENSSL_HPP
+#ifndef NDN_SECURITY_DETAIL_OPENSSL_HPP
+#define NDN_SECURITY_DETAIL_OPENSSL_HPP
 
 // suppress deprecation warnings in OSX >= 10.7
 
@@ -30,16 +30,16 @@
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 #endif // __clang__
 
-#ifdef __GNUC__
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif // __GNUC__
+#endif // __APPLE__
 
-#endif
-
-
-#include <openssl/ssl.h>
-#include <openssl/sha.h>
+#include <openssl/rand.h>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
 #include <openssl/rsa.h>
+#include <openssl/ec.h>
+#include <openssl/pem.h>
+#include <openssl/hmac.h>
+#include <openssl/x509.h>
 
 
-#endif // NDN_SECURITY_OPENSSL_HPP
+#endif // NDN_SECURITY_DETAIL_OPENSSL_HPP
diff --git a/src/security/transform.hpp b/src/security/transform.hpp
index b29f5c6..b9a960c 100644
--- a/src/security/transform.hpp
+++ b/src/security/transform.hpp
@@ -29,4 +29,9 @@
 #include "transform/bool-sink.hpp"
 #include "transform/stream-sink.hpp"
 
+#include "transform/hex-encode.hpp"
+#include "transform/hex-decode.hpp"
+#include "transform/base64-encode.hpp"
+#include "transform/base64-decode.hpp"
+
 #endif // NDN_CXX_SECURITY_TRANSFORM_HPP
diff --git a/src/security/transform/base64-decode.cpp b/src/security/transform/base64-decode.cpp
new file mode 100644
index 0000000..7f3cf54
--- /dev/null
+++ b/src/security/transform/base64-decode.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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 "base64-decode.hpp"
+#include "../../encoding/buffer.hpp"
+#include "../detail/openssl.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+
+/**
+ * @brief The implementation class which contains the internal state of the filter
+ *        which includes openssl specific structures.
+ */
+class Base64Decode::Impl
+{
+public:
+  Impl()
+    : m_base64(BIO_new(BIO_f_base64()))
+    , m_source(BIO_new(BIO_s_mem()))
+  {
+    // Input may not be written in a single time.
+    // Do not return EOF when source is empty unless explicitly requested
+    BIO_set_mem_eof_return(m_source, -1);
+
+    // connect base64 transform to the data source.
+    BIO_push(m_base64, m_source);
+  }
+
+  ~Impl()
+  {
+    BIO_free_all(m_source);
+  }
+
+public:
+  BIO* m_base64;
+  BIO* m_source; // BIO_f_base64 alone does not work without a source
+};
+
+static const size_t BUFFER_LENGTH = 1024;
+
+Base64Decode::Base64Decode(bool expectNewlineEvery64Bytes)
+  : m_impl(new Impl)
+{
+  if (!expectNewlineEvery64Bytes)
+    BIO_set_flags(m_impl->m_base64, BIO_FLAGS_BASE64_NO_NL);
+}
+
+void
+Base64Decode::preTransform()
+{
+  while (isOutputBufferEmpty()) {
+    fillOutputBuffer();
+    if (isOutputBufferEmpty()) // nothing to read from BIO, return
+      return;
+
+    flushOutputBuffer();
+  }
+}
+
+size_t
+Base64Decode::convert(const uint8_t* buf, size_t size)
+{
+  int wLen = BIO_write(m_impl->m_source, buf, size);
+
+  if (wLen <= 0) { // fail to write data
+    if (!BIO_should_retry(m_impl->m_source)) {
+      // we haven't written everything but some error happens, and we cannot retry
+      BOOST_THROW_EXCEPTION(Error(getIndex(), "Failed to accept more input"));
+    }
+    return 0;
+  }
+  else { // update number of bytes written
+    return wLen;
+  }
+}
+
+void
+Base64Decode::finalize()
+{
+  BIO_set_mem_eof_return(m_impl->m_source, 0);
+
+  fillOutputBuffer();
+
+  while (!isOutputBufferEmpty()) {
+    flushOutputBuffer();
+    if (isOutputBufferEmpty())
+      fillOutputBuffer();
+  }
+}
+
+void
+Base64Decode::fillOutputBuffer()
+{
+  // OpenSSL base64 BIO cannot give us the number bytes of partial decoded result,
+  // so we just try to read a chunk.
+  auto buffer = make_unique<OBuffer>(BUFFER_LENGTH);
+  int rLen = BIO_read(m_impl->m_base64, &(*buffer)[0], buffer->size());
+  if (rLen <= 0)
+    return;
+
+  if (static_cast<size_t>(rLen) < buffer->size())
+    buffer->erase(buffer->begin() + rLen, buffer->end());
+
+  setOutputBuffer(std::move(buffer));
+}
+
+unique_ptr<Transform>
+base64Decode(bool expectNewlineEvery64Bytes)
+{
+  return make_unique<Base64Decode>(expectNewlineEvery64Bytes);
+}
+
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/src/security/transform/base64-decode.hpp b/src/security/transform/base64-decode.hpp
new file mode 100644
index 0000000..cfd5876
--- /dev/null
+++ b/src/security/transform/base64-decode.hpp
@@ -0,0 +1,89 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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.
+ */
+
+#ifndef NDN_CXX_SECURITY_TRANSFORM_BASE64_DECODE_HPP
+#define NDN_CXX_SECURITY_TRANSFORM_BASE64_DECODE_HPP
+
+#include "transform-base.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+
+/**
+ * @brief The module to perform Base64 decoding transformation.
+ */
+class Base64Decode : public Transform
+{
+public:
+  /**
+   * @brief Create a base64 decoding module
+   *
+   * @p expectNewlineEvery64Bytes if true, expect newline after every 64 bytes, otherwise expect
+   *                              all input to be in a single line. Output is undefined if input
+   *                              does not conform to the requirement.
+   */
+  explicit
+  Base64Decode(bool expectNewlineEvery64Bytes = true);
+
+private:
+
+  /**
+   * @brief Read partial transformation results into output buffer and write them into next module.
+   */
+  virtual void
+  preTransform() final;
+
+  /**
+   * @brief Decode data @p buf in base64 format
+   *
+   * @return number of bytes that have been accepted by the converter
+   */
+  virtual size_t
+  convert(const uint8_t* buf, size_t size) final;
+
+  /**
+   * @brief Finalize base64 decoding
+   *
+   * This method with read all decoding results from the converter and write them into next module.
+   */
+  virtual void
+  finalize() final;
+
+  /**
+   * @brief Try to fill partial decoding result into output buffer.
+   */
+  void
+  fillOutputBuffer();
+
+private:
+  class Impl;
+  unique_ptr<Impl> m_impl;
+};
+
+unique_ptr<Transform>
+base64Decode(bool expectNewlineEvery64Bytes = true);
+
+} // namespace transform
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_CXX_SECURITY_TRANSFORM_BASE64_DECODE_HPP
diff --git a/src/security/transform/base64-encode.cpp b/src/security/transform/base64-encode.cpp
new file mode 100644
index 0000000..0a27bed
--- /dev/null
+++ b/src/security/transform/base64-encode.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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 "base64-encode.hpp"
+#include "../../encoding/buffer.hpp"
+#include "../detail/openssl.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+
+/**
+ * @brief The implementation class which contains the internal state of the filter
+ *        which includes openssl specific structures.
+ */
+class Base64Encode::Impl
+{
+public:
+  Impl()
+    : m_base64(BIO_new(BIO_f_base64()))
+    , m_sink(BIO_new(BIO_s_mem()))
+  {
+    // connect base64 transform to the data sink.
+    BIO_push(m_base64, m_sink);
+  }
+
+  ~Impl()
+  {
+    BIO_free_all(m_sink);
+  }
+
+public:
+  BIO* m_base64;
+  BIO* m_sink; // BIO_f_base64 alone does not work without a sink
+};
+
+Base64Encode::Base64Encode(bool needBreak)
+  : m_impl(new Impl)
+{
+  if (!needBreak)
+    BIO_set_flags(m_impl->m_base64, BIO_FLAGS_BASE64_NO_NL);
+}
+
+void
+Base64Encode::preTransform()
+{
+  fillOutputBuffer();
+}
+
+size_t
+Base64Encode::convert(const uint8_t* data, size_t dataLen)
+{
+  if (dataLen == 0)
+    return 0;
+
+  int wLen = BIO_write(m_impl->m_base64, data, dataLen);
+
+  if (wLen <= 0) { // fail to write data
+    if (!BIO_should_retry(m_impl->m_base64)) {
+      // we haven't written everything but some error happens, and we cannot retry
+      BOOST_THROW_EXCEPTION(Error(getIndex(), "Failed to accept more input"));
+    }
+    return 0;
+  }
+  else { // update number of bytes written
+    fillOutputBuffer();
+    return wLen;
+  }
+}
+
+void
+Base64Encode::finalize()
+{
+  if (BIO_flush(m_impl->m_base64) != 1)
+    BOOST_THROW_EXCEPTION(Error(getIndex(), "Failed to flush"));
+
+  while (!isConverterEmpty()) {
+    fillOutputBuffer();
+    while (!isOutputBufferEmpty()) {
+      flushOutputBuffer();
+    }
+  }
+}
+
+void
+Base64Encode::fillOutputBuffer()
+{
+  int nRead = BIO_pending(m_impl->m_sink);
+  if (nRead <= 0)
+    return;
+
+  // there is something to read from BIO
+  auto buffer = make_unique<OBuffer>(nRead);
+  int rLen = BIO_read(m_impl->m_sink, &(*buffer)[0], nRead);
+  if (rLen < 0)
+    return;
+
+  if (rLen < nRead)
+    buffer->erase(buffer->begin() + rLen, buffer->end());
+  setOutputBuffer(std::move(buffer));
+}
+
+bool
+Base64Encode::isConverterEmpty()
+{
+  return (BIO_pending(m_impl->m_sink) <= 0);
+}
+
+unique_ptr<Transform>
+base64Encode(bool needBreak)
+{
+  return make_unique<Base64Encode>(needBreak);
+}
+
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/src/security/transform/base64-encode.hpp b/src/security/transform/base64-encode.hpp
new file mode 100644
index 0000000..ac9c664
--- /dev/null
+++ b/src/security/transform/base64-encode.hpp
@@ -0,0 +1,91 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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.
+ */
+
+#ifndef NDN_CXX_SECURITY_TRANSFORM_BASE64_ENCODE_HPP
+#define NDN_CXX_SECURITY_TRANSFORM_BASE64_ENCODE_HPP
+
+#include "transform-base.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+
+/**
+ * @brief The module to perform Base64 encoding transformation.
+ */
+class Base64Encode : public Transform
+{
+public:
+  /**
+   * @brief Create a base64 encoding module
+   *
+   * @p needBreak if true, insert newline after every 64 bytes, otherwise no newline is inserted
+   */
+  explicit
+  Base64Encode(bool needBreak = true);
+
+private:
+  /**
+   * @brief Read partial transformation result (if exists) from BIO
+   */
+  virtual void
+  preTransform() final;
+
+  /**
+   * @brief Encode @data into base64 format.
+   * @return The number of input bytes that have been accepted by the converter.
+   */
+  virtual size_t
+  convert(const uint8_t* data, size_t dataLen) final;
+
+  /**
+   * @brief Finalize base64 encoding
+   *
+   * This method with read all encoding results from the converter and write them into next module.
+   */
+  virtual void
+  finalize() final;
+
+  /**
+   * @brief Fill output buffer with the transformation result from BIO.
+   */
+  void
+  fillOutputBuffer();
+
+  /**
+   * @return true if converter does not have partial result.
+   */
+  bool
+  isConverterEmpty();
+
+private:
+  class Impl;
+  unique_ptr<Impl> m_impl;
+};
+
+unique_ptr<Transform>
+base64Encode(bool needBreak = true);
+
+} // namespace transform
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_CXX_SECURITY_TRANSFORM_BASE64_ENCODE_HPP
diff --git a/src/security/transform/hex-decode.cpp b/src/security/transform/hex-decode.cpp
new file mode 100644
index 0000000..aa82461
--- /dev/null
+++ b/src/security/transform/hex-decode.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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 "hex-decode.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+
+static const int8_t C2H[256] = { // hex decoding pad.
+// 0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-15
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32-47
+   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1, // 48-63
+  -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64-79
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80-95
+  -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96-111
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 112-127
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 240-255
+};
+
+HexDecode::HexDecode()
+  : m_hasOddByte(false)
+  , m_oddByte(0)
+{
+}
+
+size_t
+HexDecode::convert(const uint8_t* hex, size_t hexLen)
+{
+  if (hexLen == 0)
+    return 0;
+
+  setOutputBuffer(toBytes(hex, hexLen));
+
+  size_t totalDecodedLen = hexLen + (m_hasOddByte ? 1 : 0);
+  if (totalDecodedLen % 2 == 1) {
+    m_oddByte = hex[hexLen - 1];
+    m_hasOddByte = true;
+  }
+  else
+    m_hasOddByte = false;
+
+  return hexLen;
+}
+
+void
+HexDecode::finalize()
+{
+  if (m_hasOddByte)
+    BOOST_THROW_EXCEPTION(Error(getIndex(), "Incomplete input"));
+}
+
+unique_ptr<Transform::OBuffer>
+HexDecode::toBytes(const uint8_t* hex, size_t hexLen)
+{
+  size_t bufferSize = (hexLen + (m_hasOddByte ? 1 : 0)) >> 1;
+  auto buffer = make_unique<OBuffer>(bufferSize);
+  uint8_t* buf = &buffer->front();
+
+  if (m_hasOddByte) {
+    if (C2H[hex[0]] < 0 || C2H[m_oddByte] < 0)
+      BOOST_THROW_EXCEPTION(Error(getIndex(), "Wrong input byte"));
+
+    buf[0] = (C2H[m_oddByte] << 4) + (C2H[hex[0]]);
+    buf += 1;
+    hex += 1;
+    hexLen -= 1;
+  }
+
+  while (hexLen > 1) {
+    if (C2H[hex[0]] < 0 || C2H[hex[1]] < 0)
+      BOOST_THROW_EXCEPTION(Error(getIndex(), "Wrong input byte"));
+
+    buf[0] = (C2H[hex[0]] << 4) + (C2H[hex[1]]);
+    buf += 1;
+    hex += 2;
+    hexLen -= 2;
+  }
+
+  return buffer;
+}
+
+unique_ptr<Transform>
+hexDecode()
+{
+  return make_unique<HexDecode>();
+}
+
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/src/security/transform/hex-decode.hpp b/src/security/transform/hex-decode.hpp
new file mode 100644
index 0000000..77d477d
--- /dev/null
+++ b/src/security/transform/hex-decode.hpp
@@ -0,0 +1,80 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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.
+ */
+
+#ifndef NDN_CXX_SECURITY_TRANSFORM_HEX_DECODE_HPP
+#define NDN_CXX_SECURITY_TRANSFORM_HEX_DECODE_HPP
+
+#include "transform-base.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+
+/**
+ * @brief The module to perform hexadecimal decoding transformation.
+ *
+ * For example, if the input is a string "012345", the output will be
+ * a byte stream: 0x01, 0x23, 0x45.
+ *
+ * If the total length of input is not even (2n + 1), the module will throw Error.
+ */
+class HexDecode : public Transform
+{
+public:
+  /**
+   * @brief Create a hex decoding module
+   */
+  HexDecode();
+
+private:
+  /**
+   * @brief Decode data @p buf, and write the result into output buffer directly.
+   *
+   * @return number of input bytes that are accepted
+   */
+  virtual size_t
+  convert(const uint8_t* buf, size_t size) final;
+
+  /**
+   * @throws Error if pending byte exists.
+   */
+  virtual void
+  finalize() final;
+
+  /**
+   * @return results of decoding concatenation of @p oddByte and @p hex.
+   */
+  unique_ptr<Transform::OBuffer>
+  toBytes(const uint8_t* hex, size_t hexLen);
+
+private:
+  bool m_hasOddByte;
+  uint8_t m_oddByte;
+};
+
+unique_ptr<Transform>
+hexDecode();
+
+} // namespace transform
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_CXX_SECURITY_TRANSFORM_HEX_DECODE_HPP
diff --git a/src/security/transform/hex-encode.cpp b/src/security/transform/hex-encode.cpp
new file mode 100644
index 0000000..6d54f0e
--- /dev/null
+++ b/src/security/transform/hex-encode.cpp
@@ -0,0 +1,76 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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 "hex-encode.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+
+static const char H2CL[16] = {
+  '0', '1', '2', '3', '4', '5', '6', '7',
+  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+};
+
+static const char H2CU[16] = {
+  '0', '1', '2', '3', '4', '5', '6', '7',
+  '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+};
+
+HexEncode::HexEncode(bool useUpperCase)
+  : m_useUpperCase(useUpperCase)
+{
+}
+
+size_t
+HexEncode::convert(const uint8_t* data, size_t dataLen)
+{
+  setOutputBuffer(toHex(data, dataLen));
+  return dataLen;
+}
+
+unique_ptr<Transform::OBuffer>
+HexEncode::toHex(const uint8_t* data, size_t dataLen)
+{
+  const char* encodePad = (m_useUpperCase) ? H2CU : H2CL;
+
+  auto encoded = make_unique<OBuffer>(dataLen * 2);
+  uint8_t* buf = &encoded->front();
+  for (size_t i = 0; i < dataLen; i++) {
+    buf[0] = encodePad[((data[i] >> 4) & 0x0F)];
+    buf++;
+    buf[0] = encodePad[(data[i] & 0x0F)];
+    buf++;
+  }
+  return encoded;
+}
+
+
+
+unique_ptr<Transform>
+hexEncode(bool useUpperCase)
+{
+  return make_unique<HexEncode>(useUpperCase);
+}
+
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/src/security/transform/hex-encode.hpp b/src/security/transform/hex-encode.hpp
new file mode 100644
index 0000000..95fc6e8
--- /dev/null
+++ b/src/security/transform/hex-encode.hpp
@@ -0,0 +1,74 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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.
+ */
+
+#ifndef NDN_CXX_SECURITY_TRANSFORM_HEX_ENCODE_HPP
+#define NDN_CXX_SECURITY_TRANSFORM_HEX_ENCODE_HPP
+
+#include "transform-base.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+
+/**
+ * @brief The module to perform hexadecimal encoding transformation.
+ *
+ * For example, if the input is a byte stream 0x01, 0x23, 0x45,
+ * the output will be a string "012345".
+ */
+class HexEncode : public Transform
+{
+public:
+  /**
+   * @brief Create a hex encoding module
+   *
+   * @param useUpperCase if true, use upper case letters, otherwise lower case
+   */
+  explicit
+  HexEncode(bool useUpperCase = false);
+
+private:
+  /**
+   * @brief Encode @p data, and write the result into next module directly.
+   *
+   * @return The number of input bytes that have been accepted by the converter.
+   */
+  virtual size_t
+  convert(const uint8_t* data, size_t dataLen) final;
+
+  /**
+   * @return results of encoding @p data
+   */
+  unique_ptr<Transform::OBuffer>
+  toHex(const uint8_t* data, size_t dataLen);
+
+private:
+  bool m_useUpperCase;
+};
+
+unique_ptr<Transform>
+hexEncode(bool useUpperCase = false);
+
+} // namespace transform
+} // namespace security
+} // namespace ndn
+
+#endif // NDN_CXX_SECURITY_TRANSFORM_HEX_ENCODE_HPP
diff --git a/src/security/transform/transform-base.cpp b/src/security/transform/transform-base.cpp
index 85df6b9..936e0ae 100644
--- a/src/security/transform/transform-base.cpp
+++ b/src/security/transform/transform-base.cpp
@@ -74,6 +74,76 @@
   }
 }
 
+Transform::Transform()
+  : m_oBuffer(nullptr)
+  , m_outputOffset(0)
+{
+}
+
+void
+Transform::flushOutputBuffer()
+{
+  if (isOutputBufferEmpty())
+    return;
+
+  size_t nWritten = m_next->write(&(*m_oBuffer)[m_outputOffset],
+                                  m_oBuffer->size() - m_outputOffset);
+  m_outputOffset += nWritten;
+}
+
+void
+Transform::setOutputBuffer(unique_ptr<OBuffer> buffer)
+{
+  BOOST_ASSERT(isOutputBufferEmpty());
+  m_oBuffer = std::move(buffer);
+  m_outputOffset = 0;
+}
+
+bool
+Transform::isOutputBufferEmpty() const
+{
+  return (m_oBuffer == nullptr || m_oBuffer->size() == m_outputOffset);
+}
+
+size_t
+Transform::doWrite(const uint8_t* data, size_t dataLen)
+{
+  flushOutputBuffer();
+  if (!isOutputBufferEmpty())
+    return 0;
+
+  preTransform();
+  flushOutputBuffer();
+  if (!isOutputBufferEmpty())
+    return 0;
+
+  size_t nConverted = convert(data, dataLen);
+
+  flushOutputBuffer();
+
+  return nConverted;
+}
+
+void
+Transform::doEnd()
+{
+  finalize();
+  m_next->end();
+}
+
+void
+Transform::preTransform()
+{
+}
+
+void
+Transform::finalize()
+{
+  while (!isOutputBufferEmpty()) {
+    flushOutputBuffer();
+  }
+}
+
 Source::Source()
   : m_nModules(1) // source per se is counted as one module
 {
diff --git a/src/security/transform/transform-base.hpp b/src/security/transform/transform-base.hpp
index 7f64145..5e3a72b 100644
--- a/src/security/transform/transform-base.hpp
+++ b/src/security/transform/transform-base.hpp
@@ -23,6 +23,7 @@
 #define NDN_CXX_SECURITY_TRANSFORM_BASE_HPP
 
 #include "../../common.hpp"
+#include <vector>
 
 namespace ndn {
 namespace security {
@@ -178,6 +179,80 @@
                   public Downstream,
                   noncopyable
 {
+protected:
+  typedef std::vector<uint8_t> OBuffer;
+
+  Transform();
+
+  /**
+   * @brief Read the content from output buffer and write it into next module.
+   */
+  void
+  flushOutputBuffer();
+
+  /**
+   * @brief Set output buffer to @p buffer
+   */
+  void
+  setOutputBuffer(unique_ptr<OBuffer> buffer);
+
+  /**
+   * @brief Check if output buffer is empty
+   */
+  bool
+  isOutputBufferEmpty() const;
+
+private:
+
+  /**
+   * @brief Abstraction of data processing in an intermediate module
+   */
+  virtual size_t
+  doWrite(const uint8_t* data, size_t dataLen) final;
+
+  /**
+   * @brief Finalize transformation in this module
+   *
+   * This method will not return until all transformation result is written into next module
+   */
+  virtual void
+  doEnd() final;
+
+  /**
+   * @brief Process before transformation.
+   *
+   * @pre output buffer is empty.
+   *
+   * This method is invoked before every convert(...) invocation.
+   *
+   * This implementation does nothing.  A subclass can override this method to perform
+   * specific pre-transformation procedure, e.g., read partial transformation results into
+   * output buffer.
+   */
+  virtual void
+  preTransform();
+
+  /**
+   * @brief Convert input @p data.
+   *
+   * @return The number of input bytes that have been accepted by the converter.
+   */
+  virtual size_t
+  convert(const uint8_t* data, size_t dataLen) = 0;
+
+  /**
+   * @brief Finalize the transformation.
+   *
+   * This implementation only flushes content in output buffer into next module.
+   * A subclass can override this method to perform specific finalization procedure, i.e.,
+   * finalize the transformation and flush the result into next module.
+   */
+  virtual void
+  finalize();
+
+private:
+  unique_ptr<OBuffer> m_oBuffer;
+  size_t m_outputOffset;
 };
 
 /**
diff --git a/tests/unit-tests/security/transform.t.cpp b/tests/unit-tests/security/transform.t.cpp
index 6999df4..ea2fa0f 100644
--- a/tests/unit-tests/security/transform.t.cpp
+++ b/tests/unit-tests/security/transform.t.cpp
@@ -46,6 +46,18 @@
 
   transform::StreamSink* streamSink = nullptr;
   BOOST_CHECK(streamSink == nullptr);
+
+  transform::HexEncode* hexEncode = nullptr;
+  BOOST_CHECK(hexEncode == nullptr);
+
+  transform::HexDecode* hexDecode = nullptr;
+  BOOST_CHECK(hexDecode == nullptr);
+
+  transform::Base64Encode* base64Encode = nullptr;
+  BOOST_CHECK(base64Encode == nullptr);
+
+  transform::Base64Decode* base64Decode = nullptr;
+  BOOST_CHECK(base64Decode == nullptr);
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestTransform
diff --git a/tests/unit-tests/security/transform/base64-decode.t.cpp b/tests/unit-tests/security/transform/base64-decode.t.cpp
new file mode 100644
index 0000000..05c9e56
--- /dev/null
+++ b/tests/unit-tests/security/transform/base64-decode.t.cpp
@@ -0,0 +1,183 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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 "security/transform/base64-decode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "encoding/buffer-stream.hpp"
+#include <iostream>
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestBase64Decode)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  std::string in =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8=\n";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> base64Decode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(NoNewLine)
+{
+  std::string in =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8=";
+
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> base64Decode(false) >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(StepByStep)
+{
+  std::string in =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  const uint8_t* input = reinterpret_cast<const uint8_t*>(in.data());
+  OBufferStream os;
+  StepSource source;
+  source >> base64Decode() >> streamSink(os);
+  source.write(input, 65);       // complete line with "\n"
+  source.write(input + 65, 64);  // complete line without "\n"
+  source.write(input + 129, 1);  // single "\n"
+  source.write(input + 130, 35); // front of a line
+  source.write(input + 165, 30); // end of a line with "\n"
+  source.write(input + 195, 25); // front of a line
+  source.write(input + 220, 20); // middle of a line
+  source.write(input + 240, 19); // end of a line without "\n"
+  source.write(input + 259, 101); // "\n" plus one and half line
+  source.write(input + 360, 65); // end of a line plus front of another line
+  source.write(input + 425, 95); // remaining
+  source.end();
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  OBufferStream os;
+  StepSource source;
+  source >> base64Decode() >> streamSink(os);
+  source.end();
+  BOOST_CHECK_EQUAL(os.buf()->size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBase64Decode
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit-tests/security/transform/base64-encode.t.cpp b/tests/unit-tests/security/transform/base64-encode.t.cpp
new file mode 100644
index 0000000..8d383ab
--- /dev/null
+++ b/tests/unit-tests/security/transform/base64-encode.t.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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 "security/transform/base64-encode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "encoding/buffer-stream.hpp"
+#include <iostream>
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestBase64Encode)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  uint8_t in[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8=\n";
+
+  OBufferStream os;
+  bufferSource(in, sizeof(in)) >> base64Encode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(NoNewLine)
+{
+  uint8_t in[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8=";
+
+  OBufferStream os;
+  BufferSource(in, sizeof(in)) >> base64Encode(false) >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(StepByStep)
+{
+  uint8_t in[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n"
+    "AAECAwQFBgcICQoLDA0ODwABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4P\n";
+
+  OBufferStream os;
+  StepSource source;
+  source >> base64Encode() >> streamSink(os);
+  source.write(in, 64); // complete chunk
+  source.write(in + 64, 32); // first half of a chunk
+  source.write(in + 96, 32); // second half of a chunk
+  source.write(in + 128, 24); // front of a chunk
+  source.write(in + 152, 20); // middle of a chunk
+  source.write(in + 172, 20); // end of a chunk
+  source.write(in + 192, 63); // odd number of bytes
+  source.write(in + 255, 85); // one and half chunk
+  source.write(in + 340, 44); // remaining part
+  source.end();
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  OBufferStream os;
+  StepSource source;
+  source >> base64Encode() >> streamSink(os);
+  source.end();
+  BOOST_CHECK_EQUAL(os.buf()->size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestBase64Encode
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit-tests/security/transform/hex-decode.t.cpp b/tests/unit-tests/security/transform/hex-decode.t.cpp
new file mode 100644
index 0000000..4a9c78e
--- /dev/null
+++ b/tests/unit-tests/security/transform/hex-decode.t.cpp
@@ -0,0 +1,295 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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 "security/transform/hex-decode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "encoding/buffer-stream.hpp"
+#include <iostream>
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestHexDecode)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  std::string in =
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> hexDecode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(UpperCase)
+{
+  std::string in =
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> hexDecode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(MixCase)
+{
+  std::string in =
+    "000102030405060708090a0b0c0d0e0f"
+    "101112131415161718191a1b1c1d1e1f"
+    "202122232425262728292a2b2c2d2e2f"
+    "303132333435363738393a3b3c3d3e3f"
+    "404142434445464748494a4b4c4d4e4f"
+    "505152535455565758595a5b5c5d5e5f"
+    "606162636465666768696a6b6c6d6e6f"
+    "707172737475767778797a7b7c7d7e7f"
+    "808182838485868788898a8b8c8d8e8f"
+    "909192939495969798999a9b9c9d9e9f"
+    "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
+    "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
+    "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+    "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
+    "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+    "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
+    "000102030405060708090A0B0C0D0E0F"
+    "101112131415161718191A1B1C1D1E1F"
+    "202122232425262728292A2B2C2D2E2F"
+    "303132333435363738393A3B3C3D3E3F"
+    "404142434445464748494A4B4C4D4E4F"
+    "505152535455565758595A5B5C5D5E5F"
+    "606162636465666768696A6B6C6D6E6F"
+    "707172737475767778797A7B7C7D7E7F"
+    "808182838485868788898A8B8C8D8E8F"
+    "909192939495969798999A9B9C9D9E9F"
+    "A0A1A2A3A4A5A6A7A8A9AAABACADAEAF"
+    "B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF"
+    "C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF"
+    "D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF"
+    "E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF"
+    "F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"
+    "aaabacadaeafaAaBaCaD"
+    "babbbcbdbebfbAbBbCbD"
+    "cacbcccdcecfcAcBcCcD"
+    "dadbdcdddedfdAdBdCdD"
+    "eaebecedeeefeAeBeCeD"
+    "fafbfcfdfefffAfBfCfD"
+    "AaAbAcAdAeAfAAABACAD"
+    "BaBbBcBdBeBfBABBBCBD"
+    "CaCbCcCdCeCfCACBCCCD"
+    "DaDbDcDdDeDfDADBDCDD";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+    0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+    0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+    0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+    0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+    0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
+    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
+    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
+    0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
+    0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
+    0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
+    0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+    0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
+    0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
+    0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xAA, 0xAB, 0xAC, 0xAD,
+    0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xBA, 0xBB, 0xBC, 0xBD,
+    0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xCA, 0xCB, 0xCC, 0xCD,
+    0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xDA, 0xDB, 0xDC, 0xDD,
+    0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xEA, 0xEB, 0xEC, 0xED,
+    0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0xFA, 0xFB, 0xFC, 0xFD,
+    0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xAA, 0xAB, 0xAC, 0xAD,
+    0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xBA, 0xBB, 0xBC, 0xBD,
+    0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xCA, 0xCB, 0xCC, 0xCD,
+    0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xDA, 0xDB, 0xDC, 0xDD
+  };
+
+  OBufferStream os;
+  bufferSource(in) >> hexDecode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+
+BOOST_AUTO_TEST_CASE(StepByStep)
+{
+  std::string in =
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
+
+  uint8_t out[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  const uint8_t* input = reinterpret_cast<const uint8_t*>(in.data());
+  OBufferStream os;
+  StepSource source;
+  source >> hexDecode() >> streamSink(os);
+  source.write(input, 128);       // complete chunk
+  source.write(input + 128, 64);  // first half of a chunk
+  source.write(input + 192, 64);  // second half of a chunk
+  source.write(input + 256, 127); // odd number of byets
+  source.write(input + 383, 192); // one and half chunk
+  source.write(input + 575, 193); // remaining part
+  source.end();
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out, out + sizeof(out), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(OddByte)
+{
+  std::string in1 = "0001020304050";
+
+  OBufferStream os1;
+  BOOST_REQUIRE_THROW(bufferSource(in1) >> hexDecode() >> streamSink(os1), transform::Error);
+
+  std::string in2 = "0001020304xy";
+
+  OBufferStream os2;
+  BOOST_REQUIRE_THROW(bufferSource(in2) >> hexDecode() >> streamSink(os2), transform::Error);
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  OBufferStream os;
+  StepSource source;
+  source >> hexDecode() >> streamSink(os);
+  source.end();
+  BOOST_CHECK_EQUAL(os.buf()->size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestHexDecode
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn
diff --git a/tests/unit-tests/security/transform/hex-encode.t.cpp b/tests/unit-tests/security/transform/hex-encode.t.cpp
new file mode 100644
index 0000000..5edcaaa
--- /dev/null
+++ b/tests/unit-tests/security/transform/hex-encode.t.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2016 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 "security/transform/hex-encode.hpp"
+#include "security/transform/buffer-source.hpp"
+#include "security/transform/step-source.hpp"
+#include "security/transform/stream-sink.hpp"
+#include "encoding/buffer-stream.hpp"
+#include <iostream>
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace security {
+namespace transform {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Security)
+BOOST_AUTO_TEST_SUITE(Transform)
+BOOST_AUTO_TEST_SUITE(TestHexEncode)
+
+BOOST_AUTO_TEST_CASE(Basic)
+{
+  uint8_t in[128] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
+
+  OBufferStream os;
+  bufferSource(in, sizeof(in)) >> hexEncode() >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(UpperCase)
+{
+  uint8_t in[128] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F"
+    "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F";
+
+  OBufferStream os;
+  bufferSource(in, sizeof(in)) >> hexEncode(true) >> streamSink(os);
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(StepByStep)
+{
+  uint8_t in[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+  };
+
+  std::string out =
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"
+    "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";
+
+  OBufferStream os;
+  StepSource source;
+  source >> hexEncode() >> streamSink(os);
+  source.write(in, 64);       // complete chunk
+  source.write(in + 64, 32);  // first half of a chunk
+  source.write(in + 96, 32);  // second half of a chunk
+  source.write(in + 128, 20); // front of a chunk
+  source.write(in + 148, 20); // middle of a chunk
+  source.write(in + 168, 24); // end of a chunk
+  source.write(in + 192, 63); // odd number of bytes
+  source.write(in + 255, 85); // one and half chunk
+  source.write(in + 340, 44); // remaining part
+  source.end();
+
+  ConstBufferPtr buf1 = os.buf();
+  BOOST_CHECK_EQUAL_COLLECTIONS(out.begin(), out.end(), buf1->begin(), buf1->end());
+}
+
+BOOST_AUTO_TEST_CASE(EmptyInput)
+{
+  OBufferStream os;
+  StepSource source;
+  source >> hexEncode() >> streamSink(os);
+  source.end();
+  BOOST_CHECK_EQUAL(os.buf()->size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestHexEncode
+BOOST_AUTO_TEST_SUITE_END() // Transform
+BOOST_AUTO_TEST_SUITE_END() // Security
+
+} // namespace tests
+} // namespace transform
+} // namespace security
+} // namespace ndn