blob: e5ef53ed3cfbd92e4b3c184feff68679eee203c8 [file] [log] [blame]
spirosmastorakisa6057f52016-01-28 13:34:41 -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
22#include "torrent-file.hpp"
23
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -080024#include <ndn-cxx/security/key-chain.hpp>
25#include <ndn-cxx/security/signing-helpers.hpp>
26
spirosmastorakisa6057f52016-01-28 13:34:41 -080027#include <algorithm>
28
29#include <boost/range/adaptors.hpp>
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -080030#include <boost/filesystem.hpp>
31#include <boost/filesystem/fstream.hpp>
32
33namespace fs = boost::filesystem;
spirosmastorakisa6057f52016-01-28 13:34:41 -080034
35namespace ndn {
36
37namespace ntorrent {
38
39BOOST_CONCEPT_ASSERT((WireEncodable<TorrentFile>));
40BOOST_CONCEPT_ASSERT((WireDecodable<TorrentFile>));
41static_assert(std::is_base_of<Data::Error, TorrentFile::Error>::value,
42 "TorrentFile::Error should inherit from Data::Error");
43
44TorrentFile::TorrentFile(const Name& torrentFileName,
45 const Name& torrentFilePtr,
46 const Name& commonPrefix,
47 const std::vector<ndn::Name>& catalog)
48 : Data(torrentFileName)
49 , m_commonPrefix(commonPrefix)
50 , m_torrentFilePtr(torrentFilePtr)
51 , m_catalog(catalog)
52{
53}
54
55TorrentFile::TorrentFile(const Name& torrentFileName,
56 const Name& commonPrefix,
57 const std::vector<ndn::Name>& catalog)
58 : Data(torrentFileName)
59 , m_commonPrefix(commonPrefix)
60 , m_catalog(catalog)
61{
62}
63
64TorrentFile::TorrentFile(const Block& block)
65{
66 this->wireDecode(block);
67}
68
spirosmastorakisa6057f52016-01-28 13:34:41 -080069void
70TorrentFile::createSuffixCatalog()
71{
72 for (auto i = m_catalog.begin() ; i != m_catalog.end(); ++i) {
73 m_suffixCatalog.push_back((*i).getSubName(m_commonPrefix.size()));
74 }
75}
76
77shared_ptr<Name>
78TorrentFile::getTorrentFilePtr() const
79{
80 if (this->hasTorrentFilePtr()) {
81 return make_shared<Name>(m_torrentFilePtr);
82 }
83 return nullptr;
84}
85
86void
87TorrentFile::constructLongNames()
88{
89 for (auto i = m_suffixCatalog.begin(); i != m_suffixCatalog.end(); ++i) {
90 Name commonPrefix = m_commonPrefix;
91 m_catalog.push_back(commonPrefix.append((*i)));
92 }
93}
94
95template<encoding::Tag TAG>
96size_t
97TorrentFile::encodeContent(EncodingImpl<TAG>& encoder) const
98{
99 // TorrentFileContent ::= CONTENT-TYPE TLV-LENGTH
100 // Suffix+
101 // CommonPrefix
102 // TorrentFilePtr?
103
104 // Suffix ::= NAME-TYPE TLV-LENGTH
105 // Name
106
107 // CommonPrefix ::= NAME-TYPE TLV-LENGTH
108 // Name
109
110 // TorrentFilePtr ::= NAME-TYPE TLV-LENGTH
111 // Name
112
113 size_t totalLength = 0;
114 for (const auto& name : m_suffixCatalog | boost::adaptors::reversed) {
115 size_t fileManifestSuffixLength = 0;
116 fileManifestSuffixLength += name.wireEncode(encoder);
117 totalLength += fileManifestSuffixLength;
118 }
119 totalLength += m_commonPrefix.wireEncode(encoder);
120 if (!m_torrentFilePtr.empty()) {
121 size_t torrentFilePtrLength = 0;
122 torrentFilePtrLength += m_torrentFilePtr.wireEncode(encoder);
123 totalLength += torrentFilePtrLength;
124 }
125
126 totalLength += encoder.prependVarNumber(totalLength);
127 totalLength += encoder.prependVarNumber(tlv::Content);
128 return totalLength;
129}
130
131bool
132TorrentFile::erase(const Name& name)
133{
134 auto found = std::find(m_catalog.begin(), m_catalog.end(), name);
135 if (found != m_catalog.end()) {
136 m_catalog.erase(found);
137 return true;
138 }
139 return false;
140}
141
142void
143TorrentFile::encodeContent()
144{
145 onChanged();
146
147 EncodingEstimator estimator;
148 size_t estimatedSize = encodeContent(estimator);
149
150 EncodingBuffer buffer(estimatedSize, 0);
151 encodeContent(buffer);
152
153 setContentType(tlv::ContentType_Blob);
154 setContent(buffer.block());
155}
156
157void
158TorrentFile::decodeContent()
159{
160 // TorrentFileContent ::= CONTENT-TYPE TLV-LENGTH
161 // Suffix+
162 // CommonPrefix
163 // TorrentFilePtr?
164
165 // Suffix ::= NAME-TYPE TLV-LENGTH
166 // Name
167
168 // CommonPrefix ::= NAME-TYPE TLV-LENGTH
169 // Name
170
171 // TorrentFilePtr ::= NAME-TYPE TLV-LENGTH
172 // Name
173
174 if (getContentType() != tlv::ContentType_Blob) {
175 BOOST_THROW_EXCEPTION(Error("Expected Content Type Blob"));
176 }
177
178 const Block& content = Data::getContent();
179 content.parse();
180
181 // Check whether there is a TorrentFilePtr
182 auto element = content.elements_begin();
183 if (content.elements_end() == element) {
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800184 BOOST_THROW_EXCEPTION(Error("Torrent-file with empty content"));
spirosmastorakisa6057f52016-01-28 13:34:41 -0800185 }
186 element->parse();
187 Name name(*element);
188 if (name.empty())
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800189 BOOST_THROW_EXCEPTION(Error("Empty name included in the torrent-file"));
spirosmastorakisa6057f52016-01-28 13:34:41 -0800190
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800191 if (name.get(name.size() - 3) == name::Component("torrent-file")) {
spirosmastorakisa6057f52016-01-28 13:34:41 -0800192 m_torrentFilePtr = name;
193 ++element;
194 m_commonPrefix = Name(*element);
195 if (m_commonPrefix.empty()) {
196 BOOST_THROW_EXCEPTION(Error("Common prefix cannot be empty"));
197 }
198 }
199 else {
200 m_commonPrefix = name;
201 }
202 element++;
203 for (; element != content.elements_end(); ++element) {
204 element->parse();
205 Name fileManifestSuffix(*element);
206 if (fileManifestSuffix.empty())
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800207 BOOST_THROW_EXCEPTION(Error("Empty manifest file name included in the torrent-file"));
spirosmastorakisa6057f52016-01-28 13:34:41 -0800208 this->insertToSuffixCatalog(fileManifestSuffix);
209 }
210 if (m_suffixCatalog.size() == 0) {
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800211 BOOST_THROW_EXCEPTION(Error("Torrent-file with empty catalog of file manifest names"));
spirosmastorakisa6057f52016-01-28 13:34:41 -0800212 }
213}
214
215void
216TorrentFile::wireDecode(const Block& wire)
217{
218 m_catalog.clear();
219 m_suffixCatalog.clear();
220 Data::wireDecode(wire);
221 this->decodeContent();
222 this->constructLongNames();
223}
224
spirosmastorakis6d4300f2016-02-29 20:18:43 -0800225void
226TorrentFile::finalize()
spirosmastorakisa6057f52016-01-28 13:34:41 -0800227{
228 this->createSuffixCatalog();
229 this->encodeContent();
230 m_suffixCatalog.clear();
spirosmastorakisa6057f52016-01-28 13:34:41 -0800231}
232
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800233std::pair<std::vector<TorrentFile>,
234 std::vector<std::pair<std::vector<FileManifest>,
235 std::vector<Data>>>>
236TorrentFile::generate(const std::string& directoryPath,
237 size_t namesPerSegment,
238 size_t subManifestSize,
239 size_t dataPacketSize,
240 bool returnData)
241{
Mickey Sweatte908a5c2016-04-08 14:10:45 -0700242 //TODO(spyros) Adapt this support subdirectories in 'directoryPath'
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800243 BOOST_ASSERT(0 < namesPerSegment);
244
245 std::vector<TorrentFile> torrentSegments;
246
247 fs::path path(directoryPath);
248 if (!fs::exists(path)) {
249 BOOST_THROW_EXCEPTION(Error(directoryPath + ": no such directory."));
250 }
251
252 Name directoryPathName(directoryPath);
253 fs::recursive_directory_iterator directoryPtr(fs::system_complete(directoryPath).string());
254
Mickey Sweatt0dc0a1e2016-05-04 11:25:49 -0700255 Name commonPrefix("/ndn/multicast/NTORRENT" +
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800256 directoryPathName.getSubName(directoryPathName.size() - 1).toUri());
257
258 Name torrentName(commonPrefix.toUri() + "/torrent-file");
259 TorrentFile currentTorrentFile(torrentName, commonPrefix, {});
260 std::vector<std::pair<std::vector<FileManifest>, std::vector<Data>>> manifestPairs;
Mickey Sweatt183d1cc2016-04-01 15:46:33 -0700261 // sort all the file names lexicographically
262 std::set<std::string> fileNames;
263 for (auto i = directoryPtr; i != fs::recursive_directory_iterator(); ++i) {
264 fileNames.insert(i->path().string());
265 }
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800266 size_t manifestFileCounter = 0u;
Mickey Sweatt183d1cc2016-04-01 15:46:33 -0700267 for (const auto& fileName : fileNames) {
Mickey Sweatt0dc0a1e2016-05-04 11:25:49 -0700268 Name manifestPrefix("/ndn/multicast/NTORRENT" +
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800269 directoryPathName.getSubName(directoryPathName.size() - 1).toUri());
270 std::pair<std::vector<FileManifest>, std::vector<Data>> currentManifestPair =
Mickey Sweatt183d1cc2016-04-01 15:46:33 -0700271 FileManifest::generate(fileName,
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800272 manifestPrefix, subManifestSize,
273 dataPacketSize, returnData);
274
275 if (manifestFileCounter != 0 && 0 == manifestFileCounter % namesPerSegment) {
276 torrentSegments.push_back(currentTorrentFile);
277 Name currentTorrentName = torrentName;
278 currentTorrentName.appendSequenceNumber(static_cast<int>(manifestFileCounter));
279 currentTorrentFile = TorrentFile(currentTorrentName, commonPrefix, {});
280 }
Mickey Sweattbe4b5192016-03-28 16:46:34 -0700281 currentTorrentFile.insert(currentManifestPair.first[0].getFullName());
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800282 currentManifestPair.first.shrink_to_fit();
283 currentManifestPair.second.shrink_to_fit();
284 manifestPairs.push_back(currentManifestPair);
285 ++manifestFileCounter;
286 }
287
288 // Sign and append the last torrent-file
289 security::KeyChain keyChain;
Mickey Sweatt22f51c52016-03-17 15:48:38 -0700290 currentTorrentFile.finalize();
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800291 keyChain.sign(currentTorrentFile, signingWithSha256());
292 torrentSegments.push_back(currentTorrentFile);
293
294 for (auto it = torrentSegments.rbegin() + 1; it != torrentSegments.rend(); ++it) {
295 auto next = it - 1;
296 it->setTorrentFilePtr(next->getFullName());
Mickey Sweatt22f51c52016-03-17 15:48:38 -0700297 it->finalize();
spirosmastorakisf5d1b6c2016-02-25 12:49:56 -0800298 keyChain.sign(*it, signingWithSha256());
299 }
300
301 torrentSegments.shrink_to_fit();
302 manifestPairs.shrink_to_fit();
303 return std::make_pair(torrentSegments, manifestPairs);
304}
305
spirosmastorakisa6057f52016-01-28 13:34:41 -0800306} // namespace ntorrent
307
308} // namespace ndn