[Core] - Support azure-core version without OpenSSL dependency (#2839)

* perf test uuid

* use win impl for uuid on linux

* use c99 base64 encode-decode
This commit is contained in:
Victor Vazquez 2021-10-27 16:20:34 -07:00 committed by GitHub
parent fe42c0f831
commit 3be793d558
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 552 additions and 91 deletions

View File

@ -16,6 +16,9 @@
### Other Changes
- Updated `base64` implementation to remove external dependency.
- Updated `Uuid` implementation for Linux to remove external dependency.
## 1.2.1 (2021-09-02)
### Bugs Fixed

View File

@ -148,6 +148,7 @@ target_link_libraries(azure-core INTERFACE Threads::Threads)
if(WIN32)
target_link_libraries(azure-core PRIVATE bcrypt crypt32)
else()
# Required for Hashing ( md5 and sha ).
find_package(OpenSSL REQUIRED)
target_link_libraries(azure-core PRIVATE OpenSSL::SSL)
endif()

View File

@ -4,93 +4,474 @@
#include "azure/core/base64.hpp"
#include "azure/core/platform.hpp"
#if defined(AZ_PLATFORM_WINDOWS)
#include <windows.h>
#elif defined(AZ_PLATFORM_POSIX)
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#endif
#include <string>
#include <vector>
namespace {
static char const Base64EncodeArray[65]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static char const EncodingPad = '=';
static int8_t const Base64DecodeArray[256] = {
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
62,
-1,
-1,
-1,
63, // 62 is placed at index 43 (for +), 63 at index 47 (for /)
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
-1,
-1,
-1,
-1,
-1,
-1, // 52-61 are placed at index 48-57 (for 0-9), 64 at index 61 (for =)
-1,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
-1,
-1,
-1,
-1,
-1, // 0-25 are placed at index 65-90 (for A-Z)
-1,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
-1,
-1,
-1,
-1,
-1, // 26-51 are placed at index 97-122 (for a-z)
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1, // Bytes over 122 ('z') are invalid and cannot be decoded. Hence, padding the map with -1,
// which indicates invalid input
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
};
static int32_t Base64Encode(const uint8_t* threeBytes)
{
int32_t i = (threeBytes[0] << 16) | (threeBytes[1] << 8) | threeBytes[2];
int32_t i0 = Base64EncodeArray[i >> 18];
int32_t i1 = Base64EncodeArray[(i >> 12) & 0x3F];
int32_t i2 = Base64EncodeArray[(i >> 6) & 0x3F];
int32_t i3 = Base64EncodeArray[i & 0x3F];
return i0 | (i1 << 8) | (i2 << 16) | (i3 << 24);
}
static int32_t Base64EncodeAndPadOne(const uint8_t* twoBytes)
{
int32_t i = twoBytes[0] << 16 | (twoBytes[1] << 8);
int32_t i0 = Base64EncodeArray[i >> 18];
int32_t i1 = Base64EncodeArray[(i >> 12) & 0x3F];
int32_t i2 = Base64EncodeArray[(i >> 6) & 0x3F];
return i0 | (i1 << 8) | (i2 << 16) | (EncodingPad << 24);
}
static int32_t Base64EncodeAndPadTwo(const uint8_t* oneByte)
{
int32_t i = oneByte[0] << 8;
int32_t i0 = Base64EncodeArray[i >> 10];
int32_t i1 = Base64EncodeArray[(i >> 4) & 0x3F];
return i0 | (i1 << 8) | (EncodingPad << 16) | (EncodingPad << 24);
}
static void Base64WriteIntAsFourBytes(char* destination, int32_t value)
{
destination[3] = static_cast<uint8_t>((value >> 24) & 0xFF);
destination[2] = static_cast<uint8_t>((value >> 16) & 0xFF);
destination[1] = static_cast<uint8_t>((value >> 8) & 0xFF);
destination[0] = static_cast<uint8_t>(value & 0xFF);
}
std::string Base64Encode(const std::vector<uint8_t>& data)
{
size_t sourceIndex = 0;
auto inputSize = data.size();
auto maxEncodedSize = ((inputSize + 2) / 3) * 4;
// Use a string with size to the max possible result
std::string encodedResult(maxEncodedSize, '0');
// Removing const from the string to update the placeholder string
auto destination = const_cast<char*>(encodedResult.data());
while (sourceIndex + 3 <= inputSize)
{
int32_t result = Base64Encode(&data[sourceIndex]);
Base64WriteIntAsFourBytes(destination, result);
destination += 4;
sourceIndex += 3;
}
if (sourceIndex + 1 == inputSize)
{
int32_t result = Base64EncodeAndPadTwo(&data[sourceIndex]);
Base64WriteIntAsFourBytes(destination, result);
destination += 4;
sourceIndex += 1;
}
else if (sourceIndex + 2 == inputSize)
{
int32_t result = Base64EncodeAndPadOne(&data[sourceIndex]);
Base64WriteIntAsFourBytes(destination, result);
destination += 4;
sourceIndex += 2;
}
return encodedResult;
}
static int32_t Base64Decode(const char* encodedBytes)
{
int32_t i0 = encodedBytes[0];
int32_t i1 = encodedBytes[1];
int32_t i2 = encodedBytes[2];
int32_t i3 = encodedBytes[3];
i0 = Base64DecodeArray[i0];
i1 = Base64DecodeArray[i1];
i2 = Base64DecodeArray[i2];
i3 = Base64DecodeArray[i3];
i0 <<= 18;
i1 <<= 12;
i2 <<= 6;
i0 |= i3;
i1 |= i2;
i0 |= i1;
return i0;
}
static void Base64WriteThreeLowOrderBytes(std::vector<uint8_t>::iterator destination, int64_t value)
{
destination[0] = static_cast<uint8_t>(value >> 16);
destination[1] = static_cast<uint8_t>(value >> 8);
destination[2] = static_cast<uint8_t>(value);
}
std::vector<uint8_t> Base64Decode(const std::string& text)
{
auto inputSize = text.size();
if (inputSize < 4)
{
return std::vector<uint8_t>(0);
}
size_t sourceIndex = 0;
auto inputPtr = text.data();
auto decodedSize = (inputSize / 4) * 3;
if (inputPtr[inputSize - 2] == EncodingPad)
{
decodedSize -= 2;
}
else if (inputPtr[inputSize - 1] == EncodingPad)
{
decodedSize -= 1;
}
std::vector<uint8_t> destination(decodedSize);
auto destinationPtr = destination.begin();
while (sourceIndex + 4 < inputSize)
{
int64_t result = Base64Decode(inputPtr + sourceIndex);
Base64WriteThreeLowOrderBytes(destinationPtr, result);
destinationPtr += 3;
sourceIndex += 4;
}
// We are guaranteed to have an input with at least 4 bytes at this point, with a size that is a
// multiple of 4.
int64_t i0 = inputPtr[inputSize - 4];
int64_t i1 = inputPtr[inputSize - 3];
int64_t i2 = inputPtr[inputSize - 2];
int64_t i3 = inputPtr[inputSize - 1];
i0 = Base64DecodeArray[i0];
i1 = Base64DecodeArray[i1];
i0 <<= 18;
i1 <<= 12;
i0 |= i1;
if (i3 != EncodingPad)
{
i2 = Base64DecodeArray[i2];
i3 = Base64DecodeArray[i3];
i2 <<= 6;
i0 |= i3;
i0 |= i2;
Base64WriteThreeLowOrderBytes(destinationPtr, i0);
destinationPtr += 3;
}
else if (i2 != EncodingPad)
{
i2 = Base64DecodeArray[i2];
i2 <<= 6;
i0 |= i2;
destinationPtr[1] = static_cast<uint8_t>(i0 >> 8);
destinationPtr[0] = static_cast<uint8_t>(i0 >> 16);
destinationPtr += 2;
}
else
{
destinationPtr[0] = static_cast<uint8_t>(i0 >> 16);
destinationPtr += 1;
}
return destination;
}
} // namespace
namespace Azure { namespace Core {
#if defined(AZ_PLATFORM_WINDOWS)
std::string Convert::Base64Encode(const std::vector<uint8_t>& data)
{
std::string encoded;
// According to RFC 4648, the encoded length should be ceiling(n / 3) * 4
DWORD encodedLength = static_cast<DWORD>((data.size() + 2) / 3 * 4);
encoded.resize(encodedLength);
CryptBinaryToStringA(
reinterpret_cast<const BYTE*>(data.data()),
static_cast<DWORD>(data.size()),
CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
static_cast<LPSTR>(&encoded[0]),
&encodedLength);
return encoded;
return ::Base64Encode(data);
}
std::vector<uint8_t> Convert::Base64Decode(const std::string& text)
{
std::vector<uint8_t> decoded;
// According to RFC 4648, the encoded length should be ceiling(n / 3) * 4, so we can infer an
// upper bound here
DWORD decodedLength = DWORD(text.length() / 4 * 3);
decoded.resize(decodedLength);
CryptStringToBinaryA(
text.data(),
static_cast<DWORD>(text.length()),
CRYPT_STRING_BASE64 | CRYPT_STRING_STRICT,
reinterpret_cast<BYTE*>(decoded.data()),
&decodedLength,
nullptr,
nullptr);
decoded.resize(decodedLength);
return decoded;
return ::Base64Decode(text);
}
#elif defined(AZ_PLATFORM_POSIX)
std::string Convert::Base64Encode(const std::vector<uint8_t>& data)
{
BIO* bio = BIO_new(BIO_s_mem());
bio = BIO_push(BIO_new(BIO_f_base64()), bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
BIO_write(bio, data.data(), static_cast<int>(data.size()));
(void)BIO_flush(bio);
BUF_MEM* bufferPtr;
BIO_get_mem_ptr(bio, &bufferPtr);
std::string toReturn(bufferPtr->data, bufferPtr->length);
BIO_free_all(bio);
return toReturn;
}
std::vector<uint8_t> Convert::Base64Decode(const std::string& text)
{
std::vector<uint8_t> decoded;
// According to RFC 4648, the encoded length should be ceiling(n / 3) * 4, so we can infer an
// upper bound here
size_t maxDecodedLength = text.length() / 4 * 3;
decoded.resize(maxDecodedLength);
BIO* bio = BIO_new_mem_buf(text.data(), -1);
bio = BIO_push(BIO_new(BIO_f_base64()), bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
int decodedLength = BIO_read(bio, &decoded[0], static_cast<int>(text.length()));
BIO_free_all(bio);
decoded.resize(decodedLength);
return decoded;
}
#endif
}} // namespace Azure::Core

View File

@ -3,13 +3,19 @@
#include "azure/core/uuid.hpp"
#if defined(AZ_PLATFORM_POSIX)
#include <openssl/rand.h> //for RAND_bytes
#endif
#include <cstdio>
#include <random>
#if defined(AZ_PLATFORM_POSIX)
#include <thread>
namespace {
// 64-bit Mersenne Twister by Matsumoto and Nishimura, 2000
// Used to generate the random numbers for the Uuid.
// The seed is generated with std::random_device.
static thread_local std::mt19937_64 randomGenerator(std::random_device{}());
} // namespace
#endif
namespace Azure { namespace Core {
std::string Uuid::ToString()
{
@ -47,22 +53,19 @@ namespace Azure { namespace Core {
#if defined(AZ_PLATFORM_WINDOWS)
std::random_device rd;
#else
std::uniform_int_distribution<uint32_t> distribution;
#endif
for (size_t i = 0; i < UuidSize; i += 4)
{
#if defined(AZ_PLATFORM_WINDOWS)
const uint32_t x = rd();
#else
const uint32_t x = distribution(randomGenerator);
#endif
std::memcpy(uuid + i, &x, 4);
}
#elif defined(AZ_PLATFORM_POSIX)
// This static cast is safe since we know Uuid size, which is a const, will always fit an int.
int ret = RAND_bytes(uuid, static_cast<int>(UuidSize));
if (ret <= 0)
{
abort();
}
#else
abort();
#endif
// SetVariant to ReservedRFC4122
uuid[8] = (uuid[8] | ReservedRFC4122) & 0x7F;

View File

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Test the Uuid component performance.
*
*/
#pragma once
#include <azure/core.hpp>
#include <azure/perf.hpp>
#include <memory>
namespace Azure { namespace Core { namespace Test {
/**
* @brief Measure the Uuid object performance.
*/
class UuidTest : public Azure::Perf::PerfTest {
public:
/**
* @brief Construct a new Uuid test.
*
* @param options The test options.
*/
UuidTest(Azure::Perf::TestOptions options) : PerfTest(options) {}
/**
* @brief Use Uuid to assign and read.
*
*/
void Run(Azure::Core::Context const&) override
{
auto const total = m_options.GetMandatoryOption<int>("count");
for (auto count = 0; count < total; count++)
{
Azure::Core::Uuid::CreateUuid();
}
}
/**
* @brief Define the test options for the test.
*
* @return The list of test options.
*/
std::vector<Azure::Perf::TestOption> GetTestOptions() override
{
return {{"count", {"-c"}, "The number of uuid objects to be created.", 1, true}};
}
/**
* @brief Get the static Test Metadata for the test.
*
* @return Azure::Perf::TestMetadata describing the test.
*/
static Azure::Perf::TestMetadata GetTestMetadata()
{
return {
"UuidTest",
"Measures the overhead of using Uuid objects",
[](Azure::Perf::TestOptions options) {
return std::make_unique<Azure::Core::Test::UuidTest>(options);
}};
}
};
}}} // namespace Azure::Core::Test

View File

@ -4,6 +4,7 @@
#include <azure/perf.hpp>
#include "azure/core/test/nullable_test.hpp"
#include "azure/core/test/uuid_test.hpp"
#include <vector>
@ -11,7 +12,9 @@ int main(int argc, char** argv)
{
// Create the test list
std::vector<Azure::Perf::TestMetadata> tests{Azure::Core::Test::NullableTest::GetTestMetadata()};
std::vector<Azure::Perf::TestMetadata> tests{
Azure::Core::Test::NullableTest::GetTestMetadata(),
Azure::Core::Test::UuidTest::GetTestMetadata()};
Azure::Perf::Program::Run(Azure::Core::Context::ApplicationContext, tests, argc, argv);