blob: ca1714860e2910a8afa5b6c753fec7a03adf542d [file] [log] [blame]
Mickey Sweatt3b0bea62016-01-25 22:12:27 -08001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
3* Copyright (c) 2016 Regents of the University of California.
4*
5* This file is part of the nTorrent codebase.
6*
7* nTorrent is free software: you can redistribute it and/or modify it under the
8* terms of the GNU Lesser General Public License as published by the Free Software
9* Foundation, either version 3 of the License, or (at your option) any later version.
10*
11* nTorrent is distributed in the hope that it will be useful, but WITHOUT ANY
12* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
14*
15* You should have received copies of the GNU General Public License and GNU Lesser
16* General Public License along with nTorrent, e.g., in COPYING.md file. If not, see
17* <http://www.gnu.org/licenses/>.
18*
19* See AUTHORS for complete list of nTorrent authors and contributors.
20*/
21#include "file-manifest.hpp"
22
Mickey Sweattebc01952016-02-19 11:38:30 -080023#include <limits>
24
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080025#include <boost/assert.hpp>
Mickey Sweattebc01952016-02-19 11:38:30 -080026#include <boost/filesystem.hpp>
27#include <boost/filesystem/fstream.hpp>
28#include <boost/lexical_cast.hpp>
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080029#include <boost/range/adaptors.hpp>
Mickey Sweattebc01952016-02-19 11:38:30 -080030#include <boost/range/irange.hpp>
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080031#include <boost/throw_exception.hpp>
Mickey Sweattebc01952016-02-19 11:38:30 -080032#include <ndn-cxx/security/key-chain.hpp>
33#include <ndn-cxx/security/signing-helpers.hpp>
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080034
Mickey Sweatt6de5dde2016-03-15 16:44:56 -070035#include <ndn-cxx/encoding/tlv.hpp>
36
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080037using std::vector;
Mickey Sweattebc01952016-02-19 11:38:30 -080038using std::streamsize;
39using boost::irange;
40
41namespace fs = boost::filesystem;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080042
43namespace ndn {
44namespace ntorrent {
45
46BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FileManifest>));
47BOOST_CONCEPT_ASSERT((WireEncodable<FileManifest>));
48BOOST_CONCEPT_ASSERT((WireDecodable<FileManifest>));
49static_assert(std::is_base_of<Data::Error, FileManifest::Error>::value,
50 "FileManifest::Error should inherit from Data::Error");
51
Mickey Sweattebc01952016-02-19 11:38:30 -080052static ndn::Name
53get_name_of_manifest(const std::string& filePath, const Name& manifestPrefix)
54{
55 Name full_path(fs::system_complete(filePath).string());
56 // Search the filePath for the leading component that matches
57 auto name_component_iter = std::find(full_path.rbegin(),
58 full_path.rend(),
59 *manifestPrefix.rbegin());
60
61 if (full_path.rend() == name_component_iter) {
62 BOOST_THROW_EXCEPTION(FileManifest::Error("No matching name component between" +
63 manifestPrefix.toUri() + " and " +
64 full_path.toUri()));
65 }
Mickey Sweattbe4b5192016-03-28 16:46:34 -070066 ndn::Name manifestName = "/NTORRENT/";
Mickey Sweattebc01952016-02-19 11:38:30 -080067 // Rebuild the name to be the suffix from the matching component
68 for (auto it = (name_component_iter.base() - 1); full_path.end() != it; ++it) {
69 manifestName.append(*it);
70 }
71 return manifestName;
72}
73
74static std::vector<Data>
Mickey Sweattbe4b5192016-03-28 16:46:34 -070075packetize_file(const fs::path& filePath,
Mickey Sweattebc01952016-02-19 11:38:30 -080076 const ndn::Name& commonPrefix,
77 size_t dataPacketSize,
78 size_t subManifestSize,
79 size_t subManifestNum)
80{
81 BOOST_ASSERT(0 < dataPacketSize);
82 size_t APPROX_BUFFER_SIZE = std::numeric_limits<int>::max(); // 2 * 1024 * 1024 *1024
83 auto file_size = fs::file_size(filePath);
84 auto start_offset = subManifestNum * subManifestSize * dataPacketSize;
85 // determine the number of bytes in this submanifest
86 auto subManifestLength = subManifestSize * dataPacketSize;
87 auto remainingFileLength = file_size - start_offset;
88 subManifestLength = remainingFileLength < subManifestLength
89 ? remainingFileLength
90 : subManifestLength;
91 std::vector<Data> packets;
92 packets.reserve(subManifestLength/dataPacketSize + 1);
93 fs::ifstream fs(filePath, fs::ifstream::binary);
94 if (!fs) {
95 BOOST_THROW_EXCEPTION(FileManifest::Error("IO Error when opening" + filePath.string()));
96 }
97 // ensure that buffer is large enough to contain whole packets
98 // buffer size is either the entire file or the smallest number of data packets >= 2 GB
99 auto buffer_size =
100 subManifestLength < APPROX_BUFFER_SIZE ?
101 subManifestLength :
102 APPROX_BUFFER_SIZE % dataPacketSize == 0 ?
103 APPROX_BUFFER_SIZE :
104 APPROX_BUFFER_SIZE + dataPacketSize - (APPROX_BUFFER_SIZE % dataPacketSize);
105 std::vector<char> file_bytes;
106 file_bytes.reserve(buffer_size);
107 size_t bytes_read = 0;
108 fs.seekg(start_offset);
109 while(fs && bytes_read < subManifestLength && !fs.eof()) {
110 // read the file into the buffer
111 fs.read(&file_bytes.front(), buffer_size);
112 auto read_size = fs.gcount();
113 if (fs.bad() || read_size < 0) {
Mickey Sweattebc01952016-02-19 11:38:30 -0800114 BOOST_THROW_EXCEPTION(FileManifest::Error("IO Error when reading" + filePath.string()));
115 }
116 bytes_read += read_size;
117 char *curr_start = &file_bytes.front();
118 for (size_t i = 0u; i < buffer_size; i += dataPacketSize) {
119 // Build a packet from the data
120 Name packetName = commonPrefix;
121 packetName.appendSequenceNumber(packets.size());
122 Data d(packetName);
123 auto content_length = i + dataPacketSize > buffer_size ? buffer_size - i : dataPacketSize;
124 d.setContent(encoding::makeBinaryBlock(tlv::Content, curr_start, content_length));
125 curr_start += content_length;
126 // append to the collection
127 packets.push_back(d);
128 }
129 file_bytes.clear();
130 // recompute the buffer_size
131 buffer_size =
132 subManifestLength - bytes_read < APPROX_BUFFER_SIZE ?
133 subManifestLength - bytes_read :
134 APPROX_BUFFER_SIZE % dataPacketSize == 0 ?
135 APPROX_BUFFER_SIZE :
136 APPROX_BUFFER_SIZE + dataPacketSize - (APPROX_BUFFER_SIZE % dataPacketSize);
137 }
138 fs.close();
139 packets.shrink_to_fit();
140 security::KeyChain key_chain;
141 // sign all the packets
142 for (auto& p : packets) {
143 key_chain.sign(p, signingWithSha256());
144 }
145 return packets;
146}
147
148// CLASS METHODS
149std::pair<std::vector<FileManifest>, std::vector<Data>>
150FileManifest::generate(const std::string& filePath,
151 const Name& manifestPrefix,
152 size_t subManifestSize,
153 size_t dataPacketSize,
154 bool returnData)
155{
156 BOOST_ASSERT(0 < subManifestSize);
157 BOOST_ASSERT(0 < dataPacketSize);
158 std::vector<FileManifest> manifests;
159 fs::path path(filePath);
160 if (!fs::exists(path)) {
161 BOOST_THROW_EXCEPTION(Error(filePath + ": no such file."));
162 }
163 size_t file_length = fs::file_size(filePath);
164 // If the file_length is not evenly divisible by subManifestSize add 1, otherwise 0
165 size_t numSubManifests = file_length / (subManifestSize * dataPacketSize) +
166 !!(file_length % (subManifestSize * dataPacketSize));
167 // Find the prefix for the Catalog
168 auto manifestName = get_name_of_manifest(filePath, manifestPrefix);
169 std::vector<Data> allPackets;
170 if (returnData) {
171 allPackets.reserve(numSubManifests * subManifestSize);
172 }
173 manifests.reserve(numSubManifests);
174 for (auto subManifestNum : irange<size_t>(0, numSubManifests)) {
175 auto curr_manifest_name = manifestName;
Mickey Sweattebc01952016-02-19 11:38:30 -0800176 // append the packet number
177 curr_manifest_name.appendSequenceNumber(manifests.size());
Mickey Sweattebc01952016-02-19 11:38:30 -0800178 FileManifest curr_manifest(curr_manifest_name, dataPacketSize, manifestPrefix);
179 auto packets = packetize_file(path,
Mickey Sweattbe4b5192016-03-28 16:46:34 -0700180 curr_manifest_name,
Mickey Sweattebc01952016-02-19 11:38:30 -0800181 dataPacketSize,
182 subManifestSize,
183 subManifestNum);
184 if (returnData) {
185 allPackets.insert(allPackets.end(), packets.begin(), packets.end());
186 }
187 curr_manifest.reserve(packets.size());
188 // Collect all the Data packets into the sub-manifests
189 for (const auto& p: packets) {
190 curr_manifest.push_back(p.getFullName());
191 }
192 // append the last manifest
193 manifests.push_back(curr_manifest);
194 }
195 allPackets.shrink_to_fit();
196 manifests.shrink_to_fit();
197 // Set all the submanifest_ptrs and sign all the manifests
198 security::KeyChain key_chain;
199 manifests.back().finalize();
200 key_chain.sign(manifests.back(), signingWithSha256());
201 for (auto it = manifests.rbegin() + 1; it != manifests.rend(); ++it) {
202 auto next = it - 1;
203 it->set_submanifest_ptr(std::make_shared<Name>(next->getFullName()));
204 it->finalize();
205 key_chain.sign(*it, signingWithSha256());
206 }
207 return {manifests, allPackets};
208}
209
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800210void
211FileManifest::wireDecode(const Block& wire)
212{
213 Data::wireDecode(wire);
214 this->decodeContent();
215}
216
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800217template<ndn::encoding::Tag TAG>
218size_t
219FileManifest::encodeContent(ndn::EncodingImpl<TAG>& encoder) const {
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800220 // ManifestContent ::= CONTENT-TYPE TLV-LENGTH
221 // DataPacketName*
222 // CatalogPrefix
223 // DataPacketSize
224 // FileManifestPtr?
225
226 // DataPacketName ::= NAME-TYPE TLV-LENGTH
227 // Name
228
229 // CatalogPrefix ::= NAME-TYPE TLV-LENGTH
230 // Name
231
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700232 // DataPacketSize ::= CONTENT-TYPE TLV-LENGTH
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800233 // nonNegativeInteger
234
235 // FileManifestPtr ::= NAME-TYPE TLV-LENGTH
236 // Name
237
238 size_t totalLength = 0;
239
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700240 // build suffix catalog
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800241 vector<Name> suffixCatalog;
242 suffixCatalog.reserve(m_catalog.size());
243 for (auto name: m_catalog) {
244 if (!m_catalogPrefix.isPrefixOf(name)) {
Mickey Sweattebc01952016-02-19 11:38:30 -0800245 BOOST_THROW_EXCEPTION(Error(name.toUri() + " does not have the prefix "
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800246 + m_catalogPrefix.toUri()));
247 }
248 name = name.getSubName(m_catalogPrefix.size());
249 if (name.empty()) {
250 BOOST_THROW_EXCEPTION(Error("Manifest cannot include empty string"));
251 }
252 suffixCatalog.push_back(name);
253 }
254
255 for (const auto& name : suffixCatalog | boost::adaptors::reversed) {
256 totalLength += name.wireEncode(encoder);
257 }
258
259 totalLength += m_catalogPrefix.wireEncode(encoder);
260
261 totalLength += prependNonNegativeIntegerBlock(encoder, tlv::Content, m_dataPacketSize);
262
263 if (nullptr != m_submanifestPtr) {
264 totalLength += m_submanifestPtr->wireEncode(encoder);
265 }
266
267 totalLength += encoder.prependVarNumber(totalLength);
268 totalLength += encoder.prependVarNumber(tlv::Content);
269 return totalLength;
270
271}
272
273// MANIPULATORS
274void
275FileManifest::push_back(const Name& name)
276{
277 BOOST_ASSERT(name != m_catalogPrefix);
278 BOOST_ASSERT(m_catalogPrefix.isPrefixOf(name));
Mickey Sweattebc01952016-02-19 11:38:30 -0800279 // TODO(msweatt) Change this to use the copy constructor once bug fixed in Name()
280 m_catalog.push_back(name.toUri());
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800281}
282
283bool
284FileManifest::remove(const ndn::Name& name) {
Mickey Sweattebc01952016-02-19 11:38:30 -0800285 const auto it = std::find(m_catalog.begin(), m_catalog.end(), name);
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800286 if (m_catalog.end() == it) {
287 return false;
288 }
289 m_catalog.erase(it);
290 return true;
291}
292
Mickey Sweatta768b242016-02-29 20:08:05 -0800293void
294FileManifest::finalize() {
295 m_catalog.shrink_to_fit();
296 encodeContent();
297}
298
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800299void FileManifest::encodeContent() {
300 // Name
301 // <file_name>/ImplicitDigest
302 // Content
303 // MetaData
304 // DataPtr*
305 // ManifestPointer?
306 //
307 // DataPtr := HashValue
308 // ManifestPtr := HashValue
309 // HashValue := OCTET[32]
310
311 // MetaData := Property*
312 // Property := DataSize | Signature
313 onChanged();
314
315 EncodingEstimator estimator;
316 size_t estimatedSize = encodeContent(estimator);
317
318 EncodingBuffer buffer(estimatedSize, 0);
319 encodeContent(buffer);
320
321 setContentType(tlv::ContentType_Blob);
322 setContent(buffer.block());
323}
324
325void
326FileManifest::decodeContent() {
327 // ManifestContent ::= CONTENT-TYPE TLV-LENGTH
328 // DataPacketName*
329 // CatalogPrefix
330 // DataPacketSize
331 // FileManifestPtr?
332
333
334 // DataPacketName ::= NAME-TYPE TLV-LENGTH
335 // Name
336
337 // CatalogPrefix ::= NAME-TYPE TLV-LENGTH
338 // Name
339
340 // DataPacketSize ::= CONTENT-TYPE TLV-LENGTH
341 // nonNegativeInteger
342
343 // FileManifestPtr ::= NAME-TYPE TLV-LENGTH
344 // Name
345
346 if (getContentType() != tlv::ContentType_Blob) {
347 BOOST_THROW_EXCEPTION(Error("Expected Content Type Blob"));
348 }
349
350 const Block& content = Data::getContent();
351 content.parse();
352
353 auto element = content.elements_begin();
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800354 if (content.elements_end() == element) {
355 BOOST_THROW_EXCEPTION(Error("FileManifest with empty content"));
356 }
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700357 if (element->type() == tlv::Name) {
358 Name name(*element);
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800359 m_submanifestPtr = std::make_shared<Name>(name);
360 ++element;
361 }
362
363 // DataPacketSize
364 m_dataPacketSize = readNonNegativeInteger(*element);
365 ++element;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800366 // CatalogPrefix
367 m_catalogPrefix = Name(*element);
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700368 ++element;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800369 // Catalog
370 m_catalog.clear();
371 for (; element != content.elements_end(); ++element) {
372 element->parse();
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700373 Name name = m_catalogPrefix;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800374 name.append(Name(*element));
375 if (name == m_catalogPrefix) {
376 BOOST_THROW_EXCEPTION(Error("Empty name included in a FileManifest"));
377 }
378 push_back(name);
379 }
380}
381
382bool operator==(const FileManifest& lhs, const FileManifest& rhs) {
383 return lhs.name() == rhs.name()
384 && lhs.data_packet_size() == rhs.data_packet_size()
385 && (lhs.submanifest_ptr() == rhs.submanifest_ptr() /* shallow equality */
386 || ( nullptr != lhs.submanifest_ptr()
387 && nullptr != rhs.submanifest_ptr()
388 && *rhs.submanifest_ptr() == *lhs.submanifest_ptr()
389 )
390 )
391 && lhs.catalog() == rhs.catalog();
392}
393
394bool operator!=(const FileManifest& lhs, const FileManifest& rhs) {
395 return lhs.name() != rhs.name()
396 || lhs.data_packet_size() != rhs.data_packet_size()
397 || (lhs.submanifest_ptr() != rhs.submanifest_ptr() /* shallow equality */
398 && (nullptr == lhs.submanifest_ptr()
399 || nullptr == rhs.submanifest_ptr()
400 || *rhs.submanifest_ptr() != *lhs.submanifest_ptr()
401 )
402 )
403 || lhs.catalog() != rhs.catalog();
404}
405
406} // end ntorrent
407} // end ndn