From c9b977b0c4dc68e34268f8a719a660b225ce42ea Mon Sep 17 00:00:00 2001 From: Stephane Janel Date: Mon, 1 Apr 2024 21:33:56 +0200 Subject: [PATCH] [SSL clean-up] Avoid memory allocations for SSL module, clean-up and factorize code --- src/api/common/include/ssl_sha.hpp | 34 +++-- src/api/common/src/ssl_sha.cpp | 120 ++++++++++++------ src/api/common/test/ssl_sha_test.cpp | 20 +-- src/api/exchanges/src/binanceprivateapi.cpp | 4 +- src/api/exchanges/src/bithumbprivateapi.cpp | 36 ++++-- src/api/exchanges/src/huobiprivateapi.cpp | 13 +- src/api/exchanges/src/krakenprivateapi.cpp | 6 +- src/api/exchanges/src/kucoinprivateapi.cpp | 6 +- src/api/exchanges/src/upbitprivateapi.cpp | 4 +- src/tech/CMakeLists.txt | 6 +- src/tech/include/base64.hpp | 64 ++++++++++ src/tech/include/codec.hpp | 21 --- src/tech/include/flatkeyvaluestring.hpp | 27 +++- src/tech/src/{codec.cpp => base64.cpp} | 40 +----- .../test/{codec_test.cpp => base64_test.cpp} | 19 +-- 15 files changed, 249 insertions(+), 171 deletions(-) create mode 100644 src/tech/include/base64.hpp delete mode 100644 src/tech/include/codec.hpp rename src/tech/src/{codec.cpp => base64.cpp} (55%) rename src/tech/test/{codec_test.cpp => base64_test.cpp} (63%) diff --git a/src/api/common/include/ssl_sha.hpp b/src/api/common/include/ssl_sha.hpp index 4c920fd7..b27c96c9 100644 --- a/src/api/common/include/ssl_sha.hpp +++ b/src/api/common/include/ssl_sha.hpp @@ -1,32 +1,40 @@ #pragma once +#include #include +#include #include #include #include -#include "cct_fixedcapacityvector.hpp" -#include "cct_string.hpp" - namespace cct::ssl { +/// @brief Helper type containing the number of bytes of the SHA +enum class ShaType : uint8_t { kSha256 = 256 / CHAR_BIT, kSha512 = 512 / CHAR_BIT }; + std::string_view GetOpenSSLVersion(); -/// @brief Helper type containing the number of bytes of the SHA -enum class ShaType : int16_t { kSha256 = 256 / CHAR_BIT, kSha512 = 512 / CHAR_BIT }; +using Md256 = std::array(ShaType::kSha256)>; +using Md512 = std::array(ShaType::kSha512)>; -using Md256 = FixedCapacityVector(ShaType::kSha256)>; -using Md512 = FixedCapacityVector(ShaType::kSha512)>; +using Sha256HexArray = std::array(ShaType::kSha256)>; +using Sha512HexArray = std::array(ShaType::kSha512)>; -/// @brief Compute Sha256 from 'data' -Md256 Sha256(std::string_view data); +using Sha256DigestArray = std::array(ShaType::kSha256)>; +using Sha512DigestArray = std::array(ShaType::kSha512)>; + +Md256 Sha256Bin(std::string_view data, std::string_view secret); +Md512 Sha512Bin(std::string_view data, std::string_view secret); -Md512 ShaBin(ShaType shaType, std::string_view data, std::string_view secret); +Md256 Sha256(std::string_view data); -string ShaHex(ShaType shaType, std::string_view data, std::string_view secret); +Sha256HexArray Sha256Hex(std::string_view data, std::string_view secret); +Sha512HexArray Sha512Hex(std::string_view data, std::string_view secret); -string ShaDigest(ShaType shaType, std::string_view data); +Sha256DigestArray Sha256Digest(std::string_view data); +Sha512DigestArray Sha512Digest(std::string_view data); -string ShaDigest(ShaType shaType, std::span data); +Sha256DigestArray Sha256Digest(std::span data); +Sha512DigestArray Sha512Digest(std::span data); } // namespace cct::ssl \ No newline at end of file diff --git a/src/api/common/src/ssl_sha.cpp b/src/api/common/src/ssl_sha.cpp index 93c6d2a5..671144f7 100644 --- a/src/api/common/src/ssl_sha.cpp +++ b/src/api/common/src/ssl_sha.cpp @@ -11,21 +11,20 @@ #include #include "cct_exception.hpp" -#include "cct_string.hpp" -#include "codec.hpp" +#include "char-hexadecimal-converter.hpp" namespace cct::ssl { namespace { -auto ShaDigestLen(ShaType shaType) { return static_cast(shaType); } +constexpr auto ShaDigestLen(ShaType shaType) { return static_cast(shaType); } -const EVP_MD* GetEVPMD(ShaType shaType) { return shaType == ShaType::kSha256 ? EVP_sha256() : EVP_sha512(); } +const EVP_MD* GetEVP_MD(ShaType shaType) { return shaType == ShaType::kSha256 ? EVP_sha256() : EVP_sha512(); } } // namespace Md256 Sha256(std::string_view data) { static_assert(SHA256_DIGEST_LENGTH == static_cast(ShaType::kSha256)); - Md256 ret(static_cast(SHA256_DIGEST_LENGTH)); + Md256 ret; SHA256(reinterpret_cast(data.data()), data.size(), reinterpret_cast(ret.data())); @@ -33,67 +32,114 @@ Md256 Sha256(std::string_view data) { return ret; } -std::string_view GetOpenSSLVersion() { return OPENSSL_VERSION_TEXT; } +std::string_view GetOpenSSLVersion() { + static constexpr std::string_view kOpenSSLVersion = OPENSSL_VERSION_TEXT; + return kOpenSSLVersion; +} + +namespace { + +template +auto ShaBin(std::string_view data, std::string_view secret) { + static constexpr unsigned int kExpectedLen = ShaDigestLen(shaType); -Md512 ShaBin(ShaType shaType, std::string_view data, std::string_view secret) { - unsigned int len = ShaDigestLen(shaType); - Md512 binData(static_cast(len)); + unsigned int len = kExpectedLen; + std::array binData; - HMAC(GetEVPMD(shaType), secret.data(), static_cast(secret.size()), + HMAC(GetEVP_MD(shaType), secret.data(), static_cast(secret.size()), reinterpret_cast(data.data()), data.size(), reinterpret_cast(binData.data()), &len); - if (len != binData.size()) { - throw exception("Unexpected result from HMAC: expected len {}, got {}", binData.size(), len); + if (len != kExpectedLen) { + throw exception("Unexpected result from HMAC: expected len {}, got {}", kExpectedLen, len); } + return binData; } -string ShaHex(ShaType shaType, std::string_view data, std::string_view secret) { - unsigned int len = ShaDigestLen(shaType); - unsigned char binData[EVP_MAX_MD_SIZE]; +template +std::array BinToLowerHex(const std::array& binData) { + std::array ret; + + auto out = ret.data(); + + for (auto beg = binData.begin(), end = binData.end(); beg != end; ++beg) { + out = to_lower_hex(*beg, out); + } + return ret; +} + +template +auto ShaHex(std::string_view data, std::string_view secret) { + return BinToLowerHex(ShaBin(data, secret)); +} + +} // namespace + +Md256 Sha256Bin(std::string_view data, std::string_view secret) { return ShaBin(data, secret); } - HMAC(GetEVPMD(shaType), secret.data(), static_cast(secret.size()), - reinterpret_cast(data.data()), data.size(), binData, &len); +Md512 Sha512Bin(std::string_view data, std::string_view secret) { return ShaBin(data, secret); } + +Sha256HexArray Sha256Hex(std::string_view data, std::string_view secret) { + return ShaHex(data, secret); +} - return BinToHex(std::span(binData, len)); +Sha512HexArray Sha512Hex(std::string_view data, std::string_view secret) { + return ShaHex(data, secret); } namespace { -using EVPMDCTXUniquePtr = std::unique_ptr; +using EVP_MD_CTX_UniquePtr = std::unique_ptr; -EVPMDCTXUniquePtr InitEVPMDCTXUniquePtr(ShaType shaType) { - EVPMDCTXUniquePtr mdctx(EVP_MD_CTX_new()); +EVP_MD_CTX_UniquePtr CreateEVP_MD_CTX_UniquePtr(ShaType shaType) { + EVP_MD_CTX_UniquePtr mdCtx(EVP_MD_CTX_new()); - EVP_DigestInit_ex(mdctx.get(), GetEVPMD(shaType), nullptr); + EVP_DigestInit_ex(mdCtx.get(), GetEVP_MD(shaType), nullptr); - return mdctx; + return mdCtx; } -string EVPBinToHex(const EVPMDCTXUniquePtr& mdctx) { - unsigned int len = 0; - unsigned char binData[EVP_MAX_MD_SIZE]; +template +auto EVPBinToHex(const EVP_MD_CTX_UniquePtr& mdCtx) { + static constexpr unsigned int kExpectedLen = ShaDigestLen(shaType); - EVP_DigestFinal_ex(mdctx.get(), binData, &len); + unsigned int len = kExpectedLen; + std::array binData; + + EVP_DigestFinal_ex(mdCtx.get(), reinterpret_cast(binData.data()), &len); + + if (len != kExpectedLen) { + throw exception("Unexpected result from EVP_DigestFinal_ex: expected len {}, got {}", kExpectedLen, len); + } - return BinToHex(std::span(binData, len)); + return BinToLowerHex(binData); } -} // namespace -string ShaDigest(ShaType shaType, std::string_view data) { - EVPMDCTXUniquePtr mdctx = InitEVPMDCTXUniquePtr(shaType); +template +auto ShaDigest(std::string_view data) { + auto mdCtx = CreateEVP_MD_CTX_UniquePtr(shaType); - EVP_DigestUpdate(mdctx.get(), data.data(), data.size()); + EVP_DigestUpdate(mdCtx.get(), data.data(), data.size()); - return EVPBinToHex(mdctx); + return EVPBinToHex(mdCtx); } -string ShaDigest(ShaType shaType, std::span data) { - EVPMDCTXUniquePtr mdctx = InitEVPMDCTXUniquePtr(shaType); +template +auto ShaDigest(std::span data) { + auto mdCtx = CreateEVP_MD_CTX_UniquePtr(shaType); - std::ranges::for_each(data, [&](std::string_view str) { EVP_DigestUpdate(mdctx.get(), str.data(), str.size()); }); + std::ranges::for_each(data, [&](std::string_view str) { EVP_DigestUpdate(mdCtx.get(), str.data(), str.size()); }); - return EVPBinToHex(mdctx); + return EVPBinToHex(mdCtx); } +} // namespace + +Sha256DigestArray Sha256Digest(std::string_view data) { return ShaDigest(data); } + +Sha512DigestArray Sha512Digest(std::string_view data) { return ShaDigest(data); } + +Sha256DigestArray Sha256Digest(std::span data) { return ShaDigest(data); } + +Sha512DigestArray Sha512Digest(std::span data) { return ShaDigest(data); } } // namespace cct::ssl diff --git a/src/api/common/test/ssl_sha_test.cpp b/src/api/common/test/ssl_sha_test.cpp index d6862939..a2fc8121 100644 --- a/src/api/common/test/ssl_sha_test.cpp +++ b/src/api/common/test/ssl_sha_test.cpp @@ -21,7 +21,7 @@ TEST(SSLTest, Sha256) { } TEST(SSLTest, ShaBin256) { - auto actual = ShaBin(ShaType::kSha256, "data1234", "secret1234"); + auto actual = Sha256Bin("data1234", "secret1234"); static constexpr char kExpectedData[] = {11, -51, -56, -21, -101, 61, 35, 28, 86, 97, -50, -8, 47, -113, -13, -107, -100, -93, 27, 71, 101, -128, -65, 101, -110, -123, 38, 73, 77, 73, -10, -39}; @@ -29,7 +29,7 @@ TEST(SSLTest, ShaBin256) { } TEST(SSLTest, ShaBin512) { - auto actual = ShaBin(ShaType::kSha512, "data1234", "secret1234"); + auto actual = Sha512Bin("data1234", "secret1234"); static constexpr char kExpectedData[] = {-22, 39, 95, -39, -44, 39, 97, -40, 29, -120, -125, 84, -112, -5, 69, -111, 3, -109, 86, 54, -31, 44, -55, 56, 111, 85, 87, 22, -61, 82, 89, 52, 105, 2, -89, -76, 63, 4, 95, @@ -39,7 +39,7 @@ TEST(SSLTest, ShaBin512) { } TEST(SSLTest, ShaHex256) { - auto actual = ShaHex(ShaType::kSha256, "data1234", "secret1234"); + auto actual = Sha256Hex("data1234", "secret1234"); static constexpr char kExpectedData[] = {48, 98, 99, 100, 99, 56, 101, 98, 57, 98, 51, 100, 50, 51, 49, 99, 53, 54, 54, 49, 99, 101, 102, 56, 50, 102, 56, 102, 102, 51, 57, 53, 57, 99, 97, 51, 49, 98, 52, 55, 54, 53, 56, 48, 98, 102, 54, 53, @@ -48,7 +48,7 @@ TEST(SSLTest, ShaHex256) { } TEST(SSLTest, ShaHex512) { - auto actual = ShaHex(ShaType::kSha512, "data1234", "secret1234"); + auto actual = Sha512Hex("data1234", "secret1234"); static constexpr char kExpectedData[] = { 101, 97, 50, 55, 53, 102, 100, 57, 100, 52, 50, 55, 54, 49, 100, 56, 49, 100, 56, 56, 56, 51, 53, 52, 57, 48, 102, 98, 52, 53, 57, 49, 48, 51, 57, 51, 53, 54, 51, 54, 101, 49, 50, 99, @@ -60,7 +60,7 @@ TEST(SSLTest, ShaHex512) { } TEST(SSLTest, ShaDigest256) { - auto actual = ShaDigest(ShaType::kSha256, "data1234"); + auto actual = Sha256Digest("data1234"); static constexpr char kExpectedData[] = {102, 50, 102, 100, 97, 57, 98, 98, 53, 49, 49, 56, 100, 100, 53, 97, 51, 50, 57, 55, 100, 50, 56, 97, 52, 55, 50, 57, 51, 102, 49, 50, 51, 97, 49, 51, 50, 54, 50, 57, 48, 101, 102, 51, 100, 55, 48, 49, @@ -69,7 +69,7 @@ TEST(SSLTest, ShaDigest256) { } TEST(SSLTest, ShaDigest512) { - auto actual = ShaDigest(ShaType::kSha512, "data1234"); + auto actual = Sha512Digest("data1234"); static constexpr char kExpectedData[] = { 99, 97, 97, 48, 50, 55, 54, 50, 57, 52, 98, 54, 49, 53, 48, 50, 51, 100, 57, 55, 50, 54, 48, 52, 55, 53, 50, 54, 98, 49, 50, 52, 102, 100, 99, 100, 51, 49, 98, 100, 97, 101, 97, 56, @@ -81,9 +81,9 @@ TEST(SSLTest, ShaDigest512) { } TEST(SSLTest, ShaDigest256Multiple) { - static const string kData[] = {"data1234", "anotherString5_-", "5_0(7)fbBBBb334G;"}; + static constexpr std::string_view kData[] = {"data1234", "anotherString5_-", "5_0(7)fbBBBb334G;"}; - auto actual = ShaDigest(ShaType::kSha256, kData); + auto actual = Sha256Digest(kData); static constexpr char kExpectedData[] = {53, 53, 100, 98, 52, 97, 49, 97, 50, 99, 52, 52, 52, 99, 97, 57, 100, 57, 97, 52, 48, 99, 51, 52, 101, 97, 50, 99, 53, 98, 97, 51, 100, 54, 55, 50, 102, 100, 51, 102, 100, 98, 51, 54, 52, 100, 98, 50, @@ -92,9 +92,9 @@ TEST(SSLTest, ShaDigest256Multiple) { } TEST(SSLTest, ShaDigest512Multiple) { - static const string kData[] = {"data1234", "anotherString5_-", "5_0(7)fbBBBb334G;"}; + static constexpr std::string_view kData[] = {"data1234", "anotherString5_-", "5_0(7)fbBBBb334G;"}; - auto actual = ShaDigest(ShaType::kSha512, kData); + auto actual = Sha512Digest(kData); static constexpr char kExpectedData[] = { 101, 56, 101, 55, 54, 98, 100, 56, 57, 53, 100, 53, 54, 99, 50, 54, 48, 56, 50, 57, 53, 97, 100, 98, 100, 48, 55, 102, 51, 56, 49, 54, 99, 55, 99, 101, 101, 98, 54, 48, 53, 52, 100, 98, diff --git a/src/api/exchanges/src/binanceprivateapi.cpp b/src/api/exchanges/src/binanceprivateapi.cpp index c45fc8cb..972d0210 100644 --- a/src/api/exchanges/src/binanceprivateapi.cpp +++ b/src/api/exchanges/src/binanceprivateapi.cpp @@ -103,7 +103,9 @@ void SetNonceAndSignature(const APIKey& apiKey, CurlPostData& postData, Duration static constexpr std::string_view kSignatureKey = "signature"; - postData.set_back(kSignatureKey, ssl::ShaHex(ssl::ShaType::kSha256, postData.str(), apiKey.privateKey())); + auto sha256Hex = ssl::Sha256Hex(postData.str(), apiKey.privateKey()); + + postData.set_back(kSignatureKey, std::string_view(sha256Hex)); } bool CheckErrorDoRetry(int statusCode, const json& ret, QueryDelayDir& queryDelayDir, Duration& sleepingTime, diff --git a/src/api/exchanges/src/bithumbprivateapi.cpp b/src/api/exchanges/src/bithumbprivateapi.cpp index a6d872ff..fa160726 100644 --- a/src/api/exchanges/src/bithumbprivateapi.cpp +++ b/src/api/exchanges/src/bithumbprivateapi.cpp @@ -16,6 +16,7 @@ #include "apiquerytypeenum.hpp" #include "balanceoptions.hpp" #include "balanceportfolio.hpp" +#include "base64.hpp" #include "bithumbpublicapi.hpp" #include "cachedresult.hpp" #include "cct_exception.hpp" @@ -25,7 +26,6 @@ #include "cct_smallvector.hpp" #include "cct_string.hpp" #include "closed-order.hpp" -#include "codec.hpp" #include "coincenterinfo.hpp" #include "curlhandle.hpp" #include "curloptions.hpp" @@ -43,6 +43,7 @@ #include "file.hpp" #include "httprequesttype.hpp" #include "market.hpp" +#include "mathhelpers.hpp" #include "monetaryamount.hpp" #include "opened-order.hpp" #include "orderid.hpp" @@ -98,22 +99,35 @@ auto GetStrData(std::string_view endpoint, std::string_view postDataStr) { return std::make_pair(std::move(strData), std::move(nonce)); } -void SetHttpHeaders(CurlOptions& opts, const APIKey& apiKey, std::string_view signature, const Nonce& nonce) { - auto& httpHeaders = opts.mutableHttpHeaders(); +void SetHttpHeaders(CurlOptions& opts, const APIKey& apiKey, const auto& signature, const Nonce& nonce) { + static constexpr std::string_view kApiKey = "API-Key"; + static constexpr std::string_view kApiSign = "API-Sign"; + static constexpr std::string_view kApiNonce = "API-Nonce"; + static constexpr std::string_view kApiClientType = "api-client-type"; + + static constexpr auto kApiClientTypeValue = 1; + + static constexpr std::size_t kNbHeaders = 4; + static constexpr auto kFixedSizePart = kApiKey.size() + kApiSign.size() + kApiNonce.size() + kApiClientType.size() + + ndigits(kApiClientTypeValue) + (kNbHeaders * 2) - 1U; + + auto& httpHeaders = opts.mutableHttpHeaders(); httpHeaders.clear(); - httpHeaders.emplace_back("API-Key", apiKey.key()); - httpHeaders.emplace_back("API-Sign", signature); - httpHeaders.emplace_back("API-Nonce", nonce); - httpHeaders.emplace_back("api-client-type", 1); + httpHeaders.underlyingBufferReserve(kFixedSizePart + apiKey.key().size() + signature.size() + nonce.size()); + + httpHeaders.emplace_back(kApiKey, apiKey.key()); + httpHeaders.emplace_back(kApiSign, signature); + httpHeaders.emplace_back(kApiNonce, nonce); + httpHeaders.emplace_back(kApiClientType, kApiClientTypeValue); } template bool LoadCurrencyInfoField(const json& currencyOrderInfoJson, std::string_view keyStr, ValueType& val, TimePoint& ts) { - auto subPartIt = currencyOrderInfoJson.find(keyStr); + const auto subPartIt = currencyOrderInfoJson.find(keyStr); if (subPartIt != currencyOrderInfoJson.end()) { - auto valIt = subPartIt->find(kValueKeyStr); - auto tsIt = subPartIt->find(kTimestampKeyStr); + const auto valIt = subPartIt->find(kValueKeyStr); + const auto tsIt = subPartIt->find(kTimestampKeyStr); if (valIt == subPartIt->end() || tsIt == subPartIt->end()) { log::warn("Unexpected format of Bithumb cache detected - do not use (will be automatically updated)"); return false; @@ -287,7 +301,7 @@ json PrivateQueryProcessWithRetries(CurlHandle& curlHandle, const APIKey& apiKey [endpoint, &apiKey](CurlOptions& opts) { auto [strData, nonce] = GetStrData(endpoint, opts.postData().str()); - auto signature = B64Encode(ssl::ShaHex(ssl::ShaType::kSha512, strData, apiKey.privateKey())); + auto signature = B64Encode(ssl::Sha512Hex(strData, apiKey.privateKey())); SetHttpHeaders(opts, apiKey, signature, nonce); }); diff --git a/src/api/exchanges/src/huobiprivateapi.cpp b/src/api/exchanges/src/huobiprivateapi.cpp index 4c096cb6..7f0e457e 100644 --- a/src/api/exchanges/src/huobiprivateapi.cpp +++ b/src/api/exchanges/src/huobiprivateapi.cpp @@ -12,13 +12,13 @@ #include "apiquerytypeenum.hpp" #include "balanceoptions.hpp" #include "balanceportfolio.hpp" +#include "base64.hpp" #include "cachedresult.hpp" #include "cct_exception.hpp" #include "cct_json.hpp" #include "cct_log.hpp" #include "cct_string.hpp" #include "cct_vector.hpp" -#include "codec.hpp" #include "coincenterinfo.hpp" #include "curlhandle.hpp" #include "curloptions.hpp" @@ -96,12 +96,11 @@ void SetNonceAndSignature(CurlHandle& curlHandle, const APIKey& apiKey, HttpRequ static constexpr std::string_view kSignatureKey = "Signature"; - signaturePostData.set_back( - kSignatureKey, URLEncode(B64Encode(ssl::ShaBin(ssl::ShaType::kSha256, - BuildParamStr(requestType, curlHandle.getNextBaseUrl(), endpoint, - signaturePostData.str()), - apiKey.privateKey())), - isNotEncoded)); + signaturePostData.set_back(kSignatureKey, + URLEncode(B64Encode(ssl::Sha256Bin(BuildParamStr(requestType, curlHandle.getNextBaseUrl(), + endpoint, signaturePostData.str()), + apiKey.privateKey())), + isNotEncoded)); } json PrivateQuery(CurlHandle& curlHandle, const APIKey& apiKey, HttpRequestType requestType, std::string_view endpoint, diff --git a/src/api/exchanges/src/krakenprivateapi.cpp b/src/api/exchanges/src/krakenprivateapi.cpp index 537af78b..a24c4f0d 100644 --- a/src/api/exchanges/src/krakenprivateapi.cpp +++ b/src/api/exchanges/src/krakenprivateapi.cpp @@ -12,13 +12,13 @@ #include "apiquerytypeenum.hpp" #include "balanceoptions.hpp" #include "balanceportfolio.hpp" +#include "base64.hpp" #include "cachedresult.hpp" #include "cct_exception.hpp" #include "cct_json.hpp" #include "cct_log.hpp" #include "cct_string.hpp" #include "cct_vector.hpp" -#include "codec.hpp" #include "coincenterinfo.hpp" #include "commonapi.hpp" #include "curlhandle.hpp" @@ -115,8 +115,8 @@ std::pair PrivateQuery(CurlHandle& curlHandle, const APIK static constexpr std::string_view kSignatureKey = "API-Sign"; // and compute HMAC - opts.mutableHttpHeaders().set_back( - kSignatureKey, B64Encode(ssl::ShaBin(ssl::ShaType::kSha512, path, B64Decode(apiKey.privateKey())))); + opts.mutableHttpHeaders().set_back(kSignatureKey, + B64Encode(ssl::Sha512Bin(path, B64Decode(apiKey.privateKey())))); }); auto resultIt = ret.find("result"); diff --git a/src/api/exchanges/src/kucoinprivateapi.cpp b/src/api/exchanges/src/kucoinprivateapi.cpp index c6cf16ee..7b5b4602 100644 --- a/src/api/exchanges/src/kucoinprivateapi.cpp +++ b/src/api/exchanges/src/kucoinprivateapi.cpp @@ -12,13 +12,13 @@ #include "apiquerytypeenum.hpp" #include "balanceoptions.hpp" #include "balanceportfolio.hpp" +#include "base64.hpp" #include "cachedresult.hpp" #include "cct_exception.hpp" #include "cct_json.hpp" #include "cct_log.hpp" #include "cct_string.hpp" #include "closed-order.hpp" -#include "codec.hpp" #include "coincenterinfo.hpp" #include "commonapi.hpp" #include "curlhandle.hpp" @@ -78,8 +78,8 @@ json PrivateQuery(CurlHandle& curlHandle, const APIKey& apiKey, HttpRequestType } } - string signature = B64Encode(ssl::ShaBin(ssl::ShaType::kSha256, strToSign, apiKey.privateKey())); - string passphrase = B64Encode(ssl::ShaBin(ssl::ShaType::kSha256, apiKey.passphrase(), apiKey.privateKey())); + auto signature = B64Encode(ssl::Sha256Bin(strToSign, apiKey.privateKey())); + auto passphrase = B64Encode(ssl::Sha256Bin(apiKey.passphrase(), apiKey.privateKey())); CurlOptions opts(requestType, std::move(postData), postDataFormat); diff --git a/src/api/exchanges/src/upbitprivateapi.cpp b/src/api/exchanges/src/upbitprivateapi.cpp index a445314f..e82cbb14 100644 --- a/src/api/exchanges/src/upbitprivateapi.cpp +++ b/src/api/exchanges/src/upbitprivateapi.cpp @@ -77,9 +77,9 @@ json PrivateQuery(CurlHandle& curlHandle, const APIKey& apiKey, HttpRequestType .set_payload_claim("nonce", jwt::claim(std::string(Nonce_TimeSinceEpochInMs()))); if (!opts.postData().empty()) { - string queryHash = ssl::ShaDigest(ssl::ShaType::kSha512, opts.postData().str()); + const auto queryHash = ssl::Sha512Digest(opts.postData().str()); - jsonWebToken.set_payload_claim("query_hash", jwt::claim(std::string(queryHash))) + jsonWebToken.set_payload_claim("query_hash", jwt::claim(std::string(queryHash.data(), queryHash.size()))) .set_payload_claim("query_hash_alg", jwt::claim(std::string("SHA512"))); } diff --git a/src/tech/CMakeLists.txt b/src/tech/CMakeLists.txt index 5bb12a8c..2988ba21 100644 --- a/src/tech/CMakeLists.txt +++ b/src/tech/CMakeLists.txt @@ -35,9 +35,9 @@ add_unit_test( ) add_unit_test( - codec_test - src/codec.cpp - test/codec_test.cpp + base64_test + src/base64.cpp + test/base64_test.cpp DEFINITIONS CCT_DISABLE_SPDLOG ) diff --git a/src/tech/include/base64.hpp b/src/tech/include/base64.hpp new file mode 100644 index 00000000..4b4d818e --- /dev/null +++ b/src/tech/include/base64.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include + +#include "cct_string.hpp" + +namespace cct { + +namespace details { +constexpr void B64Encode(std::span binData, char *out, char *endOut) { + int bitsCollected = 0; + unsigned int accumulator = 0; + + static constexpr const char *const kB64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + for (char ch : binData) { + accumulator = (accumulator << 8) | (ch & 0xFFU); + bitsCollected += 8; + while (bitsCollected >= 6) { + bitsCollected -= 6; + *out = kB64Table[(accumulator >> bitsCollected) & 0x3FU]; + ++out; + } + } + if (bitsCollected > 0) { + accumulator <<= 6 - bitsCollected; + *out = kB64Table[accumulator & 0x3FU]; + ++out; + } + + std::fill(out, endOut, '='); +} + +constexpr auto B64EncodedLen(unsigned int binDataLen) { return static_cast((binDataLen + 2) / 3) * 4; } + +} // namespace details + +[[nodiscard]] inline auto B64Encode(std::span binData) { + string ret(details::B64EncodedLen(binData.size()), 0); + details::B64Encode(binData, ret.data(), ret.data() + ret.size()); + return ret; +} +string B64Encode(const char *) = delete; + +template +constexpr auto B64Encode(const char (&binData)[N]) { + std::array ret; + details::B64Encode(binData, ret.data(), ret.data() + ret.size()); + return ret; +} + +template +constexpr auto B64Encode(const std::array &binData) { + std::array ret; + details::B64Encode(binData, ret.data(), ret.data() + ret.size()); + return ret; +} + +[[nodiscard]] string B64Decode(std::span ascData); +string B64Decode(const char *) = delete; + +} // namespace cct \ No newline at end of file diff --git a/src/tech/include/codec.hpp b/src/tech/include/codec.hpp deleted file mode 100644 index 4051ec72..00000000 --- a/src/tech/include/codec.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -#include "cct_string.hpp" - -namespace cct { - -// const char * arguments are deleted because it would construct into a span including the unwanted null -// terminating character. Use span directly, or string / string_view instead. - -[[nodiscard]] string BinToHex(std::span binData); -string BinToHex(const char *) = delete; - -[[nodiscard]] string B64Encode(std::span binData); -string B64Encode(const char *) = delete; - -[[nodiscard]] string B64Decode(std::span ascData); -string B64Decode(const char *) = delete; - -} // namespace cct \ No newline at end of file diff --git a/src/tech/include/flatkeyvaluestring.hpp b/src/tech/include/flatkeyvaluestring.hpp index 759cef37..e73e3c75 100644 --- a/src/tech/include/flatkeyvaluestring.hpp +++ b/src/tech/include/flatkeyvaluestring.hpp @@ -92,6 +92,11 @@ class FlatKeyValueString { /// "val1,val2,": value is an array of two values val1 and val2 void emplace_back(std::string_view key, std::string_view value); + template + void emplace_back(std::string_view key, const std::array &value) { + emplace_back(key, std::string_view(value.data(), N)); + } + void emplace_back(std::string_view key, std::integral auto val) { // + 1 for minus, +1 for additional partial ranges coverage std::array::digits10 + 2> buf; @@ -110,6 +115,11 @@ class FlatKeyValueString { /// Pushes a new {key, value} entry at the front of this buffer. void emplace_front(std::string_view key, std::string_view value); + template + void emplace_front(std::string_view key, const std::array &value) { + emplace_front(key, std::string_view(value.data(), N)); + } + void emplace_front(std::string_view key, std::integral auto val) { // + 1 for minus, +1 for additional partial ranges coverage std::array::digits10 + 2> buf; @@ -124,16 +134,27 @@ class FlatKeyValueString { /// Updates the value for given key, or append if not existing. void set(std::string_view key, std::string_view value); - void set(std::string_view key, std::integral auto i) { + template + void set(std::string_view key, const std::array &value) { + set(key, std::string_view(value.data(), N)); + } + + void set(std::string_view key, std::integral auto val) { // + 1 for minus, +1 for additional partial ranges coverage - char buf[std::numeric_limits::digits10 + 2]; - auto ret = std::to_chars(buf, std::end(buf), i); + char buf[std::numeric_limits::digits10 + 2]; + auto ret = std::to_chars(buf, std::end(buf), val); + set(key, std::string_view(buf, ret.ptr)); } /// Like emplace_back, but removes last entry if it has same key as given one. void set_back(std::string_view key, std::string_view value); + template + void set_back(std::string_view key, const std::array &value) { + set_back(key, std::string_view(value.data(), N)); + } + /// Erases given key if present. void erase(std::string_view key); diff --git a/src/tech/src/codec.cpp b/src/tech/src/base64.cpp similarity index 55% rename from src/tech/src/codec.cpp rename to src/tech/src/base64.cpp index 75216204..a77e9d7b 100644 --- a/src/tech/src/codec.cpp +++ b/src/tech/src/base64.cpp @@ -1,51 +1,13 @@ -#include "codec.hpp" +#include "base64.hpp" -#include #include #include "cct_cctype.hpp" #include "cct_invalid_argument_exception.hpp" #include "cct_string.hpp" -#include "char-hexadecimal-converter.hpp" namespace cct { -string BinToHex(std::span binData) { - string ret(2 * binData.size(), '\0'); - - auto out = ret.data(); - - for (auto beg = binData.data(), end = beg + binData.size(); beg != end; ++beg) { - out = to_lower_hex(*beg, out); - } - return ret; -} - -string B64Encode(std::span binData) { - const auto binLen = binData.size(); - // Use = signs so the end is properly padded. - string ret((((binLen + 2) / 3) * 4), '='); - std::size_t outPos = 0; - int bitsCollected = 0; - unsigned int accumulator = 0; - - static constexpr const char* const kB64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - for (char ch : binData) { - accumulator = (accumulator << 8) | (ch & 0xFFU); - bitsCollected += 8; - while (bitsCollected >= 6) { - bitsCollected -= 6; - ret[outPos++] = kB64Table[(accumulator >> bitsCollected) & 0x3FU]; - } - } - if (bitsCollected > 0) { // Any trailing bits that are missing. - accumulator <<= 6 - bitsCollected; - ret[outPos++] = kB64Table[accumulator & 0x3FU]; - } - return ret; -} - string B64Decode(std::span ascData) { string ret; int bitsCollected = 0; diff --git a/src/tech/test/codec_test.cpp b/src/tech/test/base64_test.cpp similarity index 63% rename from src/tech/test/codec_test.cpp rename to src/tech/test/base64_test.cpp index 8499cfb1..323e4ccc 100644 --- a/src/tech/test/codec_test.cpp +++ b/src/tech/test/base64_test.cpp @@ -1,4 +1,4 @@ -#include "codec.hpp" +#include "base64.hpp" #include @@ -7,23 +7,6 @@ namespace cct { -namespace { -std::span StringToBytes(std::string_view str) { - return {reinterpret_cast(str.data()), str.length()}; -} -} // namespace - -TEST(Base64, BinToHexEmpty) { EXPECT_EQ(BinToHex(StringToBytes("")), ""); } -TEST(Base64, BinToHex1) { EXPECT_EQ(BinToHex(StringToBytes("f")), "66"); } -TEST(Base64, BinToHex2) { EXPECT_EQ(BinToHex(StringToBytes("fo")), "666f"); } -TEST(Base64, BinToHex3) { EXPECT_EQ(BinToHex(StringToBytes("foo")), "666f6f"); } -TEST(Base64, BinToHex4) { EXPECT_EQ(BinToHex(StringToBytes("foob")), "666f6f62"); } -TEST(Base64, BinToHex5) { EXPECT_EQ(BinToHex(StringToBytes("fooba")), "666f6f6261"); } -TEST(Base64, BinToHex6) { EXPECT_EQ(BinToHex(StringToBytes("foobar")), "666f6f626172"); } -TEST(Base64, BinToHex7) { EXPECT_EQ(BinToHex(StringToBytes("foobarz")), "666f6f6261727a"); } -TEST(Base64, BinToHex8) { EXPECT_EQ(BinToHex(StringToBytes("foobarzY")), "666f6f6261727a59"); } -TEST(Base64, BinToHex9) { EXPECT_EQ(BinToHex(StringToBytes("foobarzYg")), "666f6f6261727a5967"); } - TEST(Base64, EncodeEmpty) { EXPECT_EQ(B64Encode(std::string_view("")), ""); } TEST(Base64, Encode1) { EXPECT_EQ(B64Encode(std::string_view("f")), "Zg=="); } TEST(Base64, Encode2) { EXPECT_EQ(B64Encode(std::string_view("fo")), "Zm8="); }