util: add io::loadBuffer and io::saveBuffer
Change-Id: Ifa9d3bc44bba5dd8e2bd5d419629763936f355d4
diff --git a/ndn-cxx/util/io.cpp b/ndn-cxx/util/io.cpp
index ac9f2b7..a385eea 100644
--- a/ndn-cxx/util/io.cpp
+++ b/ndn-cxx/util/io.cpp
@@ -21,13 +21,20 @@
#include "ndn-cxx/util/io.hpp"
#include "ndn-cxx/encoding/buffer-stream.hpp"
-#include "ndn-cxx/security/transform.hpp"
+#include "ndn-cxx/security/transform/base64-decode.hpp"
+#include "ndn-cxx/security/transform/base64-encode.hpp"
+#include "ndn-cxx/security/transform/buffer-source.hpp"
+#include "ndn-cxx/security/transform/hex-decode.hpp"
+#include "ndn-cxx/security/transform/hex-encode.hpp"
+#include "ndn-cxx/security/transform/stream-sink.hpp"
+#include "ndn-cxx/security/transform/stream-source.hpp"
+#include "ndn-cxx/security/transform/strip-space.hpp"
namespace ndn {
namespace io {
-optional<Block>
-loadBlock(std::istream& is, IoEncoding encoding)
+shared_ptr<Buffer>
+loadBuffer(std::istream& is, IoEncoding encoding)
{
namespace t = ndn::security::transform;
@@ -36,55 +43,65 @@
switch (encoding) {
case NO_ENCODING:
t::streamSource(is) >> t::streamSink(os);
- break;
+ return os.buf();
case BASE64:
t::streamSource(is) >> t::stripSpace("\n") >> t::base64Decode(false) >> t::streamSink(os);
- break;
+ return os.buf();
case HEX:
t::streamSource(is) >> t::hexDecode() >> t::streamSink(os);
- break;
- default:
- return nullopt;
+ return os.buf();
}
}
- catch (const t::Error&) {
- return nullopt;
+ catch (const t::Error& e) {
+ NDN_THROW_NESTED(Error(e.what()));
}
+ NDN_THROW(std::invalid_argument("Unknown IoEncoding " + to_string(encoding)));
+}
+
+optional<Block>
+loadBlock(std::istream& is, IoEncoding encoding)
+{
try {
- return make_optional<Block>(os.buf());
- }
- catch (const tlv::Error&) {
- return nullopt;
+ return make_optional<Block>(loadBuffer(is, encoding));
}
catch (const std::invalid_argument&) {
return nullopt;
}
+ catch (const std::runtime_error&) {
+ return nullopt;
+ }
}
void
-saveBlock(const Block& block, std::ostream& os, IoEncoding encoding)
+saveBuffer(const uint8_t* buf, size_t size, std::ostream& os, IoEncoding encoding)
{
namespace t = ndn::security::transform;
try {
switch (encoding) {
case NO_ENCODING:
- t::bufferSource(block.wire(), block.size()) >> t::streamSink(os);
- break;
+ t::bufferSource(buf, size) >> t::streamSink(os);
+ return;
case BASE64:
- t::bufferSource(block.wire(), block.size()) >> t::base64Encode() >> t::streamSink(os);
- break;
+ t::bufferSource(buf, size) >> t::base64Encode() >> t::streamSink(os);
+ return;
case HEX:
- t::bufferSource(block.wire(), block.size()) >> t::hexEncode(true) >> t::streamSink(os);
- break;
- default:
- NDN_THROW(Error("Unknown IoEncoding " + to_string(encoding)));
+ t::bufferSource(buf, size) >> t::hexEncode(true) >> t::streamSink(os);
+ return;
}
}
- catch (const t::Error&) {
- NDN_THROW_NESTED(Error("Transform error during save"));
+ catch (const t::Error& e) {
+ NDN_THROW_NESTED(Error(e.what()));
}
+
+ NDN_THROW(std::invalid_argument("Unknown IoEncoding " + to_string(encoding)));
+}
+
+void
+saveBlock(const Block& block, std::ostream& os, IoEncoding encoding)
+{
+ saveBuffer(block.wire(), block.size(), os, encoding);
}
} // namespace io
diff --git a/ndn-cxx/util/io.hpp b/ndn-cxx/util/io.hpp
index e8f6e8d..709fe5d 100644
--- a/ndn-cxx/util/io.hpp
+++ b/ndn-cxx/util/io.hpp
@@ -36,25 +36,25 @@
using std::runtime_error::runtime_error;
};
-/** \brief indicates how a file or stream is encoded
+/** \brief Indicates how a file or stream of bytes is encoded.
*/
enum IoEncoding {
- /** \brief binary without encoding
+ /** \brief Raw binary, without encoding
*/
NO_ENCODING,
- /** \brief base64 encoding
+ /** \brief Base64 encoding
*
- * \p save() inserts a newline after every 64 characters,
- * \p load() can accept base64 text with or without newlines
+ * `save()` inserts a newline after every 64 characters,
+ * `load()` can accept base64 text with or without newlines.
*/
BASE64,
- /** \brief hexadecimal encoding
+ /** \brief Hexadecimal encoding
*
- * \p save() uses uppercase letters A-F, \p load() can accept mixed-case
+ * `save()` uses uppercase letters A-F, `load()` can accept mixed-case.
*/
- HEX
+ HEX,
};
namespace detail {
@@ -63,29 +63,36 @@
static void
checkInnerError(typename T::Error*)
{
- static_assert(std::is_base_of<tlv::Error, typename T::Error>::value,
- "T::Error, if declared, must inherit from ndn::tlv::Error");
+ static_assert(std::is_convertible<typename T::Error*, tlv::Error*>::value,
+ "T::Error, if defined, must be a subclass of ndn::tlv::Error");
}
template<typename T>
static void
checkInnerError(...)
{
- // T::Error is not declared
+ // T::Error is not defined
}
} // namespace detail
-/** \brief loads a TLV block from a stream
- * \return if success, the Block and true
- * otherwise, a default-constructed Block and false
+/** \brief Reads bytes from a stream until EOF.
+ * \return a Buffer containing the bytes read from the stream
+ * \throw Error error during loading
+ * \throw std::invalid_argument the specified encoding is not supported
+ */
+shared_ptr<Buffer>
+loadBuffer(std::istream& is, IoEncoding encoding = BASE64);
+
+/** \brief Reads a TLV block from a stream.
+ * \return a Block, or nullopt if an error occurs
*/
optional<Block>
loadBlock(std::istream& is, IoEncoding encoding = BASE64);
-/** \brief loads a TLV element from a stream
- * \tparam T type of TLV element; T must be WireDecodable,
- * and must have a Error nested type
+/** \brief Reads a TLV element from a stream.
+ * \tparam T type of TLV element; `T` must be WireDecodable and the nested type
+ * `T::Error`, if defined, must be a subclass of ndn::tlv::Error
* \return the TLV element, or nullptr if an error occurs
*/
template<typename T>
@@ -95,7 +102,7 @@
BOOST_CONCEPT_ASSERT((WireDecodable<T>));
detail::checkInnerError<T>(nullptr);
- optional<Block> block = loadBlock(is, encoding);
+ auto block = loadBlock(is, encoding);
if (!block) {
return nullptr;
}
@@ -108,7 +115,10 @@
}
}
-/** \brief loads a TLV element from a file
+/** \brief Reads a TLV element from a file.
+ * \tparam T type of TLV element; `T` must be WireDecodable and the nested type
+ * `T::Error`, if defined, must be a subclass of ndn::tlv::Error
+ * \return the TLV element, or nullptr if an error occurs
*/
template<typename T>
shared_ptr<T>
@@ -118,16 +128,25 @@
return load<T>(is, encoding);
}
-/** \brief saves a TLV block to a stream
+/** \brief Writes a byte buffer to a stream.
* \throw Error error during saving
+ * \throw std::invalid_argument the specified encoding is not supported
+ */
+void
+saveBuffer(const uint8_t* buf, size_t size, std::ostream& os, IoEncoding encoding = BASE64);
+
+/** \brief Writes a TLV block to a stream.
+ * \throw Error error during saving
+ * \throw std::invalid_argument the specified encoding is not supported
*/
void
saveBlock(const Block& block, std::ostream& os, IoEncoding encoding = BASE64);
-/** \brief saves a TLV element to a stream
- * \tparam T type of TLV element; T must be WireEncodable,
- * and must have a Error nested type
+/** \brief Writes a TLV element to a stream.
+ * \tparam T type of TLV element; `T` must be WireEncodable and the nested type
+ * `T::Error`, if defined, must be a subclass of ndn::tlv::Error
* \throw Error error during encoding or saving
+ * \throw std::invalid_argument the specified encoding is not supported
*/
template<typename T>
void
@@ -147,7 +166,11 @@
saveBlock(block, os, encoding);
}
-/** \brief saves a TLV element to a file
+/** \brief Writes a TLV element to a file.
+ * \tparam T type of TLV element; `T` must be WireEncodable and the nested type
+ * `T::Error`, if defined, must be a subclass of ndn::tlv::Error
+ * \throw Error error during encoding or saving
+ * \throw std::invalid_argument the specified encoding is not supported
*/
template<typename T>
void
diff --git a/tests/unit/util/io.t.cpp b/tests/unit/util/io.t.cpp
index f8595c9..9340279 100644
--- a/tests/unit/util/io.t.cpp
+++ b/tests/unit/util/io.t.cpp
@@ -25,21 +25,88 @@
#include "tests/identity-management-fixture.hpp"
#include <boost/filesystem.hpp>
+#include <boost/mpl/vector.hpp>
namespace ndn {
namespace tests {
-class IoFixture
+BOOST_AUTO_TEST_SUITE(Util)
+BOOST_AUTO_TEST_SUITE(TestIo)
+
+struct NoEncoding
+{
+ const io::IoEncoding encoding{io::NO_ENCODING};
+ const std::vector<uint8_t> blob{0xd1, 0x0, 0xb0, 0x1a};
+ std::istringstream stream{std::string("\xd1\x00\xb0\x1a", 4), std::ios_base::binary};
+};
+
+struct Base64Encoding
+{
+ const io::IoEncoding encoding = io::BASE64;
+ const std::vector<uint8_t> blob{0x42, 0x61, 0x73, 0x65, 0x36, 0x34, 0x45, 0x6e, 0x63};
+ std::istringstream stream{"QmFzZTY0RW5j\n", std::ios_base::binary};
+};
+
+struct HexEncoding
+{
+ const io::IoEncoding encoding = io::HEX;
+ const std::vector<uint8_t> blob{0x48, 0x65, 0x78, 0x45, 0x6e, 0x63};
+ std::istringstream stream{"486578456E63", std::ios_base::binary};
+};
+
+using Encodings = boost::mpl::vector<NoEncoding, Base64Encoding, HexEncoding>;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(LoadBuffer, T, Encodings)
+{
+ T t;
+ shared_ptr<Buffer> buf = io::loadBuffer(t.stream, t.encoding);
+ BOOST_CHECK_EQUAL_COLLECTIONS(buf->begin(), buf->end(), t.blob.begin(), t.blob.end());
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(SaveBuffer, T, Encodings)
+{
+ T t;
+ std::ostringstream os(std::ios_base::binary);
+ io::saveBuffer(t.blob.data(), t.blob.size(), os, t.encoding);
+ BOOST_CHECK_EQUAL(os.str(), t.stream.str());
+}
+
+BOOST_AUTO_TEST_CASE(LoadBufferException)
+{
+ std::ifstream in("this-file-does-not-exist", std::ios_base::binary);
+ BOOST_CHECK_THROW(io::loadBuffer(in, io::NO_ENCODING), io::Error);
+}
+
+BOOST_AUTO_TEST_CASE(SaveBufferException)
+{
+ class NullStreambuf : public std::streambuf
+ {
+ };
+
+ NullStreambuf nullbuf;
+ std::ostream out(&nullbuf);
+ const Buffer buffer(1);
+ BOOST_CHECK_THROW(io::saveBuffer(buffer.data(), buffer.size(), out, io::NO_ENCODING), io::Error);
+}
+
+BOOST_AUTO_TEST_CASE(UnknownIoEncoding)
+{
+ std::stringstream ss;
+ BOOST_CHECK_THROW(io::loadBuffer(ss, static_cast<io::IoEncoding>(5)), std::invalid_argument);
+ BOOST_CHECK_THROW(io::saveBuffer(nullptr, 0, ss, static_cast<io::IoEncoding>(5)), std::invalid_argument);
+}
+
+class FileIoFixture
{
protected:
- IoFixture()
- : filepath(boost::filesystem::path(UNIT_TEST_CONFIG_PATH) /= "TestIo")
+ FileIoFixture()
+ : filepath(boost::filesystem::path(UNIT_TEST_CONFIG_PATH) / "TestIo")
, filename(filepath.string())
{
boost::filesystem::create_directories(filepath.parent_path());
}
- ~IoFixture()
+ ~FileIoFixture()
{
boost::system::error_code ec;
boost::filesystem::remove(filepath, ec); // ignore error
@@ -53,28 +120,29 @@
boost::filesystem::create_directory(filepath);
}
- template<typename Container, typename CharT = typename Container::value_type>
+ template<typename Container>
Container
readFile() const
{
Container container;
std::ifstream fs(filename, std::ios_base::binary);
+ BOOST_REQUIRE_MESSAGE(fs, "error opening file");
char ch;
while (fs.get(ch)) {
- container.push_back(static_cast<CharT>(ch));
+ container.push_back(static_cast<typename Container::value_type>(ch));
}
return container;
}
- template<typename Container, typename CharT = typename Container::value_type>
+ template<typename Container>
void
writeFile(const Container& content) const
{
std::ofstream fs(filename, std::ios_base::binary);
- for (CharT ch : content) {
+ BOOST_REQUIRE_MESSAGE(fs, "error opening file");
+ for (auto ch : content) {
fs.put(static_cast<char>(ch));
}
- fs.close();
BOOST_REQUIRE_MESSAGE(fs, "error writing file");
}
@@ -83,8 +151,7 @@
const std::string filename;
};
-BOOST_AUTO_TEST_SUITE(Util)
-BOOST_FIXTURE_TEST_SUITE(TestIo, IoFixture)
+BOOST_FIXTURE_TEST_SUITE(FileIo, FileIoFixture)
class EncodableType
{
@@ -104,7 +171,7 @@
bool shouldThrow = false;
};
-template<bool SHOULD_THROW = false>
+template<bool SHOULD_THROW>
class DecodableTypeTpl
{
public:
@@ -119,7 +186,7 @@
void
wireDecode(const Block& block)
{
- if (m_shouldThrow) {
+ if (SHOULD_THROW) {
NDN_THROW(tlv::Error("decode error"));
}
@@ -128,13 +195,10 @@
BOOST_REQUIRE_EQUAL(block.value_size(), 1);
BOOST_CHECK_EQUAL(block.value()[0], 0xEE);
}
-
-private:
- bool m_shouldThrow = SHOULD_THROW;
};
-typedef DecodableTypeTpl<false> DecodableType;
-typedef DecodableTypeTpl<true> DecodableTypeThrow;
+using DecodableType = DecodableTypeTpl<false>;
+using DecodableTypeThrow = DecodableTypeTpl<true>;
BOOST_AUTO_TEST_CASE(LoadNoEncoding)
{
@@ -260,7 +324,9 @@
BOOST_CHECK_THROW(io::save(encoded, filename, io::NO_ENCODING), io::Error);
}
-class IdCertFixture : public IoFixture
+BOOST_AUTO_TEST_SUITE_END() // FileIo
+
+class IdCertFixture : public FileIoFixture
, public IdentityManagementFixture
{
};