blob: 226ae2bc30129c3ba34ec4bf73600066e39e6a47 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* Copyright (c) 2016 Regents of the University of California.
*
* This file is part of the nTorrent codebase.
*
* nTorrent 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.
*
* nTorrent 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 nTorrent, e.g., in COPYING.md file. If not, see
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS for complete list of nTorrent authors and contributors.
*/
#include "file-manifest.hpp"
#include "boost-test.hpp"
#include <vector>
#include <ndn-cxx/data.hpp>
#include <ndn-cxx/security/key-chain.hpp>
#include <ndn-cxx/security/signing-helpers.hpp>
#include <ndn-cxx/signature.hpp>
#include <ndn-cxx/util/io.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/lexical_cast.hpp>
namespace fs = boost::filesystem;
BOOST_TEST_DONT_PRINT_LOG_VALUE(std::nullptr_t)
BOOST_TEST_DONT_PRINT_LOG_VALUE(std::vector<ndn::Name>)
BOOST_TEST_DONT_PRINT_LOG_VALUE(std::vector<ndn::ntorrent::FileManifest>)
BOOST_TEST_DONT_PRINT_LOG_VALUE(std::vector<ndn::ntorrent::FileManifest>::iterator)
BOOST_TEST_DONT_PRINT_LOG_VALUE(std::vector<uint8_t>)
BOOST_TEST_DONT_PRINT_LOG_VALUE(std::vector<ndn::Data>::iterator)
namespace ndn {
namespace ntorrent {
namespace tests {
using std::vector;
using ndn::Name;
BOOST_AUTO_TEST_SUITE(TestFileManifest)
BOOST_AUTO_TEST_CASE(CheckPrimaryAccessorsAndManipulators)
{
FileManifest m1("/file0/1A2B3C4D", 256, "/foo/");
// Check the values on most simply constructed m1
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL(nullptr, m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({}), m1.catalog());
// Append names to catalog and recheck all salient attributes
m1.push_back("/foo/0/ABC123");
m1.push_back("/foo/1/DEADBEFF");
m1.push_back("/foo/2/CAFEBABE");
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL(nullptr, m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/0/ABC123", "/foo/1/DEADBEFF", "/foo/2/CAFEBABE"}),
m1.catalog());
// Remove a value from the catalog and recheck all salient attributes
BOOST_CHECK_EQUAL(true, m1.remove("/foo/0/ABC123"));
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL(nullptr, m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/1/DEADBEFF", "/foo/2/CAFEBABE"}), m1.catalog());
// Try to remove a value no longer in the catalog, and recheck that all salient attributes
BOOST_CHECK_EQUAL(false, m1.remove("/foo/0/ABC123"));
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL(nullptr, m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/1/DEADBEFF", "/foo/2/CAFEBABE"}), m1.catalog());
// Try to remove a value never in the catalog, and recheck that all salient attributes
BOOST_CHECK_EQUAL(false, m1.remove("/bar/0/ABC123"));
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL(nullptr, m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/1/DEADBEFF", "/foo/2/CAFEBABE"}),
m1.catalog());
// Remove a value from the end of the list
BOOST_CHECK_EQUAL(true, m1.remove("/foo/2/CAFEBABE"));
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL(nullptr, m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/1/DEADBEFF"}),
m1.catalog());
}
BOOST_AUTO_TEST_CASE(CheckValueCtors)
{
FileManifest m1("/file0/1A2B3C4D",
256,
"/foo/",
{"/foo/0/ABC123", "/foo/1/DEADBEFF", "/foo/2/CAFEBABE"},
std::make_shared<Name>("/file0/1/5E6F7G8H"));
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL("/file0/1/5E6F7G8H", *m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/0/ABC123", "/foo/1/DEADBEFF", "/foo/2/CAFEBABE"}),
m1.catalog());
// Remove a value from the catalog and recheck all salient attributes
BOOST_CHECK_EQUAL(true, m1.remove("/foo/0/ABC123"));
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL("/file0/1/5E6F7G8H", *m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/1/DEADBEFF", "/foo/2/CAFEBABE"}), m1.catalog());
// Try to remove a value no longer in the catalog, and recheck that all salient attributes
BOOST_CHECK_EQUAL(false, m1.remove("/foo/0/ABC123"));
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL("/file0/1/5E6F7G8H", *m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/1/DEADBEFF", "/foo/2/CAFEBABE"}), m1.catalog());
// Try to remove a value never in the catalog, and recheck that all salient attributes
BOOST_CHECK_EQUAL(false, m1.remove("/bar/0/ABC123"));
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL("/file0/1/5E6F7G8H", *m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/1/DEADBEFF", "/foo/2/CAFEBABE"}), m1.catalog());
// Remove a value from the end of the list
BOOST_CHECK_EQUAL(true, m1.remove("/foo/2/CAFEBABE"));
BOOST_CHECK_EQUAL("/file0/1A2B3C4D", m1.name());
BOOST_CHECK_EQUAL(256, m1.data_packet_size());
BOOST_CHECK_EQUAL("/file0/1/5E6F7G8H", *m1.submanifest_ptr());
BOOST_CHECK_EQUAL("/foo/", m1.catalog_prefix());
BOOST_CHECK_EQUAL(vector<Name>({"/foo/1/DEADBEFF"}), m1.catalog());
}
BOOST_AUTO_TEST_CASE(CheckAssignmentOperatorEqaulityAndCopyCtor)
{
// Construct two manifests with the same attributes, and check that they related equal
{
FileManifest m1("/file0/1A2B3C4D",
256,
"/foo/",
vector<Name>({}),
std::make_shared<Name>("/file0/1/5E6F7G8H"));
FileManifest m2("/file0/1A2B3C4D",
256,
"/foo/",
vector<Name>({}),
std::make_shared<Name>("/file0/1/5E6F7G8H"));
BOOST_CHECK_EQUAL(m1, m2);
BOOST_CHECK(!(m1 != m2));
// Change value of one
m1.push_back("/foo/0/ABC123");
BOOST_CHECK_NE(m1, m2);
BOOST_CHECK(!(m1 == m2));
// Update other
m2.push_back("/foo/0/ABC123");
BOOST_CHECK_EQUAL(m1, m2);
BOOST_CHECK(!(m1 != m2));
// Change value again
m1.remove("/foo/0/ABC123");
BOOST_CHECK_NE(m1, m2);
BOOST_CHECK(!(m1 == m2));
m2.remove("/foo/0/ABC123");
BOOST_CHECK_EQUAL(m1, m2);
BOOST_CHECK(!(m1 != m2));
}
// Set sub-manifest pointer in one and not the other
{
FileManifest m1("/file0/1A2B3C4D",
256,
"/foo/",
vector<Name>({}),
std::make_shared<Name>("/file0/1/5E6F7G8H"));
FileManifest m2("/file0/1A2B3C4D", 256, "/foo/");
BOOST_CHECK_NE(m1, m2);
BOOST_CHECK(!(m1 == m2));
std::swap(m1, m2);
BOOST_CHECK_NE(m1, m2);
BOOST_CHECK(!(m1 == m2));
}
// Construct two manifests using copy ctor for one
{
FileManifest m1("/file0/1A2B3C4D", 256, "/foo/");
FileManifest m2(m1);
BOOST_CHECK_EQUAL(m1, m2);
BOOST_CHECK(!(m1 != m2));
// Change value of one
m1.push_back("/foo/0/ABC123");
BOOST_CHECK_NE(m1, m2);
BOOST_CHECK(!(m1 == m2));
// Update other
m2.push_back("/foo/0/ABC123");
BOOST_CHECK_EQUAL(m1, m2);
BOOST_CHECK(!(m1 != m2));
// Change value again
m1.remove("/foo/0/ABC123");
BOOST_CHECK_NE(m1, m2);
BOOST_CHECK(!(m1 == m2));
m2.remove("/foo/0/ABC123");
BOOST_CHECK_EQUAL(m1, m2);
BOOST_CHECK(!(m1 != m2));
}
// Use assignment operator
{
FileManifest m1("/file1/1A2B3C4D", 256, "/foo/");
FileManifest m2("/file1/5E6F7G8H", 256, "/foo/");
BOOST_CHECK_NE(m1, m2);
BOOST_CHECK(!(m1 == m2));
m2 = m1;
BOOST_CHECK_EQUAL(m1, m2);
BOOST_CHECK(!(m1 != m2));
// Change value of one
m1.push_back("/foo/0/ABC123");
BOOST_CHECK_NE(m1, m2);
BOOST_CHECK(!(m1 == m2));
// Update other
m2.push_back("/foo/0/ABC123");
BOOST_CHECK_EQUAL(m1, m2);
BOOST_CHECK(!(m1 != m2));
// Change value again
m1.remove("/foo/0/ABC123");
BOOST_CHECK_NE(m1, m2);
BOOST_CHECK(!(m1 == m2));
m2.remove("/foo/0/ABC123");
BOOST_CHECK_EQUAL(m1, m2);
BOOST_CHECK(!(m1 != m2));
}
}
BOOST_AUTO_TEST_CASE(CheckEncodeDecode)
{
// Construct new FileManifest from wire encoding of another FileManifest
{
FileManifest m1("/file0/1A2B3C4D",
256,
"/foo/",
{"/foo/0/ABC123", "/foo/1/DEADBEFF", "/foo/2/CAFEBABE"},
std::make_shared<Name>("/file0/1/5E6F7G8H"));
KeyChain keyChain;
m1.finalize();
keyChain.sign(m1);
BOOST_CHECK_EQUAL(m1, FileManifest(m1.wireEncode()));
// Change value and be sure that wireEncoding still works
m1.remove("/foo/2/CAFEBABE");
m1.finalize();
keyChain.sign(m1);
BOOST_CHECK_EQUAL(m1, FileManifest(m1.wireEncode()));
// Explicitly call wireEncode and ensure the value works
FileManifest m2 = m1;
m1.remove("/foo/0/ABC123");
keyChain.sign(m1);
m1.finalize();
m2.wireDecode(m1.wireEncode());
BOOST_CHECK_EQUAL(m1, m2);
}
{
FileManifest m1("/file0/1A2B3C4D",
256,
"/foo/",
{"/foo/0/ABC123", "/foo/1/DEADBEFF", "/foo/2/CAFEBABE"});
KeyChain keyChain;
m1.finalize();
keyChain.sign(m1);
BOOST_CHECK_EQUAL(m1, FileManifest(m1.wireEncode()));
}
}
BOOST_AUTO_TEST_CASE(CheckGenerateFileManifest)
{
const size_t TEST_FILE_LEN = fs::file_size("tests/testdata/foo/bar.txt");
const struct {
size_t d_dataPacketSize;
size_t d_subManifestSize;
const char *d_filePath;
const char *d_catalogPrefix;
bool d_getData;
bool d_shouldThrow;
} DATA [] = {
// Affirmative tests
{1 , TEST_FILE_LEN, "tests/testdata/foo/bar.txt" , "/NTORRENT/foo/", true, false },
{10 , 10 , "tests/testdata/foo/bar.txt" , "/NTORRENT/foo/", true, false },
{10 , 1 , "tests/testdata/foo/bar.txt" , "/NTORRENT/foo/", true, false },
{1 , 10 , "tests/testdata/foo/bar.txt" , "/NTORRENT/foo/", true, false },
{1 , 1 , "tests/testdata/foo/bar.txt" , "/NTORRENT/foo/", true, false },
{1024 , 1 , "tests/testdata/foo/bar1.txt", "/NTORRENT/foo/", true, false },
{1024 , 100 , "tests/testdata/foo/bar1.txt", "/NTORRENT/foo/", true, false },
{TEST_FILE_LEN, 1 , "tests/testdata/foo/bar.txt" , "/NTORRENT/foo/", true, false },
// Negative tests
// non-existent file
{128 , 128 , "tests/testdata/foo/fake.txt", "/NTORRENT/foo/", false, true },
// // prefix mismatch
{128 , 128 , "tests/testdata/foo/bar.txt", "/NTORRENT/bar/", false, true },
// scaling test
// {10240 , 5120 , "tests/testdata/foo/huge_file", "/NTORRENT/foo/", false, false },
// assertion failures (tests not supported on platforms)
// {0 , 128 , "tests/testdata/foo/bar.txt", "/NTORRENT/bar/", true },
// {128 , 0 , "tests/testdata/foo/bar.txt", "/NTORRENT/bar/", true },
};
enum { NUM_DATA = sizeof DATA / sizeof *DATA };
for (int i = 0; i < NUM_DATA; ++i) {
auto dataPacketSize = DATA[i].d_dataPacketSize;
auto subManifestSize = DATA[i].d_subManifestSize;
auto filePath = DATA[i].d_filePath;
auto catalogPrefix = DATA[i].d_catalogPrefix;
auto getData = DATA[i].d_getData;
auto shouldThrow = DATA[i].d_shouldThrow;
std::pair<std::vector<FileManifest>, std::vector<Data>> manifestsDataPair;
if (shouldThrow) {
BOOST_CHECK_THROW(FileManifest::generate(filePath,
catalogPrefix,
subManifestSize,
dataPacketSize,
getData),
FileManifest::Error);
}
else {
manifestsDataPair = FileManifest::generate(filePath,
catalogPrefix,
subManifestSize,
dataPacketSize,
getData);
auto fileSize = fs::file_size(filePath);
auto EXP_NUM_DATA_PACKETS = fileSize / dataPacketSize + !!(fileSize % dataPacketSize);
auto EXP_NUM_FILE_MANIFESTS = EXP_NUM_DATA_PACKETS / subManifestSize +
!!(EXP_NUM_DATA_PACKETS % subManifestSize);
auto manifests = manifestsDataPair.first;
auto data = manifestsDataPair.second;
// Verify the basic attributes of the manifest
BOOST_CHECK_EQUAL(manifests.size(), EXP_NUM_FILE_MANIFESTS);
for (auto it = manifests.begin(); it != manifests.end(); ++it) {
// Verify that each file manifest is signed.
BOOST_CHECK_NO_THROW(it->getFullName());
BOOST_CHECK_EQUAL(it->data_packet_size(), dataPacketSize);
BOOST_CHECK_EQUAL(it->catalog_prefix(), catalogPrefix);
auto block = it->wireEncode();
BOOST_CHECK_EQUAL(*it, FileManifest(block));
if (it != manifests.end() -1) {
BOOST_CHECK_EQUAL(it->catalog().size(), subManifestSize);
BOOST_CHECK_EQUAL(*(it->submanifest_ptr()), (it+1)->getFullName());
}
else {
BOOST_CHECK_LE(it->catalog().size(), subManifestSize);
BOOST_CHECK_EQUAL(it->submanifest_ptr(), nullptr);
}
}
// test that we can write the manifest to the disk and read it out again
{
std::string dirPath = "tests/testdata/temp/";
boost::filesystem::create_directory(dirPath);
auto fileNum = 0;
for (auto &m : manifests) {
fileNum++;
auto filename = dirPath + to_string(fileNum);
io::save(m, filename);
}
// read them back out
fileNum = 0;
for (auto &m : manifests) {
fileNum++;
auto filename = dirPath + to_string(fileNum);
auto manifest_ptr = io::load<FileManifest>(filename);
BOOST_CHECK_NE(manifest_ptr, nullptr);
BOOST_CHECK_EQUAL(m, *manifest_ptr);
}
fs::remove_all(dirPath);
}
// confirm the manifests catalogs includes exactly the data packets
if (getData) {
BOOST_CHECK_EQUAL(EXP_NUM_DATA_PACKETS, data.size());
auto data_it = data.begin();
size_t total_manifest_length = 0;
for (auto& m : manifests) {
total_manifest_length += m.wireEncode().value_size();
for (auto& data_name : m.catalog()) {
BOOST_CHECK_EQUAL(data_name, data_it->getFullName());
++data_it;
}
}
BOOST_CHECK_EQUAL(data_it, data.end());
std::vector<uint8_t> data_bytes;
data_bytes.reserve(fileSize);
for (const auto& d : data) {
auto content = d.getContent();
data_bytes.insert(data_bytes.end(), content.value_begin(), content.value_end());
}
data_bytes.shrink_to_fit();
// load the contents of the file from disk
fs::ifstream is(filePath, fs::ifstream::binary | fs::ifstream::in);
is >> std::noskipws;
std::istream_iterator<uint8_t> start(is), end;
std::vector<uint8_t> file_bytes;
file_bytes.reserve(fileSize);
file_bytes.insert(file_bytes.begin(), start, end);
file_bytes.shrink_to_fit();
BOOST_CHECK_EQUAL(file_bytes, data_bytes);
}
else {
BOOST_CHECK(data.empty());
BOOST_CHECK(data.capacity() == 0);
}
}
}
}
BOOST_AUTO_TEST_SUITE_END()
} // namespace tests
} // namespace nTorrent
} // namespace ndn