blob: 2e6dcd2e020d23e895db19e638bc721e733ad6f6 [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 Sweattfcbfb3d2016-04-13 17:05:17 -070023#include "util/io-util.hpp"
24
Mickey Sweattebc01952016-02-19 11:38:30 -080025#include <limits>
26
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080027#include <boost/assert.hpp>
Mickey Sweattebc01952016-02-19 11:38:30 -080028#include <boost/filesystem.hpp>
29#include <boost/filesystem/fstream.hpp>
30#include <boost/lexical_cast.hpp>
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080031#include <boost/range/adaptors.hpp>
Mickey Sweattebc01952016-02-19 11:38:30 -080032#include <boost/range/irange.hpp>
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080033#include <boost/throw_exception.hpp>
Mickey Sweattebc01952016-02-19 11:38:30 -080034#include <ndn-cxx/security/key-chain.hpp>
35#include <ndn-cxx/security/signing-helpers.hpp>
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080036
Mickey Sweatt6de5dde2016-03-15 16:44:56 -070037#include <ndn-cxx/encoding/tlv.hpp>
38
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080039using std::vector;
Mickey Sweattebc01952016-02-19 11:38:30 -080040using std::streamsize;
41using boost::irange;
42
43namespace fs = boost::filesystem;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -080044
45namespace ndn {
46namespace ntorrent {
47
48BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FileManifest>));
49BOOST_CONCEPT_ASSERT((WireEncodable<FileManifest>));
50BOOST_CONCEPT_ASSERT((WireDecodable<FileManifest>));
51static_assert(std::is_base_of<Data::Error, FileManifest::Error>::value,
52 "FileManifest::Error should inherit from Data::Error");
53
Mickey Sweattebc01952016-02-19 11:38:30 -080054static ndn::Name
55get_name_of_manifest(const std::string& filePath, const Name& manifestPrefix)
56{
57 Name full_path(fs::system_complete(filePath).string());
58 // Search the filePath for the leading component that matches
59 auto name_component_iter = std::find(full_path.rbegin(),
60 full_path.rend(),
61 *manifestPrefix.rbegin());
62
63 if (full_path.rend() == name_component_iter) {
64 BOOST_THROW_EXCEPTION(FileManifest::Error("No matching name component between" +
65 manifestPrefix.toUri() + " and " +
66 full_path.toUri()));
67 }
Mickey Sweattbe4b5192016-03-28 16:46:34 -070068 ndn::Name manifestName = "/NTORRENT/";
Mickey Sweattebc01952016-02-19 11:38:30 -080069 // Rebuild the name to be the suffix from the matching component
70 for (auto it = (name_component_iter.base() - 1); full_path.end() != it; ++it) {
71 manifestName.append(*it);
72 }
73 return manifestName;
74}
75
Mickey Sweattebc01952016-02-19 11:38:30 -080076// CLASS METHODS
77std::pair<std::vector<FileManifest>, std::vector<Data>>
78FileManifest::generate(const std::string& filePath,
79 const Name& manifestPrefix,
80 size_t subManifestSize,
81 size_t dataPacketSize,
82 bool returnData)
83{
84 BOOST_ASSERT(0 < subManifestSize);
85 BOOST_ASSERT(0 < dataPacketSize);
86 std::vector<FileManifest> manifests;
87 fs::path path(filePath);
88 if (!fs::exists(path)) {
89 BOOST_THROW_EXCEPTION(Error(filePath + ": no such file."));
90 }
91 size_t file_length = fs::file_size(filePath);
92 // If the file_length is not evenly divisible by subManifestSize add 1, otherwise 0
93 size_t numSubManifests = file_length / (subManifestSize * dataPacketSize) +
94 !!(file_length % (subManifestSize * dataPacketSize));
95 // Find the prefix for the Catalog
96 auto manifestName = get_name_of_manifest(filePath, manifestPrefix);
97 std::vector<Data> allPackets;
98 if (returnData) {
99 allPackets.reserve(numSubManifests * subManifestSize);
100 }
101 manifests.reserve(numSubManifests);
102 for (auto subManifestNum : irange<size_t>(0, numSubManifests)) {
103 auto curr_manifest_name = manifestName;
Mickey Sweattebc01952016-02-19 11:38:30 -0800104 // append the packet number
105 curr_manifest_name.appendSequenceNumber(manifests.size());
Mickey Sweattebc01952016-02-19 11:38:30 -0800106 FileManifest curr_manifest(curr_manifest_name, dataPacketSize, manifestPrefix);
Mickey Sweattfcbfb3d2016-04-13 17:05:17 -0700107 auto packets = IoUtil::packetize_file(path,
108 curr_manifest_name,
109 dataPacketSize,
110 subManifestSize,
111 subManifestNum);
Mickey Sweattebc01952016-02-19 11:38:30 -0800112 if (returnData) {
113 allPackets.insert(allPackets.end(), packets.begin(), packets.end());
114 }
115 curr_manifest.reserve(packets.size());
116 // Collect all the Data packets into the sub-manifests
117 for (const auto& p: packets) {
118 curr_manifest.push_back(p.getFullName());
119 }
120 // append the last manifest
121 manifests.push_back(curr_manifest);
122 }
123 allPackets.shrink_to_fit();
124 manifests.shrink_to_fit();
125 // Set all the submanifest_ptrs and sign all the manifests
126 security::KeyChain key_chain;
127 manifests.back().finalize();
128 key_chain.sign(manifests.back(), signingWithSha256());
129 for (auto it = manifests.rbegin() + 1; it != manifests.rend(); ++it) {
130 auto next = it - 1;
131 it->set_submanifest_ptr(std::make_shared<Name>(next->getFullName()));
132 it->finalize();
133 key_chain.sign(*it, signingWithSha256());
134 }
135 return {manifests, allPackets};
136}
137
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800138void
139FileManifest::wireDecode(const Block& wire)
140{
141 Data::wireDecode(wire);
142 this->decodeContent();
143}
144
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800145template<ndn::encoding::Tag TAG>
146size_t
147FileManifest::encodeContent(ndn::EncodingImpl<TAG>& encoder) const {
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800148 // ManifestContent ::= CONTENT-TYPE TLV-LENGTH
149 // DataPacketName*
150 // CatalogPrefix
151 // DataPacketSize
152 // FileManifestPtr?
153
154 // DataPacketName ::= NAME-TYPE TLV-LENGTH
155 // Name
156
157 // CatalogPrefix ::= NAME-TYPE TLV-LENGTH
158 // Name
159
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700160 // DataPacketSize ::= CONTENT-TYPE TLV-LENGTH
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800161 // nonNegativeInteger
162
163 // FileManifestPtr ::= NAME-TYPE TLV-LENGTH
164 // Name
165
166 size_t totalLength = 0;
167
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700168 // build suffix catalog
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800169 vector<Name> suffixCatalog;
170 suffixCatalog.reserve(m_catalog.size());
171 for (auto name: m_catalog) {
172 if (!m_catalogPrefix.isPrefixOf(name)) {
Mickey Sweattebc01952016-02-19 11:38:30 -0800173 BOOST_THROW_EXCEPTION(Error(name.toUri() + " does not have the prefix "
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800174 + m_catalogPrefix.toUri()));
175 }
176 name = name.getSubName(m_catalogPrefix.size());
177 if (name.empty()) {
178 BOOST_THROW_EXCEPTION(Error("Manifest cannot include empty string"));
179 }
180 suffixCatalog.push_back(name);
181 }
182
183 for (const auto& name : suffixCatalog | boost::adaptors::reversed) {
184 totalLength += name.wireEncode(encoder);
185 }
186
187 totalLength += m_catalogPrefix.wireEncode(encoder);
188
189 totalLength += prependNonNegativeIntegerBlock(encoder, tlv::Content, m_dataPacketSize);
190
191 if (nullptr != m_submanifestPtr) {
192 totalLength += m_submanifestPtr->wireEncode(encoder);
193 }
194
195 totalLength += encoder.prependVarNumber(totalLength);
196 totalLength += encoder.prependVarNumber(tlv::Content);
197 return totalLength;
198
199}
200
201// MANIPULATORS
202void
203FileManifest::push_back(const Name& name)
204{
205 BOOST_ASSERT(name != m_catalogPrefix);
206 BOOST_ASSERT(m_catalogPrefix.isPrefixOf(name));
Mickey Sweattebc01952016-02-19 11:38:30 -0800207 // TODO(msweatt) Change this to use the copy constructor once bug fixed in Name()
208 m_catalog.push_back(name.toUri());
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800209}
210
211bool
212FileManifest::remove(const ndn::Name& name) {
Mickey Sweattebc01952016-02-19 11:38:30 -0800213 const auto it = std::find(m_catalog.begin(), m_catalog.end(), name);
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800214 if (m_catalog.end() == it) {
215 return false;
216 }
217 m_catalog.erase(it);
218 return true;
219}
220
Mickey Sweatta768b242016-02-29 20:08:05 -0800221void
222FileManifest::finalize() {
223 m_catalog.shrink_to_fit();
224 encodeContent();
225}
226
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800227void FileManifest::encodeContent() {
228 // Name
229 // <file_name>/ImplicitDigest
230 // Content
231 // MetaData
232 // DataPtr*
233 // ManifestPointer?
234 //
235 // DataPtr := HashValue
236 // ManifestPtr := HashValue
237 // HashValue := OCTET[32]
238
239 // MetaData := Property*
240 // Property := DataSize | Signature
241 onChanged();
242
243 EncodingEstimator estimator;
244 size_t estimatedSize = encodeContent(estimator);
245
246 EncodingBuffer buffer(estimatedSize, 0);
247 encodeContent(buffer);
248
249 setContentType(tlv::ContentType_Blob);
250 setContent(buffer.block());
251}
252
253void
254FileManifest::decodeContent() {
255 // ManifestContent ::= CONTENT-TYPE TLV-LENGTH
256 // DataPacketName*
257 // CatalogPrefix
258 // DataPacketSize
259 // FileManifestPtr?
260
261
262 // DataPacketName ::= NAME-TYPE TLV-LENGTH
263 // Name
264
265 // CatalogPrefix ::= NAME-TYPE TLV-LENGTH
266 // Name
267
268 // DataPacketSize ::= CONTENT-TYPE TLV-LENGTH
269 // nonNegativeInteger
270
271 // FileManifestPtr ::= NAME-TYPE TLV-LENGTH
272 // Name
273
274 if (getContentType() != tlv::ContentType_Blob) {
275 BOOST_THROW_EXCEPTION(Error("Expected Content Type Blob"));
276 }
277
278 const Block& content = Data::getContent();
279 content.parse();
280
281 auto element = content.elements_begin();
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800282 if (content.elements_end() == element) {
283 BOOST_THROW_EXCEPTION(Error("FileManifest with empty content"));
284 }
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700285 if (element->type() == tlv::Name) {
286 Name name(*element);
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800287 m_submanifestPtr = std::make_shared<Name>(name);
288 ++element;
289 }
290
291 // DataPacketSize
292 m_dataPacketSize = readNonNegativeInteger(*element);
293 ++element;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800294 // CatalogPrefix
295 m_catalogPrefix = Name(*element);
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700296 ++element;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800297 // Catalog
298 m_catalog.clear();
299 for (; element != content.elements_end(); ++element) {
300 element->parse();
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700301 Name name = m_catalogPrefix;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800302 name.append(Name(*element));
303 if (name == m_catalogPrefix) {
304 BOOST_THROW_EXCEPTION(Error("Empty name included in a FileManifest"));
305 }
306 push_back(name);
307 }
308}
309
310bool operator==(const FileManifest& lhs, const FileManifest& rhs) {
311 return lhs.name() == rhs.name()
312 && lhs.data_packet_size() == rhs.data_packet_size()
313 && (lhs.submanifest_ptr() == rhs.submanifest_ptr() /* shallow equality */
314 || ( nullptr != lhs.submanifest_ptr()
315 && nullptr != rhs.submanifest_ptr()
316 && *rhs.submanifest_ptr() == *lhs.submanifest_ptr()
317 )
318 )
319 && lhs.catalog() == rhs.catalog();
320}
321
322bool operator!=(const FileManifest& lhs, const FileManifest& rhs) {
323 return lhs.name() != rhs.name()
324 || lhs.data_packet_size() != rhs.data_packet_size()
325 || (lhs.submanifest_ptr() != rhs.submanifest_ptr() /* shallow equality */
326 && (nullptr == lhs.submanifest_ptr()
327 || nullptr == rhs.submanifest_ptr()
328 || *rhs.submanifest_ptr() != *lhs.submanifest_ptr()
329 )
330 )
331 || lhs.catalog() != rhs.catalog();
332}
333
334} // end ntorrent
335} // end ndn