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 | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame^] | 23 | #include <limits> |
| 24 | |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 25 | #include <boost/assert.hpp> |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame^] | 26 | #include <boost/filesystem.hpp> |
| 27 | #include <boost/filesystem/fstream.hpp> |
| 28 | #include <boost/lexical_cast.hpp> |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 29 | #include <boost/range/adaptors.hpp> |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame^] | 30 | #include <boost/range/irange.hpp> |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 31 | #include <boost/throw_exception.hpp> |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame^] | 32 | #include <ndn-cxx/security/key-chain.hpp> |
| 33 | #include <ndn-cxx/security/signing-helpers.hpp> |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 34 | |
| 35 | using std::vector; |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame^] | 36 | using std::streamsize; |
| 37 | using boost::irange; |
| 38 | |
| 39 | namespace fs = boost::filesystem; |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 40 | |
| 41 | namespace ndn { |
| 42 | namespace ntorrent { |
| 43 | |
| 44 | BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FileManifest>)); |
| 45 | BOOST_CONCEPT_ASSERT((WireEncodable<FileManifest>)); |
| 46 | BOOST_CONCEPT_ASSERT((WireDecodable<FileManifest>)); |
| 47 | static_assert(std::is_base_of<Data::Error, FileManifest::Error>::value, |
| 48 | "FileManifest::Error should inherit from Data::Error"); |
| 49 | |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame^] | 50 | static ndn::Name |
| 51 | get_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 | |
| 72 | static std::vector<Data> |
| 73 | packetize_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 |
| 148 | std::pair<std::vector<FileManifest>, std::vector<Data>> |
| 149 | FileManifest::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 Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 212 | void |
| 213 | FileManifest::wireDecode(const Block& wire) |
| 214 | { |
| 215 | Data::wireDecode(wire); |
| 216 | this->decodeContent(); |
| 217 | } |
| 218 | |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 219 | template<ndn::encoding::Tag TAG> |
| 220 | size_t |
| 221 | FileManifest::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 Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame^] | 249 | BOOST_THROW_EXCEPTION(Error(name.toUri() + " does not have the prefix " |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 250 | + 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 |
| 278 | void |
| 279 | FileManifest::push_back(const Name& name) |
| 280 | { |
| 281 | BOOST_ASSERT(name != m_catalogPrefix); |
| 282 | BOOST_ASSERT(m_catalogPrefix.isPrefixOf(name)); |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame^] | 283 | // TODO(msweatt) Change this to use the copy constructor once bug fixed in Name() |
| 284 | m_catalog.push_back(name.toUri()); |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 285 | } |
| 286 | |
| 287 | bool |
| 288 | FileManifest::remove(const ndn::Name& name) { |
Mickey Sweatt | ebc0195 | 2016-02-19 11:38:30 -0800 | [diff] [blame^] | 289 | const auto it = std::find(m_catalog.begin(), m_catalog.end(), name); |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 290 | if (m_catalog.end() == it) { |
| 291 | return false; |
| 292 | } |
| 293 | m_catalog.erase(it); |
| 294 | return true; |
| 295 | } |
| 296 | |
Mickey Sweatt | a768b24 | 2016-02-29 20:08:05 -0800 | [diff] [blame] | 297 | void |
| 298 | FileManifest::finalize() { |
| 299 | m_catalog.shrink_to_fit(); |
| 300 | encodeContent(); |
| 301 | } |
| 302 | |
Mickey Sweatt | 3b0bea6 | 2016-01-25 22:12:27 -0800 | [diff] [blame] | 303 | void 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 | |
| 329 | void |
| 330 | FileManifest::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 | |
| 393 | bool 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 | |
| 405 | bool 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 |