blob: 0d024750724e9da2f945ae768c2becbb8329109d [file] [log] [blame]
Mickey Sweattfcbfb3d2016-04-13 17:05:17 -07001#include "util/io-util.hpp"
2
3#include "file-manifest.hpp"
4#include "torrent-file.hpp"
5
6#include <boost/filesystem.hpp>
7#include <boost/filesystem/fstream.hpp>
8
9#include <ndn-cxx/security/key-chain.hpp>
10#include <ndn-cxx/security/signing-helpers.hpp>
11
12namespace fs = boost::filesystem;
13
14using std::string;
15using std::vector;
16
17namespace ndn {
18namespace ntorrent {
19
20std::vector<ndn::Data>
21IoUtil::packetize_file(const fs::path& filePath,
22 const ndn::Name& commonPrefix,
23 size_t dataPacketSize,
24 size_t subManifestSize,
25 size_t subManifestNum)
26{
27 BOOST_ASSERT(0 < dataPacketSize);
28 size_t APPROX_BUFFER_SIZE = std::numeric_limits<int>::max(); // 2 * 1024 * 1024 *1024
29 auto file_size = fs::file_size(filePath);
30 auto start_offset = subManifestNum * subManifestSize * dataPacketSize;
31 // determine the number of bytes in this submanifest
32 auto subManifestLength = subManifestSize * dataPacketSize;
33 auto remainingFileLength = file_size - start_offset;
34 subManifestLength = remainingFileLength < subManifestLength
35 ? remainingFileLength
36 : subManifestLength;
37 vector<ndn::Data> packets;
38 packets.reserve(subManifestLength/dataPacketSize + 1);
39 fs::ifstream fs(filePath, fs::ifstream::binary);
40 if (!fs) {
41 BOOST_THROW_EXCEPTION(Data::Error("IO Error when opening" + filePath.string()));
42 }
43 // ensure that buffer is large enough to contain whole packets
44 // buffer size is either the entire file or the smallest number of data packets >= 2 GB
45 auto buffer_size =
46 subManifestLength < APPROX_BUFFER_SIZE ?
47 subManifestLength :
48 APPROX_BUFFER_SIZE % dataPacketSize == 0 ?
49 APPROX_BUFFER_SIZE :
50 APPROX_BUFFER_SIZE + dataPacketSize - (APPROX_BUFFER_SIZE % dataPacketSize);
51 vector<char> file_bytes;
52 file_bytes.reserve(buffer_size);
53 size_t bytes_read = 0;
54 fs.seekg(start_offset);
55 while(fs && bytes_read < subManifestLength && !fs.eof()) {
56 // read the file into the buffer
57 fs.read(&file_bytes.front(), buffer_size);
58 auto read_size = fs.gcount();
59 if (fs.bad() || read_size < 0) {
60 BOOST_THROW_EXCEPTION(Data::Error("IO Error when reading" + filePath.string()));
61 }
62 bytes_read += read_size;
63 char *curr_start = &file_bytes.front();
64 for (size_t i = 0u; i < buffer_size; i += dataPacketSize) {
65 // Build a packet from the data
66 Name packetName = commonPrefix;
67 packetName.appendSequenceNumber(packets.size());
68 Data d(packetName);
69 auto content_length = i + dataPacketSize > buffer_size ? buffer_size - i : dataPacketSize;
70 d.setContent(encoding::makeBinaryBlock(tlv::Content, curr_start, content_length));
71 curr_start += content_length;
72 // append to the collection
73 packets.push_back(d);
74 }
75 file_bytes.clear();
76 // recompute the buffer_size
77 buffer_size =
78 subManifestLength - bytes_read < APPROX_BUFFER_SIZE ?
79 subManifestLength - bytes_read :
80 APPROX_BUFFER_SIZE % dataPacketSize == 0 ?
81 APPROX_BUFFER_SIZE :
82 APPROX_BUFFER_SIZE + dataPacketSize - (APPROX_BUFFER_SIZE % dataPacketSize);
83 }
84 fs.close();
85 packets.shrink_to_fit();
86 ndn::security::KeyChain key_chain;
87 // sign all the packets
88 for (auto& p : packets) {
89 key_chain.sign(p, signingWithSha256());
90 }
91 return packets;
92}
93
94bool IoUtil::writeTorrentSegment(const TorrentFile& segment, const std::string& path)
95{
96 // validate that this torrent segment belongs to our torrent
97 auto segmentNum = segment.getSegmentNumber();
98 // write to disk at path
99 if (!fs::exists(path)) {
100 fs::create_directories(path);
101 }
102 auto filename = path + to_string(segmentNum);
103 // if there is already a file on disk for this torrent segment, determine if we should override
104 if (fs::exists(filename)) {
105 auto segmentOnDisk_ptr = io::load<TorrentFile>(filename);
106 if (nullptr != segmentOnDisk_ptr && *segmentOnDisk_ptr == segment) {
107 return false;
108 }
109 }
110 io::save(segment, filename);
111 // add to collection
112 return true;
113}
114
115bool IoUtil::writeFileManifest(const FileManifest& manifest, const std::string& path)
116{
117 auto subManifestNum = manifest.submanifest_number();
118 fs::path filename = path + manifest.file_name() + "/" + to_string(subManifestNum);
119
120 // write to disk at path
121 if (!fs::exists(filename.parent_path())) {
122 boost::filesystem::create_directories(filename.parent_path());
123 }
124 // if there is already a file on disk for this file manifest, determine if we should override
125 if (fs::exists(filename)) {
126 auto submanifestOnDisk_ptr = io::load<FileManifest>(filename.string());
127 if (nullptr != submanifestOnDisk_ptr && *submanifestOnDisk_ptr == manifest) {
128 return false;
129 }
130 }
131 io::save(manifest, filename.string());
132 return true;
133}
134bool
135IoUtil::writeData(const Data& packet, const FileManifest& manifest, size_t subManifestSize, fs::fstream& os)
136{
137 auto packetName = packet.getName();
138 auto packetNum = packetName.get(packetName.size() - 1).toSequenceNumber();
139 auto dataPacketSize = manifest.data_packet_size();
140 auto initial_offset = manifest.submanifest_number() * subManifestSize * dataPacketSize;
141 auto packetOffset = initial_offset + packetNum * dataPacketSize;
142 // write data to disk
143 os.seekp(packetOffset);
144 try {
145 auto content = packet.getContent();
146 std::vector<char> data(content.value_begin(), content.value_end());
147 os.write(&data[0], data.size());
148 return true;
149 }
150 catch (io::Error &e) {
151 std::cerr << e.what() << std::endl;
152 return false;
153 }
154}
155
156std::shared_ptr<Data>
157IoUtil::readDataPacket(const Name& packetFullName,
158 const FileManifest& manifest,
159 size_t subManifestSize,
160 fs::fstream& is)
161{
162 auto dataPacketSize = manifest.data_packet_size();
163 auto start_offset = manifest.submanifest_number() * subManifestSize * dataPacketSize;
164 auto packetNum = packetFullName.get(packetFullName.size() - 2).toSequenceNumber();
165 // seek to packet
166 is.sync();
167 is.seekg(start_offset + packetNum * dataPacketSize);
168 if (is.tellg() < 0) {
169 std::cerr << "bad seek" << std::endl;
170 }
171 // read contents
172 std::vector<char> bytes(dataPacketSize);
173 is.read(&bytes.front(), dataPacketSize);
174 auto read_size = is.gcount();
175 if (is.bad() || read_size < 0) {
176 std::cerr << "Bad read" << std::endl;
177 return nullptr;
178 }
179 // construct packet
180 auto packetName = packetFullName.getSubName(0, packetFullName.size() - 1);
181 auto d = make_shared<Data>(packetName);
182 d->setContent(encoding::makeBinaryBlock(tlv::Content, &bytes.front(), read_size));
183 ndn::security::KeyChain key_chain;
184 key_chain.sign(*d, signingWithSha256());
185 return d->getFullName() == packetFullName ? d : nullptr;
186}
187
188IoUtil::NAME_TYPE
189IoUtil::findType(const Name& name)
190{
191 NAME_TYPE rval = UNKNOWN;
192 if (name.get(name.size() - 2).toUri() == "torrent-file" ||
193 name.get(name.size() - 3).toUri() == "torrent-file") {
194 rval = TORRENT_FILE;
195 }
196 else if (name.get(name.size() - 2).isSequenceNumber() &&
197 name.get(name.size() - 3).isSequenceNumber()) {
198 rval = DATA_PACKET;
199 }
200 else if (name.get(name.size() - 2).isSequenceNumber() &&
201 !(name.get(name.size() - 3).isSequenceNumber())) {
202 rval = FILE_MANIFEST;
203 }
204 return rval;
205}
206
207
208
209} // namespace ntorrent
210} // namespace ndn}