Optimization attempt

This commit is contained in:
Michele Rodolfi 2020-12-14 12:42:18 +01:00
parent d8a050b183
commit 26533e2769
6 changed files with 179 additions and 97 deletions

View File

@ -12,28 +12,8 @@
#include "utils.h" #include "utils.h"
namespace horcrux { namespace horcrux {
/** Cipher context class
* This is a wrapper for OpenSSL context object, just for RAII pattern
*/
class EvpCipherCtx {
EVP_CIPHER_CTX *ptr;
public: void AES256_CBC::init(Mode mode, const raw_data& key,
EvpCipherCtx() {
ptr = EVP_CIPHER_CTX_new();
}
~EvpCipherCtx() {
EVP_CIPHER_CTX_free(ptr);
}
EVP_CIPHER_CTX* get() {
return ptr;
}
};
EvpCipherCtx AES256_CBC::init(Mode mode, const raw_data& key,
const raw_data& iv) { const raw_data& iv) {
if (key.size() != kKeySize) { if (key.size() != kKeySize) {
throw std::runtime_error("Wrong key size"); throw std::runtime_error("Wrong key size");
@ -42,24 +22,54 @@ EvpCipherCtx AES256_CBC::init(Mode mode, const raw_data& key,
throw std::runtime_error("Wrong IV size"); throw std::runtime_error("Wrong IV size");
} }
EvpCipherCtx ctx; ctx = std::make_unique<EvpCipherCtx>();
if (mode == Mode::kEncrypt) { if (mode == Mode::kEncrypt) {
if (EVP_EncryptInit(ctx.get(), EVP_aes_256_cbc(), if (EVP_EncryptInit(ctx->get(), EVP_aes_256_cbc(),
key.data(), iv.data()) == 0) { key.data(), iv.data()) == 0) {
throw std::runtime_error("EVP_EncryptInit"); throw std::runtime_error("EVP_EncryptInit");
} }
} else if (mode == Mode::kDecrypt) { } else if (mode == Mode::kDecrypt) {
if (EVP_DecryptInit(ctx.get(), EVP_aes_256_cbc(), if (EVP_DecryptInit(ctx->get(), EVP_aes_256_cbc(),
key.data(), iv.data()) == 0) { key.data(), iv.data()) == 0) {
throw std::runtime_error("EVP_DecryptInit"); throw std::runtime_error("EVP_DecryptInit");
} }
} else { } else {
throw std::invalid_argument("Invalid Cipher mode"); throw std::invalid_argument("Invalid Cipher mode");
} }
return ctx;
} }
size_t AES256_CBC::process_chunk(Mode mode, EvpCipherCtx& ctx, size_t AES256_CBC::process_start(Mode mode,
raw_data::const_iterator begin,
raw_data::const_iterator end,
raw_data& output,
bool resize_in, bool resize_out) {
auto current = begin;
size_t output_offset = 0;
raw_data iv(kIvSize);
if (mode == Mode::kEncrypt) {
// Make sure output is large enough to add IV
output.resize(kIvSize);
// generate IV
iv = generate_random(kIvSize);
// write IV to output
std::copy(iv.begin(), iv.end(), output.begin());
output_offset += kIvSize;
} else {
// kDecrypt
if (end - begin < kIvSize){
throw std::invalid_argument(
"First encrypted chunk must contain the whole IV");
}
// read IV from input
std::copy(current, current + kIvSize, iv.begin());
current += kIvSize;
}
init(mode, encryption_key, iv);
return output_offset + process_chunk(mode, current, end, output, output_offset, resize_in, resize_out);
}
size_t AES256_CBC::process_chunk(Mode mode,
raw_data::const_iterator begin, raw_data::const_iterator begin,
raw_data::const_iterator end, raw_data::const_iterator end,
raw_data& output, size_t output_offset, raw_data& output, size_t output_offset,
@ -71,13 +81,13 @@ size_t AES256_CBC::process_chunk(Mode mode, EvpCipherCtx& ctx,
output.resize(output_offset + chunk_size + kIvSize); output.resize(output_offset + chunk_size + kIvSize);
} }
if (mode == Mode::kEncrypt) { if (mode == Mode::kEncrypt) {
if (1 != EVP_EncryptUpdate(ctx.get(), if (1 != EVP_EncryptUpdate(ctx->get(),
output.data() + output_offset, &len, output.data() + output_offset, &len,
&*begin, chunk_size)) { &*begin, chunk_size)) {
throw std::runtime_error("EVP_EncryptUpdate"); throw std::runtime_error("EVP_EncryptUpdate");
} }
} else { } else {
if (1 != EVP_DecryptUpdate(ctx.get(), if (1 != EVP_DecryptUpdate(ctx->get(),
output.data() + output_offset, &len, output.data() + output_offset, &len,
&*begin, chunk_size)) { &*begin, chunk_size)) {
throw std::runtime_error("EVP_DecryptUpdate"); throw std::runtime_error("EVP_DecryptUpdate");
@ -88,7 +98,7 @@ size_t AES256_CBC::process_chunk(Mode mode, EvpCipherCtx& ctx,
return len; return len;
} }
size_t AES256_CBC::process_final(Mode mode, EvpCipherCtx& ctx, size_t AES256_CBC::process_final(Mode mode,
raw_data& output, size_t output_offset, raw_data& output, size_t output_offset,
bool resize_in, bool resize_out) { bool resize_in, bool resize_out) {
int len; int len;
@ -97,12 +107,12 @@ size_t AES256_CBC::process_final(Mode mode, EvpCipherCtx& ctx,
output.resize(output_offset + kIvSize); output.resize(output_offset + kIvSize);
} }
if (mode == Mode::kEncrypt) { if (mode == Mode::kEncrypt) {
if (1 != EVP_EncryptFinal_ex(ctx.get(), output.data() + output_offset, if (1 != EVP_EncryptFinal_ex(ctx->get(), output.data() + output_offset,
&len)) { &len)) {
throw std::runtime_error("EVP_EncryptFinal"); throw std::runtime_error("EVP_EncryptFinal");
} }
} else { } else {
if (1 != EVP_DecryptFinal_ex(ctx.get(), (output.data()) + output_offset, if (1 != EVP_DecryptFinal_ex(ctx->get(), (output.data()) + output_offset,
&len)) { &len)) {
throw std::runtime_error("EVP_DecryptFinal"); throw std::runtime_error("EVP_DecryptFinal");
} }
@ -112,62 +122,78 @@ size_t AES256_CBC::process_final(Mode mode, EvpCipherCtx& ctx,
return len; return len;
} }
size_t AES256_CBC::process_all(Mode mode, EvpCipherCtx& ctx, size_t AES256_CBC::process_all(Mode mode,
raw_data::const_iterator begin, raw_data::const_iterator begin,
raw_data::const_iterator end, raw_data::const_iterator end,
raw_data& output, size_t output_offset, raw_data& output, size_t output_offset,
bool resize_in, bool resize_out) { bool resize_in, bool resize_out) {
int len = process_chunk(mode, ctx, begin, end, output, output_offset, int len = process_chunk(mode, begin, end, output, output_offset,
resize_in, false); resize_in, false);
len += process_final(mode, ctx, output, output_offset + len, false, len += process_final(mode, output, output_offset + len, false,
resize_out); resize_out);
return len; return len;
} }
raw_data AES256_CBC::process(const raw_data& inputdata) { raw_data AES256_CBC::process(const raw_data& inputdata, bool begin, bool end) {
if (mode == Mode::kEncrypt) //if (mode == Mode::kEncrypt)
return encrypt(inputdata); // return encrypt(inputdata, begin, end);
//else
// return decrypt(inputdata, begin, end);
raw_data output;
size_t len;
if (begin)
len = process_start(mode, inputdata.begin(), inputdata.end(), output);
else else
return decrypt(inputdata); len = process_chunk(mode, inputdata.begin(), inputdata.end(), output, 0);
if (end)
process_final(mode, output, len);
return output;
} }
raw_data AES256_CBC::encrypt(const raw_data& plaintext) { raw_data AES256_CBC::encrypt(const raw_data& plaintext, bool begin, bool end) {
return encrypt(encryption_key, horcrux::generate_random(kIvSize), //return encrypt(encryption_key, horcrux::generate_random(kIvSize),
plaintext); // plaintext);
raw_data output;
size_t len;
if (begin)
len = process_start(Mode::kEncrypt, plaintext.begin(), plaintext.end(), output);
else
len = process_chunk(Mode::kEncrypt, plaintext.begin(), plaintext.end(), output, 0);
if (end)
process_final(Mode::kEncrypt, output, len);
return output;
} }
raw_data AES256_CBC::decrypt(const raw_data& ciphertext) { raw_data AES256_CBC::decrypt(const raw_data& ciphertext, bool begin, bool end) {
return decrypt(encryption_key, ciphertext); //return decrypt(encryption_key, ciphertext);
//raw_data output;
//init(Mode::kDecrypt, key, iv);
//process_all(Mode::kDecrypt, begin, end, output, 0);
//auto len = process_start(Mode::kDecrypt, ciphertext.begin(), ciphertext.end(), output);
//process_final(Mode::kDecrypt, output, len);
//return output;
raw_data output;
size_t len;
if (begin)
len = process_start(Mode::kDecrypt, ciphertext.begin(), ciphertext.end(), output);
else
len = process_chunk(Mode::kDecrypt, ciphertext.begin(), ciphertext.end(), output, 0);
if (end)
process_final(Mode::kDecrypt, output, len);
return output;
} }
raw_data AES256_CBC::encrypt(const raw_data& key, const raw_data& iv, raw_data AES256_CBC::encrypt(const raw_data& key, const raw_data& iv,
const raw_data& input) { const raw_data& input) {
auto ctx = init(Mode::kEncrypt, key, iv); init(Mode::kEncrypt, key, iv);
// Make sure ouput is large enough to contain IV + encrypted data + padding // Make sure ouput is large enough to contain IV + encrypted data + padding
raw_data output(input.size() + (2 * kIvSize)); raw_data output(input.size() + (2 * kIvSize));
std::copy(iv.begin(), iv.end(), output.begin()); std::copy(iv.begin(), iv.end(), output.begin());
process_all(Mode::kEncrypt, ctx, input.begin(), input.end(), output, process_all(Mode::kEncrypt, input.begin(), input.end(), output,
kIvSize); kIvSize);
return output; return output;
} }
/*
int AES256_CBC::encrypt(const raw_data& key, const raw_data& iv,
std::istream input, const size_t input_len,
std::ostream output, size_t& output_len) {
auto inbuf = raw_data(input_len);
auto outbuf = raw_data(input_len + 16);
input.read(reinterpret_cast<char*>(inbuf.data()), input_len);
encrypt(key, iv, inbuf, outbuf);
output.write(reinterpret_cast<char*>(outbuf.data()), outbuf.size());
return 0;
}
*/
raw_data AES256_CBC::decrypt(const raw_data& key, const raw_data& ciphertext) { raw_data AES256_CBC::decrypt(const raw_data& key, const raw_data& ciphertext) {
raw_data iv(ciphertext.begin(), ciphertext.begin() + kIvSize); raw_data iv(ciphertext.begin(), ciphertext.begin() + kIvSize);
return decrypt(key, iv, ciphertext.begin() + kIvSize, ciphertext.end()); return decrypt(key, iv, ciphertext.begin() + kIvSize, ciphertext.end());
@ -181,9 +207,9 @@ raw_data AES256_CBC::decrypt(const raw_data& key, const raw_data& iv,
raw_data AES256_CBC::decrypt(const raw_data& key, const raw_data& iv, raw_data AES256_CBC::decrypt(const raw_data& key, const raw_data& iv,
raw_data::const_iterator begin, raw_data::const_iterator begin,
raw_data::const_iterator end) { raw_data::const_iterator end) {
auto ctx = init(Mode::kDecrypt, key, iv);
raw_data output; raw_data output;
process_all(Mode::kDecrypt, ctx, begin, end, output, 0); init(Mode::kDecrypt, key, iv);
process_all(Mode::kDecrypt, begin, end, output, 0);
return output; return output;
} }
}; // namespace horcrux }; // namespace horcrux

View File

@ -1,5 +1,6 @@
#ifndef HORCRUX_SRC_CRYPTO_H #ifndef HORCRUX_SRC_CRYPTO_H
#define HORCRUX_SRC_CRYPTO_H #define HORCRUX_SRC_CRYPTO_H
#include <openssl/evp.h>
#include <vector> #include <vector>
#include <string> #include <string>
#include <cstddef> #include <cstddef>
@ -48,28 +49,51 @@ public:
/** @brief process inputdata according to Cipher mode /** @brief process inputdata according to Cipher mode
* @param inputdata buffer containing data to process * @param inputdata buffer containing data to process
* @param begin set to true if this is the first chunk of data to process.
* @param end set to true if this is the last chunk of data to process.
* @return processed data * @return processed data
*/ */
virtual raw_data process( virtual raw_data process(
const raw_data& inputdata) = 0; const raw_data& inputdata, bool begin = true, bool end = true) = 0;
/** @brief encrypt the content of a buffer using Cipher key /** @brief encrypt the content of a buffer using Cipher key
* @param plaintext the input buffer * @param plaintext the input buffer
* @param begin set to true if this is the first chunk of data to process.
* @param end set to true if this is the last chunk of data to process.
* @return encrypted data * @return encrypted data
*/ */
virtual raw_data encrypt( virtual raw_data encrypt(
const raw_data& plaintext) = 0; const raw_data& plaintext, bool begin = true, bool end = true) = 0;
/** @brief decrypt the content of a buffer using Cipher key /** @brief decrypt the content of a buffer using Cipher key
* @param decrypt the input buffer * @param decrypt the input buffer
* @param begin set to true if this is the first chunk of data to process.
* @param end set to true if this is the last chunk of data to process.
* @return decrypted data * @return decrypted data
*/ */
virtual raw_data decrypt( virtual raw_data decrypt(
const raw_data& ciphertext) = 0; const raw_data& ciphertext, bool begin = true, bool end = true) = 0;
}; };
// forward declaration /** Cipher context class
class EvpCipherCtx; * This is a wrapper for OpenSSL context object, just for RAII pattern
*/
class EvpCipherCtx {
EVP_CIPHER_CTX *ptr;
public:
EvpCipherCtx() {
ptr = EVP_CIPHER_CTX_new();
}
~EvpCipherCtx() {
EVP_CIPHER_CTX_free(ptr);
}
EVP_CIPHER_CTX* get() {
return ptr;
}
};
/** @brief AES256 Cipher /** @brief AES256 Cipher
* @details * @details
@ -90,6 +114,8 @@ class AES256_CBC : public Cipher {
/** AES256 CBC initialization vector size, from AES256 specs */ /** AES256 CBC initialization vector size, from AES256 specs */
const size_t kIvSize = 16; const size_t kIvSize = 16;
std::unique_ptr<EvpCipherCtx> ctx = nullptr;
public: public:
/** @brief Create cipher in encrypt mode with random key */ /** @brief Create cipher in encrypt mode with random key */
AES256_CBC() : Cipher(Mode::kEncrypt) { AES256_CBC() : Cipher(Mode::kEncrypt) {
@ -108,24 +134,36 @@ public:
/** @brief process inputdata according to Cipher mode /** @brief process inputdata according to Cipher mode
* @param inputdata buffer containing data to process * @param inputdata buffer containing data to process
* @param begin set to true if this is the first chunk of data to process.
* (deals with IV)
* @param end set to true if this is the last chunk of data to process.
* (deals with padding)
* @return processed data * @return processed data
*/ */
raw_data process( raw_data process(
const raw_data& inputdata) override; const raw_data& inputdata, bool begin = true, bool end = true) override;
/** @brief encrypt the content of a buffer using Cipher key /** @brief encrypt the content of a buffer using Cipher key
* @param plaintext the input buffer * @param plaintext the input buffer
* @param begin set to true if this is the first chunk of data to process.
* (deals with IV)
* @param end set to true if this is the last chunk of data to process.
* (deals with padding)
* @return encrypted data * @return encrypted data
*/ */
raw_data encrypt( raw_data encrypt(
const raw_data& plaintext) override; const raw_data& plaintext, bool begin = true, bool end = true) override;
/** @brief decrypt the content of a buffer using Cipher key /** @brief decrypt the content of a buffer using Cipher key
* @param decrypt the input buffer * @param decrypt the input buffer
* @param begin set to true if this is the first chunk of data to process.
* (deals with IV)
* @param end set to true if this is the last chunk of data to process.
* (deals with padding)
* @return decrypted data * @return decrypted data
*/ */
raw_data decrypt( raw_data decrypt(
const raw_data& ciphertext) override; const raw_data& ciphertext, bool begin = true, bool end = true) override;
/** @brief Read the whole input and return a buffer with the encrypted data /** @brief Read the whole input and return a buffer with the encrypted data
* @param key the encryption key * @param key the encryption key
@ -182,18 +220,16 @@ public:
raw_data::const_iterator end); raw_data::const_iterator end);
private: private:
/** @brief Create a Cipher context object needed for enc/dec operations /** @brief Initialize the Cipher context object needed for enc/dec operations
* @param mode encrypt or decrypt * @param mode encrypt or decrypt
* @param key the enc/dec key * @param key the enc/dec key
* @param the initialization vector * @param the initialization vector
* @return the Cipher context object
*/ */
EvpCipherCtx init(Mode mode, const raw_data& key, void init(Mode mode, const raw_data& key,
const raw_data& iv); const raw_data& iv);
/** @brief process a chunk of input /** @brief process a chunk of input
* @param mode encrypt or decrypt * @param mode encrypt or decrypt
* @param ctx the Cipher context object
* @param begin beginning of the input chunk * @param begin beginning of the input chunk
* @param end end of the input chunk * @param end end of the input chunk
* @param output output buffer * @param output output buffer
@ -203,17 +239,21 @@ private:
* order to make sure there is enough room. * order to make sure there is enough room.
* @param resize_out shrink the output buffer at the end * @param resize_out shrink the output buffer at the end
*/ */
size_t process_chunk(Mode mode, EvpCipherCtx& ctx, size_t process_chunk(Mode mode,
raw_data::const_iterator begin, raw_data::const_iterator begin,
raw_data::const_iterator end, raw_data::const_iterator end,
raw_data& output, raw_data& output,
size_t output_offset, size_t output_offset,
bool resize_in = true, bool resize_out = true); bool resize_in = true, bool resize_out = true);
size_t process_start(Mode mode,
raw_data::const_iterator begin,
raw_data::const_iterator end,
raw_data& output,
bool resize_in = true, bool resize_out = true);
/** @brief Finalize a encryption/decryption process. Write remaining bytes /** @brief Finalize a encryption/decryption process. Write remaining bytes
* to the output * to the output
* @param mode encrypt or decrypt * @param mode encrypt or decrypt
* @param ctx the Cipher context object
* @param output output buffer * @param output output buffer
* @param output_offset where to start to append the last data within * @param output_offset where to start to append the last data within
* the output buffer * the output buffer
@ -221,14 +261,13 @@ private:
* order to make sure there is enough room. * order to make sure there is enough room.
* @param resize_out shrink the output buffer at the end * @param resize_out shrink the output buffer at the end
*/ */
size_t process_final(Mode mode, EvpCipherCtx& ctx, size_t process_final(Mode mode,
raw_data& output, raw_data& output,
size_t output_offset, size_t output_offset,
bool resize_in = true, bool resize_out = true); bool resize_in = true, bool resize_out = true);
/** @brief process a input and finalize it. /** @brief process a input and finalize it.
* @param mode encrypt or decrypt * @param mode encrypt or decrypt
* @param ctx the Cipher context object
* @param begin beginning of the input chunk * @param begin beginning of the input chunk
* @param end end of the input chunk * @param end end of the input chunk
* @param output output buffer * @param output output buffer
@ -238,7 +277,7 @@ private:
* order to make sure there is enough room. * order to make sure there is enough room.
* @param resize_out shrink the output buffer at the end * @param resize_out shrink the output buffer at the end
*/ */
size_t process_all(Mode mode, EvpCipherCtx& ctx, size_t process_all(Mode mode,
raw_data::const_iterator begin, raw_data::const_iterator begin,
raw_data::const_iterator end, raw_data::const_iterator end,
raw_data& output, size_t output_offset, raw_data& output, size_t output_offset,

View File

@ -5,6 +5,15 @@
namespace horcrux { namespace horcrux {
void Horcrux::read_chunks(size_t size) {
raw_data buf = input->read(size);
output->write_chunk(cipher->process(buf, true, false));
while ((buf = input->read(size)).size() == size) {
output->write_chunk(cipher->process(buf, false, false));
}
output->write_chunk(cipher->process(buf, false, true));
}
void Horcrux::init() { void Horcrux::init() {
if (options.count <= 0) { if (options.count <= 0) {
throw std::invalid_argument("Invalid horcrux count"); throw std::invalid_argument("Invalid horcrux count");
@ -32,7 +41,8 @@ void Horcrux::init() {
void Horcrux::run() { void Horcrux::run() {
if (options.mode == Mode::kEncrypt) if (options.mode == Mode::kEncrypt)
output->write(cipher->encrypt(input->read())); //output->write(cipher->encrypt(input->read()));
read_chunks(512);
else else
output->write(cipher->decrypt(input->read())); output->write(cipher->decrypt(input->read()));
} }

View File

@ -38,6 +38,7 @@ class Horcrux {
* Used cipher, input and output are generic and the actual implementation * Used cipher, input and output are generic and the actual implementation
* can be selected in this function */ * can be selected in this function */
void init(); void init();
void read_chunks(size_t size);
public: public:
/** remove default constructor */ /** remove default constructor */

View File

@ -68,7 +68,9 @@ raw_data FsCryptoInput::read(size_t size) {
while (data_read < size && current_file < file_paths.size()) { while (data_read < size && current_file < file_paths.size()) {
if (!file_stream.is_open()) if (!file_stream.is_open())
file_stream.open(file_paths[current_file].first, std::ios::binary); file_stream.open(file_paths[current_file].first, std::ios::binary);
file_stream.read(reinterpret_cast<char*>(result.data()) + data_read, size - data_read); file_stream.read(
reinterpret_cast<char*>(result.data()) + data_read,
size - data_read);
data_read += file_stream.gcount(); data_read += file_stream.gcount();
if(file_stream.eof()) { if(file_stream.eof()) {
file_stream.close(); file_stream.close();
@ -93,7 +95,8 @@ size_t FsPlainOutput::write(const raw_data& to_write) {
} }
size_t FsPlainOutput::write_chunk(const raw_data& to_write) { size_t FsPlainOutput::write_chunk(const raw_data& to_write) {
file_stream.open(file_path, std::ios::binary); file_stream.open(file_path, std::ios::binary);
file_stream.write(reinterpret_cast<const char*>(to_write.data()), to_write.size()); file_stream.write(reinterpret_cast<const char*>(to_write.data()),
to_write.size());
return to_write.size(); return to_write.size();
} }
@ -129,29 +132,37 @@ size_t FsCryptoOutput::write(const raw_data& to_write) {
// } // }
// return data_written; // return data_written;
} }
size_t FsCryptoOutput::write_chunk(const raw_data& to_write) { size_t FsCryptoOutput::write_chunk(const raw_data& to_write) {
// horcrux file size must be known in advance
if (size <= 0) { if (size <= 0) {
throw std::invalid_argument("Invalid horcrux size"); throw std::invalid_argument("Invalid horcrux size");
} }
size_t data_written{0}; size_t data_written{0};
std::cout << to_write.size() << " " << data_written << std::endl;
while (data_written < to_write.size()){ while (data_written < to_write.size()){
if(!file_stream.is_open()){ if(!file_stream.is_open()){
std::string name = base_name + '.' + std::to_string(created_files.size()) + ".enc"; // create a new file
std::string name = base_name +
'.' + std::to_string(created_files.size()) + ".enc";
created_files.emplace_back(folder_path / name); created_files.emplace_back(folder_path / name);
file_stream.open(created_files.back(), file_stream.open(created_files.back(),
std::ios::binary | std::ios::trunc); std::ios::binary | std::ios::trunc);
} }
// write up to fill the file size or consume the input buffer
auto writable = std::min(size - file_stream.tellp(),
to_write.size() - data_written);
// the last file may contain more data (size % num) // the last file may contain more data (size % num)
auto writable = std::min(size - file_stream.tellp(), to_write.size() - data_written); if (num == created_files.size()) {
if (num == created_files.size()) { writable = to_write.size() - data_written; } writable = to_write.size() - data_written;
file_stream.write(reinterpret_cast<const char*>(to_write.data()) + data_written, }
file_stream.write(
reinterpret_cast<const char*>(to_write.data()) + data_written,
writable); writable);
data_written += writable; data_written += writable;
if (file_stream.tellp() >= size){ if (file_stream.tellp() >= size){
// close the file if it has been filled
file_stream.close(); file_stream.close();
} }
std::cout << to_write.size() << " " << data_written << std::endl;
} }
return data_written; return data_written;
} }

View File

@ -19,12 +19,7 @@ public:
virtual raw_data read() = 0; virtual raw_data read() = 0;
virtual raw_data read(size_t size) = 0; virtual raw_data read(size_t size) = 0;
// template<class UnaryFunction>
// void read_chunks(size_t size, UnaryFunction f) {
// while ((auto buf = read(size)) == size) {
// f(buf);
// }
// }
}; };
/** @brief Base Class for Output /** @brief Base Class for Output