blob: 71ba3a60d0eb8d8892d9fd27f3ab4dfbc79ea382 [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
35using std::vector;
Mickey Sweattebc01952016-02-19 11:38:30 -080036using std::streamsize;
37using boost::irange;
38
39namespace fs = boost::filesystem;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080040
41namespace ndn {
42namespace ntorrent {
43
44BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FileManifest>));
45BOOST_CONCEPT_ASSERT((WireEncodable<FileManifest>));
46BOOST_CONCEPT_ASSERT((WireDecodable<FileManifest>));
47static_assert(std::is_base_of<Data::Error, FileManifest::Error>::value,
48 "FileManifest::Error should inherit from Data::Error");
49
Mickey Sweattebc01952016-02-19 11:38:30 -080050static ndn::Name
51get_name_of_manifest(const std::string& filePath, const Name& manifestPrefix)
52{
53 Name full_path(fs::system_complete(filePath).string());
54 // Search the filePath for the leading component that matches
55 auto name_component_iter = std::find(full_path.rbegin(),
56 full_path.rend(),
57 *manifestPrefix.rbegin());
58
59 if (full_path.rend() == name_component_iter) {
60 BOOST_THROW_EXCEPTION(FileManifest::Error("No matching name component between" +
61 manifestPrefix.toUri() + " and " +
62 full_path.toUri()));
63 }
64 ndn::Name manifestName;
65 // Rebuild the name to be the suffix from the matching component
66 for (auto it = (name_component_iter.base() - 1); full_path.end() != it; ++it) {
67 manifestName.append(*it);
68 }
69 return manifestName;
70}
71
72static std::vector<Data>
73packetize_file(const fs::path& filePath,
74 const ndn::Name& commonPrefix,
75 size_t dataPacketSize,
76 size_t subManifestSize,
77 size_t subManifestNum)
78{
79 BOOST_ASSERT(0 < dataPacketSize);
80 size_t APPROX_BUFFER_SIZE = std::numeric_limits<int>::max(); // 2 * 1024 * 1024 *1024
81 auto file_size = fs::file_size(filePath);
82 auto start_offset = subManifestNum * subManifestSize * dataPacketSize;
83 // determine the number of bytes in this submanifest
84 auto subManifestLength = subManifestSize * dataPacketSize;
85 auto remainingFileLength = file_size - start_offset;
86 subManifestLength = remainingFileLength < subManifestLength
87 ? remainingFileLength
88 : subManifestLength;
89 std::vector<Data> packets;
90 packets.reserve(subManifestLength/dataPacketSize + 1);
91 fs::ifstream fs(filePath, fs::ifstream::binary);
92 if (!fs) {
93 BOOST_THROW_EXCEPTION(FileManifest::Error("IO Error when opening" + filePath.string()));
94 }
95 // ensure that buffer is large enough to contain whole packets
96 // buffer size is either the entire file or the smallest number of data packets >= 2 GB
97 auto buffer_size =
98 subManifestLength < APPROX_BUFFER_SIZE ?
99 subManifestLength :
100 APPROX_BUFFER_SIZE % dataPacketSize == 0 ?
101 APPROX_BUFFER_SIZE :
102 APPROX_BUFFER_SIZE + dataPacketSize - (APPROX_BUFFER_SIZE % dataPacketSize);
103 std::vector<char> file_bytes;
104 file_bytes.reserve(buffer_size);
105 size_t bytes_read = 0;
106 fs.seekg(start_offset);
107 while(fs && bytes_read < subManifestLength && !fs.eof()) {
108 // read the file into the buffer
109 fs.read(&file_bytes.front(), buffer_size);
110 auto read_size = fs.gcount();
111 if (fs.bad() || read_size < 0) {
112
113 BOOST_THROW_EXCEPTION(FileManifest::Error("IO Error when reading" + filePath.string()));
114 }
115 bytes_read += read_size;
116 char *curr_start = &file_bytes.front();
117 for (size_t i = 0u; i < buffer_size; i += dataPacketSize) {
118 // Build a packet from the data
119 Name packetName = commonPrefix;
120 packetName.appendSequenceNumber(packets.size());
121 Data d(packetName);
122 auto content_length = i + dataPacketSize > buffer_size ? buffer_size - i : dataPacketSize;
123 d.setContent(encoding::makeBinaryBlock(tlv::Content, curr_start, content_length));
124 curr_start += content_length;
125 // append to the collection
126 packets.push_back(d);
127 }
128 file_bytes.clear();
129 // recompute the buffer_size
130 buffer_size =
131 subManifestLength - bytes_read < APPROX_BUFFER_SIZE ?
132 subManifestLength - bytes_read :
133 APPROX_BUFFER_SIZE % dataPacketSize == 0 ?
134 APPROX_BUFFER_SIZE :
135 APPROX_BUFFER_SIZE + dataPacketSize - (APPROX_BUFFER_SIZE % dataPacketSize);
136 }
137 fs.close();
138 packets.shrink_to_fit();
139 security::KeyChain key_chain;
140 // sign all the packets
141 for (auto& p : packets) {
142 key_chain.sign(p, signingWithSha256());
143 }
144 return packets;
145}
146
147// CLASS METHODS
148std::pair<std::vector<FileManifest>, std::vector<Data>>
149FileManifest::generate(const std::string& filePath,
150 const Name& manifestPrefix,
151 size_t subManifestSize,
152 size_t dataPacketSize,
153 bool returnData)
154{
155 BOOST_ASSERT(0 < subManifestSize);
156 BOOST_ASSERT(0 < dataPacketSize);
157 std::vector<FileManifest> manifests;
158 fs::path path(filePath);
159 if (!fs::exists(path)) {
160 BOOST_THROW_EXCEPTION(Error(filePath + ": no such file."));
161 }
162 size_t file_length = fs::file_size(filePath);
163 // If the file_length is not evenly divisible by subManifestSize add 1, otherwise 0
164 size_t numSubManifests = file_length / (subManifestSize * dataPacketSize) +
165 !!(file_length % (subManifestSize * dataPacketSize));
166 // Find the prefix for the Catalog
167 auto manifestName = get_name_of_manifest(filePath, manifestPrefix);
168 std::vector<Data> allPackets;
169 if (returnData) {
170 allPackets.reserve(numSubManifests * subManifestSize);
171 }
172 manifests.reserve(numSubManifests);
173 for (auto subManifestNum : irange<size_t>(0, numSubManifests)) {
174 auto curr_manifest_name = manifestName;
175 curr_manifest_name.append("manifest" + boost::lexical_cast<std::string>(subManifestNum));
176 // append the packet number
177 curr_manifest_name.appendSequenceNumber(manifests.size());
178 auto subManifestPrefix = manifestPrefix;
179 subManifestPrefix.appendNumber(subManifestNum);
180 FileManifest curr_manifest(curr_manifest_name, dataPacketSize, manifestPrefix);
181 auto packets = packetize_file(path,
182 subManifestPrefix,
183 dataPacketSize,
184 subManifestSize,
185 subManifestNum);
186 if (returnData) {
187 allPackets.insert(allPackets.end(), packets.begin(), packets.end());
188 }
189 curr_manifest.reserve(packets.size());
190 // Collect all the Data packets into the sub-manifests
191 for (const auto& p: packets) {
192 curr_manifest.push_back(p.getFullName());
193 }
194 // append the last manifest
195 manifests.push_back(curr_manifest);
196 }
197 allPackets.shrink_to_fit();
198 manifests.shrink_to_fit();
199 // Set all the submanifest_ptrs and sign all the manifests
200 security::KeyChain key_chain;
201 manifests.back().finalize();
202 key_chain.sign(manifests.back(), signingWithSha256());
203 for (auto it = manifests.rbegin() + 1; it != manifests.rend(); ++it) {
204 auto next = it - 1;
205 it->set_submanifest_ptr(std::make_shared<Name>(next->getFullName()));
206 it->finalize();
207 key_chain.sign(*it, signingWithSha256());
208 }
209 return {manifests, allPackets};
210}
211
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800212void
213FileManifest::wireDecode(const Block& wire)
214{
215 Data::wireDecode(wire);
216 this->decodeContent();
217}
218
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800219template<ndn::encoding::Tag TAG>
220size_t
221FileManifest::encodeContent(ndn::EncodingImpl<TAG>& encoder) const {
222 // FileManifestName ::= NAME-TYPE TLV-LENGTH
223 // Name
224
225 // ManifestContent ::= CONTENT-TYPE TLV-LENGTH
226 // DataPacketName*
227 // CatalogPrefix
228 // DataPacketSize
229 // FileManifestPtr?
230
231 // DataPacketName ::= NAME-TYPE TLV-LENGTH
232 // Name
233
234 // CatalogPrefix ::= NAME-TYPE TLV-LENGTH
235 // Name
236
237 // DataPacketSize ::= CONTENT-TYPE TLV-LENGTH
238 // nonNegativeInteger
239
240 // FileManifestPtr ::= NAME-TYPE TLV-LENGTH
241 // Name
242
243 size_t totalLength = 0;
244
245 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();
358
359 if (content.elements_end() == element) {
360 BOOST_THROW_EXCEPTION(Error("FileManifest with empty content"));
361 }
362
363 element->parse();
364 Name name(*element);
365
366 // Submanifest ptr
367 if (!name.empty()) {
368 m_submanifestPtr = std::make_shared<Name>(name);
369 ++element;
370 }
371
372 // DataPacketSize
373 m_dataPacketSize = readNonNegativeInteger(*element);
374 ++element;
375
376 // CatalogPrefix
377 m_catalogPrefix = Name(*element);
378 element++;
379
380 // Catalog
381 m_catalog.clear();
382 for (; element != content.elements_end(); ++element) {
383 element->parse();
384 name = m_catalogPrefix;
385 name.append(Name(*element));
386 if (name == m_catalogPrefix) {
387 BOOST_THROW_EXCEPTION(Error("Empty name included in a FileManifest"));
388 }
389 push_back(name);
390 }
391}
392
393bool operator==(const FileManifest& lhs, const FileManifest& rhs) {
394 return lhs.name() == rhs.name()
395 && lhs.data_packet_size() == rhs.data_packet_size()
396 && (lhs.submanifest_ptr() == rhs.submanifest_ptr() /* shallow equality */
397 || ( nullptr != lhs.submanifest_ptr()
398 && nullptr != rhs.submanifest_ptr()
399 && *rhs.submanifest_ptr() == *lhs.submanifest_ptr()
400 )
401 )
402 && lhs.catalog() == rhs.catalog();
403}
404
405bool operator!=(const FileManifest& lhs, const FileManifest& rhs) {
406 return lhs.name() != rhs.name()
407 || lhs.data_packet_size() != rhs.data_packet_size()
408 || (lhs.submanifest_ptr() != rhs.submanifest_ptr() /* shallow equality */
409 && (nullptr == lhs.submanifest_ptr()
410 || nullptr == rhs.submanifest_ptr()
411 || *rhs.submanifest_ptr() != *lhs.submanifest_ptr()
412 )
413 )
414 || lhs.catalog() != rhs.catalog();
415}
416
417} // end ntorrent
418} // end ndn