From b2c7e3d9f228978603a23e8e874a60b3658c0545 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 19 Jun 2024 16:39:19 +0000 Subject: [PATCH 1/5] big number implementation, needs docs and finishing. for later use --- include/dpp/bignum.h | 54 +++++++++++++++++++++++ include/dpp/dpp.h | 1 + src/dpp/bignum.cpp | 86 +++++++++++++++++++++++++++++++++++++ src/dpp/cluster/channel.cpp | 2 - src/unittest/test.cpp | 11 +++++ src/unittest/test.h | 2 + 6 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 include/dpp/bignum.h create mode 100644 src/dpp/bignum.cpp diff --git a/include/dpp/bignum.h b/include/dpp/bignum.h new file mode 100644 index 0000000000..06fb094a54 --- /dev/null +++ b/include/dpp/bignum.h @@ -0,0 +1,54 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#pragma once +#include +#include +#include + +namespace dpp { + +struct openssl_bignum; + +/** +* @brief An arbitrary length integer number +*/ +class DPP_EXPORT bignumber { + std::shared_ptr ssl_bn{nullptr}; +public: + /** + * @brief Construct a new bignumber object + */ + bignumber() = default; + + bignumber(const std::string& number_string); + + bignumber(std::vector bits); + + ~bignumber() = default; + + [[nodiscard]] std::string get_number(bool hex = false) const; + + [[nodiscard]] std::vector get_binary() const; +}; + +} // namespace dpp diff --git a/include/dpp/dpp.h b/include/dpp/dpp.h index 9e60093b47..5e40a88a0e 100644 --- a/include/dpp/dpp.h +++ b/include/dpp/dpp.h @@ -74,3 +74,4 @@ #include #include #include +#include diff --git a/src/dpp/bignum.cpp b/src/dpp/bignum.cpp new file mode 100644 index 0000000000..8ed4523100 --- /dev/null +++ b/src/dpp/bignum.cpp @@ -0,0 +1,86 @@ +/************************************************************************************ + * + * D++, A Lightweight C++ library for Discord + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright 2021 Craig Edwards and D++ contributors + * (https://github.com/brainboxdotcc/DPP/graphs/contributors) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +namespace dpp { + +struct openssl_bignum { + BIGNUM* bn{nullptr}; + + openssl_bignum() : bn(BN_new()) { + } + + ~openssl_bignum() { + BN_free(bn); + } +}; + +bignumber::bignumber(const std::string& number_string) { + ssl_bn = std::make_shared(); + if (dpp::lowercase(number_string.substr(0, 2)) == "0x") { + BN_hex2bn(&ssl_bn->bn, number_string.substr(2, number_string.length() - 2).c_str()); + } else { + BN_dec2bn(&ssl_bn->bn, number_string.c_str()); + } +} + +bignumber::bignumber(std::vector bits) { + ssl_bn = std::make_shared(); + std::reverse(bits.begin(), bits.end()); + for (auto& chunk : bits) { + chunk = ((((chunk) & 0xff00000000000000ull) >> 56) + | (((chunk) & 0x00ff000000000000ull) >> 40) + | (((chunk) & 0x0000ff0000000000ull) >> 24) + | (((chunk) & 0x000000ff00000000ull) >> 8) + | (((chunk) & 0x00000000ff000000ull) << 8) + | (((chunk) & 0x0000000000ff0000ull) << 24) + | (((chunk) & 0x000000000000ff00ull) << 40) + | (((chunk) & 0x00000000000000ffull) << 56)); + } + BN_bin2bn((unsigned char *)bits.data(), bits.size() * sizeof(uint64_t), ssl_bn->bn); +} + +std::string bignumber::get_number(bool hex) const { + char* number_str = hex ? BN_bn2hex(ssl_bn->bn) : BN_bn2dec(ssl_bn->bn); + std::string returned{number_str}; + OPENSSL_free(number_str); + return returned; +} + +std::vector bignumber::get_binary() const { + std::size_t size = BN_num_bytes(ssl_bn->bn); + auto size_64_bit = (std::size_t)ceil((double)size / sizeof(uint64_t)); + std::vector returned; + returned.reserve(size_64_bit); + BN_bn2binpad(ssl_bn->bn, (unsigned char*)returned.data(), returned.size() * sizeof(uint64_t)); + std::reverse(returned.begin(), returned.end()); + return returned; +} + +} diff --git a/src/dpp/cluster/channel.cpp b/src/dpp/cluster/channel.cpp index 496169c11c..4e0e41cc80 100644 --- a/src/dpp/cluster/channel.cpp +++ b/src/dpp/cluster/channel.cpp @@ -21,8 +21,6 @@ #include #include -#include - namespace dpp { void cluster::channel_create(const class channel &c, command_completion_event_t callback) { diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 369e417f40..75602106dc 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -96,6 +96,17 @@ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; u3.id = 777; set_test(COMPARISON, u1 == u2 && u1 != u3); + set_test(BIGNUM, false); + std::string big_in{"1234567890123456789012345678901234567890"}; + dpp::bignumber big(big_in); + std::string returned = big.get_number(); + set_test(BIGNUM, big_in == returned); + + set_test(BIGNUM2, false); + std::vector vec{0xff00ff00ff00ff00, 0x1122334455667788}; + dpp::bignumber big2(vec); + returned = big2.get_number(true); + set_test(BIGNUM2, dpp::lowercase(returned) == "1122334455667788ff00ff00ff00ff00"); set_test(ERRORS, false); diff --git a/src/unittest/test.h b/src/unittest/test.h index cb34b207da..ed7ec0c6a1 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -83,6 +83,8 @@ struct test_t { /* Current list of unit tests */ DPP_TEST(SNOWFLAKE, "dpp::snowflake class", tf_offline); +DPP_TEST(BIGNUM, "dpp::bignumber decimal to raw buffer", tf_offline); +DPP_TEST(BIGNUM2, "dpp::bignumber raw buffer to hex", tf_offline); DPP_TEST(JSON_INTERFACE, "dpp::json_interface class", tf_offline); DPP_TEST(CLUSTER, "Instantiate DPP cluster", tf_offline); DPP_TEST(BOTSTART, "cluster::start method", tf_online); From f4c2600fccd812a123488e55c5ad1e9e3b459fe8 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 19 Jun 2024 18:03:08 +0000 Subject: [PATCH 2/5] test to_binary() --- src/dpp/bignum.cpp | 25 ++++++++++++++++--------- src/unittest/test.cpp | 4 ++++ src/unittest/test.h | 1 + 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/dpp/bignum.cpp b/src/dpp/bignum.cpp index 8ed4523100..6af9156e70 100644 --- a/src/dpp/bignum.cpp +++ b/src/dpp/bignum.cpp @@ -50,18 +50,22 @@ bignumber::bignumber(const std::string& number_string) { } } +inline uint64_t flip_bytes(uint64_t bytes) { + return ((((bytes) & 0xff00000000000000ull) >> 56) + | (((bytes) & 0x00ff000000000000ull) >> 40) + | (((bytes) & 0x0000ff0000000000ull) >> 24) + | (((bytes) & 0x000000ff00000000ull) >> 8) + | (((bytes) & 0x00000000ff000000ull) << 8) + | (((bytes) & 0x0000000000ff0000ull) << 24) + | (((bytes) & 0x000000000000ff00ull) << 40) + | (((bytes) & 0x00000000000000ffull) << 56)); +} + bignumber::bignumber(std::vector bits) { ssl_bn = std::make_shared(); std::reverse(bits.begin(), bits.end()); for (auto& chunk : bits) { - chunk = ((((chunk) & 0xff00000000000000ull) >> 56) - | (((chunk) & 0x00ff000000000000ull) >> 40) - | (((chunk) & 0x0000ff0000000000ull) >> 24) - | (((chunk) & 0x000000ff00000000ull) >> 8) - | (((chunk) & 0x00000000ff000000ull) << 8) - | (((chunk) & 0x0000000000ff0000ull) << 24) - | (((chunk) & 0x000000000000ff00ull) << 40) - | (((chunk) & 0x00000000000000ffull) << 56)); + chunk = flip_bytes(chunk); } BN_bin2bn((unsigned char *)bits.data(), bits.size() * sizeof(uint64_t), ssl_bn->bn); } @@ -77,9 +81,12 @@ std::vector bignumber::get_binary() const { std::size_t size = BN_num_bytes(ssl_bn->bn); auto size_64_bit = (std::size_t)ceil((double)size / sizeof(uint64_t)); std::vector returned; - returned.reserve(size_64_bit); + returned.resize(size_64_bit); BN_bn2binpad(ssl_bn->bn, (unsigned char*)returned.data(), returned.size() * sizeof(uint64_t)); std::reverse(returned.begin(), returned.end()); + for (auto& chunk : returned) { + chunk = flip_bytes(chunk); + } return returned; } diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 75602106dc..cf159d46e6 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -108,6 +108,10 @@ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; returned = big2.get_number(true); set_test(BIGNUM2, dpp::lowercase(returned) == "1122334455667788ff00ff00ff00ff00"); + set_test(BIGNUM3, false); + std::vector ret_bin = big2.get_binary(); + set_test(BIGNUM3, ret_bin.size() == 2 && ret_bin[0] == 0xff00ff00ff00ff00 && ret_bin[1] == 0x1122334455667788); + set_test(ERRORS, false); /* Prepare a confirmation_callback_t in error state (400) */ diff --git a/src/unittest/test.h b/src/unittest/test.h index ed7ec0c6a1..88840728a4 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -85,6 +85,7 @@ struct test_t { DPP_TEST(SNOWFLAKE, "dpp::snowflake class", tf_offline); DPP_TEST(BIGNUM, "dpp::bignumber decimal to raw buffer", tf_offline); DPP_TEST(BIGNUM2, "dpp::bignumber raw buffer to hex", tf_offline); +DPP_TEST(BIGNUM3, "dpp::bignumber to_binary()", tf_offline); DPP_TEST(JSON_INTERFACE, "dpp::json_interface class", tf_offline); DPP_TEST(CLUSTER, "Instantiate DPP cluster", tf_offline); DPP_TEST(BOTSTART, "cluster::start method", tf_online); From d16816f17793ff056d5c164416d5e53c4246af06 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 19 Jun 2024 19:29:51 +0000 Subject: [PATCH 3/5] document bignum --- include/dpp/bignum.h | 49 +++++++++++++++++++++++++++++++++++++++++++- src/dpp/bignum.cpp | 23 ++++++++++++++------- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/include/dpp/bignum.h b/include/dpp/bignum.h index 06fb094a54..4d786accb5 100644 --- a/include/dpp/bignum.h +++ b/include/dpp/bignum.h @@ -27,12 +27,26 @@ namespace dpp { +/** + * @brief This contains the OpenSSL structs. It is not public, + * so that the public interface doesnt depend on OpenSSL directly. + */ struct openssl_bignum; /** -* @brief An arbitrary length integer number +* @brief An arbitrary length integer number. + * Officially, the Discord documentation says that permission values can be any arbitrary + * number of digits. At time of writing there are only 50 bits of permissions, but this is + * set to grow larger and potentially past 64 bits. They will continue to send this data + * as a huge single integer at that point, because this is obviously sensible. /s + * + * @note dpp::bignumber uses OpenSSL BN_* under the hood, as we include openssl anyway + * for HTTPS. */ class DPP_EXPORT bignumber { + /** + * @brief Internal opaque struct to contain OpenSSL things + */ std::shared_ptr ssl_bn{nullptr}; public: /** @@ -40,14 +54,47 @@ class DPP_EXPORT bignumber { */ bignumber() = default; + /** + * @brief Parse a std::string of an arbitrary length number into + * a bignumber. + * @param number_string string representation of a number. The + * number must be an integer, and can be positive or negative. + * @note Prefixing number_string with 0x will parse it as hexadecimal. + * This is not case sensitive. + */ bignumber(const std::string& number_string); + /** + * @brief Build a bignumber from a vector of 64 bit values. + * The values are accepted in "reverse order", so the first vector + * entry at index 0 is the leftmost 64 bits of the bignum. + * The vector can be any arbitrary length. + * @param bits Vector of 64 bit values which represent the number + */ bignumber(std::vector bits); + /** + * @brief Default destructor + */ ~bignumber() = default; + /** + * @brief Get the string representation of the bignumber. + * @param hex If false (the default) the number is returned in + * decimal, else if this parameter is true, it will be returned + * as hex (without leading '0x') + * @return String representation of bignumber + */ [[nodiscard]] std::string get_number(bool hex = false) const; + /** + * @brief Get the array of 64 bit values that represents the + * bignumber. This is what we should use to store bignumbers + * in memory, not this bignumber class itself, as the bignumber + * class instantiates OpenSSL structs and takes significantly + * more ram than just a vector. + * @return Vector of 64 bit values representing the bignumber + */ [[nodiscard]] std::vector get_binary() const; }; diff --git a/src/dpp/bignum.cpp b/src/dpp/bignum.cpp index 6af9156e70..b89f838b65 100644 --- a/src/dpp/bignum.cpp +++ b/src/dpp/bignum.cpp @@ -24,25 +24,30 @@ #include #include #include -#include -#include -#include namespace dpp { struct openssl_bignum { + /** + * @brief OpenSSL BIGNUM pointer + */ BIGNUM* bn{nullptr}; + /** + * @brief Construct BIGNUM using RAII + */ openssl_bignum() : bn(BN_new()) { } + /** + * @brief Destruct BIGNUM using RAII + */ ~openssl_bignum() { BN_free(bn); } }; -bignumber::bignumber(const std::string& number_string) { - ssl_bn = std::make_shared(); +bignumber::bignumber(const std::string& number_string) : ssl_bn(std::make_shared()) { if (dpp::lowercase(number_string.substr(0, 2)) == "0x") { BN_hex2bn(&ssl_bn->bn, number_string.substr(2, number_string.length() - 2).c_str()); } else { @@ -50,6 +55,11 @@ bignumber::bignumber(const std::string& number_string) { } } +/** + * Flip (reverse) bytes in a uint64_t + * @param bytes 64 bit value + * @return flipped 64 bit value + */ inline uint64_t flip_bytes(uint64_t bytes) { return ((((bytes) & 0xff00000000000000ull) >> 56) | (((bytes) & 0x00ff000000000000ull) >> 40) @@ -61,8 +71,7 @@ inline uint64_t flip_bytes(uint64_t bytes) { | (((bytes) & 0x00000000000000ffull) << 56)); } -bignumber::bignumber(std::vector bits) { - ssl_bn = std::make_shared(); +bignumber::bignumber(std::vector bits): ssl_bn(std::make_shared()) { std::reverse(bits.begin(), bits.end()); for (auto& chunk : bits) { chunk = flip_bytes(chunk); From 1666b113c34a9b02147622f5d3febe5a06fc9391 Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Wed, 19 Jun 2024 19:33:38 +0000 Subject: [PATCH 4/5] spelling --- include/dpp/bignum.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/dpp/bignum.h b/include/dpp/bignum.h index 4d786accb5..2966d7b8b0 100644 --- a/include/dpp/bignum.h +++ b/include/dpp/bignum.h @@ -29,7 +29,7 @@ namespace dpp { /** * @brief This contains the OpenSSL structs. It is not public, - * so that the public interface doesnt depend on OpenSSL directly. + * so that the public interface doesn't depend on OpenSSL directly. */ struct openssl_bignum; From 2dc648cf59cbdbd02ea43595545bc5aafaa619eb Mon Sep 17 00:00:00 2001 From: Craig Edwards Date: Thu, 20 Jun 2024 08:35:59 +0000 Subject: [PATCH 5/5] style: static and reinterpret casts --- src/dpp/bignum.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dpp/bignum.cpp b/src/dpp/bignum.cpp index b89f838b65..f3d56b26e0 100644 --- a/src/dpp/bignum.cpp +++ b/src/dpp/bignum.cpp @@ -76,7 +76,7 @@ bignumber::bignumber(std::vector bits): ssl_bn(std::make_sharedbn); + BN_bin2bn(reinterpret_cast(bits.data()), bits.size() * sizeof(uint64_t), ssl_bn->bn); } std::string bignumber::get_number(bool hex) const { @@ -88,10 +88,10 @@ std::string bignumber::get_number(bool hex) const { std::vector bignumber::get_binary() const { std::size_t size = BN_num_bytes(ssl_bn->bn); - auto size_64_bit = (std::size_t)ceil((double)size / sizeof(uint64_t)); + auto size_64_bit = static_cast(ceil(static_cast(size) / sizeof(uint64_t))); std::vector returned; returned.resize(size_64_bit); - BN_bn2binpad(ssl_bn->bn, (unsigned char*)returned.data(), returned.size() * sizeof(uint64_t)); + BN_bn2binpad(ssl_bn->bn, reinterpret_cast(returned.data()), returned.size() * sizeof(uint64_t)); std::reverse(returned.begin(), returned.end()); for (auto& chunk : returned) { chunk = flip_bytes(chunk);