blob: 0d024750724e9da2f945ae768c2becbb8329109d [file] [log] [blame]
#include "util/io-util.hpp"
#include "file-manifest.hpp"
#include "torrent-file.hpp"
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <ndn-cxx/security/key-chain.hpp>
#include <ndn-cxx/security/signing-helpers.hpp>
namespace fs = boost::filesystem;
using std::string;
using std::vector;
namespace ndn {
namespace ntorrent {
std::vector<ndn::Data>
IoUtil::packetize_file(const fs::path& filePath,
const ndn::Name& commonPrefix,
size_t dataPacketSize,
size_t subManifestSize,
size_t subManifestNum)
{
BOOST_ASSERT(0 < dataPacketSize);
size_t APPROX_BUFFER_SIZE = std::numeric_limits<int>::max(); // 2 * 1024 * 1024 *1024
auto file_size = fs::file_size(filePath);
auto start_offset = subManifestNum * subManifestSize * dataPacketSize;
// determine the number of bytes in this submanifest
auto subManifestLength = subManifestSize * dataPacketSize;
auto remainingFileLength = file_size - start_offset;
subManifestLength = remainingFileLength < subManifestLength
? remainingFileLength
: subManifestLength;
vector<ndn::Data> packets;
packets.reserve(subManifestLength/dataPacketSize + 1);
fs::ifstream fs(filePath, fs::ifstream::binary);
if (!fs) {
BOOST_THROW_EXCEPTION(Data::Error("IO Error when opening" + filePath.string()));
}
// ensure that buffer is large enough to contain whole packets
// buffer size is either the entire file or the smallest number of data packets >= 2 GB
auto buffer_size =
subManifestLength < APPROX_BUFFER_SIZE ?
subManifestLength :
APPROX_BUFFER_SIZE % dataPacketSize == 0 ?
APPROX_BUFFER_SIZE :
APPROX_BUFFER_SIZE + dataPacketSize - (APPROX_BUFFER_SIZE % dataPacketSize);
vector<char> file_bytes;
file_bytes.reserve(buffer_size);
size_t bytes_read = 0;
fs.seekg(start_offset);
while(fs && bytes_read < subManifestLength && !fs.eof()) {
// read the file into the buffer
fs.read(&file_bytes.front(), buffer_size);
auto read_size = fs.gcount();
if (fs.bad() || read_size < 0) {
BOOST_THROW_EXCEPTION(Data::Error("IO Error when reading" + filePath.string()));
}
bytes_read += read_size;
char *curr_start = &file_bytes.front();
for (size_t i = 0u; i < buffer_size; i += dataPacketSize) {
// Build a packet from the data
Name packetName = commonPrefix;
packetName.appendSequenceNumber(packets.size());
Data d(packetName);
auto content_length = i + dataPacketSize > buffer_size ? buffer_size - i : dataPacketSize;
d.setContent(encoding::makeBinaryBlock(tlv::Content, curr_start, content_length));
curr_start += content_length;
// append to the collection
packets.push_back(d);
}
file_bytes.clear();
// recompute the buffer_size
buffer_size =
subManifestLength - bytes_read < APPROX_BUFFER_SIZE ?
subManifestLength - bytes_read :
APPROX_BUFFER_SIZE % dataPacketSize == 0 ?
APPROX_BUFFER_SIZE :
APPROX_BUFFER_SIZE + dataPacketSize - (APPROX_BUFFER_SIZE % dataPacketSize);
}
fs.close();
packets.shrink_to_fit();
ndn::security::KeyChain key_chain;
// sign all the packets
for (auto& p : packets) {
key_chain.sign(p, signingWithSha256());
}
return packets;
}
bool IoUtil::writeTorrentSegment(const TorrentFile& segment, const std::string& path)
{
// validate that this torrent segment belongs to our torrent
auto segmentNum = segment.getSegmentNumber();
// write to disk at path
if (!fs::exists(path)) {
fs::create_directories(path);
}
auto filename = path + to_string(segmentNum);
// if there is already a file on disk for this torrent segment, determine if we should override
if (fs::exists(filename)) {
auto segmentOnDisk_ptr = io::load<TorrentFile>(filename);
if (nullptr != segmentOnDisk_ptr && *segmentOnDisk_ptr == segment) {
return false;
}
}
io::save(segment, filename);
// add to collection
return true;
}
bool IoUtil::writeFileManifest(const FileManifest& manifest, const std::string& path)
{
auto subManifestNum = manifest.submanifest_number();
fs::path filename = path + manifest.file_name() + "/" + to_string(subManifestNum);
// write to disk at path
if (!fs::exists(filename.parent_path())) {
boost::filesystem::create_directories(filename.parent_path());
}
// if there is already a file on disk for this file manifest, determine if we should override
if (fs::exists(filename)) {
auto submanifestOnDisk_ptr = io::load<FileManifest>(filename.string());
if (nullptr != submanifestOnDisk_ptr && *submanifestOnDisk_ptr == manifest) {
return false;
}
}
io::save(manifest, filename.string());
return true;
}
bool
IoUtil::writeData(const Data& packet, const FileManifest& manifest, size_t subManifestSize, fs::fstream& os)
{
auto packetName = packet.getName();
auto packetNum = packetName.get(packetName.size() - 1).toSequenceNumber();
auto dataPacketSize = manifest.data_packet_size();
auto initial_offset = manifest.submanifest_number() * subManifestSize * dataPacketSize;
auto packetOffset = initial_offset + packetNum * dataPacketSize;
// write data to disk
os.seekp(packetOffset);
try {
auto content = packet.getContent();
std::vector<char> data(content.value_begin(), content.value_end());
os.write(&data[0], data.size());
return true;
}
catch (io::Error &e) {
std::cerr << e.what() << std::endl;
return false;
}
}
std::shared_ptr<Data>
IoUtil::readDataPacket(const Name& packetFullName,
const FileManifest& manifest,
size_t subManifestSize,
fs::fstream& is)
{
auto dataPacketSize = manifest.data_packet_size();
auto start_offset = manifest.submanifest_number() * subManifestSize * dataPacketSize;
auto packetNum = packetFullName.get(packetFullName.size() - 2).toSequenceNumber();
// seek to packet
is.sync();
is.seekg(start_offset + packetNum * dataPacketSize);
if (is.tellg() < 0) {
std::cerr << "bad seek" << std::endl;
}
// read contents
std::vector<char> bytes(dataPacketSize);
is.read(&bytes.front(), dataPacketSize);
auto read_size = is.gcount();
if (is.bad() || read_size < 0) {
std::cerr << "Bad read" << std::endl;
return nullptr;
}
// construct packet
auto packetName = packetFullName.getSubName(0, packetFullName.size() - 1);
auto d = make_shared<Data>(packetName);
d->setContent(encoding::makeBinaryBlock(tlv::Content, &bytes.front(), read_size));
ndn::security::KeyChain key_chain;
key_chain.sign(*d, signingWithSha256());
return d->getFullName() == packetFullName ? d : nullptr;
}
IoUtil::NAME_TYPE
IoUtil::findType(const Name& name)
{
NAME_TYPE rval = UNKNOWN;
if (name.get(name.size() - 2).toUri() == "torrent-file" ||
name.get(name.size() - 3).toUri() == "torrent-file") {
rval = TORRENT_FILE;
}
else if (name.get(name.size() - 2).isSequenceNumber() &&
name.get(name.size() - 3).isSequenceNumber()) {
rval = DATA_PACKET;
}
else if (name.get(name.size() - 2).isSequenceNumber() &&
!(name.get(name.size() - 3).isSequenceNumber())) {
rval = FILE_MANIFEST;
}
return rval;
}
} // namespace ntorrent
} // namespace ndn}