diff --git a/.gitignore b/.gitignore index c8dea6c..e9b416e 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ Makefile.in *.l[ao] *~ *.pc +*.a # CMake dir build diff --git a/src/aescrypto.cpp b/src/aescrypto.cpp index 0fdef1d..fbe5475 100644 --- a/src/aescrypto.cpp +++ b/src/aescrypto.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/src/aescrypto.h b/src/aescrypto.h index 0a09e5f..065618e 100644 --- a/src/aescrypto.h +++ b/src/aescrypto.h @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -31,18 +31,6 @@ namespace crypto { class AES : public Cipher { public: - /** - * Length of the PIN if it contains the password for encrypted pasted data. - * It consists of 16 hexadecimal characters: 16*4 = 64 bits. Last 32 bits - * encode the password. Otherwise, PIN should be 32 bits. - */ - static const constexpr size_t PIN_WITH_PASS_LEN {16}; - /** - * Number of bytes offsetting the password in the binary representation of - * the hexadecimal password. - */ - static const constexpr size_t CODE_PASS_OFFSET {4}; - AES() {} virtual ~AES () {} diff --git a/src/bin.cpp b/src/bin.cpp index 42b04b6..d80a8b2 100644 --- a/src/bin.cpp +++ b/src/bin.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -23,10 +23,14 @@ #include #include #include +#include +#include #include #include "bin.h" + +#include "code.h" #include "conf.h" #include "log.h" #include "gpgcrypto.h" @@ -38,7 +42,7 @@ const constexpr uint8_t Bin::PROTO_VERSION; Bin::Bin() { /* load dpaste config */ - auto config_file = conf::ConfigurationFile(); + conf::ConfigurationFile config_file {}; config_file.load(); conf_ = config_file.getConfiguration(); @@ -55,32 +59,34 @@ Bin::Bin() { std::string Bin::code_from_dpaste_uri(const std::string& uri) { static const std::string DUP {DPASTE_URI_PREFIX}; const auto p = uri.find(DUP); - return uri.substr(p != std::string::npos ? p+DUP.length() : 0); + return uri.substr(p != std::string::npos ? p+DUP.length() : 0, code::PIN_WITH_PASS_LEN); } -std::pair Bin::get(std::string&& code, bool no_decrypt) { - code = code_from_dpaste_uri(code); - const auto offset = crypto::AES::CODE_PASS_OFFSET*2; - const auto lcode = code.substr(0, offset); - const auto pwd = code.substr(offset); - - /* first try http server */ - auto data_str = http_client_->get(lcode); - std::vector data {data_str.begin(), data_str.end()}; - - /* if fail, then perform request from local node */ - if (data.empty()) { - /* get a pasted blob */ - auto values = node.get(lcode); - if (not values.empty()) - data = values.front(); - } +std::tuple +Bin::parse_code_info(const std::string& code) { + std::stringstream ns(code.substr(code::DPASTE_PIN_LEN, code::DPASTE_NPACKETS_LEN)); + uint32_t npackets; + ns >> std::hex >> npackets; + return { + code.substr(0, code::DPASTE_PIN_LEN), + npackets, + code.substr(code::DPASTE_PIN_LEN+code::DPASTE_NPACKETS_LEN, code::PASSWORD_LEN) + }; +} - if (not data.empty()) { - Packet p; +std::vector +Bin::parse_data(const std::string& code, + std::vector>&& values, + std::string pwd, + bool no_decrypt) const +{ + for (const auto& v : values) { try { - p.deserialize(data); + Packet p; + p.deserialize(v); + std::vector data; auto cipher = crypto::Cipher::get(p.data, code); + if (cipher and not no_decrypt) { std::shared_ptr params; if (auto aes = std::dynamic_pointer_cast(cipher)) { @@ -90,6 +96,7 @@ std::pair Bin::get(std::string&& code, bool no_decrypt) { data = cipher->processCipherText(p.data, std::move(params)); } else data = std::move(p.data); + if (not (cipher or p.signature.empty())) { auto gc = std::dynamic_pointer_cast(crypto::Cipher::get(crypto::Cipher::Scheme::GPG, {})); DPASTE_MSG("Data is GPG signed. Verifying..."); @@ -97,45 +104,89 @@ std::pair Bin::get(std::string&& code, bool no_decrypt) { if (res.numSignatures() > 0) gc->comment_on_signature(res.signature(0)); } + + return data; } catch (const GpgME::Exception& e) { DPASTE_MSG("%s", e.what()); - return {false, ""}; + return {}; } catch (const dht::crypto::DecryptError& e) { DPASTE_MSG("%s", e.what()); - return {false, ""}; + return {}; } catch (msgpack::type_error& e) { } /* backward compatibility with <=0.3.3 */ + } + + return {}; +} +std::pair Bin::get(std::string&& code, bool no_decrypt) { + code = code_from_dpaste_uri(code); + const auto parsed_code = parse_code_info(code); + const auto& lcode = std::get<0>(parsed_code); + const auto& npackets = std::get<1>(parsed_code); + const auto& pwd = std::get<2>(parsed_code); + + std::vector packets(npackets); + + auto available = http_client_->isAvailable(); + auto get_method = [this,&available](const auto& c) { + /* if available, try http server */ + if (available) { + auto datas = http_client_->get(c); + std::vector> data; + std::transform(datas.begin(), datas.end(), std::back_inserter(data), [](const auto& s) { + std::vector blob; + std::move(s.begin(), s.end(), std::back_inserter(blob)); + return blob; + }); + return data; + /* if fail, then perform request from local node */ + } else return node.get(c); + }; + + std::vector whole_data; + uint32_t licode; + { + std::stringstream css(lcode); + css >> std::hex >> licode; + } + for (uint8_t s = 0; s < npackets; ++s) { + std::string target = code_from_pin(licode + s); + auto values = get_method(target); + auto data = parse_data(code, std::move(values), pwd, no_decrypt); + if (data.empty()) + return {false, {}}; + std::move(data.begin(), data.end(), std::back_inserter(whole_data)); } - return {true, {data.begin(), data.end()}}; + + std::string ret; + std::move(whole_data.begin(), whole_data.end(), std::back_inserter(ret)); + return {true, std::move(ret)}; } -std::vector Bin::data_from_stream(std::stringstream&& input_stream) { +std::vector Bin::data_from_stream(std::stringstream& input_stream, const size_t& count) { std::vector buffer; - buffer.resize(dht::MAX_VALUE_SIZE); - input_stream.read(reinterpret_cast(buffer.data()), dht::MAX_VALUE_SIZE); + buffer.resize(count); + input_stream.read(reinterpret_cast(buffer.data()), count); buffer.resize(input_stream.gcount()); return buffer; } -std::string Bin::random_pin() { - static std::uniform_int_distribution dist; - static std::mt19937_64 rand_; - static std::random_device rdev; - static std::seed_seq seed {rdev(), rdev()}; - static bool initialized = false; - if (not initialized) - rand_.seed(seed); - - auto pin = dist(rand_); - std::stringstream ss; - ss << std::setfill('0') << std::setw(Bin::DPASTE_PIN_LEN) << std::hex << pin; - auto pin_s = ss.str(); +std::string Bin::code_from_pin(uint32_t pin) { + auto pin_s = hexStrFromInt(pin, code::DPASTE_PIN_LEN); std::transform(pin_s.begin(), pin_s.end(), pin_s.begin(), ::toupper); return pin_s; } -std::pair Bin::prepare_data(std::vector&& data, std::unique_ptr&& params) { - Packet p; +std::string Bin::hexStrFromInt(uint32_t i, size_t len) { + std::stringstream ss; + ss << std::setfill('0') << std::setw(len) << std::hex << i; + return ss.str(); +} + +std::pair, std::string> +Bin::prepare_data(std::stringstream&& input_stream, + std::unique_ptr&& params) +{ std::string pwd = ""; std::shared_ptr sparams(std::move(params)); std::shared_ptr init_params; @@ -144,48 +195,75 @@ std::pair Bin::prepare_data(std::vector&& dat bool to_sign {false}; if (auto gp = std::get_if(sparams.get())) { auto& keyid = conf_.at("pgp_key_id"); - to_sign = gp->sign and not keyid.empty(); - scheme = gp->scheme; + to_sign = gp->sign and not keyid.empty(); + scheme = gp->scheme; init_params = std::make_shared(); init_params->emplace(keyid); } else if (auto aesp = std::get_if(sparams.get())) { - scheme = aesp->scheme; - pwd = random_pin(); + scheme = aesp->scheme; + pwd = random_pin(); aesp->password = pwd; } auto cipher = crypto::Cipher::get(scheme, std::move(init_params)); - if (cipher) { - auto cipher_text = cipher->processPlainText(data, std::move(sparams)); - if (cipher_text.empty()) { - p.data.insert(p.data.end(), data.begin(), data.end()); - if (to_sign) { - DPASTE_MSG("Signing data..."); - auto res = std::dynamic_pointer_cast(cipher)->sign(p.data); - p.signature = res.first; - } + uint32_t rsiz = 0; + const size_t SIZE_PER_PACKET = dht::MAX_VALUE_SIZE - Packet::EXTRA_SERIALIZATION_BYTES; + std::vector packets; + packets.reserve(std::ceil(((double)Bin::DPASTE_MAX_SIZE) / SIZE_PER_PACKET)); + std::vector data; + while (rsiz < Bin::DPASTE_MAX_SIZE + and (data = data_from_stream(input_stream, SIZE_PER_PACKET)).size() > 0) + { + rsiz += data.size(); + + Packet p; + if (cipher) { + auto cipher_text = cipher->processPlainText(data, std::move(sparams)); + if (cipher_text.empty()) { + p.data.insert(p.data.end(), data.begin(), data.end()); + if (to_sign) { + DPASTE_MSG("Signing data..."); + auto res = std::dynamic_pointer_cast(cipher)->sign(p.data); + p.signature = std::move(res.first); + } + } else + p.data = std::move(cipher_text); } else - p.data = cipher_text; - } else - p.data.insert(p.data.end(), data.begin(), data.end()); - return {p, pwd}; + p.data = std::move(data); + + packets.push_back(std::move(p)); + } + + packets.shrink_to_fit(); + return {packets, std::move(pwd)}; } -std::string Bin::paste(std::vector&& data, std::unique_ptr&& params) { - auto code = random_pin(); +std::string +Bin::paste(std::stringstream&& input_stream, std::unique_ptr&& params) { + auto pin = random_.integer(); + auto available = http_client_->isAvailable(); + auto paste_method = [this,&available](const auto& c, auto&& d) { + if (available) return http_client_->put(c, {d.begin(), d.end()}); + else return node.paste(c, std::forward>(d)); + }; - auto pp = prepare_data(std::forward>(data), std::forward>(params)); - auto& p = pp.first; - auto& pwd = pp.second; + auto pp = prepare_data(std::forward(input_stream), + std::forward>(params)); + auto& packets = pp.first; + auto& pwd = pp.second; DPASTE_MSG("Pasting data..."); - auto bin_packet = p.serialize(); - auto success = http_client_->put(code, {bin_packet.begin(), bin_packet.end()}); - if (not success) - success = node.paste(code, std::move(bin_packet)); + size_t shift = 0; + for (const auto& p : packets) { + auto code = code_from_pin(pin + shift++); + auto bin_packet = p.serialize(); + bool success = paste_method(code, std::move(bin_packet)); + if (not success) + return {}; + } - return success ? DPASTE_URI_PREFIX+code+pwd : ""; + return DPASTE_URI_PREFIX+code_from_pin(pin)+hexStrFromInt(shift, code::DPASTE_NPACKETS_LEN)+pwd; } msgpack::object* diff --git a/src/bin.h b/src/bin.h index 50acf0d..e44a93c 100644 --- a/src/bin.h +++ b/src/bin.h @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -46,7 +46,11 @@ class Bin { #endif public: - static const constexpr unsigned int DPASTE_PIN_LEN {8}; + /** + * Maximal size to be read from the input stream and pasted on the DHT + * (possibly split in several packets). + */ + static const constexpr size_t DPASTE_MAX_SIZE {2 * 1024 * 1024}; Bin(); virtual ~Bin () {} @@ -69,11 +73,13 @@ class Bin { * * @return the code (key) to the pasted data. If empty, then process failed. */ - std::string paste(std::vector&& data, std::unique_ptr&& params); - std::string paste(std::stringstream&& input_stream, std::unique_ptr&& params) { - return paste(data_from_stream(std::move(input_stream)), - std::forward>(params)); + std::string paste(std::vector&& data, std::unique_ptr&& params) { + std::string is; + std::move(data.begin(), data.end(), std::back_inserter(is)); + std::stringstream ss(std::move(is)); + return paste(std::move(ss), std::forward>(params)); } + std::string paste(std::stringstream&& input_stream, std::unique_ptr&& params); private: /* constants */ @@ -81,6 +87,13 @@ class Bin { static const constexpr uint8_t PROTO_VERSION = 0; struct Packet { + /** + * Bytes needed for the two layers of serialization. This accounts for + * dpaste's usage of msgpack as well as OpenDHT's and GPG + * encryption/signature. + */ + static const constexpr size_t EXTRA_SERIALIZATION_BYTES {12 * 1024}; + std::vector data {}; std::vector signature {}; @@ -88,25 +101,67 @@ class Bin { void deserialize(const std::vector& pbuffer); }; + /*! + * @class Random + * @brief Random number generator state encapsulator. + * @details + * The structure contains the the state of the random number generator used + * by the Bin class to perform its random code generation. The structure + * is embeded inside the Bin class as a private member. + */ + struct Random { + Random() { + std::random_device rdev; + std::seed_seq seed {rdev(), rdev()}; + gen.seed(seed); + } + + inline uint32_t integer() { return dist(gen); } + + std::uniform_int_distribution dist; + std::mt19937_64 gen; + }; + /** * Create a Packet from input data and crypto parameters. * - * @param data input data in bytes. - * @param params cryptographic parameters (either GPGParameters or - * AESParameters). + * @param input_stream input data stream of bytes. + * @param params cryptographic parameters (either GPGParameters or + * AESParameters). * * @return an ordered pair of a Packet and associated password. */ - std::pair prepare_data(std::vector&& data, std::unique_ptr&& params); + std::pair, std::string> + prepare_data(std::stringstream&& input_stream, std::unique_ptr&& params); + + /** + * Parses the DHT values recovered from the DHT get operation and returns + * the first value found the the given code identifier. + * + * @param code String of bytes consisting in the location code + * information. + * @param values The values to parse. + * @param pwd The password used to decrypt the AES encrypted data + * (if it was present in the input code). + * @param no_decrypt A bool indicating if the data should be decrypted + * before being returned. + * + * @return The value (or part of the value) recovered from the DHT. + */ + std::vector parse_data(const std::string& code, + std::vector>&& values, + std::string pwd, + bool no_decrypt) const; /** * Get data from input stream. * * @param input_stream The stream to read to get the data. + * @param count Number of bytes to be read from the stream. * * @return the data */ - static std::vector data_from_stream(std::stringstream&& input_stream); + static std::vector data_from_stream(std::stringstream& input_stream, const size_t& count); /** * Parse dpaste uri for code. @@ -117,13 +172,52 @@ class Bin { */ static std::string code_from_dpaste_uri(const std::string& uri); - static std::string random_pin(); + /** + * Parses the input code into three separate parts consisting respectivly in + * the location code information, the number of packets the file is split + * into and the password used to decrypt AES encrypted data. + * + * @param code The input code. + * + * @return The tuple described above. + */ + static std::tuple parse_code_info(const std::string& code); + + /** + * Converts a number into its hexadecimal `len` bytes long string value + * with zeroes padding in prefix when necessary. + * + * @param i The integer. + * @param len The length of the resulting string. + * + * @return the resulting hexadecimal string representation of the integer. + */ + static std::string hexStrFromInt(uint32_t i, size_t len); + /** + * Converts a PIN number into a string code, i.e. an hexadecimal string + * representation of the PIN in upper case. + * + * @param i The integer. + * + * @return the resulting hexadecimal string representation of the integer. + */ + static std::string code_from_pin(uint32_t i); + + /** + * Generates a random PIN using a 32-bit random number generator. The + * resulting string describes a 32-bit value. + */ + inline std::string random_pin() { return code_from_pin(random_.integer()); } + + /* A map containing the configuration read from the config file on disk. */ std::map conf_; /* transport */ std::unique_ptr http_client_ {}; Node node {}; + + Random random_ {}; }; } /* dpaste */ diff --git a/src/cipher.cpp b/src/cipher.cpp index 9c1c52c..99d125c 100644 --- a/src/cipher.cpp +++ b/src/cipher.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -19,6 +19,8 @@ */ #include "cipher.h" + +#include "code.h" #include "gpgcrypto.h" #include "aescrypto.h" @@ -37,7 +39,7 @@ void Cipher::init() { std::shared_ptr Cipher::get(const std::vector& cipher_text, const std::string& pin="") { if (GPG::isGPGencrypted(cipher_text)) return std::make_shared(); - else if (pin.size() == AES::PIN_WITH_PASS_LEN) + else if (pin.size() == code::PIN_WITH_PASS_LEN) return std::make_shared(); return {}; } diff --git a/src/cipher.h b/src/cipher.h index 1ee0fe6..e6f6905 100644 --- a/src/cipher.h +++ b/src/cipher.h @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/src/code.h b/src/code.h new file mode 100644 index 0000000..2a5ac5a --- /dev/null +++ b/src/code.h @@ -0,0 +1,53 @@ +/* + * Copyright © 2020 Simon Désaulniers + * Author: Simon Désaulniers + * + * This file is part of dpaste. + * + * dpaste is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * dpaste is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with dpaste. If not, see . + */ + +#pragma once + +#include + +namespace dpaste { +namespace code { + +/** + * Number of bytes in a dpaste code accounting for the number of packets the + * file was split into + */ +static const constexpr unsigned int DPASTE_NPACKETS_LEN {2}; +/** + * Number of bytes in a dpaste code accounting for the location PIN. */ +static const constexpr unsigned int DPASTE_PIN_LEN {8}; + +/** + * Number of bytes used to encode the AES password. + */ +static const constexpr size_t PASSWORD_LEN {8}; + +/** + * Length of the PIN if it contains the password for encrypted pasted data. + * It consists of 18 hexadecimal characters: 18*4 = 72 bits. Last 32 bits + * encode the password. Otherwise, PIN should be 40 bits. + */ +static const constexpr size_t PIN_WITH_PASS_LEN {DPASTE_PIN_LEN + DPASTE_NPACKETS_LEN + PASSWORD_LEN}; + +} +} /* dpaste */ + +/* vim: set sts=4 ts=4 sw=4 tw=120 et :*/ + diff --git a/src/conf.cpp b/src/conf.cpp index 1a31f90..6a22b75 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/src/conf.h b/src/conf.h index 68edde7..d92144a 100644 --- a/src/conf.h +++ b/src/conf.h @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/src/gpgcrypto.cpp b/src/gpgcrypto.cpp index 23bfd9d..ed54472 100644 --- a/src/gpgcrypto.cpp +++ b/src/gpgcrypto.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/src/gpgcrypto.h b/src/gpgcrypto.h index 99865d5..45cfbf4 100644 --- a/src/gpgcrypto.h +++ b/src/gpgcrypto.h @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/src/http_client.cpp b/src/http_client.cpp index aeda97d..e1577d9 100644 --- a/src/http_client.cpp +++ b/src/http_client.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -38,12 +38,17 @@ using json = nlohmann::json; static std::ofstream null("/dev/null"); -std::string HttpClient::get(const std::string& code) const { +bool HttpClient::isAvailable() const { + return put(dht::InfoHash::getRandom().toString(), "hello world"); +} + +std::vector HttpClient::get(const std::string& code) const { try { curlpp::Cleanup mycleanup; curlpp::Easy req; req.setOpt(port); - std::stringstream response, oss; + std::stringstream response; + std::vector data; req.setOpt(HTTP_PROTO+ host+"/"+dht::InfoHash::get(code).toString() +"?user_type="+dpaste::Node::DPASTE_USER_TYPE @@ -55,15 +60,17 @@ std::string HttpClient::get(const std::string& code) const { /* server gives code 200 when everything is fine. */ if (curlpp::Infos::ResponseCode::get(req) == 200) { auto pr = json::parse(response.str()); - if (not pr.empty()) { - std::istringstream iss((*pr.begin())["base64"].dump()); + for (const auto& entry : pr) { + std::istringstream iss(entry["base64"].dump()); + std::stringstream oss; base64::decoder d; d.decode(iss, oss); + data.emplace_back(oss.str()); } } } catch (curlpp::RuntimeError & e) { } - return oss.str(); + return data; } catch (curlpp::LogicError & e) { return {}; } } diff --git a/src/http_client.h b/src/http_client.h index 1c11465..ac9e69d 100644 --- a/src/http_client.h +++ b/src/http_client.h @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -20,6 +20,7 @@ #pragma once +#include #include namespace dpaste { @@ -29,7 +30,28 @@ class HttpClient { HttpClient (std::string host, long port) : host(host), port(port) {} virtual ~HttpClient () {} - std::string get(const std::string& code) const; + /** + * Tells if the server responds to requests. + */ + bool isAvailable() const; + + /** + * Send a GET request to the REST server. + * + * @param code The location code. + * + * @return A vector of the values recovered from the DHT by the REST server. + */ + std::vector get(const std::string& code) const; + + /** + * Send a PUT request to the REST server. + * + * @param code The location code. + * @param data The data to publish. + * + * @return True if the server accepted and completed the request, false otherwise. + */ bool put(const std::string& code, const std::string& data) const; private: diff --git a/src/log.cpp b/src/log.cpp index ce93ebd..b8259e8 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/src/log.h b/src/log.h index b78042b..4c7f943 100644 --- a/src/log.h +++ b/src/log.h @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/src/main.cpp b/src/main.cpp index 32a3552..786ad39 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -102,7 +102,7 @@ ParsedArgs parseArgs(int argc, char *argv[]) { } void print_help() { - std::cout << PACKAGE_NAME << " -- A simple pastebin for light values (max 64KB)" + std::cout << PACKAGE_NAME << " -- A simple pastebin (for values of size up to 2MiB)" << " using OpenDHT distributed hash table." << std::endl << std::endl; std::cout << "SYNOPSIS" << std::endl diff --git a/src/node.cpp b/src/node.cpp index d6b084b..41950c3 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/src/node.h b/src/node.h index 48f1f75..841175f 100644 --- a/src/node.h +++ b/src/node.h @@ -1,5 +1,5 @@ /* - * Copyright © 2017 Simon Désaulniers + * Copyright © 2017-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. diff --git a/tests/aes.cpp b/tests/aes.cpp index d79041b..343d649 100644 --- a/tests/aes.cpp +++ b/tests/aes.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2018 Simon Désaulniers + * Copyright © 2018-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -20,15 +20,15 @@ #include -#include - #include "cipher.h" #include "aescrypto.h" +#include "catch.h" + namespace dpaste { namespace tests { -TEST_CASE("AES process plain/cipher text", "[AES][processPlainText][processCipherText]") { +CATCH_TEST_CASE("AES process plain/cipher text", "[AES][processPlainText][processCipherText]") { crypto::AES aes {}; std::vector data {0,1,2,3,4}; auto pwd = "this_is_a_really_good_pwd"; @@ -37,10 +37,10 @@ TEST_CASE("AES process plain/cipher text", "[AES][processPlainText][processCiphe p->emplace(pwd); auto ct = aes.processPlainText(data, std::move(p)); - REQUIRE ( not ct.empty() ); + CATCH_REQUIRE ( not ct.empty() ); auto pt = aes.processCipherText(ct, std::move(pp)); - REQUIRE ( pt == data ); + CATCH_REQUIRE ( pt == data ); } } /* tests */ diff --git a/tests/bin.cpp b/tests/bin.cpp index ceb54fb..0e53969 100644 --- a/tests/bin.cpp +++ b/tests/bin.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2018 Simon Désaulniers + * Copyright © 2018-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -20,10 +20,11 @@ #include -#include +#include "code.h" +#include "bin.h" +#include "catch.h" #include "tests.h" -#include "bin.h" namespace dpaste { namespace tests { @@ -31,93 +32,156 @@ namespace tests { class PirateBinTester { #define YARRRRR public: - static const constexpr unsigned int LOCATION_CODE_LEN = 8; - static const constexpr char* DPASTE_URI_PREFIX = "dpaste:"; PirateBinTester () {} virtual ~PirateBinTester () {} - std::string code_from_dpaste_uri(const std::string& uri) const { + static std::string dpaste_uri_prefix() { return Bin::DPASTE_URI_PREFIX; } + + static std::string hexStrFromInt(uint32_t i, size_t len) { + return Bin::hexStrFromInt(i, len); + } + + static std::tuple parse_code_info(const std::string& uri) { + return Bin::parse_code_info(uri); + } + + static std::string code_from_dpaste_uri(const std::string& uri) { return Bin::code_from_dpaste_uri(uri); } - std::vector data_from_stream(std::stringstream&& input_stream) const { - return Bin::data_from_stream(std::forward(input_stream)); + std::vector data_from_stream(std::stringstream& input_stream, size_t count) const { + return Bin::data_from_stream(input_stream, count); } }; -TEST_CASE("Bin get/paste on DHT", "[Bin][get][paste]") { +void test_paste(Bin& bin, const std::vector& data) { using pbt = PirateBinTester; + auto code = bin.paste(std::vector {data}, {}); + CATCH_REQUIRE ( code.size() == pbt::dpaste_uri_prefix().size()+code::DPASTE_PIN_LEN+code::DPASTE_NPACKETS_LEN ); + + CATCH_SECTION ( "getting pasted blob back from the DHT" ) { + auto rd = bin.get(std::move(code)).second; + std::vector rdv {rd.begin(), rd.end()}; + CATCH_REQUIRE ( data == rdv ); + } +} + +void test_paste_encrypted(Bin& bin, const std::vector& data) { + using pbt = PirateBinTester; + auto p = std::make_unique(); + p->emplace(); + auto code = bin.paste(std::vector {data}, std::move(p)); + CATCH_REQUIRE ( code.size() == pbt::dpaste_uri_prefix().size()+code::DPASTE_PIN_LEN+code::DPASTE_NPACKETS_LEN+code::PASSWORD_LEN ); + + CATCH_SECTION ( "getting pasted AES encrypted blob back from the DHT" ) { + auto rd = bin.get(std::move(code)).second; + std::vector rdv {rd.begin(), rd.end()}; + CATCH_REQUIRE ( data == rdv ); + } +} + +CATCH_TEST_CASE("Bin get/paste on DHT", "[Bin][get][paste]") { std::vector data = {0, 1, 2, 3, 4}; Bin bin {}; crypto::Cipher::init(); - SECTION ( "pasting data {0,1,2,3,4}" ) { - auto code = bin.paste(std::vector {data}, {}); - REQUIRE ( code.size() == pbt::LOCATION_CODE_LEN+sizeof(pbt::DPASTE_URI_PREFIX)-1 ); - - SECTION ( "getting pasted blob back from the DHT" ) { - auto rd = bin.get(std::move(code)).second; - std::vector rdv {rd.begin(), rd.end()}; - REQUIRE ( data == rdv ); - } + CATCH_SECTION ( "pasting data {0,1,2,3,4}" ) { + test_paste(bin, data); } - SECTION ( "pasting AES encrypted {0,1,2,3,4}" ) { - auto p = std::make_unique(); - p->emplace(); - auto code = bin.paste(std::vector {data}, std::move(p)); - REQUIRE ( code.size() == 2*pbt::LOCATION_CODE_LEN+sizeof(pbt::DPASTE_URI_PREFIX)-1 ); - - SECTION ( "getting pasted AES encrypted blob back from the DHT" ) { - auto rd = bin.get(std::move(code)).second; - std::vector rdv {rd.begin(), rd.end()}; - REQUIRE ( data == rdv ); + CATCH_SECTION ( "pasting AES encrypted {0,1,2,3,4}" ) { + test_paste_encrypted(bin, data); + } + CATCH_SECTION ( "pasting data (size over 64KB)" ) { + std::array sizes = {32 * 1024, 64 * 1024, 512 * 1024}; + for (const auto& s : sizes) { + std::stringstream ss; + ss << s; + CATCH_SECTION ( "trying for size of " + ss.str() + "B" ) { + std::vector buffer(s); + CATCH_SECTION ( "plain data" ) { + test_paste(bin, buffer); + } + CATCH_SECTION ( "AES encrypted data" ) { + test_paste_encrypted(bin, buffer); + } + } } } } -TEST_CASE("Bin parsing of uri code ([dpaste:]XXXXXXXX)", "[Bin][code_from_dpaste_uri]") { - PirateBinTester pt; - const std::string PIN = random_pin(); - std::string pin_upper {PIN.begin(), PIN.end()}; +CATCH_TEST_CASE("Bin parsing of uri code ([dpaste:]XXXXXXXX)", "[Bin][code_from_dpaste_uri][parse_code_info]") { + using pbt = PirateBinTester; + const uint32_t NPACKETS = (uint32_t)random_number() % 256; + const std::string NPACKETSS = pbt::hexStrFromInt(NPACKETS, 2); + const std::string LCODE = random_code(); + const std::string CODE = LCODE + NPACKETSS; + const std::string PWD = random_code(); + std::string pin_upper {CODE.begin(), CODE.end()}; std::transform(pin_upper.begin(), pin_upper.end(), pin_upper.begin(), ::toupper); - SECTION ( "good pins" ) { - auto gc1 = "dpaste:"+PIN; - auto gc2 = PIN; - REQUIRE ( pt.code_from_dpaste_uri(gc1) == PIN ); - REQUIRE ( pt.code_from_dpaste_uri(gc2) == PIN ); - - SECTION ( "good pin (upper case)" ) { - auto gc3 = "dpaste:"+pin_upper; - auto gc4 = pin_upper; - - REQUIRE ( pt.code_from_dpaste_uri(gc3) == pin_upper ); - REQUIRE ( pt.code_from_dpaste_uri(gc4) == pin_upper ); + auto test_code_parsing = [&LCODE,&NPACKETS](const std::string& unparsed_code, const std::string& pwd) { + const auto code_tuple1 = pbt::parse_code_info(unparsed_code); + + CATCH_REQUIRE ( std::get<0>(code_tuple1) == LCODE ); + CATCH_REQUIRE ( std::get<1>(code_tuple1) == NPACKETS ); + CATCH_REQUIRE ( std::get<2>(code_tuple1) == pwd ); + }; + + CATCH_SECTION ( "good pins" ) { + auto gc1 = "dpaste:"+CODE; + auto gc2 = CODE; + auto gc3 = "dpaste:"+CODE+PWD; + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(gc1) == CODE ); + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(gc2) == CODE ); + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(gc3) == CODE+PWD ); + + CATCH_SECTION ( "good pin (upper case)" ) { + auto gc4 = "dpaste:"+pin_upper; + auto gc5 = pin_upper; + auto gc6 = "dpaste:"+pin_upper+PWD; + + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(gc4) == pin_upper ); + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(gc5) == pin_upper ); + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(gc6) == pin_upper+PWD ); + } + CATCH_SECTION ( "testing Bin::parse_code_info" ) { + test_code_parsing(CODE, ""); + } + CATCH_SECTION ( "testing Bin::parse_code_info with password" ) { + test_code_parsing(CODE+PWD, PWD); } } - SECTION ( "bad pins" ) { - std::string bc1 = "DPASTE:"+PIN; - std::string bc2 = "DPaste:"+PIN; - REQUIRE ( pt.code_from_dpaste_uri(bc1) != PIN ); - REQUIRE ( pt.code_from_dpaste_uri(bc2) != PIN ); + CATCH_SECTION ( "bad pins" ) { + std::string bc1 = "DPASTE:"+CODE; + std::string bc2 = "DPaste:"+CODE; + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(bc1) != CODE ); + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(bc2) != CODE ); - SECTION ( "bad pins (upper case)" ) { + CATCH_SECTION ( "bad pins (upper case)" ) { auto bc3 = "DPASTE:"+pin_upper; auto bc4 = "DPaste:"+pin_upper; - REQUIRE ( pt.code_from_dpaste_uri(bc3) != pin_upper ); - REQUIRE ( pt.code_from_dpaste_uri(bc4) != pin_upper ); + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(bc3) != pin_upper ); + CATCH_REQUIRE ( pbt::code_from_dpaste_uri(bc4) != pin_upper ); } } } -TEST_CASE("Bin conversion of stringstream to vector", "[Bin][data_from_stream]") { +CATCH_TEST_CASE("Bin conversion to hex string from int", "[Bin][hexStrFromInt]") { + using pbt = PirateBinTester; + CATCH_REQUIRE ( pbt::hexStrFromInt(255, 2) == "ff" ); + CATCH_REQUIRE ( pbt::hexStrFromInt(121, 2) == "79" ); + CATCH_REQUIRE ( pbt::hexStrFromInt(0, 2) == "00" ); +} + + +CATCH_TEST_CASE("Bin conversion of stringstream to vector", "[Bin][data_from_stream]") { PirateBinTester pt; const std::string DATA = "SOME DATA"; std::stringstream ss(DATA); std::vector d {DATA.begin(), DATA.end()}; - REQUIRE ( pt.data_from_stream(std::move(ss)) == d ); + CATCH_REQUIRE ( pt.data_from_stream(ss, DATA.size()) == d ); } } /* tests */ diff --git a/tests/catch.h b/tests/catch.h new file mode 100644 index 0000000..4767657 --- /dev/null +++ b/tests/catch.h @@ -0,0 +1,28 @@ +/* + * Copyright © 2020 Simon Désaulniers + * Author: Simon Désaulniers + * + * This file is part of dpaste. + * + * dpaste is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * dpaste is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with dpaste. If not, see . + */ + +#pragma once + +#define CATCH_CONFIG_PREFIX_ALL 0 + +#include + +/* vim: set sts=4 ts=4 sw=4 tw=120 et :*/ + diff --git a/tests/conf.cpp b/tests/conf.cpp index fe1eee3..6731cb7 100644 --- a/tests/conf.cpp +++ b/tests/conf.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2018 Simon Désaulniers + * Copyright © 2018-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -18,37 +18,37 @@ * along with dpaste. If not, see . */ -#include - #include "conf.h" +#include "catch.h" + namespace dpaste { namespace tests { const std::string CONFIG_FILE_PATH_PREFIX = "./tests/misc/dpaste.conf"; -TEST_CASE("ConfigurationFile loading/parsing (./misc/dpaste.conf)", "[ConfigurationFile][get][load]") { - SECTION ( "Well-formed file" ) { +CATCH_TEST_CASE("ConfigurationFile loading/parsing (./misc/dpaste.conf)", "[ConfigurationFile][get][load]") { + CATCH_SECTION ( "Well-formed file" ) { conf::ConfigurationFile f(CONFIG_FILE_PATH_PREFIX+".1"); - REQUIRE ( f.load() == 0 ); + CATCH_REQUIRE ( f.load() == 0 ); auto c = f.getConfiguration(); - REQUIRE ( c.at("host") == "192.168.1.100" ); - REQUIRE ( c.at("port") == "6500" ); + CATCH_REQUIRE ( c.at("host") == "192.168.1.100" ); + CATCH_REQUIRE ( c.at("port") == "6500" ); } - SECTION ( "Malformed file (bad host)" ) { + CATCH_SECTION ( "Malformed file (bad host)" ) { conf::ConfigurationFile f(CONFIG_FILE_PATH_PREFIX+".2"); - REQUIRE ( f.load() == 0 ); + CATCH_REQUIRE ( f.load() == 0 ); auto c = f.getConfiguration(); - REQUIRE ( c.at("host") == "127.0.0.1" ); /* the default */ - REQUIRE ( c.at("port") == "6500" ); /* recovered from file */ + CATCH_REQUIRE ( c.at("host") == "127.0.0.1" ); /* the default */ + CATCH_REQUIRE ( c.at("port") == "6500" ); /* recovered from file */ } - SECTION ( "File not found" ) { + CATCH_SECTION ( "File not found" ) { conf::ConfigurationFile f("/dpaste.conf"); /* This should not work on any computer owned by a sane person */ - REQUIRE ( f.load() == 1 ); + CATCH_REQUIRE ( f.load() == 1 ); } } diff --git a/tests/node.cpp b/tests/node.cpp index 4624061..c31041b 100644 --- a/tests/node.cpp +++ b/tests/node.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2018 Simon Désaulniers + * Copyright © 2018-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -18,10 +18,10 @@ * along with dpaste. If not, see . */ -#include +#include "node.h" +#include "catch.h" #include "tests.h" -#include "node.h" namespace dpaste { namespace tests { @@ -35,25 +35,25 @@ class PirateNodeTester { bool is_running(const dpaste::Node& n) const { return n.running_; } }; -TEST_CASE("Node get/paste on DHT", "[Node][get][paste]") { +CATCH_TEST_CASE("Node get/paste on DHT", "[Node][get][paste]") { PirateNodeTester pt; - const std::string PIN = random_pin(); + const std::string PIN = random_code(); std::vector data = {0, 1, 2, 3, 4}; dpaste::Node node {}; node.run(); - SECTION ( "pasting data {0,1,2,3,4}" ) { - REQUIRE ( node.paste(PIN, std::vector {data}) ); + CATCH_SECTION ( "pasting data {0,1,2,3,4}" ) { + CATCH_REQUIRE ( node.paste(PIN, std::vector {data}) ); - SECTION ( "getting pasted blob back from the DHT" ) { + CATCH_SECTION ( "getting pasted blob back from the DHT" ) { auto rd = node.get(PIN); - REQUIRE ( data == rd.front() ); + CATCH_REQUIRE ( data == rd.front() ); } } node.stop(); - REQUIRE ( not pt.is_running(node) ); + CATCH_REQUIRE ( not pt.is_running(node) ); } } /* tests */ diff --git a/tests/tests.cpp b/tests/tests.cpp index da2b051..939cb38 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -1,5 +1,5 @@ /* - * Copyright © 2018 Simon Désaulniers + * Copyright © 2018-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -21,7 +21,8 @@ #include #define CATCH_CONFIG_MAIN -#include + +#include "catch.h" namespace dpaste { namespace tests { @@ -44,7 +45,7 @@ int random_number() { return dist(rand_); } -std::string random_pin() { +std::string random_code() { const auto i = random_number(); std::stringstream ss; ss << std::hex << i; diff --git a/tests/tests.h b/tests/tests.h index 025cd69..1052a91 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -1,5 +1,5 @@ /* - * Copyright © 2018 Simon Désaulniers + * Copyright © 2018-2020 Simon Désaulniers * Author: Simon Désaulniers * * This file is part of dpaste. @@ -18,11 +18,13 @@ * along with dpaste. If not, see . */ +#include + namespace dpaste { namespace tests { int random_number(); -std::string random_pin(); +std::string random_code(); } /* tests */ } /* dpaste */