blob: 350abf6b67aad403e20f181961741c13171ac9f8 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2013-2024 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 "ndn-cxx/util/io.hpp"
#include "tests/boost-test.hpp"
#include "tests/key-chain-fixture.hpp"
#include <boost/mp11/list.hpp>
#include <filesystem>
#include <system_error>
namespace ndn::tests {
BOOST_AUTO_TEST_SUITE(Util)
BOOST_AUTO_TEST_SUITE(TestIo)
struct NoEncoding
{
static constexpr io::IoEncoding encoding = io::NO_ENCODING;
static inline 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
{
static constexpr io::IoEncoding encoding = io::BASE64;
static inline 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
{
static constexpr io::IoEncoding encoding = io::HEX;
static inline const std::vector<uint8_t> blob{0x48, 0x65, 0x78, 0x45, 0x6e, 0x63};
std::istringstream stream{"486578456E63", std::ios_base::binary};
};
using Encodings = boost::mp11::mp_list<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, 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, out, io::NO_ENCODING), io::Error);
}
BOOST_AUTO_TEST_CASE(UnknownIoEncoding)
{
std::stringstream ss;
BOOST_CHECK_THROW(io::loadTlv<Name>(ss, static_cast<io::IoEncoding>(5)), std::invalid_argument);
BOOST_CHECK_THROW(io::loadBuffer(ss, static_cast<io::IoEncoding>(5)), std::invalid_argument);
BOOST_CHECK_THROW(io::saveBuffer({}, ss, static_cast<io::IoEncoding>(5)), std::invalid_argument);
}
class FileIoFixture
{
protected:
FileIoFixture()
{
std::filesystem::create_directories(filename.parent_path());
}
~FileIoFixture()
{
std::error_code ec;
std::filesystem::remove(filename, ec); // ignore error
}
/**
* \brief Create a directory at `filename`, so that it's neither readable nor writable as a file.
*/
void
mkdir() const
{
std::filesystem::create_directory(filename);
}
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<typename Container::value_type>(ch));
}
return container;
}
template<typename Container>
void
writeFile(const Container& content) const
{
std::ofstream fs(filename, std::ios_base::binary);
BOOST_REQUIRE_MESSAGE(fs, "error opening file");
for (auto ch : content) {
fs.put(static_cast<char>(ch));
}
BOOST_REQUIRE_MESSAGE(fs, "error writing file");
}
protected:
const std::filesystem::path filename{std::filesystem::path(UNIT_TESTS_TMPDIR) / "TestIo"};
};
BOOST_FIXTURE_TEST_SUITE(FileIo, FileIoFixture)
class EncodableType
{
public:
Block
wireEncode() const
{
if (shouldThrow) {
NDN_THROW(tlv::Error("encode error"));
}
// block will be 0xAA, 0x01, 0xDD
return makeNonNegativeIntegerBlock(0xAA, 0xDD);
}
public:
bool shouldThrow = false;
};
class DecodableType
{
public:
DecodableType() = default;
explicit
DecodableType(const Block& block)
{
wireDecode(block);
}
void
wireDecode(const Block& block)
{
BOOST_TEST(block == "BB01EE"_block);
}
};
class DecodableTypeThrow
{
public:
DecodableTypeThrow() = default;
explicit
DecodableTypeThrow(const Block& block)
{
wireDecode(block);
}
void
wireDecode(const Block&)
{
NDN_THROW(tlv::Error("decode error"));
}
};
BOOST_AUTO_TEST_CASE(LoadNoEncoding)
{
this->writeFile<std::vector<uint8_t>>({0xBB, 0x01, 0xEE});
shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::NO_ENCODING);
BOOST_CHECK(decoded != nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_NO_THROW(io::loadTlv<DecodableType>(ifs, io::NO_ENCODING));
}
BOOST_AUTO_TEST_CASE(LoadBase64)
{
this->writeFile<std::string>("uwHu\n"); // printf '\xBB\x01\xEE' | base64
shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::BASE64);
BOOST_CHECK(decoded != nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_NO_THROW(io::loadTlv<DecodableType>(ifs, io::BASE64));
}
BOOST_AUTO_TEST_CASE(LoadBase64Newline64)
{
this->writeFile<std::string>(
"CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
"AAAAAAAAAAAA\n");
// printf '\x08\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
// \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
// \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
// \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | base64
shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
BOOST_CHECK(decoded != nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_NO_THROW(io::loadTlv<name::Component>(ifs, io::BASE64));
}
BOOST_AUTO_TEST_CASE(LoadBase64Newline32)
{
this->writeFile<std::string>(
"CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
"AAAAAAAAAAAA\n");
shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
BOOST_CHECK(decoded != nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_NO_THROW(io::loadTlv<name::Component>(ifs, io::BASE64));
}
BOOST_AUTO_TEST_CASE(LoadBase64NewlineEnd)
{
this->writeFile<std::string>(
"CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n");
shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
BOOST_CHECK(decoded != nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_NO_THROW(io::loadTlv<name::Component>(ifs, io::BASE64));
}
BOOST_AUTO_TEST_CASE(LoadBase64NoNewline)
{
this->writeFile<std::string>(
"CEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
shared_ptr<name::Component> decoded = io::load<name::Component>(filename, io::BASE64);
BOOST_CHECK(decoded != nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_NO_THROW(io::loadTlv<name::Component>(ifs, io::BASE64));
}
BOOST_AUTO_TEST_CASE(LoadHex)
{
this->writeFile<std::string>("BB01EE");
shared_ptr<DecodableType> decoded = io::load<DecodableType>(filename, io::HEX);
BOOST_CHECK(decoded != nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_NO_THROW(io::loadTlv<DecodableType>(ifs, io::HEX));
}
BOOST_AUTO_TEST_CASE(LoadDecodeException)
{
this->writeFile<std::vector<uint8_t>>({0xBB, 0x01, 0xEE});
shared_ptr<DecodableTypeThrow> decoded;
BOOST_CHECK_NO_THROW(decoded = io::load<DecodableTypeThrow>(filename, io::NO_ENCODING));
BOOST_CHECK(decoded == nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_THROW(io::loadTlv<DecodableTypeThrow>(ifs, io::NO_ENCODING), io::Error);
}
BOOST_AUTO_TEST_CASE(LoadNotHex)
{
this->writeFile<std::string>("not-hex");
shared_ptr<DecodableType> decoded;
BOOST_CHECK_NO_THROW(decoded = io::load<DecodableType>(filename, io::HEX));
BOOST_CHECK(decoded == nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_THROW(io::loadTlv<DecodableType>(ifs, io::HEX), io::Error);
}
BOOST_AUTO_TEST_CASE(LoadEmpty)
{
this->writeFile<std::vector<uint8_t>>({});
shared_ptr<DecodableType> decoded;
BOOST_CHECK_NO_THROW(decoded = io::load<DecodableType>(filename, io::NO_ENCODING));
BOOST_CHECK(decoded == nullptr);
std::ifstream ifs(filename);
BOOST_CHECK_THROW(io::loadTlv<DecodableType>(ifs, io::NO_ENCODING), io::Error);
}
BOOST_AUTO_TEST_CASE(LoadFileNotReadable)
{
shared_ptr<DecodableType> decoded;
BOOST_CHECK_NO_THROW(decoded = io::load<DecodableType>(filename, io::NO_ENCODING));
BOOST_CHECK(decoded == nullptr);
}
BOOST_AUTO_TEST_CASE(SaveNoEncoding)
{
EncodableType encoded;
BOOST_CHECK_NO_THROW(io::save(encoded, filename, io::NO_ENCODING));
auto content = this->readFile<std::vector<uint8_t>>();
uint8_t expected[] = {0xAA, 0x01, 0xDD};
BOOST_CHECK_EQUAL_COLLECTIONS(content.begin(), content.end(),
expected, expected + sizeof(expected));
}
BOOST_AUTO_TEST_CASE(SaveBase64)
{
EncodableType encoded;
BOOST_CHECK_NO_THROW(io::save(encoded, filename, io::BASE64));
auto content = this->readFile<std::string>();
BOOST_CHECK_EQUAL(content, "qgHd\n"); // printf '\xAA\x01\xDD' | base64
}
BOOST_AUTO_TEST_CASE(SaveHex)
{
EncodableType encoded;
BOOST_CHECK_NO_THROW(io::save(encoded, filename, io::HEX));
auto content = this->readFile<std::string>();
BOOST_CHECK_EQUAL(content, "AA01DD");
}
BOOST_AUTO_TEST_CASE(SaveException)
{
EncodableType encoded;
encoded.shouldThrow = true;
BOOST_CHECK_THROW(io::save(encoded, filename, io::NO_ENCODING), io::Error);
}
BOOST_AUTO_TEST_CASE(SaveFileNotWritable)
{
this->mkdir();
EncodableType encoded;
encoded.shouldThrow = true;
BOOST_CHECK_THROW(io::save(encoded, filename, io::NO_ENCODING), io::Error);
}
BOOST_AUTO_TEST_SUITE_END() // FileIo
class IdCertFixture : public FileIoFixture
, public KeyChainFixture
{
};
BOOST_FIXTURE_TEST_CASE(IdCert, IdCertFixture)
{
auto identity = m_keyChain.createIdentity("/TestIo/IdCert", RsaKeyParams());
auto key = identity.getDefaultKey();
const auto& cert = key.getDefaultCertificate();
io::save(cert, filename);
auto readCert = io::load<security::Certificate>(filename);
BOOST_REQUIRE(readCert != nullptr);
BOOST_CHECK_EQUAL(cert.getName(), readCert->getName());
this->writeFile<std::string>("");
readCert = io::load<security::Certificate>(filename);
BOOST_REQUIRE(readCert == nullptr);
}
BOOST_AUTO_TEST_SUITE_END() // TestIo
BOOST_AUTO_TEST_SUITE_END() // Util
} // namespace ndn::tests