blob: 40726e67e95e8431987e766e5cddbe9a70fd1e06 [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 }
66 ndn::Name manifestName;
67 // 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>
75packetize_file(const fs::path& filePath,
76 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) {
114
115 BOOST_THROW_EXCEPTION(FileManifest::Error("IO Error when reading" + filePath.string()));
116 }
117 bytes_read += read_size;
118 char *curr_start = &file_bytes.front();
119 for (size_t i = 0u; i < buffer_size; i += dataPacketSize) {
120 // Build a packet from the data
121 Name packetName = commonPrefix;
122 packetName.appendSequenceNumber(packets.size());
123 Data d(packetName);
124 auto content_length = i + dataPacketSize > buffer_size ? buffer_size - i : dataPacketSize;
125 d.setContent(encoding::makeBinaryBlock(tlv::Content, curr_start, content_length));
126 curr_start += content_length;
127 // append to the collection
128 packets.push_back(d);
129 }
130 file_bytes.clear();
131 // recompute the buffer_size
132 buffer_size =
133 subManifestLength - bytes_read < APPROX_BUFFER_SIZE ?
134 subManifestLength - bytes_read :
135 APPROX_BUFFER_SIZE % dataPacketSize == 0 ?
136 APPROX_BUFFER_SIZE :
137 APPROX_BUFFER_SIZE + dataPacketSize - (APPROX_BUFFER_SIZE % dataPacketSize);
138 }
139 fs.close();
140 packets.shrink_to_fit();
141 security::KeyChain key_chain;
142 // sign all the packets
143 for (auto& p : packets) {
144 key_chain.sign(p, signingWithSha256());
145 }
146 return packets;
147}
148
149// CLASS METHODS
150std::pair<std::vector<FileManifest>, std::vector<Data>>
151FileManifest::generate(const std::string& filePath,
152 const Name& manifestPrefix,
153 size_t subManifestSize,
154 size_t dataPacketSize,
155 bool returnData)
156{
157 BOOST_ASSERT(0 < subManifestSize);
158 BOOST_ASSERT(0 < dataPacketSize);
159 std::vector<FileManifest> manifests;
160 fs::path path(filePath);
161 if (!fs::exists(path)) {
162 BOOST_THROW_EXCEPTION(Error(filePath + ": no such file."));
163 }
164 size_t file_length = fs::file_size(filePath);
165 // If the file_length is not evenly divisible by subManifestSize add 1, otherwise 0
166 size_t numSubManifests = file_length / (subManifestSize * dataPacketSize) +
167 !!(file_length % (subManifestSize * dataPacketSize));
168 // Find the prefix for the Catalog
169 auto manifestName = get_name_of_manifest(filePath, manifestPrefix);
170 std::vector<Data> allPackets;
171 if (returnData) {
172 allPackets.reserve(numSubManifests * subManifestSize);
173 }
174 manifests.reserve(numSubManifests);
175 for (auto subManifestNum : irange<size_t>(0, numSubManifests)) {
176 auto curr_manifest_name = manifestName;
177 curr_manifest_name.append("manifest" + boost::lexical_cast<std::string>(subManifestNum));
178 // append the packet number
179 curr_manifest_name.appendSequenceNumber(manifests.size());
180 auto subManifestPrefix = manifestPrefix;
181 subManifestPrefix.appendNumber(subManifestNum);
182 FileManifest curr_manifest(curr_manifest_name, dataPacketSize, manifestPrefix);
183 auto packets = packetize_file(path,
184 subManifestPrefix,
185 dataPacketSize,
186 subManifestSize,
187 subManifestNum);
188 if (returnData) {
189 allPackets.insert(allPackets.end(), packets.begin(), packets.end());
190 }
191 curr_manifest.reserve(packets.size());
192 // Collect all the Data packets into the sub-manifests
193 for (const auto& p: packets) {
194 curr_manifest.push_back(p.getFullName());
195 }
196 // append the last manifest
197 manifests.push_back(curr_manifest);
198 }
199 allPackets.shrink_to_fit();
200 manifests.shrink_to_fit();
201 // Set all the submanifest_ptrs and sign all the manifests
202 security::KeyChain key_chain;
203 manifests.back().finalize();
204 key_chain.sign(manifests.back(), signingWithSha256());
205 for (auto it = manifests.rbegin() + 1; it != manifests.rend(); ++it) {
206 auto next = it - 1;
207 it->set_submanifest_ptr(std::make_shared<Name>(next->getFullName()));
208 it->finalize();
209 key_chain.sign(*it, signingWithSha256());
210 }
211 return {manifests, allPackets};
212}
213
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800214void
215FileManifest::wireDecode(const Block& wire)
216{
217 Data::wireDecode(wire);
218 this->decodeContent();
219}
220
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800221template<ndn::encoding::Tag TAG>
222size_t
223FileManifest::encodeContent(ndn::EncodingImpl<TAG>& encoder) const {
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800224 // ManifestContent ::= CONTENT-TYPE TLV-LENGTH
225 // DataPacketName*
226 // CatalogPrefix
227 // DataPacketSize
228 // FileManifestPtr?
229
230 // DataPacketName ::= NAME-TYPE TLV-LENGTH
231 // Name
232
233 // CatalogPrefix ::= NAME-TYPE TLV-LENGTH
234 // Name
235
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700236 // DataPacketSize ::= CONTENT-TYPE TLV-LENGTH
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800237 // nonNegativeInteger
238
239 // FileManifestPtr ::= NAME-TYPE TLV-LENGTH
240 // Name
241
242 size_t totalLength = 0;
243
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700244 // build suffix catalog
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800245 vector<Name> suffixCatalog;
246 suffixCatalog.reserve(m_catalog.size());
247 for (auto name: m_catalog) {
248 if (!m_catalogPrefix.isPrefixOf(name)) {
Mickey Sweattebc01952016-02-19 11:38:30 -0800249 BOOST_THROW_EXCEPTION(Error(name.toUri() + " does not have the prefix "
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800250 + m_catalogPrefix.toUri()));
251 }
252 name = name.getSubName(m_catalogPrefix.size());
253 if (name.empty()) {
254 BOOST_THROW_EXCEPTION(Error("Manifest cannot include empty string"));
255 }
256 suffixCatalog.push_back(name);
257 }
258
259 for (const auto& name : suffixCatalog | boost::adaptors::reversed) {
260 totalLength += name.wireEncode(encoder);
261 }
262
263 totalLength += m_catalogPrefix.wireEncode(encoder);
264
265 totalLength += prependNonNegativeIntegerBlock(encoder, tlv::Content, m_dataPacketSize);
266
267 if (nullptr != m_submanifestPtr) {
268 totalLength += m_submanifestPtr->wireEncode(encoder);
269 }
270
271 totalLength += encoder.prependVarNumber(totalLength);
272 totalLength += encoder.prependVarNumber(tlv::Content);
273 return totalLength;
274
275}
276
277// MANIPULATORS
278void
279FileManifest::push_back(const Name& name)
280{
281 BOOST_ASSERT(name != m_catalogPrefix);
282 BOOST_ASSERT(m_catalogPrefix.isPrefixOf(name));
Mickey Sweattebc01952016-02-19 11:38:30 -0800283 // TODO(msweatt) Change this to use the copy constructor once bug fixed in Name()
284 m_catalog.push_back(name.toUri());
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800285}
286
287bool
288FileManifest::remove(const ndn::Name& name) {
Mickey Sweattebc01952016-02-19 11:38:30 -0800289 const auto it = std::find(m_catalog.begin(), m_catalog.end(), name);
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800290 if (m_catalog.end() == it) {
291 return false;
292 }
293 m_catalog.erase(it);
294 return true;
295}
296
Mickey Sweatta768b242016-02-29 20:08:05 -0800297void
298FileManifest::finalize() {
299 m_catalog.shrink_to_fit();
300 encodeContent();
301}
302
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800303void FileManifest::encodeContent() {
304 // Name
305 // <file_name>/ImplicitDigest
306 // Content
307 // MetaData
308 // DataPtr*
309 // ManifestPointer?
310 //
311 // DataPtr := HashValue
312 // ManifestPtr := HashValue
313 // HashValue := OCTET[32]
314
315 // MetaData := Property*
316 // Property := DataSize | Signature
317 onChanged();
318
319 EncodingEstimator estimator;
320 size_t estimatedSize = encodeContent(estimator);
321
322 EncodingBuffer buffer(estimatedSize, 0);
323 encodeContent(buffer);
324
325 setContentType(tlv::ContentType_Blob);
326 setContent(buffer.block());
327}
328
329void
330FileManifest::decodeContent() {
331 // ManifestContent ::= CONTENT-TYPE TLV-LENGTH
332 // DataPacketName*
333 // CatalogPrefix
334 // DataPacketSize
335 // FileManifestPtr?
336
337
338 // DataPacketName ::= NAME-TYPE TLV-LENGTH
339 // Name
340
341 // CatalogPrefix ::= NAME-TYPE TLV-LENGTH
342 // Name
343
344 // DataPacketSize ::= CONTENT-TYPE TLV-LENGTH
345 // nonNegativeInteger
346
347 // FileManifestPtr ::= NAME-TYPE TLV-LENGTH
348 // Name
349
350 if (getContentType() != tlv::ContentType_Blob) {
351 BOOST_THROW_EXCEPTION(Error("Expected Content Type Blob"));
352 }
353
354 const Block& content = Data::getContent();
355 content.parse();
356
357 auto element = content.elements_begin();
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800358 if (content.elements_end() == element) {
359 BOOST_THROW_EXCEPTION(Error("FileManifest with empty content"));
360 }
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700361 if (element->type() == tlv::Name) {
362 Name name(*element);
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800363 m_submanifestPtr = std::make_shared<Name>(name);
364 ++element;
365 }
366
367 // DataPacketSize
368 m_dataPacketSize = readNonNegativeInteger(*element);
369 ++element;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800370 // CatalogPrefix
371 m_catalogPrefix = Name(*element);
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700372 ++element;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800373 // Catalog
374 m_catalog.clear();
375 for (; element != content.elements_end(); ++element) {
376 element->parse();
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700377 Name name = m_catalogPrefix;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800378 name.append(Name(*element));
379 if (name == m_catalogPrefix) {
380 BOOST_THROW_EXCEPTION(Error("Empty name included in a FileManifest"));
381 }
382 push_back(name);
383 }
384}
385
386bool operator==(const FileManifest& lhs, const FileManifest& rhs) {
387 return lhs.name() == rhs.name()
388 && lhs.data_packet_size() == rhs.data_packet_size()
389 && (lhs.submanifest_ptr() == rhs.submanifest_ptr() /* shallow equality */
390 || ( nullptr != lhs.submanifest_ptr()
391 && nullptr != rhs.submanifest_ptr()
392 && *rhs.submanifest_ptr() == *lhs.submanifest_ptr()
393 )
394 )
395 && lhs.catalog() == rhs.catalog();
396}
397
398bool operator!=(const FileManifest& lhs, const FileManifest& rhs) {
399 return lhs.name() != rhs.name()
400 || lhs.data_packet_size() != rhs.data_packet_size()
401 || (lhs.submanifest_ptr() != rhs.submanifest_ptr() /* shallow equality */
402 && (nullptr == lhs.submanifest_ptr()
403 || nullptr == rhs.submanifest_ptr()
404 || *rhs.submanifest_ptr() != *lhs.submanifest_ptr()
405 )
406 )
407 || lhs.catalog() != rhs.catalog();
408}
409
410} // end ntorrent
411} // end ndn