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

feat: big number implementation, for future use in permissions bitfields #1176

Merged
merged 5 commits into from
Jun 20, 2024
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
101 changes: 101 additions & 0 deletions include/dpp/bignum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/************************************************************************************
*
* 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 <dpp/export.h>
#include <dpp/snowflake.h>
#include <memory>

namespace dpp {

/**
* @brief This contains the OpenSSL structs. It is not public,
* so that the public interface doesn't depend on OpenSSL directly.
*/
struct openssl_bignum;

/**
* @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<openssl_bignum> ssl_bn{nullptr};
public:
/**
* @brief Construct a new bignumber object
*/
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<uint64_t> 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<uint64_t> get_binary() const;
};

} // namespace dpp
1 change: 1 addition & 0 deletions include/dpp/dpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,4 @@
#include <dpp/discordevents.h>
#include <dpp/timed_listener.h>
#include <dpp/collector.h>
#include <dpp/bignum.h>
102 changes: 102 additions & 0 deletions src/dpp/bignum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/************************************************************************************
*
* 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 <dpp/bignum.h>
#include <dpp/stringops.h>
#include <openssl/bn.h>
#include <cmath>

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<openssl_bignum>()) {
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());
}
}

/**
* 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)
| (((bytes) & 0x0000ff0000000000ull) >> 24)
| (((bytes) & 0x000000ff00000000ull) >> 8)
| (((bytes) & 0x00000000ff000000ull) << 8)
| (((bytes) & 0x0000000000ff0000ull) << 24)
| (((bytes) & 0x000000000000ff00ull) << 40)
| (((bytes) & 0x00000000000000ffull) << 56));
}

bignumber::bignumber(std::vector<uint64_t> bits): ssl_bn(std::make_shared<openssl_bignum>()) {
std::reverse(bits.begin(), bits.end());
for (auto& chunk : bits) {
chunk = flip_bytes(chunk);
}
BN_bin2bn(reinterpret_cast<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<uint64_t> bignumber::get_binary() const {
std::size_t size = BN_num_bytes(ssl_bn->bn);
auto size_64_bit = static_cast<std::size_t>(ceil(static_cast<double>(size) / sizeof(uint64_t)));
std::vector<uint64_t> returned;
returned.resize(size_64_bit);
BN_bn2binpad(ssl_bn->bn, reinterpret_cast<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;
}

}
2 changes: 0 additions & 2 deletions src/dpp/cluster/channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
#include <dpp/channel.h>
#include <dpp/restrequest.h>

#include <utility>

namespace dpp {

void cluster::channel_create(const class channel &c, command_completion_event_t callback) {
Expand Down
15 changes: 15 additions & 0 deletions src/unittest/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,21 @@ 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<uint64_t> vec{0xff00ff00ff00ff00, 0x1122334455667788};
dpp::bignumber big2(vec);
returned = big2.get_number(true);
set_test(BIGNUM2, dpp::lowercase(returned) == "1122334455667788ff00ff00ff00ff00");

set_test(BIGNUM3, false);
std::vector<uint64_t> ret_bin = big2.get_binary();
set_test(BIGNUM3, ret_bin.size() == 2 && ret_bin[0] == 0xff00ff00ff00ff00 && ret_bin[1] == 0x1122334455667788);

set_test(ERRORS, false);

Expand Down
3 changes: 3 additions & 0 deletions src/unittest/test.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ 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(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);
Expand Down
Loading