Skip to content

Commit

Permalink
Merge pull request #3294 from randombit/chore/generic_interface_bufcomp
Browse files Browse the repository at this point in the history
Chore: Buffered_Computation can deal with generic containers
  • Loading branch information
reneme authored Mar 17, 2023
2 parents 738906e + c4582aa commit 8460ca2
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 29 deletions.
67 changes: 40 additions & 27 deletions src/lib/base/buf_comp.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
#ifndef BOTAN_BUFFERED_COMPUTATION_H_
#define BOTAN_BUFFERED_COMPUTATION_H_

#include <botan/concepts.h>
#include <botan/secmem.h>
#include <string>
#include <string_view>
#include <span>

namespace Botan {

Expand All @@ -34,18 +36,9 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation

/**
* Add new input to process.
* @param in the input to process as a secure_vector
* @param in the input to process as a contiguous data range
*/
void update(const secure_vector<uint8_t>& in)
{
add_data(in.data(), in.size());
}

/**
* Add new input to process.
* @param in the input to process as a std::vector
*/
void update(const std::vector<uint8_t>& in)
void update(std::span<const uint8_t> in)
{
add_data(in.data(), in.size());
}
Expand All @@ -60,10 +53,10 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation

/**
* Add new input to process.
* @param str the input to process as a std::string. Will be interpreted
* @param str the input to process as a std::string_view. Will be interpreted
* as a byte array based on the strings encoding.
*/
void update(const std::string& str)
void update(std::string_view str)
{
add_data(cast_char_ptr_to_uint8(str.data()), str.size());
}
Expand All @@ -82,23 +75,40 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation
*/
void final(uint8_t out[]) { final_result(out); }

/**
* Complete the computation and retrieve the
* final result as a container of your choice.
* @return a contiguous container holding the result
*/
template<typename T>
requires(concepts::contiguous_container<T> &&
concepts::resizable_container<T>)
T final()
{
T output(output_length());
final_result(output.data());
return output;
}

/**
* Complete the computation and retrieve the
* final result.
* @return secure_vector holding the result
*/
secure_vector<uint8_t> final()
{
secure_vector<uint8_t> output(output_length());
final_result(output.data());
return output;
return final<secure_vector<uint8_t>>();
}

std::vector<uint8_t> final_stdvec()
{
std::vector<uint8_t> output(output_length());
final_result(output.data());
return output;
return final<std::vector<uint8_t>>();
}

void final(std::span<uint8_t> out)
{
BOTAN_ASSERT_NOMSG(out.size() >= output_length());
final_result(out.data());
}

template<typename Alloc>
Expand Down Expand Up @@ -127,7 +137,7 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation
* @param in the input to process
* @result the result of the call to final()
*/
secure_vector<uint8_t> process(const secure_vector<uint8_t>& in)
secure_vector<uint8_t> process(std::span<const uint8_t> in)
{
add_data(in.data(), in.size());
return final();
Expand All @@ -136,25 +146,28 @@ class BOTAN_PUBLIC_API(2,0) Buffered_Computation
/**
* Update and finalize computation. Does the same as calling update()
* and final() consecutively.
* @param in the input to process
* @param in the input to process as a string
* @result the result of the call to final()
*/
secure_vector<uint8_t> process(const std::vector<uint8_t>& in)
secure_vector<uint8_t> process(std::string_view in)
{
add_data(in.data(), in.size());
update(in);
return final();
}

/**
* Update and finalize computation. Does the same as calling update()
* and final() consecutively.
* @param in the input to process as a string
* @param in the input to process as a contiguous container or string-like
* @result the result of the call to final()
*/
secure_vector<uint8_t> process(const std::string& in)
template<typename OutT, typename T>
requires(concepts::convertible_to<T, std::string_view> ||
concepts::convertible_to<T, std::span<const uint8_t>>)
OutT process(T in)
{
update(in);
return final();
return final<OutT>();
}

virtual ~Buffered_Computation() = default;
Expand Down
8 changes: 6 additions & 2 deletions src/lib/utils/concepts.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,12 @@ concept has_empty = requires(T a)

template <typename T>
concept resizable_container =
container<T> &&
requires(T& c, typename T::size_type s) { c.resize(s); };
container<T> &&
requires(T& c, typename T::size_type s)
{
T(s);
c.resize(s);
};

template<typename T>
concept streamable = requires(std::ostream& os, T a)
Expand Down
4 changes: 4 additions & 0 deletions src/lib/utils/strong_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class Strong_Adapter<T> : public Strong_Base<T>
requires(concepts::contiguous_container<T>)
: Strong_Adapter(T(span.begin(), span.end())) {}

explicit Strong_Adapter(size_t size)
requires(concepts::resizable_container<T>)
: Strong_Adapter(T(size)) {}

// Disambiguates the usage of string literals, otherwise:
// Strong_Adapter(std::span<>) and Strong_Adapter(const char*)
// would be ambiguous.
Expand Down
144 changes: 144 additions & 0 deletions src/tests/test_bufcomp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* (C) 2023 Jack Lloyd
* 2023 Philippe Lieser - Rohde & Schwarz Cybersecurity
* 2023 René Meusel - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include "tests.h"

#include <botan/buf_comp.h>
#include <botan/secmem.h>
#include <botan/mem_ops.h>
#include <botan/strong_type.h>

#include <array>
#include <string>
#include <vector>

namespace Botan_Tests {

namespace {

class Test_Buf_Comp final : public Botan::Buffered_Computation
{
public:
Test_Buf_Comp(Test::Result& res)
: m_result(res)
, m_counter(0) {}

size_t output_length() const override { return sizeof(m_counter); }

void add_data(const uint8_t input[], size_t length) override
{
if(m_result.test_eq("input length as expected", length, size_t(5)))
{
m_result.confirm("input[0] == 'A'", input[0] == 'A');
m_result.confirm("input[0] == 'B'", input[1] == 'B');
m_result.confirm("input[0] == 'C'", input[2] == 'C');
m_result.confirm("input[0] == 'D'", input[3] == 'D');
m_result.confirm("input[0] == 'E'", input[4] == 'E');
}

++m_counter;
}

void final_result(uint8_t out[]) override
{
const uint8_t* counter = reinterpret_cast<const uint8_t*>(&m_counter);
std::copy(counter, counter + sizeof(m_counter), out);
}

size_t counter() const { return m_counter; }

private:
Test::Result& m_result;
size_t m_counter;
};

void check(Test::Result& result, std::span<const uint8_t> produced, size_t expected)
{
const uint8_t* eptr = reinterpret_cast<const uint8_t*>(&expected);
result.confirm("result is correct", Botan::same_mem(produced.data(), eptr, sizeof(size_t)));
}

using TestStdVector = Botan::Strong<std::vector<uint8_t>, struct TestStdVector_>;
using TestSecureVector = Botan::Strong<Botan::secure_vector<uint8_t>, struct TestSecureVector_>;

Test::Result test_buffered_computation_convenience_api()
{
// This is mainly to test compilability of the various container
// types as in and out parameters. Hence, we refrain from checking
// the 'final' output everywhere.
Test::Result result("Convenience API of Buffered_Computation");

Test_Buf_Comp t(result);

constexpr auto test_string = "ABCDE";
const std::vector<uint8_t> test_vector = {'A', 'B', 'C', 'D', 'E'};
const std::array<uint8_t, 5> test_array = {'A', 'B', 'C', 'D', 'E'};
const TestStdVector test_strong_type(test_vector);

Botan::secure_vector<uint8_t> out_sv;
std::vector<uint8_t> out_vec;
std::array<uint8_t, sizeof(std::size_t)> out_arr;
TestSecureVector out_strong_type;

// update with basic string-ish types
t.update("ABCDE");
t.update(test_string);
t.update(std::string(test_string));

// update with container types
t.update(test_vector);
t.update(test_array);
t.update(test_strong_type);

// final returning result
out_sv = t.final();
out_vec = t.final_stdvec();
out_strong_type = t.final<TestSecureVector>();

// final using out param
t.final(out_sv);
t.final(out_arr);
t.final(out_strong_type);

check(result, out_strong_type, 6);

// test resizing of final out param
out_vec.resize(0);
t.final(out_vec);
out_vec.resize(t.output_length()*2);
t.final(out_vec);
result.test_int_eq("out vector is resized", out_vec.size(), t.output_length());

check(result, out_vec, 6);

// process with basic string-ish types as input
out_sv = t.process(test_string);
out_sv = t.process(std::string(test_string));

check(result, out_sv, 8);

// process with container types as input
out_sv = t.process(test_vector);
out_sv = t.process(test_array);

check(result, out_sv, 10);

// process with specific in and out type
out_vec = t.process<std::vector<uint8_t>>(test_vector);
const auto out_strong_sec_vec = t.process<TestSecureVector>(test_vector);

check(result, out_strong_sec_vec, 12);

return result;
}

BOTAN_REGISTER_TEST_FN("base", "bufcomp_base_api", test_buffered_computation_convenience_api);

}

}

0 comments on commit 8460ca2

Please sign in to comment.