Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 1 | /* -*- 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 Sweatt | fcbfb3d | 2016-04-13 17:05:17 -0700 | [diff] [blame^] | 23 | #include "util/io-util.hpp" |
| 24 | |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 25 | #include <limits> |
| 26 | |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 27 | #include <boost/assert.hpp> |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 28 | #include <boost/filesystem.hpp> |
| 29 | #include <boost/filesystem/fstream.hpp> |
| 30 | #include <boost/lexical_cast.hpp> |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 31 | #include <boost/range/adaptors.hpp> |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 32 | #include <boost/range/irange.hpp> |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 33 | #include <boost/throw_exception.hpp> |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 34 | #include <ndn-cxx/security/key-chain.hpp> |
| 35 | #include <ndn-cxx/security/signing-helpers.hpp> |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 36 | |
Mickey Sweatt | 6de5dde | 2016-03-15 16:44:56 -0700 | [diff] [blame] | 37 | #include <ndn-cxx/encoding/tlv.hpp> |
| 38 | |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 39 | using std::vector; |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 40 | using std::streamsize; |
| 41 | using boost::irange; |
| 42 | |
| 43 | namespace fs = boost::filesystem; |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 44 | |
| 45 | namespace ndn { |
| 46 | namespace ntorrent { |
| 47 | |
| 48 | BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FileManifest>)); |
| 49 | BOOST_CONCEPT_ASSERT((WireEncodable<FileManifest>)); |
| 50 | BOOST_CONCEPT_ASSERT((WireDecodable<FileManifest>)); |
| 51 | static_assert(std::is_base_of<Data::Error, FileManifest::Error>::value, |
| 52 | "FileManifest::Error should inherit from Data::Error"); |
| 53 | |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 54 | static ndn::Name |
| 55 | get_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 Sweatt | be4b519 | 2016-03-28 16:46:34 -0700 | [diff] [blame] | 68 | ndn::Name manifestName = "/NTORRENT/"; |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 69 | // 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 Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 76 | // CLASS METHODS |
| 77 | std::pair<std::vector<FileManifest>, std::vector<Data>> |
| 78 | FileManifest::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 Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 104 | // append the packet number |
| 105 | curr_manifest_name.appendSequenceNumber(manifests.size()); |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 106 | FileManifest curr_manifest(curr_manifest_name, dataPacketSize, manifestPrefix); |
Mickey Sweatt | fcbfb3d | 2016-04-13 17:05:17 -0700 | [diff] [blame^] | 107 | auto packets = IoUtil::packetize_file(path, |
| 108 | curr_manifest_name, |
| 109 | dataPacketSize, |
| 110 | subManifestSize, |
| 111 | subManifestNum); |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 112 | 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 Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 138 | void |
| 139 | FileManifest::wireDecode(const Block& wire) |
| 140 | { |
| 141 | Data::wireDecode(wire); |
| 142 | this->decodeContent(); |
| 143 | } |
| 144 | |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 145 | template<ndn::encoding::Tag TAG> |
| 146 | size_t |
| 147 | FileManifest::encodeContent(ndn::EncodingImpl<TAG>& encoder) const { |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 148 | // 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 Sweatt | 6de5dde | 2016-03-15 16:44:56 -0700 | [diff] [blame] | 160 | // DataPacketSize ::= CONTENT-TYPE TLV-LENGTH |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 161 | // nonNegativeInteger |
| 162 | |
| 163 | // FileManifestPtr ::= NAME-TYPE TLV-LENGTH |
| 164 | // Name |
| 165 | |
| 166 | size_t totalLength = 0; |
| 167 | |
Mickey Sweatt | 6de5dde | 2016-03-15 16:44:56 -0700 | [diff] [blame] | 168 | // build suffix catalog |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 169 | vector<Name> suffixCatalog; |
| 170 | suffixCatalog.reserve(m_catalog.size()); |
| 171 | for (auto name: m_catalog) { |
| 172 | if (!m_catalogPrefix.isPrefixOf(name)) { |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 173 | BOOST_THROW_EXCEPTION(Error(name.toUri() + " does not have the prefix " |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 174 | + 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 |
| 202 | void |
| 203 | FileManifest::push_back(const Name& name) |
| 204 | { |
| 205 | BOOST_ASSERT(name != m_catalogPrefix); |
| 206 | BOOST_ASSERT(m_catalogPrefix.isPrefixOf(name)); |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 207 | // TODO(msweatt) Change this to use the copy constructor once bug fixed in Name() |
| 208 | m_catalog.push_back(name.toUri()); |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 209 | } |
| 210 | |
| 211 | bool |
| 212 | FileManifest::remove(const ndn::Name& name) { |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame] | 213 | const auto it = std::find(m_catalog.begin(), m_catalog.end(), name); |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 214 | if (m_catalog.end() == it) { |
| 215 | return false; |
| 216 | } |
| 217 | m_catalog.erase(it); |
| 218 | return true; |
| 219 | } |
| 220 | |
Mickey Sweatt | a768b24 | 2016-02-29 20:08:05 -0800 | [diff] [blame] | 221 | void |
| 222 | FileManifest::finalize() { |
| 223 | m_catalog.shrink_to_fit(); |
| 224 | encodeContent(); |
| 225 | } |
| 226 | |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 227 | void 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 | |
| 253 | void |
| 254 | FileManifest::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 Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 282 | if (content.elements_end() == element) { |
| 283 | BOOST_THROW_EXCEPTION(Error("FileManifest with empty content")); |
| 284 | } |
Mickey Sweatt | 6de5dde | 2016-03-15 16:44:56 -0700 | [diff] [blame] | 285 | if (element->type() == tlv::Name) { |
| 286 | Name name(*element); |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 287 | m_submanifestPtr = std::make_shared<Name>(name); |
| 288 | ++element; |
| 289 | } |
| 290 | |
| 291 | // DataPacketSize |
| 292 | m_dataPacketSize = readNonNegativeInteger(*element); |
| 293 | ++element; |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 294 | // CatalogPrefix |
| 295 | m_catalogPrefix = Name(*element); |
Mickey Sweatt | 6de5dde | 2016-03-15 16:44:56 -0700 | [diff] [blame] | 296 | ++element; |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 297 | // Catalog |
| 298 | m_catalog.clear(); |
| 299 | for (; element != content.elements_end(); ++element) { |
| 300 | element->parse(); |
Mickey Sweatt | 6de5dde | 2016-03-15 16:44:56 -0700 | [diff] [blame] | 301 | Name name = m_catalogPrefix; |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 302 | 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 | |
| 310 | bool 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 | |
| 322 | bool 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 |