/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0/ * * or in the "license" file accompanying this file. This file 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 #include #include #include #include #include "AVSCommon/Utils/Logger/Logger.h" #include "AVSCommon/Utils/UUIDGeneration/UUIDGeneration.h" namespace alexaClientSDK { namespace avsCommon { namespace utils { namespace uuidGeneration { /// String to identify log entries originating from this file. static const std::string TAG("UUIDGeneration"); /** * Create a LogEntry using this file's TAG and the specified event string. * * @param The event string for this @c LogEntry. */ #define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event) /// The UUID version (Version 4), shifted into the correct position in the byte. static const uint8_t UUID_VERSION_VALUE = 4 << 4; /// The UUID variant (Variant 1), shifted into the correct position in the byte. static const size_t UUID_VARIANT_VALUE = 2 << 6; /// Separator used between UUID fields. static const std::string SEPARATOR("-"); /// Number of bits in the replacement value. static const size_t MAX_NUM_REPLACEMENT_BITS = CHAR_BIT; /// Number of bits in a hex digit. static const size_t BITS_IN_HEX_DIGIT = 4; /// Indicates if next UUID should be seeded. Must not be accessed unless g_mutex is locked. static bool g_seedNeeded = true; /// Lock to avoid collisions in generationing uuid and setting seed. static std::mutex g_mutex; /// Entropy Threshold for sufficient uniqueness. Value chosen by experiment. static const double ENTROPY_THRESHOLD = 600; /// extra seeds static const size_t MAX_SEEDS_POOL_SIZE = 1024; /// pool of seeds static std::list seedsPool; /// last time generateUUID was invoked static uint64_t lastInvokeTime; /// Catch for platforms where entropy is a hard coded value. Value chosen by experiment. static const int ENTROPY_REPEAT_THRESHOLD = 16; /// The default read entropy function. This can be customized using UUIDGeneration::setEntropyReader(). static std::function readEntropyFunc = []() { std::random_device rd; return rd.entropy(); }; void setEntropyReader(std::function func) { readEntropyFunc = func; } void setSalt(const std::string& newSalt) { std::unique_lock lock(g_mutex); std::copy_n(newSalt.begin(), std::min(newSalt.size(), MAX_SEEDS_POOL_SIZE), std::front_inserter(seedsPool)); g_seedNeeded = true; } void addSeeds(const std::vector& seeds) { std::unique_lock lock(g_mutex); std::copy_n(seeds.begin(), std::min(seeds.size(), MAX_SEEDS_POOL_SIZE), std::front_inserter(seedsPool)); g_seedNeeded = true; } /** * Randomly generate a string of hex digits. Before the conversion of hex to string, * the function allows replacement of the bits of the first two generated hex digits. * Replacement happens starting at the most significant, to the least significant bit. * * @param ibe A random number generator. * @param numDigits The number of hex digits [0-9],[a-f] to generate. * @param replacementBits The replacement value for up to the first two digits generated. * @param numReplacementBits The number of bits of @c replacementBits to use. * * @return A hex string of length @c numDigits. */ static const std::string generateHexWithReplacement( std::independent_bits_engine& ibe, unsigned int numDigits, uint8_t replacementBits, uint8_t numReplacementBits) { if (numReplacementBits > MAX_NUM_REPLACEMENT_BITS) { ACSDK_ERROR(LX("generateHexWithReplacementFailed") .d("reason", "replacingMoreBitsThanProvided") .d("numReplacementBitsLimit", MAX_NUM_REPLACEMENT_BITS) .d("numReplacementBits", numReplacementBits)); return ""; } if (numReplacementBits > (numDigits * BITS_IN_HEX_DIGIT)) { ACSDK_ERROR(LX("generateHexWithReplacementFailed") .d("reason", "replacingMoreBitsThanGenerated") .d("numDigitsInBits", numDigits * BITS_IN_HEX_DIGIT) .d("numReplacementBits", numReplacementBits)); return ""; } const size_t arrayLen = (numDigits + 1) / 2; std::vector bytes(arrayLen); std::generate(bytes.begin(), bytes.end(), std::ref(ibe)); // Replace the specified number of bits from the first byte. bytes.at(0) &= (0xff >> numReplacementBits); replacementBits &= (0xff << (MAX_NUM_REPLACEMENT_BITS - numReplacementBits)); bytes.at(0) |= replacementBits; std::ostringstream oss; for (const auto& byte : bytes) { oss << std::hex << std::setfill('0') << std::setw(2) << static_cast(byte); } std::string bytesText = oss.str(); // Remove the last digit for odd numDigits case. bytesText.resize(numDigits); return bytesText; } /** * Randomly generate a string of hex digits. * * @param ibe A random number generator. * @param numDigits The number of hex digits [0-9],[a-f] to generate. * * @return A hex string of length @c numDigits. */ static const std::string generateHex( std::independent_bits_engine& ibe, unsigned int numDigits) { return generateHexWithReplacement(ibe, numDigits, 0, 0); } /** * Add default seed to the pool, should only be used in generateUUID and locked by g_mutex. */ static void addDefaultSeedLocked() { std::random_device rd; uint64_t timeSeed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); if (lastInvokeTime > 0) { seedsPool.push_front((uint32_t)(timeSeed - lastInvokeTime)); // interval between two invocations } lastInvokeTime = timeSeed; seedsPool.push_front((uint32_t)timeSeed); // lower 32bits of current time seedsPool.push_front(rd()); // random device seedsPool.push_front( reinterpret_cast(&timeSeed)); // lower 32bits of memory address of temporary variable if (seedsPool.size() > MAX_SEEDS_POOL_SIZE) { seedsPool.resize(MAX_SEEDS_POOL_SIZE); } } const std::string generateUUID() { static std::independent_bits_engine ibe; std::unique_lock lock(g_mutex); if (g_seedNeeded) { static int consistentEntropyReports = 0; static double priorEntropyResult = 0; addDefaultSeedLocked(); std::seed_seq seed(seedsPool.begin(), seedsPool.end()); ibe.seed(seed); double currentEntropy = readEntropyFunc(); if (std::fabs(currentEntropy - priorEntropyResult) < std::numeric_limits::epsilon()) { ++consistentEntropyReports; } else { consistentEntropyReports = 0; } priorEntropyResult = currentEntropy; if (currentEntropy > ENTROPY_THRESHOLD) { g_seedNeeded = false; } else { ACSDK_INFO(LX("low entropy on call to generate UUID").d("current entropy", currentEntropy)); if (consistentEntropyReports > ENTROPY_REPEAT_THRESHOLD) { g_seedNeeded = false; ACSDK_INFO(LX("multiple repeat values for entropy") .d("current entropy", currentEntropy) .d("consistent entropy reports", consistentEntropyReports)); } } } std::ostringstream uuidText; uuidText << generateHex(ibe, 8) << SEPARATOR << generateHex(ibe, 4) << SEPARATOR << generateHexWithReplacement(ibe, 4, UUID_VERSION_VALUE, 4) << SEPARATOR << generateHexWithReplacement(ibe, 4, UUID_VARIANT_VALUE, 2) << SEPARATOR << generateHex(ibe, 12); lock.unlock(); return uuidText.str(); } } // namespace uuidGeneration } // namespace utils } // namespace avsCommon } // namespace alexaClientSDK