Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: Buffered_Computation can deal with generic containers #3294

Merged
merged 4 commits into from
Mar 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);

}

}