Skip to content

Commit

Permalink
big number implementation, for future use in permissions bitfields (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
braindigitalis authored Jun 20, 2024
2 parents 3ccd583 + 2dc648c commit 46ae489
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 2 deletions.
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

0 comments on commit 46ae489

Please sign in to comment.