blob: 1ccb4ef0ad4815aac98114842ce64ad87921577c [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 }
spirosmastorakisd351c6b2016-05-06 17:02:48 -070068 ndn::Name manifestName(SharedConstants::commonPrefix + std::string("/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 m_catalog.push_back(name.toUri());
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800208}
209
210bool
211FileManifest::remove(const ndn::Name& name) {
Mickey Sweattebc01952016-02-19 11:38:30 -0800212 const auto it = std::find(m_catalog.begin(), m_catalog.end(), name);
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800213 if (m_catalog.end() == it) {
214 return false;
215 }
216 m_catalog.erase(it);
217 return true;
218}
219
Mickey Sweatta768b242016-02-29 20:08:05 -0800220void
221FileManifest::finalize() {
222 m_catalog.shrink_to_fit();
223 encodeContent();
224}
225
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800226void FileManifest::encodeContent() {
227 // Name
228 // <file_name>/ImplicitDigest
229 // Content
230 // MetaData
231 // DataPtr*
232 // ManifestPointer?
233 //
234 // DataPtr := HashValue
235 // ManifestPtr := HashValue
236 // HashValue := OCTET[32]
237
238 // MetaData := Property*
239 // Property := DataSize | Signature
240 onChanged();
241
242 EncodingEstimator estimator;
243 size_t estimatedSize = encodeContent(estimator);
244
245 EncodingBuffer buffer(estimatedSize, 0);
246 encodeContent(buffer);
247
248 setContentType(tlv::ContentType_Blob);
249 setContent(buffer.block());
250}
251
252void
253FileManifest::decodeContent() {
254 // ManifestContent ::= CONTENT-TYPE TLV-LENGTH
255 // DataPacketName*
256 // CatalogPrefix
257 // DataPacketSize
258 // FileManifestPtr?
259
260
261 // DataPacketName ::= NAME-TYPE TLV-LENGTH
262 // Name
263
264 // CatalogPrefix ::= NAME-TYPE TLV-LENGTH
265 // Name
266
267 // DataPacketSize ::= CONTENT-TYPE TLV-LENGTH
268 // nonNegativeInteger
269
270 // FileManifestPtr ::= NAME-TYPE TLV-LENGTH
271 // Name
272
273 if (getContentType() != tlv::ContentType_Blob) {
274 BOOST_THROW_EXCEPTION(Error("Expected Content Type Blob"));
275 }
276
277 const Block& content = Data::getContent();
278 content.parse();
279
280 auto element = content.elements_begin();
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800281 if (content.elements_end() == element) {
282 BOOST_THROW_EXCEPTION(Error("FileManifest with empty content"));
283 }
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700284 if (element->type() == tlv::Name) {
285 Name name(*element);
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800286 m_submanifestPtr = std::make_shared<Name>(name);
287 ++element;
288 }
289
290 // DataPacketSize
291 m_dataPacketSize = readNonNegativeInteger(*element);
292 ++element;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800293 // CatalogPrefix
294 m_catalogPrefix = Name(*element);
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700295 ++element;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800296 // Catalog
297 m_catalog.clear();
298 for (; element != content.elements_end(); ++element) {
299 element->parse();
Mickey Sweatt6de5dde2016-03-15 16:44:56 -0700300 Name name = m_catalogPrefix;
Mickey Sweatt3b0bea62016-01-25 22:12:27 -0800301 name.append(Name(*element));
302 if (name == m_catalogPrefix) {
303 BOOST_THROW_EXCEPTION(Error("Empty name included in a FileManifest"));
304 }
305 push_back(name);
306 }
307}
308
309bool operator==(const FileManifest& lhs, const FileManifest& rhs) {
310 return lhs.name() == rhs.name()
311 && lhs.data_packet_size() == rhs.data_packet_size()
312 && (lhs.submanifest_ptr() == rhs.submanifest_ptr() /* shallow equality */
313 || ( nullptr != lhs.submanifest_ptr()
314 && nullptr != rhs.submanifest_ptr()
315 && *rhs.submanifest_ptr() == *lhs.submanifest_ptr()
316 )
317 )
318 && lhs.catalog() == rhs.catalog();
319}
320
321bool operator!=(const FileManifest& lhs, const FileManifest& rhs) {
322 return lhs.name() != rhs.name()
323 || lhs.data_packet_size() != rhs.data_packet_size()
324 || (lhs.submanifest_ptr() != rhs.submanifest_ptr() /* shallow equality */
325 && (nullptr == lhs.submanifest_ptr()
326 || nullptr == rhs.submanifest_ptr()
327 || *rhs.submanifest_ptr() != *lhs.submanifest_ptr()
328 )
329 )
330 || lhs.catalog() != rhs.catalog();
331}
332
333} // end ntorrent
spirosmastorakisd351c6b2016-05-06 17:02:48 -0700334} // end ndn