From 5d90e8d89b705ebbdf8dd536676754cd74cfb4c5 Mon Sep 17 00:00:00 2001 From: Aaron Vaage Date: Wed, 24 Jun 2020 15:30:50 -0700 Subject: [PATCH] Benchmarking and Unmasking In this code drop we introduce the benchmarking tests that allow us to compare the performance of different implementations. Like the other tests, any implementation can link with them to create their own binary. There are two types of benchmarks: 1 - Throughput, which measures the speed that a function can process information (bits per second). These are used for AEAD decrypt and license white-box decrypt functions. 2 - Samples, which measures the min, 25% percentile, median, 75% percentile, and max observed values. These is used for all other functions as a way to measure the execute duration of a call. The other change in this code drop is the update to the unmasking function to only unmask a subset of the bytes in the masked buffer. This was added to better align with the decoder behaviour in the CDM. --- api/BUILD | 44 ++ api/aead_whitebox_benchmark.cc | 158 ++++++ api/license_whitebox.h | 34 +- api/license_whitebox_benchmark.cc | 63 +++ api/license_whitebox_benchmark.h | 47 ++ api/license_whitebox_decrypt_benchmark.cc | 182 +++++++ api/license_whitebox_masked_decrypt_test.cc | 336 ++++++++----- ...ebox_process_license_response_benchmark.cc | 133 +++++ api/license_whitebox_sign_benchmark.cc | 92 ++++ api/license_whitebox_verify_benchmark.cc | 70 +++ benchmarking/BUILD | 30 ++ benchmarking/data_source.cc | 467 ++++++++++++++++++ benchmarking/data_source.h | 23 + benchmarking/measurements.cc | 78 +++ benchmarking/measurements.h | 72 +++ impl/reference/BUILD | 20 + impl/reference/license_whitebox_impl.cc | 56 +-- 17 files changed, 1738 insertions(+), 167 deletions(-) create mode 100644 api/aead_whitebox_benchmark.cc create mode 100644 api/license_whitebox_benchmark.cc create mode 100644 api/license_whitebox_benchmark.h create mode 100644 api/license_whitebox_decrypt_benchmark.cc create mode 100644 api/license_whitebox_process_license_response_benchmark.cc create mode 100644 api/license_whitebox_sign_benchmark.cc create mode 100644 api/license_whitebox_verify_benchmark.cc create mode 100644 benchmarking/BUILD create mode 100644 benchmarking/data_source.cc create mode 100644 benchmarking/data_source.h create mode 100644 benchmarking/measurements.cc create mode 100644 benchmarking/measurements.h diff --git a/api/BUILD b/api/BUILD index 055748d..fb5978d 100644 --- a/api/BUILD +++ b/api/BUILD @@ -60,6 +60,7 @@ cc_library( testonly = True, srcs = ["test_license_builder.cc"], hdrs = ["test_license_builder.h"], + visibility = ["//visibility:public"], deps = [ "//chromium_deps/cdm/keys:dev_certs", "//chromium_deps/cdm/protos:license_protocol_proto", @@ -86,6 +87,23 @@ cc_library( ], ) +cc_library( + name = "aead_whitebox_benchmark", + testonly = True, + srcs = [ + "aead_whitebox_benchmark.cc", + ], + visibility = ["//visibility:public"], + deps = [ + ":aead_whitebox", + ":test_data", + "//benchmarking:data_source", + "//benchmarking:measurements", + "//chromium_deps/base:glog", + "//chromium_deps/testing", + ], +) + cc_library( name = "license_whitebox_test", testonly = True, @@ -116,3 +134,29 @@ cc_library( "//crypto_utils:rsa_key", ], ) + +cc_library( + name = "license_whitebox_benchmark", + testonly = True, + srcs = [ + "license_whitebox_benchmark.cc", + "license_whitebox_decrypt_benchmark.cc", + "license_whitebox_process_license_response_benchmark.cc", + "license_whitebox_sign_benchmark.cc", + "license_whitebox_verify_benchmark.cc", + ], + hdrs = [ + "license_whitebox_benchmark.h", + ], + visibility = ["//visibility:public"], + deps = [ + ":license_whitebox", + ":test_data", + ":test_license_builder", + "//benchmarking:data_source", + "//benchmarking:measurements", + "//chromium_deps/base:glog", + "//chromium_deps/testing", + "//crypto_utils:crypto_util", + ], +) diff --git a/api/aead_whitebox_benchmark.cc b/api/aead_whitebox_benchmark.cc new file mode 100644 index 0000000..c78e2e7 --- /dev/null +++ b/api/aead_whitebox_benchmark.cc @@ -0,0 +1,158 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include +#include + +#include + +#include "api/aead_whitebox.h" +#include "api/test_data.h" +#include "base/logging.h" +#include "benchmarking/data_source.h" +#include "benchmarking/measurements.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace widevine { +namespace { +constexpr size_t kSafeOverhead = 128; +} // namespace + +// Param: +// - The number of bytes that should be pushed through Decrypt() each time. +// - The number of iterations to run. +class AeadWhiteboxBenchmark + : public ::testing::Test, + public testing::WithParamInterface> { + protected: + void SetUp() override { + const std::vector context = data_source_.Get(16); + const std::vector init_data = GetValidAeadInitData(); + ASSERT_EQ(WB_RESULT_OK, + WB_Aead_Create(init_data.data(), init_data.size(), context.data(), + context.size(), &whitebox_)); + + std::tie(plaintext_size_, iterations_) = GetParam(); + ciphertext_size_ = plaintext_size_ + kSafeOverhead; + } + + void TearDown() override { WB_Aead_Delete(whitebox_); } + + void InitWithCiphertextAsInput() { + // Make sure both buffers are the correct size for the test now that we know + // that the ciphertext is the input and the plaintext is the output. + input_.resize(ciphertext_size_); + output_.resize(plaintext_size_); + + const auto plaintext = data_source_.Get(plaintext_size_); + + size_t size = ciphertext_size_; + ASSERT_EQ(WB_RESULT_OK, + WB_Aead_Encrypt(whitebox_, plaintext.data(), plaintext.size(), + input_.data(), &size)); + input_.resize(size); + } + + void InitWithPlaintextAsInput() { + // Make sure both buffers are the correct size for the test now that we know + // that the plaintext is the input and the ciphertext is the output. + input_ = data_source_.Get(plaintext_size_); + output_.resize(ciphertext_size_); + } + + WB_Aead_Whitebox* whitebox_ = nullptr; + + DataSource data_source_; + + size_t plaintext_size_; + size_t ciphertext_size_; + + std::vector input_; + std::vector output_; + + size_t iterations_; +}; + +TEST_P(AeadWhiteboxBenchmark, EncryptDuration) { + InitWithPlaintextAsInput(); + + Timer timer; + Sampler sampler; + + for (size_t i = 0; i < iterations_; i++) { + timer.Reset(); + + size_t output_size = output_.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_Aead_Encrypt(whitebox_, input_.data(), input_.size(), + output_.data(), &output_size)); + + sampler.Push(timer.Get()); + } + + PrettyPrint("AEAD Encrypt Duration", sampler, input_.size()); +} + +TEST_P(AeadWhiteboxBenchmark, EncryptThroughput) { + InitWithPlaintextAsInput(); + + Timer timer; + timer.Reset(); + + for (size_t i = 0; i < iterations_; i++) { + size_t output_size = output_.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_Aead_Encrypt(whitebox_, input_.data(), input_.size(), + output_.data(), &output_size)); + } + + Throughput throughput(timer.Get(), input_.size() * iterations_); + PrettyPrint("AEAD Encrypt Throughput", throughput, input_.size()); +} + +TEST_P(AeadWhiteboxBenchmark, DecryptDuration) { + InitWithCiphertextAsInput(); + + Timer timer; + Sampler sampler; + + for (size_t i = 0; i < iterations_; i++) { + timer.Reset(); + + size_t output_size = output_.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_Aead_Decrypt(whitebox_, input_.data(), input_.size(), + output_.data(), &output_size)); + + sampler.Push(timer.Get()); + } + + PrettyPrint("AEAD Decrypt Duration", sampler, input_.size()); +} + +TEST_P(AeadWhiteboxBenchmark, DecryptThroughput) { + InitWithCiphertextAsInput(); + + Timer timer; + timer.Reset(); + + for (size_t i = 0; i < iterations_; i++) { + size_t output_size = output_.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_Aead_Decrypt(whitebox_, input_.data(), input_.size(), + output_.data(), &output_size)); + } + + Throughput throughput(timer.Get(), iterations_ * input_.size()); + PrettyPrint("AEAD Encrypt Throughput", throughput, input_.size()); +} + +// Run the test will different sizes of input-per-call. The values we have +// picked are divided into three groups, "bytes" and "kilobytes". The services +// team says that a license will normally by 1KB as a base size and then 50 to +// 100 bytes per key. +INSTANTIATE_TEST_SUITE_P(DifferentBytesPerCall, + AeadWhiteboxBenchmark, + ::testing::Values(std::make_tuple(1024, 100), + std::make_tuple(16 * 1024, 50), + std::make_tuple(256 * 1024, 25))); +} // namespace widevine diff --git a/api/license_whitebox.h b/api/license_whitebox.h index 3071fe6..65ea6e3 100644 --- a/api/license_whitebox.h +++ b/api/license_whitebox.h @@ -370,23 +370,35 @@ WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox, uint8_t* masked_output_data, size_t* masked_output_data_size); -// Unmasks the data in |buffer| using |secret_string|. |buffer| is operated on -// in-place. +// Unmasks a subset of the data in |masked_data| using |secret_string| and +// writes it to |unmasked_data|. +// +// The subset is denoted as |offset| (inclusive) to |offset + size| (exclusive). +// It is assumed that |offset| and |offset + size - 1| are both valid indexes +// into |masked_data|. +// +// It is assumed that indexes between 0 and |size - 1| (inclusive) are all valid +// indexes into |unmasked_data|. // // Args: -// secret_string (in) : The "key" used to unmask the data in |buffer|. +// masked_data (in) : The masked data to read from. +// +// offset (in) : The index into |masked_data| from where to start reading data. +// +// size (in) : The number of bytes from |masked_data| to unmask and copy into +// |unmasked_data|. +// +// secret_string (in) : The auxiliary data for unmasking |masked_data|. // // secret_string_size (in) : The number of bytes in |secret_string|. // -// buffer (in/out) : As input, this is the masked data. As output, this is the -// unmasked data. The number of bytes in the masked data will be equal to the -// number of bytes in the unmasked data. -// -// buffer_size (in) : The number of bytes in |buffer|. -void WB_License_Unmask(const uint8_t* secret_string, +// unmasked_data (out) : The output buffer to write the unmasked data to. +void WB_License_Unmask(const uint8_t* masked_data, + size_t offset, + size_t size, + const uint8_t* secret_string, size_t secret_string_size, - uint8_t* buffer, - size_t buffer_size); + uint8_t* unmasked_data); #ifdef __cplusplus } diff --git a/api/license_whitebox_benchmark.cc b/api/license_whitebox_benchmark.cc new file mode 100644 index 0000000..89fa32b --- /dev/null +++ b/api/license_whitebox_benchmark.cc @@ -0,0 +1,63 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "api/license_whitebox_benchmark.h" + +#include +#include + +#include +#include + +#include "api/license_whitebox.h" +#include "api/test_data.h" +#include "api/test_license_builder.h" +#include "benchmarking/data_source.h" +#include "crypto_utils/crypto_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace widevine { +namespace { +constexpr size_t kBlockSize = 16; // This must align with the AES block size. +} // namespace + +void LicenseWhiteboxBenchmark::SetUp() { + key_id_ = data_source_.Get(8); // The id size is not meaningful. + key_ = data_source_.Get(kBlockSize); + iv_ = data_source_.Get(kBlockSize); + + const auto public_key_data = GetMatchingLicensePublicKey(); + public_key_.reset(widevine::RsaPublicKey::Create( + std::string(public_key_data.begin(), public_key_data.end()))); + ASSERT_TRUE(public_key_); +} + +License LicenseWhiteboxBenchmark::CreateLicense() const { + widevine::TestLicenseBuilder license_builder; + license_builder.AddSigningKey(TestLicenseBuilder::DefaultSigningKey()); + // Use secure crypto as it will work with both Decrypt() and + // MaskedDecrypt(). + license_builder.AddContentKey( + video_widevine::License_KeyContainer_SecurityLevel_SW_SECURE_CRYPTO, + key_id_, key_); + + widevine::License license; + license_builder.Build(*public_key_, &license); + + return license; +} + +std::vector LicenseWhiteboxBenchmark::SignAsServer( + const std::vector& message) const { + // The server key is the first half of the signing key. + const auto key = TestLicenseBuilder::DefaultSigningKey(); + const std::string server_key(key.begin(), + key.begin() + crypto_util::kSigningKeySizeBytes); + + // crypto util uses strings, so we will need to convert the result back to a + // vector before we return it. + const auto signature = crypto_util::CreateSignatureHmacSha256( + server_key, std::string(message.begin(), message.end())); + return std::vector(signature.begin(), signature.end()); +} + +} // namespace widevine diff --git a/api/license_whitebox_benchmark.h b/api/license_whitebox_benchmark.h new file mode 100644 index 0000000..45acce4 --- /dev/null +++ b/api/license_whitebox_benchmark.h @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#ifndef WHITEBOX_API_LICENSE_WHITEBOX_BENCHMARK_H_ +#define WHITEBOX_API_LICENSE_WHITEBOX_BENCHMARK_H_ + +#include + +#include +#include + +#include "api/license_whitebox.h" +#include "api/test_license_builder.h" +#include "benchmarking/data_source.h" +#include "crypto_utils/rsa_key.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace widevine { +class LicenseWhiteboxBenchmark : public ::testing::Test { + protected: + virtual void SetUp() override; + + License CreateLicense() const; + + std::vector SignAsServer(const std::vector& message) const; + + DataSource& Data() { return data_source_; } + + const RsaPublicKey* PublicKey() const { return public_key_.get(); } + + const std::vector& ContentKeyId() const { return key_id_; } + + const std::vector& ContentKey() const { return key_; } + + const std::vector& ContentIV() const { return iv_; } + + private: + DataSource data_source_; + std::unique_ptr public_key_; + + std::vector key_id_; + std::vector key_; + std::vector iv_; +}; + +} // namespace widevine + +#endif // WHITEBOX_API_LICENSE_WHITEBOX_BENCHMARK_H_ diff --git a/api/license_whitebox_decrypt_benchmark.cc b/api/license_whitebox_decrypt_benchmark.cc new file mode 100644 index 0000000..b93fa47 --- /dev/null +++ b/api/license_whitebox_decrypt_benchmark.cc @@ -0,0 +1,182 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include +#include + +#include +#include + +#include "api/license_whitebox.h" +#include "api/license_whitebox_benchmark.h" +#include "api/result.h" +#include "api/test_data.h" +#include "api/test_license_builder.h" +#include "base/logging.h" +#include "benchmarking/data_source.h" +#include "benchmarking/measurements.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace widevine { +namespace { +// The mask size can be any size (depends on the implementation), so 256 should +// be more than enough for any implementation. +constexpr size_t kMaskSize = 256; +} // namespace + +// Test Parameter: +// - The number of blocks given to each decrypt call. Each block will be 16 +// bytes. +// - The number of iterations to run. +class LicenseWhiteboxDecryptBenchmark + : public LicenseWhiteboxBenchmark, + public testing::WithParamInterface> { + protected: + void SetUp() override { + LicenseWhiteboxBenchmark::SetUp(); + + // Extract all parameters. + size_t blocks_per_call; + std::tie(blocks_per_call, iterations_) = GetParam(); + + // We are using AES with no padding, the input and output will be the same + // size. + const size_t bytes_per_call = blocks_per_call * 16; + ciphertext_ = Data().Get(bytes_per_call); + masked_text_.resize(bytes_per_call); + plaintext_.resize(bytes_per_call); + + const auto init_data = GetLicenseInitData(); + ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_), + WB_RESULT_OK); + + const auto license = CreateLicense(); + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), + WB_RESULT_OK); + } + + void TearDown() override { WB_License_Delete(whitebox_); } + + WB_License_Whitebox* whitebox_; + + std::vector ciphertext_; + std::vector masked_text_; + std::vector plaintext_; + + size_t iterations_; +}; + +TEST_P(LicenseWhiteboxDecryptBenchmark, DecryptCBCThroughput) { + Timer timer; + timer.Reset(); + + for (size_t i = 0; i < iterations_; i++) { + size_t plaintext_size = plaintext_.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CBC, + ContentKeyId().data(), ContentKeyId().size(), + ciphertext_.data(), ciphertext_.size(), + ContentIV().data(), ContentIV().size(), + plaintext_.data(), &plaintext_size)); + } + + Throughput throughput(timer.Get(), iterations_ * ciphertext_.size()); + PrettyPrint("License Decrypt CBC Throughput", throughput, ciphertext_.size()); +} + +TEST_P(LicenseWhiteboxDecryptBenchmark, DecryptCTRThroughput) { + Timer timer; + timer.Reset(); + + for (size_t i = 0; i < iterations_; i++) { + size_t plaintext_size = plaintext_.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_License_Decrypt(whitebox_, WB_CIPHER_MODE_CTR, + ContentKeyId().data(), ContentKeyId().size(), + ciphertext_.data(), ciphertext_.size(), + ContentIV().data(), ContentIV().size(), + plaintext_.data(), &plaintext_size)); + } + + Throughput throughput(timer.Get(), iterations_ * ciphertext_.size()); + PrettyPrint("License Decrypt CTR Throughput", throughput, ciphertext_.size()); +} + +TEST_P(LicenseWhiteboxDecryptBenchmark, MaskedDecryptCBCThroughput) { + std::vector mask(kMaskSize); + size_t mask_size = mask.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_License_GetSecretString( + whitebox_, WB_CIPHER_MODE_CBC, ContentKeyId().data(), + ContentKeyId().size(), mask.data(), &mask_size)); + mask.resize(mask_size); + + Timer timer; + timer.Reset(); + + for (size_t i = 0; i < iterations_; i++) { + // Include the unmask in the timing as it will be part of the flow from + // encrypted to consumption. + size_t plaintext_size = plaintext_.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, ContentKeyId().data(), + ContentKeyId().size(), ciphertext_.data(), ciphertext_.size(), + ContentIV().data(), ContentIV().size(), masked_text_.data(), + &plaintext_size)); + + WB_License_Unmask(masked_text_.data(), 0, plaintext_size, mask.data(), + mask.size(), plaintext_.data()); + } + + Throughput throughput(timer.Get(), iterations_ * ciphertext_.size()); + PrettyPrint("License Masked Decrypt CBC Throughput", throughput, + ciphertext_.size()); +} + +TEST_P(LicenseWhiteboxDecryptBenchmark, MaskedDecryptCTRThroughput) { + std::vector mask(kMaskSize); + size_t mask_size = mask.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_License_GetSecretString( + whitebox_, WB_CIPHER_MODE_CTR, ContentKeyId().data(), + ContentKeyId().size(), mask.data(), &mask_size)); + mask.resize(mask_size); + + Timer timer; + timer.Reset(); + + for (size_t i = 0; i < iterations_; i++) { + // Include the unmask in the timing as it will be part of the flow from + // encrypted to consumption. + size_t plaintext_size = plaintext_.size(); + ASSERT_EQ(WB_RESULT_OK, + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CTR, ContentKeyId().data(), + ContentKeyId().size(), ciphertext_.data(), ciphertext_.size(), + ContentIV().data(), ContentIV().size(), masked_text_.data(), + &plaintext_size)); + + WB_License_Unmask(masked_text_.data(), 0, plaintext_size, mask.data(), + mask.size(), plaintext_.data()); + } + + Throughput throughput(timer.Get(), iterations_ * ciphertext_.size()); + PrettyPrint("License Masked Decrypt CTR Throughput", throughput, + ciphertext_.size()); +} + +// The first value is the number of blocks. Each block is 16 bytes. +INSTANTIATE_TEST_SUITE_P( + DifferentBytesPerCall, + LicenseWhiteboxDecryptBenchmark, + ::testing::Values(std::make_tuple(1, 100), // 16 B + std::make_tuple(16, 100), // 256 B + std::make_tuple(64, 100), // 1 KB + std::make_tuple(1024, 100), // 16 KB + std::make_tuple(16 * 1024, 25), // 256 KB + std::make_tuple(64 * 1024, 10))); // 1 MB +} // namespace widevine diff --git a/api/license_whitebox_masked_decrypt_test.cc b/api/license_whitebox_masked_decrypt_test.cc index 17db729..2042055 100644 --- a/api/license_whitebox_masked_decrypt_test.cc +++ b/api/license_whitebox_masked_decrypt_test.cc @@ -25,13 +25,13 @@ class LicenseWhiteboxMaskedDecryptTest : public LicenseWhiteboxTestBase { // Because we are going to use the same buffer for both tests, make sure it // will be large enough for either. - plaintext_size_ = std::max(golden_data_.CBCContent().ciphertext.size(), - golden_data_.CTRContent().ciphertext.size()); - plaintext_.resize(plaintext_size_); + masked_text_size_ = std::max(golden_data_.CBCContent().ciphertext.size(), + golden_data_.CTRContent().ciphertext.size()); + masked_text_.resize(masked_text_size_); // We have no idea how big the secret string will be, but it should be safe // to assume it won't be larger than the plaintext. - secret_string_size_ = plaintext_size_; + secret_string_size_ = masked_text_size_; secret_string_.resize(secret_string_size_); golden_data_.MakeKeyIdDifferent(&non_content_key_id_); @@ -82,7 +82,9 @@ class LicenseWhiteboxMaskedDecryptTest : public LicenseWhiteboxTestBase { size_t secret_string_size_; std::vector secret_string_; - size_t plaintext_size_; + size_t masked_text_size_; + std::vector masked_text_; + std::vector plaintext_; }; @@ -96,16 +98,16 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCbcDataInCbcMode) { golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Returned data is masked, so it should be the correct size but not // match the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CBCDecodeKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CBCDecodeKey().content->plaintext); // Now unmask the data. ASSERT_EQ( @@ -115,8 +117,12 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCbcDataInCbcMode) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + ASSERT_EQ(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); } @@ -130,16 +136,16 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCtrDataInCtrMode) { golden_data_.CTRDecodeKey().content->ciphertext.data(), golden_data_.CTRDecodeKey().content->ciphertext.size(), golden_data_.CTRDecodeKey().content->iv.data(), - golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CTRDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Returned data is masked, so it should be the correct size but not // match the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CTRDecodeKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CTRDecodeKey().content->plaintext); // Now unmask the data. ASSERT_EQ( @@ -149,8 +155,12 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCtrDataInCtrMode) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + ASSERT_EQ(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); } @@ -166,15 +176,15 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCbcDataInCtrMode) { golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Whatever is returned must not be the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CBCDecodeKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CBCDecodeKey().content->plaintext); // Now unmask the data. Still should not match. ASSERT_EQ( @@ -184,9 +194,13 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCbcDataInCtrMode) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); - ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + + ASSERT_NE(masked_text_, golden_data_.CBCDecodeKey().content->plaintext); } // We try to decrypt CTR encrypted data in CBC mode. All operations should be @@ -201,15 +215,15 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCtrDataInCbcMode) { golden_data_.CTRDecodeKey().content->ciphertext.data(), golden_data_.CTRDecodeKey().content->ciphertext.size(), golden_data_.CTRDecodeKey().content->iv.data(), - golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CTRDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Whatever is returned must not be the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CTRDecodeKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CTRDecodeKey().content->plaintext); // Now unmask the data. Still should not match. ASSERT_EQ( @@ -219,9 +233,13 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, DecodeKeyWithCtrDataInCbcMode) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); - ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + + ASSERT_NE(masked_text_, golden_data_.CTRDecodeKey().content->plaintext); } TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataInCbcMode) { @@ -234,16 +252,16 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataInCbcMode) { golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Returned data is masked, so it should be the correct size but not // match the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CBCDecodeKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CBCDecodeKey().content->plaintext); // Now unmask the data. ASSERT_EQ( @@ -253,8 +271,12 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataInCbcMode) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + ASSERT_EQ(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); } @@ -268,16 +290,16 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataInCtrMode) { golden_data_.CTRDecodeKey().content->ciphertext.data(), golden_data_.CTRDecodeKey().content->ciphertext.size(), golden_data_.CTRDecodeKey().content->iv.data(), - golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CTRDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Returned data is masked, so it should be the correct size but not // match the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CTRDecodeKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CTRDecodeKey().content->plaintext); // Now unmask the data. ASSERT_EQ( @@ -287,8 +309,12 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataInCtrMode) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + ASSERT_EQ(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); } @@ -304,15 +330,15 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataInCtrMode) { golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Whatever is returned must not be the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CBCDecodeKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CBCDecodeKey().content->plaintext); // Now unmask the data. Still should not match. ASSERT_EQ( @@ -322,9 +348,13 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataInCtrMode) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); - ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + + ASSERT_NE(masked_text_, golden_data_.CBCDecodeKey().content->plaintext); } // We try to decrypt CTR encrypted data in CBC mode. All operations should be @@ -339,15 +369,15 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataInCbcMode) { golden_data_.CTRCryptoKey().content->ciphertext.data(), golden_data_.CTRCryptoKey().content->ciphertext.size(), golden_data_.CTRCryptoKey().content->iv.data(), - golden_data_.CTRCryptoKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CTRCryptoKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Whatever is returned must not be the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CTRCryptoKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CTRCryptoKey().content->plaintext); // Now unmask the data. Still should not match. ASSERT_EQ( @@ -357,9 +387,13 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataInCbcMode) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); - ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + + ASSERT_NE(masked_text_, golden_data_.CTRCryptoKey().content->plaintext); } TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataAndPKCS8Padding) { @@ -372,16 +406,16 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataAndPKCS8Padding) { golden_data_.CBCCryptoKey().content->ciphertext.data(), golden_data_.CBCCryptoKey().content->ciphertext.size(), golden_data_.CBCCryptoKey().content->iv.data(), - golden_data_.CBCCryptoKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCCryptoKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Returned data is masked, so it should be the correct size but not // match the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CBCCryptoKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CBCCryptoKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CBCCryptoKey().content->plaintext); // Now unmask the data. ASSERT_EQ( @@ -391,8 +425,12 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCbcDataAndPKCS8Padding) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + ASSERT_EQ(plaintext_, golden_data_.CBCCryptoKey().content->plaintext); } @@ -406,16 +444,16 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataAndPKCS8Padding) { golden_data_.CTRCryptoKey().content->ciphertext.data(), golden_data_.CTRCryptoKey().content->ciphertext.size(), golden_data_.CTRCryptoKey().content->iv.data(), - golden_data_.CTRCryptoKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CTRCryptoKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Returned data is masked, so it should be the correct size but not // match the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CTRCryptoKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CTRCryptoKey().content->plaintext); // Now unmask the data. ASSERT_EQ( @@ -425,8 +463,12 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, CryptoKeyWithCtrDataAndPKCS8Padding) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + ASSERT_EQ(plaintext_, golden_data_.CTRCryptoKey().content->plaintext); } @@ -445,16 +487,16 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, SuccessWithMultipleKeys) { golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Returned data is masked, so it should be the correct size but not // match the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CBCDecodeKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CBCDecodeKey().content->plaintext); // Now unmask the data. ASSERT_EQ( @@ -464,14 +506,18 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, SuccessWithMultipleKeys) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + ASSERT_EQ(plaintext_, golden_data_.CBCDecodeKey().content->plaintext); // Reset our output buffer. - plaintext_.clear(); - plaintext_size_ = golden_data_.CTRDecodeKey().content->plaintext.size(); - plaintext_.resize(plaintext_size_); + masked_text_.clear(); + masked_text_size_ = golden_data_.CTRDecodeKey().content->plaintext.size(); + masked_text_.resize(masked_text_size_); ASSERT_EQ( WB_License_MaskedDecrypt( @@ -480,20 +526,20 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, SuccessWithMultipleKeys) { golden_data_.CTRDecodeKey().content->ciphertext.data(), golden_data_.CTRDecodeKey().content->ciphertext.size(), golden_data_.CTRDecodeKey().content->iv.data(), - golden_data_.CTRDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CTRDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_OK); - plaintext_.resize(plaintext_size_); + masked_text_.resize(masked_text_size_); // Returned data is masked, so it should be the correct size but not // match the original text. - ASSERT_EQ(plaintext_.size(), + ASSERT_EQ(masked_text_.size(), golden_data_.CTRDecodeKey().content->plaintext.size()); - ASSERT_NE(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); + ASSERT_NE(masked_text_, golden_data_.CTRDecodeKey().content->plaintext); // Now unmask the data. secret_string_.clear(); - secret_string_size_ = plaintext_.size(); + secret_string_size_ = masked_text_.size(); secret_string_.resize(secret_string_size_); ASSERT_EQ( WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CTR, @@ -502,8 +548,12 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, SuccessWithMultipleKeys) { secret_string_.data(), &secret_string_size_), WB_RESULT_OK); secret_string_.resize(secret_string_size_); - WB_License_Unmask(secret_string_.data(), secret_string_.size(), - plaintext_.data(), plaintext_.size()); + + plaintext_.resize(masked_text_size_); + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + plaintext_.data()); + ASSERT_EQ(plaintext_, golden_data_.CTRDecodeKey().content->plaintext); } @@ -517,8 +567,8 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullWhitebox) { golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -537,7 +587,7 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForInvalidCipherMode) { golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), golden_data_.CBCDecodeKey().content->iv.size(), - plaintext_.data(), &plaintext_size_), + masked_text_.data(), &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -551,7 +601,7 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullKeyId) { golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), golden_data_.CBCDecodeKey().content->iv.size(), - plaintext_.data(), &plaintext_size_), + masked_text_.data(), &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -564,8 +614,8 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullZeroKeyIdSize) { 0, golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -578,8 +628,8 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullInputData) { golden_data_.CBCDecodeKey().id.size(), nullptr, golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -595,8 +645,8 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, golden_data_.CBCDecodeKey().id.size(), golden_data_.CBCDecodeKey().content->ciphertext.data(), 14, golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -610,8 +660,8 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForZeroInputDataSize) { golden_data_.CBCDecodeKey().id.size(), golden_data_.CBCDecodeKey().content->ciphertext.data(), 0, golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -624,8 +674,8 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullIV) { golden_data_.CBCDecodeKey().id.size(), golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), nullptr, - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -639,8 +689,8 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForInvalidIVSize) { golden_data_.CBCDecodeKey().id.size(), golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), - golden_data_.CBCDecodeKey().content->iv.data(), 9, plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.data(), 9, + masked_text_.data(), &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -655,7 +705,7 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullOutput) { golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), golden_data_.CBCDecodeKey().content->iv.size(), nullptr, - &plaintext_size_), + &masked_text_size_), WB_RESULT_INVALID_PARAMETER); } @@ -669,7 +719,7 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidParameterForNullOutputSize) { golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), nullptr), WB_RESULT_INVALID_PARAMETER); } @@ -687,7 +737,7 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, KeyUnavailableForMissingKeyId) { golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), golden_data_.CBCDecodeKey().content->iv.size(), - plaintext_.data(), &plaintext_size_), + masked_text_.data(), &masked_text_size_), WB_RESULT_KEY_UNAVAILABLE); } @@ -701,7 +751,7 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, KeyUnavailableForNonContentKey) { golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), golden_data_.CBCDecodeKey().content->iv.size(), - plaintext_.data(), &plaintext_size_), + masked_text_.data(), &masked_text_size_), WB_RESULT_KEY_UNAVAILABLE); } @@ -719,7 +769,7 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, golden_data_.CBCHardwareKey().content->ciphertext.size(), golden_data_.CBCHardwareKey().content->iv.data(), golden_data_.CBCHardwareKey().content->iv.size(), - plaintext_.data(), &plaintext_size_), + masked_text_.data(), &masked_text_size_), WB_RESULT_INSUFFICIENT_SECURITY_LEVEL); } @@ -734,8 +784,8 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, InvalidState) { golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_INVALID_STATE); } @@ -744,7 +794,7 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, BufferTooSmall) { // Our ciphertext will be large enough that we should not need to worry about // using a constant here. - plaintext_size_ = 8; + masked_text_size_ = 8; ASSERT_EQ( WB_License_MaskedDecrypt( @@ -753,14 +803,52 @@ TEST_F(LicenseWhiteboxMaskedDecryptTest, BufferTooSmall) { golden_data_.CBCDecodeKey().content->ciphertext.data(), golden_data_.CBCDecodeKey().content->ciphertext.size(), golden_data_.CBCDecodeKey().content->iv.data(), - golden_data_.CBCDecodeKey().content->iv.size(), plaintext_.data(), - &plaintext_size_), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), WB_RESULT_BUFFER_TOO_SMALL); // We don't use padding so the reported plaintext size should be the same as // the cipher text size. - ASSERT_EQ(plaintext_size_, + ASSERT_EQ(masked_text_size_, golden_data_.CBCDecodeKey().content->ciphertext.size()); } +// Check that the result of unmasking only a small portion of the data is the +// same as when we unmask the whole buffer. +TEST_F(LicenseWhiteboxMaskedDecryptTest, SuccessForSubRangeUnmask) { + LoadLicense(TestLicenseBuilder::NoPadding()); + + ASSERT_EQ( + WB_License_MaskedDecrypt( + whitebox_, WB_CIPHER_MODE_CBC, golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + golden_data_.CBCDecodeKey().content->ciphertext.data(), + golden_data_.CBCDecodeKey().content->ciphertext.size(), + golden_data_.CBCDecodeKey().content->iv.data(), + golden_data_.CBCDecodeKey().content->iv.size(), masked_text_.data(), + &masked_text_size_), + WB_RESULT_OK); + + ASSERT_EQ( + WB_License_GetSecretString(whitebox_, WB_CIPHER_MODE_CBC, + golden_data_.CBCDecodeKey().id.data(), + golden_data_.CBCDecodeKey().id.size(), + secret_string_.data(), &secret_string_size_), + WB_RESULT_OK); + secret_string_.resize(secret_string_size_); + + std::vector full_unmask(masked_text_size_); + std::vector partial_unmask(3); + + WB_License_Unmask(masked_text_.data(), 0, masked_text_size_, + secret_string_.data(), secret_string_.size(), + full_unmask.data()); + WB_License_Unmask(masked_text_.data(), 4, partial_unmask.size(), + secret_string_.data(), secret_string_.size(), + partial_unmask.data()); + + ASSERT_EQ(full_unmask[4], partial_unmask[0]); + ASSERT_EQ(full_unmask[5], partial_unmask[1]); + ASSERT_EQ(full_unmask[6], partial_unmask[2]); +} } // namespace widevine diff --git a/api/license_whitebox_process_license_response_benchmark.cc b/api/license_whitebox_process_license_response_benchmark.cc new file mode 100644 index 0000000..ab51f84 --- /dev/null +++ b/api/license_whitebox_process_license_response_benchmark.cc @@ -0,0 +1,133 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include +#include + +#include "api/license_whitebox.h" +#include "api/license_whitebox_benchmark.h" +#include "api/result.h" +#include "api/test_data.h" +#include "api/test_license_builder.h" +#include "base/logging.h" +#include "benchmarking/measurements.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace widevine { +namespace { +// The white-box implementation is slow, so the build bots keep timing out. To +// work around this, use a fixed number of iterations. +constexpr size_t kIterations = 100; +} // namespace +class LicenseWhiteboxProcessLicenseResponseBenchmark + : public LicenseWhiteboxBenchmark { + protected: + void SetUp() override { + LicenseWhiteboxBenchmark::SetUp(); + license_ = CreateLicense(); + } + + void TearDown() override { WB_License_Delete(whitebox_); } + + WB_License_Whitebox* whitebox_; + License license_; +}; + +TEST_F(LicenseWhiteboxProcessLicenseResponseBenchmark, + CreateAndProcessLicenseResponseAndDelete) { + Timer timer; + Sampler sampler; + + for (size_t i = 0; i < kIterations; i++) { + const auto init_data = GetLicenseInitData(); + + // We can only call ProcessLicenseResponse() once per whitebox instance. So + // we need to create a new instance each time. Collect samples for create + + // process license + delete so that we know the cost of getting a new + // license. + timer.Reset(); + + ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_), + WB_RESULT_OK); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_OK); + + WB_License_Delete(whitebox_); + + sampler.Push(timer.Get()); + + whitebox_ = nullptr; + } + + PrettyPrint("License White-box Create + Process License + Delete Duration", + sampler, license_.message.size()); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseBenchmark, + CreateAndProcessLicenseResponse) { + Timer timer; + Sampler sampler; + + for (size_t i = 0; i < kIterations; i++) { + const auto init_data = GetLicenseInitData(); + + // We can only call ProcessLicenseResponse() once per whitebox instance. So + // we need to create a new instance each time. Collect samples for create + + // process license so that we know the true start-up cost. + timer.Reset(); + + ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_), + WB_RESULT_OK); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_OK); + + sampler.Push(timer.Get()); + + WB_License_Delete(whitebox_); + whitebox_ = nullptr; + } + + PrettyPrint("License White-box Create + Process License Duration", sampler, + license_.message.size()); +} + +TEST_F(LicenseWhiteboxProcessLicenseResponseBenchmark, ProcessLicenseResponse) { + Timer timer; + Sampler sampler; + + for (size_t i = 0; i < kIterations; i++) { + // We can only call ProcessLicenseResponse() once per whitebox instance. So + // we need to create a new instance each time. Do this before we reset the + // timer so that we are not counting it in the execution time. + const auto init_data = GetLicenseInitData(); + ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_), + WB_RESULT_OK); + + timer.Reset(); + + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license_.message.data(), license_.message.size(), + license_.signature.data(), license_.signature.size(), + license_.session_key.data(), license_.session_key.size(), + license_.request.data(), license_.request.size()), + WB_RESULT_OK); + + sampler.Push(timer.Get()); + + WB_License_Delete(whitebox_); + whitebox_ = nullptr; + } + + PrettyPrint("License White-box Process License Duration", sampler, + license_.message.size()); +} +} // namespace widevine diff --git a/api/license_whitebox_sign_benchmark.cc b/api/license_whitebox_sign_benchmark.cc new file mode 100644 index 0000000..4ec076b --- /dev/null +++ b/api/license_whitebox_sign_benchmark.cc @@ -0,0 +1,92 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include +#include + +#include +#include + +#include "api/license_whitebox.h" +#include "api/license_whitebox_benchmark.h" +#include "api/result.h" +#include "api/test_data.h" +#include "api/test_license_builder.h" +#include "base/logging.h" +#include "benchmarking/data_source.h" +#include "benchmarking/measurements.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace widevine { +namespace { + +constexpr size_t kMessageSize = 4 * 1024; +constexpr size_t kSignatureSize = 256; +constexpr size_t kIterations = 100; + +} // namespace + +class LicenseWhiteboxSignBenchmark : public LicenseWhiteboxBenchmark { + protected: + void SetUp() override { + LicenseWhiteboxBenchmark::SetUp(); + + message_ = Data().Get(kMessageSize); + signature_.resize(kSignatureSize); + + const auto init_data = GetLicenseInitData(); + ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_), + WB_RESULT_OK); + + const auto license = CreateLicense(); + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), + WB_RESULT_OK); + } + + void TearDown() override { WB_License_Delete(whitebox_); } + + WB_License_Whitebox* whitebox_; + + std::vector message_; + std::vector signature_; +}; + +TEST_F(LicenseWhiteboxSignBenchmark, SignLicenseRequest) { + Timer timer; + Sampler sampler; + + for (size_t i = 0; i < kIterations; i++) { + size_t signature_size = signature_.size(); + + timer.Reset(); + ASSERT_EQ(WB_RESULT_OK, WB_License_SignLicenseRequest( + whitebox_, message_.data(), message_.size(), + signature_.data(), &signature_size)); + sampler.Push(timer.Get()); + } + + PrettyPrint("License White-box Sign License Request Duration", sampler, + message_.size()); +} + +TEST_F(LicenseWhiteboxSignBenchmark, SignRenewalRequest) { + Timer timer; + Sampler sampler; + + for (size_t i = 0; i < kIterations; i++) { + size_t signature_size = signature_.size(); + + timer.Reset(); + ASSERT_EQ(WB_RESULT_OK, WB_License_SignRenewalRequest( + whitebox_, message_.data(), message_.size(), + signature_.data(), &signature_size)); + sampler.Push(timer.Get()); + } + + PrettyPrint("License White-box Sign Renewal Request Duration", sampler, + message_.size()); +} +} // namespace widevine diff --git a/api/license_whitebox_verify_benchmark.cc b/api/license_whitebox_verify_benchmark.cc new file mode 100644 index 0000000..7340888 --- /dev/null +++ b/api/license_whitebox_verify_benchmark.cc @@ -0,0 +1,70 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include +#include + +#include +#include + +#include "api/license_whitebox.h" +#include "api/license_whitebox_benchmark.h" +#include "api/result.h" +#include "api/test_data.h" +#include "api/test_license_builder.h" +#include "base/logging.h" +#include "benchmarking/data_source.h" +#include "benchmarking/measurements.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace widevine { +namespace { +constexpr size_t kMessageSize = 4 * 1024; // 4 KB license response. +} + +class LicenseWhiteboxVerifyBenchmark : public LicenseWhiteboxBenchmark { + protected: + void SetUp() override { + LicenseWhiteboxBenchmark::SetUp(); + + message_ = Data().Get(kMessageSize); + signature_ = SignAsServer(message_); + + const auto init_data = GetLicenseInitData(); + ASSERT_EQ(WB_License_Create(init_data.data(), init_data.size(), &whitebox_), + WB_RESULT_OK); + + const auto license = CreateLicense(); + ASSERT_EQ(WB_License_ProcessLicenseResponse( + whitebox_, license.message.data(), license.message.size(), + license.signature.data(), license.signature.size(), + license.session_key.data(), license.session_key.size(), + license.request.data(), license.request.size()), + WB_RESULT_OK); + } + + void TearDown() override { WB_License_Delete(whitebox_); } + + WB_License_Whitebox* whitebox_; + + std::vector message_; + std::vector signature_; +}; + +TEST_F(LicenseWhiteboxVerifyBenchmark, VerifyRenewalResponse) { + constexpr size_t kIterations = 100; + + Timer timer; + Sampler sampler; + + for (size_t i = 0; i < kIterations; i++) { + timer.Reset(); + ASSERT_EQ(WB_RESULT_OK, WB_License_VerifyRenewalResponse( + whitebox_, message_.data(), message_.size(), + signature_.data(), signature_.size())); + sampler.Push(timer.Get()); + } + + PrettyPrint("License White-box Verify Renewal Response Duration", sampler, + message_.size()); +} +} // namespace widevine diff --git a/benchmarking/BUILD b/benchmarking/BUILD new file mode 100644 index 0000000..bdc671a --- /dev/null +++ b/benchmarking/BUILD @@ -0,0 +1,30 @@ +# Copyright 2020 Google LLC. All Rights Reserved. + +package(default_visibility = [ + "//visibility:public", +]) + +cc_library( + name = "data_source", + testonly = True, + srcs = [ + "data_source.cc", + ], + hdrs = [ + "data_source.h", + ], +) + +cc_library( + name = "measurements", + testonly = True, + srcs = [ + "measurements.cc", + ], + hdrs = [ + "measurements.h", + ], + deps = [ + "//chromium_deps/base:glog", + ], +) diff --git a/benchmarking/data_source.cc b/benchmarking/data_source.cc new file mode 100644 index 0000000..0d07448 --- /dev/null +++ b/benchmarking/data_source.cc @@ -0,0 +1,467 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "benchmarking/data_source.h" + +#include +#include // memcpy + +namespace widevine { +namespace { +const uint8_t kGoldenData[] = { + 0x91, 0x08, 0xed, 0x18, 0xe2, 0x8f, 0x22, 0x85, 0x9f, 0x7b, 0x07, 0xdd, + 0xd3, 0x37, 0x09, 0xca, 0x53, 0x94, 0x37, 0xcd, 0x09, 0xff, 0x52, 0x77, + 0xf0, 0xa5, 0x98, 0x07, 0x65, 0x25, 0x0a, 0x5a, 0x5a, 0xea, 0xfd, 0x24, + 0xb0, 0xce, 0xa9, 0xb4, 0x64, 0x4e, 0x1f, 0x00, 0xa6, 0xb8, 0xf1, 0x3b, + 0xc0, 0x5a, 0xa4, 0xe8, 0xb0, 0x32, 0x07, 0xff, 0x73, 0xcc, 0xc7, 0xd1, + 0xfa, 0x2c, 0xb4, 0x43, 0x4a, 0xb4, 0xae, 0x91, 0xee, 0xb5, 0x6b, 0xce, + 0xf6, 0x90, 0xb0, 0x22, 0x0b, 0xe7, 0x98, 0x77, 0xfe, 0x78, 0x68, 0xfd, + 0x0e, 0xae, 0xf5, 0x1d, 0x37, 0xb8, 0x34, 0xf3, 0xca, 0xb5, 0x66, 0xba, + 0xa2, 0x13, 0xdc, 0x4c, 0xb5, 0x13, 0x79, 0x9c, 0x55, 0xeb, 0x9f, 0x68, + 0xd4, 0xfc, 0x0d, 0x41, 0x38, 0x3f, 0xca, 0x02, 0xe8, 0x22, 0xbf, 0xc1, + 0xb0, 0x0a, 0xaf, 0x73, 0x9e, 0x9f, 0xee, 0xad, 0xa8, 0x10, 0x03, 0x32, + 0xa8, 0x5c, 0x0c, 0x01, 0x64, 0xae, 0x01, 0x44, 0x10, 0x44, 0x79, 0x13, + 0x30, 0x3b, 0xa9, 0xa5, 0xb5, 0x20, 0x2b, 0xfa, 0x1b, 0xa0, 0x85, 0x90, + 0xfc, 0xef, 0xff, 0x19, 0xcb, 0x18, 0x0e, 0xc2, 0xdf, 0xfa, 0xd2, 0xf2, + 0x6e, 0xd7, 0x4a, 0x36, 0xdc, 0xa9, 0x55, 0x67, 0x08, 0x68, 0x19, 0xbd, + 0x47, 0x4e, 0xeb, 0x1a, 0x24, 0x05, 0x0e, 0xb9, 0x04, 0x01, 0xaf, 0xf0, + 0x89, 0xa4, 0xc8, 0x2a, 0xa5, 0xf4, 0xd3, 0x67, 0x00, 0xbc, 0xe6, 0x6c, + 0x32, 0x50, 0x15, 0x7f, 0x7e, 0xd6, 0x31, 0x9e, 0xc0, 0x43, 0x87, 0xa9, + 0x29, 0x28, 0x50, 0x3e, 0xd3, 0x47, 0x10, 0xbf, 0xfa, 0x83, 0x20, 0x42, + 0x6f, 0x72, 0xac, 0xbc, 0x81, 0xc8, 0xf6, 0xcc, 0x1f, 0xa7, 0xfe, 0x1d, + 0xf7, 0x11, 0xe5, 0xe6, 0x6e, 0x9e, 0x38, 0x9e, 0xdd, 0x3b, 0x4b, 0x3d, + 0x99, 0x23, 0xe1, 0xc4, 0xe1, 0x93, 0xde, 0x1a, 0x4f, 0xa1, 0x4c, 0xd4, + 0x3f, 0xbe, 0x25, 0x37, 0xdd, 0x6f, 0xf3, 0xd4, 0x02, 0xa4, 0x5b, 0x86, + 0xf7, 0xea, 0x97, 0x41, 0x67, 0x83, 0xad, 0x61, 0xe4, 0x2a, 0x01, 0x29, + 0x6e, 0xdc, 0xe8, 0x73, 0x7d, 0xea, 0x22, 0xd4, 0xfb, 0xe5, 0x5e, 0x56, + 0x1a, 0x32, 0x9f, 0x98, 0x20, 0x44, 0x67, 0x4e, 0x22, 0x51, 0xd5, 0x43, + 0xbe, 0xb1, 0x65, 0x11, 0x13, 0x08, 0xfd, 0xad, 0x8b, 0xcb, 0x26, 0x2b, + 0xee, 0x1e, 0x12, 0xe6, 0x2c, 0xe8, 0xe1, 0x81, 0x6a, 0x14, 0xf1, 0x45, + 0x8f, 0x74, 0x94, 0x53, 0xd2, 0x3f, 0x01, 0x3d, 0x07, 0x34, 0xbc, 0xc1, + 0x25, 0x32, 0x1d, 0x60, 0x8c, 0x15, 0xb1, 0x1a, 0xd1, 0x17, 0x04, 0x18, + 0xfa, 0xdd, 0x48, 0x0f, 0xd3, 0xf9, 0xf4, 0x1f, 0x32, 0xb6, 0xd0, 0xa9, + 0x66, 0x61, 0x40, 0x4d, 0x1a, 0xe1, 0xe4, 0x70, 0x6e, 0x04, 0x94, 0xd6, + 0xc8, 0xa4, 0x43, 0x57, 0x2a, 0x19, 0x16, 0xfc, 0x56, 0x79, 0x2c, 0x3a, + 0x8b, 0xf3, 0xad, 0x7b, 0x4f, 0xf8, 0xc5, 0x4d, 0x14, 0x13, 0x8a, 0xbd, + 0x5e, 0xef, 0xdd, 0x79, 0x31, 0xdf, 0x65, 0xe1, 0x62, 0x92, 0x99, 0xf6, + 0x1a, 0xd5, 0x18, 0x24, 0xb5, 0x27, 0x22, 0xb2, 0x1a, 0x2c, 0xab, 0x02, + 0x7a, 0x47, 0xb4, 0xd6, 0x8b, 0x26, 0xb3, 0x5d, 0x9b, 0xf9, 0x87, 0x78, + 0x5e, 0x8e, 0xaa, 0x7d, 0x0d, 0x8c, 0x7c, 0xf7, 0xbc, 0x4c, 0x9b, 0xad, + 0x53, 0x8a, 0xdc, 0x50, 0xf5, 0x01, 0xcb, 0x74, 0x98, 0x83, 0x45, 0x41, + 0x78, 0xf3, 0x00, 0x55, 0xb7, 0xd0, 0xdf, 0xff, 0xd3, 0xd9, 0xa1, 0xcc, + 0xa1, 0xe8, 0x98, 0x33, 0x10, 0xcf, 0x8f, 0xcf, 0x5c, 0x3e, 0x50, 0xd6, + 0xdc, 0xf9, 0x70, 0x9f, 0x7e, 0xc4, 0x1b, 0x87, 0x90, 0xbd, 0xb7, 0x2a, + 0xb7, 0x91, 0x06, 0x1c, 0xad, 0x6c, 0xce, 0x60, 0x84, 0x51, 0xc4, 0x87, + 0xf4, 0x46, 0x87, 0x9e, 0x72, 0x0d, 0x91, 0xe9, 0x69, 0xbf, 0xa3, 0x87, + 0x6f, 0x24, 0xf9, 0x24, 0x75, 0xac, 0x7d, 0x36, 0x55, 0x85, 0x50, 0x23, + 0x48, 0xb9, 0x74, 0xd3, 0xb7, 0x79, 0x70, 0xcf, 0x13, 0xee, 0x1d, 0x0f, + 0xd9, 0x03, 0xe7, 0xdc, 0x4a, 0x3b, 0x06, 0x50, 0x7f, 0x1a, 0x37, 0x8e, + 0xdd, 0xd4, 0x6f, 0xc7, 0xd2, 0x13, 0x8b, 0x00, 0xd0, 0x83, 0xe6, 0x15, + 0x0f, 0x6b, 0x42, 0x65, 0xdb, 0xcd, 0x20, 0x8f, 0xdc, 0x3b, 0x0d, 0x32, + 0x6a, 0x11, 0x18, 0x08, 0xf6, 0x3e, 0x4c, 0xdc, 0x9f, 0x5f, 0x45, 0xdf, + 0xf4, 0x28, 0xbc, 0xd6, 0xb8, 0x53, 0x86, 0xe8, 0xfd, 0xa0, 0x68, 0x4e, + 0x26, 0xba, 0x80, 0x93, 0x1d, 0x62, 0x2f, 0x4d, 0x2d, 0xf1, 0x65, 0xcf, + 0xda, 0xbc, 0x32, 0xfb, 0xaf, 0x0b, 0xa0, 0x24, 0xe1, 0xdd, 0x94, 0x64, + 0x0e, 0xb7, 0xf5, 0x04, 0xaf, 0x20, 0xf4, 0xaf, 0x8a, 0x67, 0xe6, 0x47, + 0xc9, 0xb2, 0x25, 0x8b, 0x2c, 0xa1, 0xa8, 0x80, 0xa4, 0x70, 0xb9, 0xbe, + 0xb9, 0x0b, 0x98, 0xbf, 0x62, 0xad, 0xdc, 0x73, 0x38, 0x0d, 0x74, 0xc4, + 0x1b, 0xd5, 0xad, 0x6e, 0x50, 0x94, 0x46, 0xcd, 0xcf, 0x9a, 0x53, 0x04, + 0x18, 0x40, 0xe9, 0xad, 0x92, 0x09, 0xd7, 0x6e, 0x16, 0x9c, 0xcf, 0xce, + 0x20, 0xd6, 0x2a, 0xbc, 0x84, 0xbf, 0x33, 0xd0, 0x3f, 0x49, 0xa0, 0x66, + 0x8c, 0xdf, 0xc7, 0x1f, 0xf7, 0x7c, 0xfa, 0xd3, 0xef, 0x8c, 0x51, 0x13, + 0x6b, 0xe5, 0x3d, 0x80, 0x15, 0x11, 0x25, 0x2d, 0xe4, 0x23, 0xe1, 0xd9, + 0xfe, 0x69, 0x81, 0xd0, 0x01, 0xc6, 0x41, 0xa1, 0xcf, 0x73, 0x5f, 0xca, + 0x3c, 0xe9, 0x59, 0x25, 0x07, 0x97, 0x60, 0xda, 0xc4, 0x57, 0x98, 0x51, + 0xd7, 0x30, 0xa5, 0x39, 0x7c, 0x0b, 0x65, 0xb2, 0x41, 0x03, 0xf5, 0x99, + 0x1e, 0x0a, 0xc4, 0x2a, 0x14, 0x4e, 0x00, 0xf0, 0x27, 0xa7, 0xe6, 0x73, + 0x15, 0xf6, 0x3d, 0x21, 0xc7, 0xeb, 0x53, 0xa7, 0x9b, 0x61, 0xdc, 0x0a, + 0xcf, 0x0b, 0x6c, 0xbe, 0xb5, 0x77, 0xb3, 0xbc, 0xe6, 0x29, 0x9e, 0x09, + 0x8d, 0x91, 0xde, 0xde, 0xb7, 0xd2, 0x18, 0x89, 0xc0, 0xf0, 0x79, 0xfd, + 0xa6, 0xe4, 0xc1, 0x53, 0x04, 0x08, 0xdb, 0x0e, 0x97, 0xda, 0x1e, 0xcd, + 0x1a, 0x63, 0x65, 0x24, 0xc2, 0x01, 0x1b, 0x6a, 0xae, 0xb5, 0x56, 0xa8, + 0x94, 0xc5, 0x37, 0xb0, 0x2e, 0x69, 0xb0, 0xb9, 0x90, 0xe3, 0xb9, 0x2e, + 0x03, 0xad, 0xe2, 0xc5, 0x1e, 0x36, 0x2e, 0xa9, 0xa8, 0xbe, 0xf0, 0xc6, + 0xa8, 0x64, 0xb6, 0xb9, 0x9d, 0x25, 0xf1, 0x06, 0x28, 0xf9, 0x6e, 0x3a, + 0x4e, 0x23, 0xcd, 0x32, 0x79, 0xa3, 0x19, 0x4a, 0x36, 0x8a, 0xfd, 0x81, + 0x8c, 0x0e, 0x4e, 0xd0, 0x6c, 0x06, 0xf7, 0x2f, 0x8d, 0x19, 0x90, 0x47, + 0x76, 0x7c, 0x7a, 0xb0, 0x26, 0x85, 0xaa, 0xfc, 0x3b, 0x03, 0xb7, 0x4c, + 0xdd, 0xfb, 0xa9, 0x6c, 0x8c, 0x35, 0x88, 0xd9, 0x41, 0xf5, 0xd7, 0x74, + 0x30, 0xea, 0xa5, 0xb1, 0xf5, 0x08, 0xeb, 0x29, 0x72, 0x1c, 0x18, 0x5f, + 0xf6, 0x7e, 0x5b, 0x1b, 0xc1, 0x40, 0xa7, 0x4f, 0xf2, 0x87, 0x61, 0xca, + 0xcd, 0x9a, 0x94, 0xa8, 0xce, 0x33, 0x1d, 0xa4, 0x07, 0x67, 0xb6, 0x37, + 0xf4, 0xd0, 0x34, 0xa0, 0x4b, 0x5c, 0x7f, 0xc2, 0x06, 0xaa, 0x6d, 0x23, + 0xb8, 0x68, 0xc5, 0xe7, 0x3b, 0xd5, 0x10, 0xce, 0x36, 0x07, 0x60, 0x7f, + 0xbb, 0xc0, 0x5b, 0xd2, 0x9f, 0x96, 0x31, 0x67, 0x6c, 0x30, 0x55, 0x88, + 0xd3, 0xf0, 0xfe, 0x5e, 0x2f, 0x96, 0x90, 0xcc, 0xfa, 0xe4, 0x08, 0x6e, + 0xd4, 0xa1, 0x4a, 0x52, 0xf9, 0x8b, 0xf5, 0x3a, 0x92, 0xae, 0x90, 0x7b, + 0xf1, 0xc0, 0x2f, 0xb1, 0xe1, 0x3a, 0x05, 0x60, 0x62, 0x81, 0xd1, 0xaa, + 0x47, 0xb9, 0x44, 0x35, 0x6e, 0x77, 0x0b, 0x55, 0x37, 0x31, 0x02, 0xde, + 0xfd, 0xa7, 0x25, 0x4a, 0x61, 0x24, 0x8b, 0x0d, 0x68, 0x77, 0x66, 0x31, + 0xa9, 0xf4, 0x07, 0x6b, 0x59, 0x51, 0x5a, 0x79, 0x11, 0x2c, 0x45, 0x92, + 0xb4, 0x96, 0xe9, 0x7a, 0x1e, 0x0a, 0xf2, 0xd1, 0x74, 0x1e, 0xbb, 0x16, + 0x62, 0xd3, 0x20, 0x59, 0x9d, 0x63, 0xd3, 0xa0, 0x36, 0x86, 0xcc, 0xa2, + 0xe3, 0x40, 0xdc, 0x65, 0x6b, 0x2d, 0x72, 0xeb, 0x3e, 0x12, 0x71, 0x5a, + 0x8d, 0xab, 0xa8, 0xb6, 0x7e, 0x7f, 0x37, 0xf7, 0x12, 0x00, 0xc4, 0xfe, + 0xa6, 0xd6, 0xf9, 0xde, 0x4f, 0x43, 0xd2, 0x06, 0x19, 0x70, 0x14, 0x88, + 0xe9, 0x69, 0xf5, 0xd1, 0x83, 0x9b, 0x1d, 0x8c, 0x64, 0xd1, 0xa0, 0x3e, + 0x11, 0x85, 0x93, 0x9f, 0xf7, 0xaa, 0x43, 0x5d, 0x9f, 0x91, 0x50, 0xbb, + 0xe4, 0x0e, 0xba, 0x12, 0x22, 0xc5, 0x9e, 0x93, 0xd7, 0x5c, 0x74, 0xb5, + 0x63, 0xaf, 0xf1, 0xae, 0xf6, 0xaf, 0x9a, 0xf4, 0xb4, 0x9f, 0x4d, 0x64, + 0x6a, 0xc7, 0x5f, 0xcb, 0x95, 0x5c, 0xc9, 0x62, 0x85, 0x52, 0xb7, 0x58, + 0xdd, 0x75, 0x18, 0xf9, 0xfc, 0xf8, 0xe4, 0x82, 0x3b, 0xc3, 0x12, 0x0e, + 0x9c, 0xef, 0xa8, 0x2a, 0xd1, 0xf8, 0xc6, 0x53, 0x59, 0xac, 0x69, 0x87, + 0x1b, 0xf6, 0xd2, 0x06, 0x4e, 0x9e, 0x25, 0x0c, 0x0e, 0xc3, 0x66, 0x73, + 0x00, 0xda, 0x3f, 0xc5, 0x12, 0xed, 0x8d, 0x43, 0xbf, 0x78, 0x7c, 0xa4, + 0xc2, 0x96, 0x8e, 0x15, 0x27, 0x38, 0xbd, 0xc7, 0x05, 0xd6, 0xdd, 0x3a, + 0x5f, 0xdd, 0xcd, 0x22, 0x9b, 0x19, 0x57, 0x95, 0x2f, 0x51, 0x2a, 0xf9, + 0xb5, 0x21, 0x85, 0x26, 0x2c, 0x5f, 0x5b, 0x9c, 0xff, 0x70, 0x9a, 0x78, + 0xc2, 0x86, 0x5a, 0xe3, 0x23, 0x30, 0xd7, 0xb0, 0xf7, 0x28, 0xf6, 0xbb, + 0x81, 0x85, 0x17, 0x46, 0x22, 0xfa, 0xb9, 0xff, 0x8e, 0x4c, 0xca, 0x58, + 0x25, 0x8b, 0xa3, 0x3e, 0xa4, 0xd8, 0xe4, 0x4b, 0x2e, 0x65, 0x80, 0x55, + 0xe5, 0x1f, 0x84, 0x13, 0xdf, 0x78, 0x65, 0xce, 0x91, 0x07, 0x6e, 0xe7, + 0x59, 0x79, 0x5c, 0x84, 0xf2, 0x38, 0xb4, 0x80, 0xca, 0x71, 0xfe, 0x80, + 0xf6, 0x80, 0x09, 0x31, 0x5b, 0xd2, 0xbb, 0x98, 0xcc, 0x27, 0x4e, 0xe2, + 0x34, 0x13, 0xd6, 0x79, 0xdf, 0xc0, 0x6e, 0x91, 0xbf, 0xb4, 0x67, 0x49, + 0x73, 0x0c, 0x0b, 0xd3, 0x7d, 0x31, 0x01, 0x3b, 0x68, 0xd4, 0x2e, 0x35, + 0x09, 0x94, 0x90, 0x83, 0x10, 0x0c, 0x7e, 0xcd, 0x8f, 0xa3, 0xed, 0xd9, + 0x8c, 0x23, 0xeb, 0x65, 0x9c, 0xfb, 0x4a, 0x0d, 0x08, 0x6f, 0x13, 0xac, + 0x21, 0xff, 0x1d, 0xda, 0xf9, 0xf8, 0x90, 0x35, 0x36, 0x2f, 0xc0, 0xec, + 0x8c, 0xca, 0xbf, 0xfb, 0x3f, 0x23, 0x60, 0x0e, 0xca, 0xf5, 0x24, 0x27, + 0x0a, 0x15, 0xcd, 0xb7, 0x9d, 0x05, 0x67, 0xe3, 0xd6, 0xe6, 0x22, 0x90, + 0x68, 0x68, 0x4a, 0x63, 0x5f, 0xfd, 0xcd, 0xfa, 0xaf, 0xd5, 0x92, 0x5a, + 0x09, 0xdc, 0x08, 0xef, 0x01, 0xb8, 0x0f, 0x1f, 0x1d, 0xcf, 0x2d, 0xd3, + 0xfc, 0xfd, 0x2e, 0x9b, 0xab, 0x45, 0xa0, 0xaf, 0xe1, 0x06, 0xf9, 0xa4, + 0x44, 0xe2, 0x8a, 0x09, 0xdf, 0x91, 0xe7, 0x24, 0x63, 0x78, 0x42, 0xce, + 0x69, 0xdc, 0x88, 0x34, 0xde, 0xfd, 0x82, 0x43, 0xf0, 0x3d, 0x0d, 0x48, + 0x74, 0xa3, 0x74, 0xde, 0xc7, 0xf1, 0x7c, 0xcd, 0x05, 0xd9, 0xee, 0x8b, + 0x59, 0x3f, 0x20, 0x25, 0x8a, 0x61, 0x97, 0x18, 0xda, 0xdd, 0xe4, 0x85, + 0x06, 0x82, 0x0b, 0x3a, 0x41, 0xe0, 0x48, 0xf7, 0x8c, 0xc2, 0xf1, 0xc8, + 0x93, 0x96, 0x25, 0x71, 0xe4, 0x26, 0x8c, 0xd9, 0x4e, 0x9b, 0x55, 0x6d, + 0x1a, 0x1f, 0xcf, 0x68, 0x26, 0xd0, 0xdd, 0xa5, 0x12, 0x12, 0x75, 0xec, + 0x5e, 0x41, 0xdd, 0xad, 0x0c, 0xfc, 0xae, 0x0c, 0x4d, 0xfd, 0x7b, 0xa0, + 0x20, 0xcb, 0x47, 0xc2, 0xef, 0x77, 0xa2, 0xcf, 0x44, 0x24, 0x75, 0xd3, + 0xf7, 0x39, 0x49, 0xad, 0x8c, 0x09, 0xa6, 0x4a, 0x40, 0xb4, 0x38, 0x8e, + 0xb7, 0xd2, 0x8b, 0xd3, 0xe5, 0xf6, 0x6c, 0xe7, 0x24, 0x65, 0x04, 0xcb, + 0xd4, 0xeb, 0xcb, 0xa2, 0x75, 0xc4, 0x00, 0x60, 0xcc, 0x20, 0xde, 0xa0, + 0x83, 0xc9, 0x5b, 0xb8, 0x9a, 0x6c, 0xaa, 0x58, 0xc9, 0x18, 0x21, 0x2d, + 0xd1, 0x3e, 0xe3, 0xb7, 0xb8, 0x52, 0x0d, 0x53, 0xc7, 0x8c, 0xbe, 0xa3, + 0xb9, 0x15, 0x05, 0x3e, 0x6a, 0x4e, 0xeb, 0xc0, 0xee, 0xab, 0x26, 0x05, + 0x94, 0x5c, 0x5a, 0x2d, 0xe2, 0xe5, 0x35, 0x6d, 0x59, 0xba, 0x74, 0xb5, + 0x78, 0x07, 0x89, 0xb9, 0x26, 0x1f, 0xd3, 0x08, 0xcb, 0xe0, 0xe3, 0xc3, + 0x41, 0xf8, 0x68, 0x19, 0xf3, 0xb5, 0x35, 0x7e, 0xe1, 0x3e, 0xc7, 0xc3, + 0x4d, 0x53, 0x78, 0x9d, 0xbf, 0x83, 0x2a, 0xfe, 0xcd, 0x21, 0x82, 0xba, + 0x47, 0x11, 0x00, 0x8f, 0xab, 0xb9, 0x30, 0xb0, 0xfc, 0x7b, 0x23, 0x3b, + 0xc9, 0x6a, 0xcd, 0x68, 0x12, 0x9a, 0xd4, 0x72, 0xb9, 0x47, 0x66, 0x3f, + 0xf5, 0xe0, 0x05, 0x0e, 0x9c, 0x3f, 0xc1, 0x91, 0xd8, 0xa5, 0x1c, 0x18, + 0xce, 0x5b, 0xa6, 0x91, 0xc0, 0x24, 0xc1, 0xa9, 0x76, 0xe8, 0x5f, 0x78, + 0xea, 0x0f, 0x41, 0x59, 0xb9, 0x19, 0xa9, 0xd0, 0x19, 0x88, 0x12, 0xbe, + 0x63, 0x60, 0xff, 0x8c, 0x2e, 0x14, 0xeb, 0x8b, 0x73, 0x5a, 0x35, 0x02, + 0xff, 0xe3, 0xf0, 0xe9, 0x19, 0xa5, 0xd8, 0x1c, 0x1b, 0xdd, 0x5c, 0x5a, + 0x7b, 0xff, 0x1e, 0x06, 0x2e, 0x78, 0xa6, 0xf9, 0x62, 0xfd, 0xb0, 0x75, + 0xec, 0x95, 0x3e, 0xef, 0x4d, 0xba, 0xd6, 0x40, 0xdf, 0x94, 0x47, 0xb3, + 0x74, 0x5c, 0x8e, 0xe4, 0xd6, 0x23, 0x13, 0x40, 0x15, 0x06, 0x55, 0x87, + 0xd1, 0x3b, 0x53, 0x9e, 0x4f, 0xfd, 0x94, 0xf2, 0x6c, 0xfe, 0x66, 0x8c, + 0xa8, 0xf7, 0xf0, 0x40, 0xb2, 0x46, 0x3b, 0xcc, 0x6a, 0x01, 0xee, 0x64, + 0x51, 0x73, 0x55, 0x46, 0xd3, 0xb1, 0x9d, 0x8b, 0xbe, 0xfe, 0x5d, 0xe9, + 0x65, 0x52, 0xda, 0x4c, 0x47, 0x6d, 0xa0, 0x16, 0xc2, 0xad, 0x3f, 0x35, + 0x27, 0x74, 0x26, 0x2f, 0xdb, 0x8a, 0x1b, 0x25, 0x38, 0xeb, 0x98, 0xfb, + 0x62, 0x40, 0x94, 0xcb, 0x33, 0xcd, 0xc8, 0x80, 0x7f, 0x0e, 0x28, 0x76, + 0x0b, 0xd0, 0xe6, 0x67, 0x17, 0x43, 0xba, 0xf3, 0x56, 0x9d, 0x12, 0xc6, + 0xf0, 0xb5, 0x8d, 0x1f, 0xaf, 0x88, 0x44, 0x1d, 0xd2, 0x9b, 0x61, 0xb3, + 0x08, 0x7a, 0xc9, 0x39, 0x99, 0xf2, 0x80, 0xbc, 0xdd, 0x05, 0x37, 0x09, + 0x75, 0x69, 0x7b, 0x91, 0x52, 0xae, 0x4f, 0x26, 0xa9, 0x70, 0xaa, 0xb7, + 0x6c, 0xa3, 0x64, 0x0e, 0xae, 0xc0, 0xb0, 0xe6, 0xdb, 0xd4, 0x9b, 0x28, + 0x2d, 0xce, 0x49, 0xbd, 0xc6, 0xe0, 0xdd, 0x04, 0x1a, 0x8f, 0x43, 0x58, + 0x3b, 0xd8, 0x31, 0x19, 0x71, 0xb2, 0x0e, 0x30, 0x77, 0x84, 0x71, 0xbe, + 0x9a, 0x66, 0x07, 0xdf, 0x94, 0xe1, 0xed, 0x14, 0xf0, 0x4e, 0xbd, 0xde, + 0xbb, 0xcd, 0xd8, 0xe2, 0xff, 0x49, 0xef, 0x60, 0x0b, 0x9b, 0xb5, 0x99, + 0x35, 0x57, 0xba, 0xf8, 0xeb, 0xdd, 0x2e, 0x5f, 0xc5, 0x1b, 0x15, 0xb2, + 0xa0, 0x02, 0xf4, 0xb4, 0x0f, 0x5c, 0x5a, 0x89, 0x69, 0xf1, 0x3d, 0xf4, + 0x51, 0x39, 0x55, 0xa1, 0x6a, 0x82, 0x43, 0x33, 0xf4, 0xe8, 0xcf, 0x09, + 0x69, 0xcc, 0x3b, 0xf3, 0xd4, 0x8e, 0x46, 0x7d, 0xe7, 0x21, 0x2a, 0x9e, + 0x3d, 0x73, 0x9e, 0x5d, 0x5e, 0xdf, 0x5b, 0x5d, 0xcc, 0x10, 0xdb, 0x88, + 0xee, 0x4b, 0xdc, 0x19, 0xd2, 0xb2, 0x3e, 0x57, 0x0f, 0x73, 0x48, 0x3e, + 0xed, 0x50, 0xae, 0xc3, 0xa1, 0x98, 0xbe, 0xaa, 0x02, 0x5a, 0xf0, 0xad, + 0x53, 0x4a, 0xdf, 0x86, 0x6d, 0x49, 0xac, 0xe5, 0xcf, 0x06, 0x02, 0x10, + 0x51, 0x2a, 0xce, 0xd6, 0xae, 0x59, 0x7a, 0x10, 0xc5, 0xbf, 0x78, 0xc9, + 0x71, 0x20, 0x40, 0x05, 0x95, 0xc8, 0x6c, 0x49, 0x14, 0x95, 0xfd, 0xcc, + 0x56, 0x19, 0x87, 0x00, 0xbf, 0x7c, 0xef, 0x55, 0x42, 0x45, 0xf8, 0x39, + 0x30, 0x14, 0x0c, 0xc0, 0x2a, 0x98, 0xc2, 0xf8, 0x53, 0x49, 0x7a, 0xd0, + 0x6d, 0xb7, 0x4f, 0x5e, 0x3c, 0x9b, 0x6b, 0x86, 0x42, 0xdc, 0x8c, 0xa3, + 0xf8, 0xac, 0xef, 0x18, 0x5f, 0x9b, 0xbf, 0x68, 0x2a, 0xb9, 0x73, 0xa8, + 0x79, 0x5c, 0x31, 0xb4, 0x57, 0x24, 0x11, 0x67, 0xc3, 0x2c, 0x13, 0x85, + 0x77, 0xf7, 0x10, 0x78, 0x87, 0x8f, 0xba, 0xd1, 0x17, 0x38, 0x96, 0x54, + 0xa7, 0xca, 0x5e, 0x01, 0x6d, 0xb6, 0xd8, 0x54, 0x40, 0x9e, 0x5d, 0xac, + 0x68, 0xf4, 0x67, 0x95, 0x1b, 0x72, 0xbb, 0x33, 0x59, 0x89, 0x23, 0xc6, + 0xa9, 0xa9, 0xe4, 0xbc, 0x1e, 0x5f, 0xdc, 0x74, 0xd3, 0x35, 0x6d, 0x46, + 0x7d, 0xc3, 0x82, 0xc9, 0xef, 0xa6, 0xd6, 0xc8, 0x3c, 0x6a, 0xc6, 0xb3, + 0xa3, 0xbe, 0xeb, 0xee, 0x76, 0xf7, 0x79, 0xa0, 0xd0, 0xce, 0x39, 0x20, + 0xb0, 0xff, 0x18, 0x1a, 0xe1, 0x31, 0xae, 0x60, 0xf2, 0x8a, 0xdb, 0xbe, + 0xdb, 0x7d, 0x86, 0x88, 0xdc, 0xcb, 0x57, 0xc4, 0x8a, 0x28, 0x3a, 0x93, + 0x8f, 0x4f, 0xae, 0xa0, 0x35, 0x12, 0x79, 0x42, 0x30, 0xed, 0xf1, 0x1a, + 0x6c, 0xb3, 0x3e, 0x2b, 0x2f, 0xda, 0x4c, 0x6f, 0x8a, 0x02, 0x35, 0x9d, + 0x72, 0xaa, 0x4c, 0x87, 0x25, 0x05, 0xe0, 0x73, 0xd8, 0xbb, 0xac, 0x0a, + 0x9e, 0xef, 0xba, 0xd6, 0x70, 0x2b, 0xc3, 0x56, 0x3a, 0xd5, 0xea, 0x6c, + 0x86, 0xb8, 0x92, 0xa5, 0xd8, 0xc6, 0x5c, 0xc1, 0xad, 0xc5, 0x69, 0x59, + 0xa6, 0xb8, 0xa1, 0x82, 0x11, 0x2c, 0x83, 0x7d, 0xe3, 0xff, 0x5a, 0xb1, + 0xa9, 0xa9, 0x79, 0x22, 0x4c, 0x7e, 0xfb, 0x53, 0x4a, 0x39, 0x86, 0x3a, + 0xac, 0x63, 0xdb, 0x72, 0xb2, 0xe2, 0x6e, 0x65, 0x25, 0xfb, 0x79, 0x04, + 0xe6, 0x4a, 0xb7, 0xf0, 0x1d, 0x13, 0x61, 0x7d, 0x64, 0x19, 0xcd, 0x5e, + 0x22, 0xc5, 0x9b, 0xf1, 0x38, 0xfb, 0xc0, 0x3c, 0x7a, 0xcc, 0xeb, 0xe5, + 0x15, 0x27, 0x10, 0x43, 0xa1, 0xbb, 0x22, 0x2d, 0xcc, 0x1f, 0x88, 0xf4, + 0xda, 0x9b, 0x1f, 0xa0, 0xbc, 0x69, 0x80, 0x24, 0x28, 0xba, 0xf6, 0xe8, + 0xfc, 0x11, 0x12, 0x94, 0xda, 0x2b, 0xc0, 0xb7, 0x67, 0xeb, 0xfc, 0x54, + 0xe2, 0x40, 0xf1, 0x3f, 0x36, 0x12, 0xa2, 0xa5, 0xa7, 0x7f, 0xf8, 0xfb, + 0x16, 0x03, 0x6d, 0x9d, 0xcf, 0x96, 0xb7, 0xdd, 0x48, 0xe0, 0x9f, 0x1a, + 0xe4, 0x4f, 0xdd, 0x5b, 0x64, 0x79, 0x0d, 0xdb, 0x27, 0x40, 0x2f, 0x10, + 0xe5, 0xd3, 0x63, 0x9f, 0x41, 0xd8, 0x79, 0x68, 0x00, 0xf3, 0x33, 0xb7, + 0x38, 0x83, 0x93, 0xe7, 0xb1, 0x2f, 0xbb, 0xbd, 0x13, 0xa8, 0x55, 0x56, + 0x79, 0x8a, 0x77, 0x1b, 0x35, 0x80, 0x9e, 0x20, 0xc0, 0x5b, 0x45, 0x6b, + 0xb7, 0x48, 0xcf, 0x37, 0x5e, 0x03, 0xd4, 0x41, 0x1b, 0x1c, 0x02, 0x7f, + 0x8a, 0x25, 0x76, 0x4d, 0xfc, 0xd3, 0x76, 0x43, 0x67, 0x6d, 0xda, 0x75, + 0xf9, 0x24, 0xe4, 0xcb, 0x0b, 0x61, 0x73, 0x91, 0x66, 0x14, 0x9d, 0x8b, + 0x66, 0x8e, 0x8f, 0x9d, 0xcc, 0x1b, 0xd0, 0x94, 0x0e, 0x9d, 0x31, 0xc7, + 0xce, 0xfd, 0x51, 0x98, 0xa6, 0x0d, 0xf7, 0x22, 0x0c, 0xc7, 0x02, 0x24, + 0x19, 0xe5, 0x6a, 0xa4, 0x32, 0x38, 0xab, 0x0f, 0xb8, 0xb8, 0x85, 0xe8, + 0xc1, 0x86, 0x5d, 0x91, 0xa8, 0xf5, 0x2e, 0xf5, 0x7b, 0x43, 0xf3, 0x47, + 0x83, 0xe7, 0x4b, 0xa7, 0x75, 0x5b, 0x04, 0x4d, 0x72, 0x11, 0x3a, 0x9b, + 0x1c, 0x3a, 0x06, 0xe6, 0xd3, 0x11, 0x8a, 0x2e, 0x6f, 0x70, 0xc6, 0xba, + 0xd0, 0xf9, 0x02, 0x41, 0x12, 0xae, 0x58, 0xf0, 0x6b, 0x06, 0x2c, 0xcb, + 0x40, 0xb1, 0x56, 0x90, 0x83, 0xbc, 0x33, 0x4d, 0x70, 0x62, 0xd7, 0x64, + 0xb9, 0x96, 0x2f, 0x51, 0x33, 0x99, 0x72, 0x80, 0x09, 0xb9, 0x2b, 0xd5, + 0x7e, 0x96, 0x1d, 0x99, 0xf6, 0x4d, 0xe4, 0x1d, 0x15, 0xb3, 0x85, 0xd0, + 0x75, 0xa9, 0x6d, 0x9d, 0xfc, 0xfa, 0xd9, 0xf1, 0x0a, 0x28, 0x63, 0x6b, + 0x64, 0x06, 0xf5, 0x78, 0x17, 0x9a, 0x72, 0x28, 0x00, 0xb9, 0x8f, 0x3a, + 0xd3, 0x87, 0xc0, 0x62, 0x25, 0x9d, 0xd5, 0xcb, 0x36, 0xef, 0x3c, 0x67, + 0xdd, 0x4d, 0x50, 0x0d, 0x47, 0x91, 0x9d, 0x60, 0x8b, 0xcb, 0x28, 0x8a, + 0x57, 0x9b, 0x7a, 0x0a, 0xfd, 0x3c, 0xf6, 0x18, 0x7d, 0x32, 0xce, 0x74, + 0x38, 0xcd, 0x2c, 0x2b, 0x91, 0x72, 0x28, 0xc3, 0x27, 0x20, 0x7d, 0x9e, + 0x72, 0xce, 0xf1, 0x4e, 0x18, 0xfb, 0xc3, 0x89, 0x10, 0x4f, 0x84, 0x23, + 0xf0, 0x79, 0x64, 0x15, 0xa3, 0xa5, 0xe8, 0xc1, 0xb0, 0x2c, 0x32, 0x39, + 0x4c, 0x40, 0x6b, 0x6b, 0xa4, 0x7d, 0xdd, 0x21, 0x63, 0xe5, 0xf6, 0x00, + 0x74, 0x73, 0xd8, 0x62, 0x2e, 0xb8, 0xc6, 0xa5, 0x3e, 0xf7, 0xdb, 0xc3, + 0x52, 0x21, 0x85, 0xa9, 0x98, 0xb5, 0x79, 0x10, 0x93, 0x5d, 0xd4, 0x3f, + 0x3c, 0xa9, 0x2d, 0xb8, 0xe9, 0xa8, 0x09, 0xe9, 0xef, 0xe1, 0x03, 0x34, + 0xe5, 0x24, 0x08, 0x03, 0x8e, 0x83, 0xb9, 0x66, 0x2e, 0xa6, 0x89, 0xb5, + 0x61, 0x0b, 0x16, 0x0b, 0x6b, 0xa5, 0x4b, 0x7d, 0xe7, 0x8a, 0x28, 0xda, + 0xaf, 0x85, 0xd6, 0x27, 0x7e, 0x6a, 0xa0, 0x35, 0xe1, 0x65, 0x50, 0xe5, + 0xa9, 0x38, 0xf3, 0xf5, 0x48, 0xa7, 0x7a, 0xbb, 0x77, 0x4f, 0xb5, 0x52, + 0x77, 0xec, 0x48, 0x6a, 0xe3, 0x2d, 0xd3, 0x7c, 0xc5, 0xe9, 0xe3, 0x26, + 0x14, 0x76, 0xc2, 0x3e, 0xfa, 0x19, 0xa2, 0x9a, 0x00, 0x02, 0x84, 0xae, + 0xee, 0xce, 0xf6, 0x49, 0x40, 0x50, 0x56, 0x8f, 0x48, 0x67, 0x8d, 0xb1, + 0x8a, 0x56, 0xe0, 0x27, 0x1b, 0xb6, 0xdb, 0x37, 0x78, 0x34, 0x49, 0x26, + 0x8d, 0xa8, 0x21, 0xba, 0xf2, 0xd9, 0xe7, 0x67, 0x6a, 0x81, 0x98, 0x16, + 0xef, 0x77, 0x51, 0x22, 0x78, 0xa4, 0xb8, 0x75, 0xe1, 0x92, 0xa8, 0x77, + 0xbe, 0x03, 0x83, 0xc1, 0xef, 0x10, 0x59, 0xde, 0x92, 0x4c, 0x15, 0x7d, + 0xd4, 0x11, 0x5e, 0x5e, 0x40, 0xee, 0xc7, 0x2f, 0x5f, 0xca, 0xda, 0x65, + 0x74, 0x92, 0xbd, 0xc9, 0x6b, 0x52, 0x44, 0x66, 0x3a, 0x42, 0x3a, 0x37, + 0xeb, 0xb3, 0xb5, 0x73, 0xaa, 0xbf, 0x2d, 0xea, 0x8e, 0x17, 0xd1, 0xa6, + 0xf3, 0x99, 0x3d, 0x8d, 0x94, 0x93, 0x7a, 0x0e, 0x45, 0xe6, 0xcf, 0xd1, + 0xb2, 0xf3, 0xc6, 0xd5, 0xc6, 0xa1, 0x24, 0x03, 0x4a, 0x12, 0xe3, 0x1e, + 0x94, 0x57, 0xac, 0x11, 0x5d, 0xda, 0xdc, 0x9a, 0xfd, 0x7a, 0x37, 0x9c, + 0x07, 0x08, 0x2b, 0x3b, 0x59, 0xc5, 0x7f, 0x72, 0x30, 0xf4, 0x40, 0x08, + 0x61, 0x19, 0x33, 0x12, 0xaa, 0xaf, 0xbb, 0xa5, 0x8f, 0xfc, 0x37, 0xdb, + 0xa0, 0xc6, 0xe1, 0xa4, 0xe7, 0x2f, 0xef, 0xd3, 0x0a, 0x50, 0x10, 0x65, + 0xfb, 0xbd, 0xe6, 0x94, 0x4a, 0x5c, 0xc8, 0x11, 0xee, 0xf9, 0xb5, 0xcc, + 0x77, 0xe8, 0x74, 0xe8, 0x86, 0x0c, 0xea, 0x03, 0x38, 0x31, 0x85, 0x6f, + 0xe8, 0xa7, 0xd5, 0xf7, 0xae, 0x5f, 0x29, 0xa7, 0x53, 0xff, 0x49, 0xfc, + 0x62, 0x10, 0xfc, 0xf6, 0x2b, 0xdb, 0x02, 0x86, 0x24, 0x90, 0x43, 0x12, + 0x94, 0x34, 0x91, 0xfd, 0x5c, 0xa4, 0x42, 0xb1, 0xdf, 0x90, 0xeb, 0x96, + 0x2b, 0xfb, 0xd6, 0x23, 0x5e, 0xb7, 0x64, 0x20, 0x24, 0xd2, 0xc4, 0x88, + 0x2b, 0x93, 0xb5, 0x7b, 0x4e, 0x71, 0x0e, 0xbc, 0x4b, 0xdc, 0xeb, 0x05, + 0x50, 0xb4, 0x1a, 0x41, 0xce, 0x61, 0xdf, 0x83, 0xe3, 0x15, 0xae, 0x1a, + 0x3c, 0xc6, 0x2b, 0x2e, 0x71, 0x50, 0xf1, 0x79, 0xf3, 0x33, 0xdf, 0x37, + 0xfa, 0x02, 0x7e, 0x6c, 0x28, 0xf8, 0x31, 0x43, 0x56, 0x82, 0xd3, 0x2f, + 0x5f, 0x55, 0x1d, 0xea, 0x10, 0xe3, 0x74, 0x85, 0x1b, 0xcc, 0x00, 0x35, + 0x21, 0x76, 0xf3, 0xac, 0x81, 0xf2, 0x4e, 0x42, 0xfc, 0xfb, 0x88, 0xd9, + 0x2b, 0xaf, 0x8e, 0x89, 0x24, 0xea, 0x68, 0x2c, 0x1a, 0x91, 0x05, 0x21, + 0xc5, 0x6b, 0x80, 0xcb, 0xa1, 0x17, 0x61, 0xf5, 0xac, 0xc8, 0x65, 0xec, + 0x6f, 0x5d, 0x4e, 0xa6, 0xd9, 0xb9, 0x13, 0xe9, 0x7e, 0xb7, 0xf2, 0x8d, + 0x29, 0x00, 0x49, 0xf3, 0x52, 0x99, 0x3f, 0xd8, 0xf3, 0xfd, 0x86, 0x1c, + 0x03, 0x49, 0x21, 0xc1, 0x81, 0xb6, 0xb0, 0xaf, 0x82, 0x30, 0x0d, 0xa1, + 0xb6, 0xbf, 0xd2, 0xd8, 0x8c, 0x02, 0xea, 0xdd, 0xcb, 0x36, 0x49, 0x42, + 0xe4, 0x25, 0xfe, 0x2a, 0x4e, 0x58, 0x26, 0x96, 0xc6, 0xec, 0x92, 0x34, + 0x21, 0x94, 0x55, 0xd3, 0x06, 0xa7, 0x05, 0x60, 0x75, 0x93, 0x46, 0x71, + 0xec, 0xba, 0x7c, 0x56, 0xfb, 0xd2, 0x08, 0xa3, 0x26, 0x62, 0x8c, 0x78, + 0x53, 0x2c, 0x75, 0x36, 0x38, 0x85, 0xfe, 0xe3, 0x01, 0x20, 0x34, 0x75, + 0x51, 0x66, 0xd2, 0x32, 0xcf, 0x4a, 0xf6, 0x21, 0xfe, 0x9a, 0xc2, 0xfc, + 0xaa, 0xae, 0x1c, 0x55, 0xa1, 0x2c, 0x24, 0xa1, 0x86, 0x89, 0x54, 0x5e, + 0x47, 0x42, 0xd4, 0x63, 0x0e, 0x85, 0xf8, 0x97, 0x55, 0x8c, 0x75, 0xa2, + 0x94, 0x25, 0xaf, 0x9c, 0x9e, 0x17, 0xdb, 0xea, 0x16, 0x1e, 0x48, 0x1a, + 0x6d, 0xa6, 0x4c, 0x31, 0x25, 0x7e, 0x44, 0xdb, 0x10, 0x0f, 0xf3, 0xaf, + 0x63, 0x71, 0xb3, 0xad, 0x09, 0x16, 0xbc, 0xa0, 0x35, 0x5d, 0x17, 0x56, + 0xe7, 0x69, 0xd1, 0x5b, 0x26, 0x9d, 0xc3, 0x2d, 0x06, 0x3a, 0x69, 0xfd, + 0xfc, 0xa9, 0x31, 0xa7, 0xc5, 0xd4, 0x8f, 0x52, 0x45, 0x44, 0xe2, 0xd2, + 0x7b, 0x15, 0xe9, 0xa7, 0xac, 0xe3, 0x51, 0xf0, 0x3d, 0x18, 0xd6, 0x9b, + 0xf6, 0xbd, 0x5f, 0x1f, 0xff, 0x27, 0x46, 0x26, 0xfa, 0x0a, 0x63, 0xe0, + 0x56, 0x8e, 0x6e, 0xc1, 0x9f, 0xd2, 0xcb, 0x24, 0x22, 0x39, 0x5c, 0x21, + 0x83, 0x7d, 0xcf, 0x9f, 0x5e, 0xa1, 0x31, 0x1b, 0x7c, 0x6a, 0x80, 0x88, + 0x23, 0x4f, 0xe0, 0x51, 0x4d, 0x01, 0x94, 0xf8, 0x13, 0xf5, 0xf8, 0xb2, + 0x20, 0x94, 0xb8, 0x99, 0x5d, 0x5d, 0xa1, 0xf4, 0x4d, 0x02, 0x1a, 0x9e, + 0xb6, 0x9b, 0x3b, 0x4e, 0xa5, 0xee, 0x22, 0x26, 0xba, 0x32, 0x29, 0xa3, + 0xa1, 0x36, 0x72, 0xde, 0x84, 0x7b, 0xbb, 0x0b, 0x77, 0xad, 0x65, 0xec, + 0x0b, 0x0c, 0x5d, 0x8a, 0xfe, 0x98, 0x6a, 0x42, 0xd7, 0xad, 0x91, 0x08, + 0x66, 0xc6, 0x30, 0xab, 0x8d, 0x6c, 0x7c, 0xe2, 0x3a, 0xf7, 0x55, 0xe8, + 0x15, 0xa5, 0x2a, 0x64, 0xad, 0x1a, 0x3b, 0xcc, 0x93, 0xad, 0xe7, 0x2c, + 0xae, 0x80, 0x0a, 0xbb, 0x98, 0xf7, 0x88, 0x3f, 0x1b, 0x30, 0x7b, 0xd6, + 0x5e, 0x68, 0x39, 0x63, 0xc3, 0x2b, 0x35, 0xf3, 0x99, 0x3d, 0xa0, 0x36, + 0x62, 0x9a, 0xcf, 0xb4, 0xe8, 0x89, 0x0f, 0x13, 0x56, 0x08, 0xef, 0x3c, + 0x1c, 0x18, 0x9e, 0x7e, 0x0e, 0xa4, 0xa1, 0x76, 0x5c, 0x0a, 0xa8, 0x83, + 0x72, 0xca, 0x35, 0xe6, 0xee, 0x44, 0x6e, 0x01, 0x5d, 0x43, 0x87, 0xd5, + 0x5d, 0x77, 0xac, 0x08, 0x99, 0xd2, 0x70, 0x53, 0x57, 0x35, 0xbd, 0xee, + 0x3d, 0x71, 0xfd, 0x4e, 0x31, 0xe0, 0xbc, 0x24, 0x4d, 0x27, 0xf3, 0x41, + 0x0b, 0xde, 0xc7, 0x21, 0x6b, 0x8c, 0xab, 0xf2, 0x65, 0x86, 0x4c, 0xa5, + 0x18, 0x77, 0xa9, 0xbb, 0x71, 0xf9, 0x9d, 0x97, 0x5a, 0x96, 0x61, 0xe8, + 0x42, 0x03, 0x07, 0x12, 0x60, 0x33, 0x58, 0x03, 0xf4, 0x3b, 0x2b, 0xd0, + 0x68, 0x70, 0x83, 0xf6, 0x3a, 0x73, 0x56, 0xcc, 0x8a, 0x3b, 0xef, 0xf4, + 0xcc, 0x89, 0xa1, 0x77, 0x94, 0x62, 0x78, 0x51, 0xb9, 0x0c, 0x0f, 0xf6, + 0x00, 0xa9, 0x1e, 0xa5, 0x95, 0xb7, 0x12, 0x36, 0xb8, 0x0e, 0x9e, 0x02, + 0xdb, 0x60, 0x5f, 0xe8, 0xfe, 0x91, 0x20, 0x22, 0x23, 0xdd, 0x6d, 0xf9, + 0x08, 0xfc, 0xfc, 0x00, 0x14, 0x48, 0xbd, 0x5a, 0x2f, 0xba, 0x70, 0x2b, + 0xd1, 0xa6, 0x4f, 0xde, 0x66, 0x4c, 0xaa, 0xd1, 0xc1, 0xcf, 0x3f, 0x5d, + 0xaa, 0x3a, 0xe9, 0x00, 0x17, 0xa0, 0x96, 0x90, 0x7f, 0xf5, 0x9f, 0x27, + 0xf0, 0x93, 0x42, 0x16, 0x79, 0x2f, 0xaa, 0x95, 0x23, 0x9e, 0x56, 0x31, + 0xc2, 0x33, 0x99, 0xc1, 0x6a, 0x0b, 0xdf, 0xd9, 0x6b, 0x27, 0x0b, 0x81, + 0x37, 0x19, 0xf9, 0x78, 0xff, 0xfd, 0x88, 0x31, 0x6c, 0xba, 0xa0, 0x3c, + 0x22, 0x9f, 0x5f, 0x4c, 0x1a, 0xe8, 0x29, 0x84, 0xcf, 0x64, 0x72, 0xde, + 0x60, 0x43, 0x13, 0x69, 0x11, 0x29, 0xd9, 0xec, 0x7e, 0x0d, 0x96, 0xe0, + 0xfb, 0x67, 0x14, 0x1d, 0x4a, 0x20, 0x1f, 0xfd, 0xc7, 0xe5, 0x7b, 0x5e, + 0x5f, 0xda, 0x7d, 0x85, 0x2c, 0xa9, 0x84, 0x98, 0x63, 0x0a, 0x2e, 0xef, + 0xba, 0xfd, 0x41, 0x6d, 0xfa, 0xad, 0xcd, 0x60, 0xa5, 0xe9, 0x08, 0xdf, + 0x3d, 0xd2, 0xaf, 0x3d, 0xc4, 0xb7, 0xa0, 0x0d, 0x99, 0xdd, 0x9c, 0xcb, + 0x31, 0xf6, 0x1d, 0xe1, 0x4f, 0x64, 0x2c, 0x5c, 0xca, 0x2e, 0xc9, 0x67, + 0x0b, 0x4e, 0x14, 0xf6, 0x98, 0x20, 0x09, 0xab, 0x3b, 0x22, 0x01, 0xf0, + 0x0b, 0x8e, 0x41, 0x25, 0x4f, 0x9d, 0x6b, 0x02, 0x3d, 0xab, 0xb4, 0xbb, + 0xbe, 0x12, 0x84, 0x88, 0xf6, 0xe6, 0x20, 0xcd, 0x45, 0xca, 0xd0, 0x3c, + 0xe3, 0x87, 0x33, 0x9b, 0x27, 0xf4, 0x30, 0xa2, 0x61, 0xde, 0x06, 0x07, + 0x46, 0x47, 0x8a, 0xae, 0x65, 0x17, 0x25, 0xf5, 0x57, 0x7b, 0x9c, 0xd8, + 0x74, 0xc5, 0xa6, 0xd3, 0xa4, 0xee, 0x50, 0x02, 0x9a, 0x22, 0x57, 0x3b, + 0xf6, 0xcf, 0xa0, 0xb4, 0x7a, 0x4f, 0x31, 0x6c, 0xc1, 0x16, 0x05, 0xfd, + 0xa8, 0x70, 0x9a, 0xc4, 0xdf, 0xac, 0x0d, 0xcc, 0x9b, 0x61, 0x8c, 0xf7, + 0x58, 0xec, 0xf0, 0x06, 0x1b, 0x56, 0x98, 0xad, 0x37, 0xbb, 0xd4, 0x72, + 0x60, 0x59, 0x84, 0xbf, 0xd8, 0xad, 0x08, 0x89, 0x4d, 0x28, 0x94, 0x35, + 0x0a, 0x6c, 0xc1, 0x37, 0x7b, 0xc8, 0xb1, 0x3f, 0x84, 0x6b, 0xe9, 0x67, + 0x18, 0x9a, 0x9d, 0x6e, 0xc1, 0xa2, 0x0a, 0x3e, 0x20, 0x58, 0xf5, 0x97, + 0x12, 0xa7, 0x38, 0xeb, 0x0e, 0xd8, 0x67, 0xfb, 0x1e, 0xc1, 0xaa, 0x32, + 0x27, 0x4c, 0x14, 0xe7, 0x39, 0x8d, 0x21, 0x68, 0x05, 0x93, 0x78, 0x12, + 0xbc, 0x19, 0xe4, 0x57, 0x0f, 0xd2, 0xbf, 0x61, 0x3d, 0x96, 0x5a, 0xa6, + 0xdb, 0xb3, 0xde, 0xf1, 0x2d, 0x7e, 0x44, 0x6f, 0xde, 0x81, 0x46, 0x9e, + 0xec, 0x1a, 0xc8, 0x38, 0x97, 0x73, 0x5e, 0x1a, 0xc8, 0xf2, 0x6d, 0x1d, + 0xdd, 0x30, 0x0c, 0x85, 0x73, 0x6a, 0xfd, 0x05, 0x57, 0x4c, 0x75, 0x43, + 0x59, 0xc1, 0x11, 0x15, 0x64, 0xf3, 0x88, 0x50, 0x8d, 0xf3, 0xf4, 0xbf, + 0x25, 0x5a, 0xc2, 0x78, 0x95, 0xf4, 0xb4, 0x70, 0x6d, 0x7a, 0x4a, 0xb8, + 0x56, 0xe9, 0xad, 0x0f, 0xbf, 0x29, 0xd1, 0x09, 0x38, 0xef, 0xca, 0xab, + 0x4d, 0x87, 0xd9, 0x10, 0x5d, 0x57, 0x6b, 0xe0, 0xea, 0x71, 0x51, 0x36, + 0x8e, 0xb3, 0x7a, 0xe5, 0xb7, 0x13, 0x66, 0x70, 0x30, 0xa5, 0xd2, 0xc6, + 0x49, 0x12, 0xc4, 0x70, 0x08, 0xe9, 0x8f, 0x3d, 0xd5, 0x6a, 0xbf, 0xf5, + 0x61, 0x3e, 0x35, 0xc2, 0xfa, 0xb0, 0x55, 0x92, 0x32, 0xf2, 0x27, 0x6a, + 0x19, 0x9f, 0xc5, 0xc8, 0xee, 0x3a, 0xa2, 0x8c, 0x23, 0x40, 0xd6, 0x7d, + 0x8e, 0x15, 0x78, 0xcd, 0x22, 0xad, 0xea, 0xf2, 0x2a, 0xf7, 0x43, 0x50, + 0xc9, 0xa1, 0x53, 0x12, 0x4e, 0x67, 0x38, 0xb5, 0x80, 0xae, 0xdf, 0x26, + 0x98, 0xb4, 0x75, 0x88, 0x5f, 0x1f, 0xf5, 0xa9, 0x93, 0x90, 0x60, 0x28, + 0xba, 0xd2, 0x26, 0x7d, 0x22, 0xa4, 0x23, 0xd4, 0xff, 0x4e, 0xac, 0x1b, + 0x2f, 0xac, 0x6a, 0x7c, 0xfa, 0x9e, 0x0d, 0xfb, 0x17, 0x7a, 0x9d, 0x04, + 0xd8, 0xfb, 0xd9, 0x96, 0xd1, 0xd1, 0x7d, 0xc0, 0xe9, 0xf0, 0x34, 0x19, + 0x89, 0xe2, 0x15, 0x65, 0x7b, 0x9c, 0x94, 0x6a, 0x17, 0x59, 0x28, 0x1f, + 0x28, 0x10, 0x98, 0xaf, 0x1a, 0xc7, 0x62, 0x41, 0x77, 0xe2, 0x00, 0x7f, + 0x20, 0x0d, 0xb3, 0x35, 0x86, 0xfb, 0xae, 0xa5, 0xaf, 0xf7, 0x3c, 0x3c, + 0x63, 0xe2, 0xa2, 0xd4, 0x44, 0x3a, 0xe1, 0x2d, 0x3b, 0x38, 0x01, 0x89, + 0x0d, 0xca, 0x67, 0xa4, 0x5f, 0xcb, 0x9b, 0xdc, 0x7a, 0xb9, 0xf2, 0x8e, + 0x43, 0x18, 0x74, 0xd9, 0xd6, 0x4d, 0x95, 0x80, 0x54, 0x62, 0xf9, 0x15, + 0xdc, 0x0a, 0xcb, 0xac, 0x52, 0xf4, 0xe9, 0xb8, 0x2b, 0x75, 0xda, 0x17, + 0x70, 0x3f, 0xe5, 0xc4, 0x87, 0x11, 0x87, 0x27, 0x0c, 0xe1, 0xf8, 0x8e, + 0xec, 0xae, 0x33, 0x63, 0x5e, 0xef, 0x94, 0xe4, 0xa6, 0x85, 0xd0, 0x52, + 0x60, 0xc3, 0x47, 0x02, 0x21, 0xff, 0xd6, 0x42, 0x98, 0xf3, 0x36, 0xfd, + 0xca, 0x00, 0xf0, 0x58, 0x24, 0x02, 0x4e, 0xf5, 0x1f, 0xc6, 0x46, 0x9c, + 0xa2, 0x36, 0x3f, 0x50, 0x9a, 0x25, 0x34, 0x03, 0xe5, 0xc3, 0x43, 0xe2, + 0x5d, 0x3d, 0xf0, 0xa1, 0x70, 0xb6, 0xa1, 0x5e, 0x54, 0x7d, 0x8e, 0xbe, + 0x80, 0xbd, 0x62, 0xd6, 0x71, 0xf6, 0xe2, 0xc0, 0x3d, 0xa3, 0x5d, 0x6f, + 0xd3, 0x1c, 0x00, 0x1e, 0x9b, 0x4e, 0x02, 0xd9, 0x2e, 0x4f, 0x30, 0xc6, + 0x0d, 0xba, 0xef, 0x7a, 0xe1, 0xc5, 0xc7, 0x77, 0xef, 0x10, 0x27, 0x9f, + 0x8f, 0x4b, 0x3f, 0xb9, 0xa9, 0x8b, 0xfa, 0x8d, 0x1f, 0x27, 0xf0, 0xf5, + 0x01, 0x65, 0x7b, 0x8c, 0xdf, 0x4b, 0xa9, 0x1e, 0xbe, 0x3a, 0xae, 0x14, + 0x8b, 0x16, 0xf8, 0xa9, 0xa3, 0xe1, 0xc3, 0xe9, 0x1f, 0xbc, 0x86, 0x70, + 0x5c, 0xe7, 0x53, 0x0a, 0xb7, 0x61, 0x52, 0x5f, 0x6f, 0xa4, 0x80, 0x64, + 0x0f, 0xa9, 0x97, 0x07, 0xb5, 0x5d, 0x80, 0x6e, 0xb3, 0xbe, 0xfb, 0x0e, + 0xdf, 0xe1, 0x71, 0xb9, 0xf0, 0xa6, 0xaf, 0xdf, 0xa6, 0xca, 0xd7, 0x4b, + 0x9d, 0xc2, 0x45, 0xc3, 0x8d, 0x99, 0x2b, 0xc1, 0x2b, 0xb8, 0x92, 0x61, + 0x41, 0x54, 0xaa, 0x6a, 0xab, 0xea, 0x7c, 0x48, 0xee, 0xdd, 0x32, 0x7b, + 0xf4, 0xad, 0xe8, 0xbe, 0x71, 0x5e, 0x3e, 0x57, 0xb4, 0x1a, 0x78, 0x7a, + 0x1d, 0xcd, 0x26, 0xfa, 0x72, 0x17, 0x55, 0xd7, 0x93, 0x2a, 0xc8, 0x34, + 0xd2, 0x50, 0x2a, 0x44, 0x5e, 0x2f, 0x99, 0xd8, 0x08, 0x86, 0xa7, 0xb6, + 0x07, 0x1e, 0x59, 0x2b, 0x65, 0x99, 0x2a, 0xe4, 0xf4, 0x7a, 0x1a, 0x5b, + 0xf6, 0x28, 0x38, 0x2f, 0x56, 0x1a, 0xf3, 0xec, 0x81, 0xde, 0xc3, 0x4b, + 0xd9, 0x62, 0xc8, 0x72, 0x3b, 0x96, 0x09, 0x8c, 0xda, 0x9f, 0x58, 0x13, + 0x7b, 0x72, 0x32, 0x71, 0xff, 0x3b, 0x29, 0x31, 0x2d, 0x52, 0x15, 0xb9, + 0xef, 0x09, 0x03, 0x66, 0xfb, 0xa0, 0x64, 0xdf, 0xa3, 0x99, 0x96, 0xc4, + 0xb0, 0x8d, 0xcd, 0x82, 0xac, 0xfe, 0xc7, 0xf4, 0x5d, 0x97, 0x75, 0x4d, + 0xb6, 0x81, 0xd7, 0xdb, 0xa1, 0x5d, 0xd8, 0x33, 0x59, 0x70, 0x35, 0x36, + 0x41, 0x89, 0x70, 0xa5, 0x81, 0xdd, 0x2b, 0x84, 0x4b, 0x1c, 0xd7, 0xef, + 0x4b, 0x3f, 0xdb, 0xdd, 0x7c, 0xe3, 0x9a, 0xb2, 0xd7, 0x66, 0x32, 0x53, + 0xc4, 0xa0, 0x1c, 0x8b, 0xd9, 0x9f, 0x03, 0x0b, 0xf9, 0x6c, 0x18, 0x68, + 0x10, 0x70, 0xb1, 0xd7, 0xdf, 0xa0, 0x7a, 0xbb, 0x78, 0x23, 0x8b, 0x20, + 0xf9, 0x30, 0x07, 0x76, 0x1f, 0x64, 0x79, 0x78, 0x5b, 0x0f, 0x40, 0x90, + 0x4d, 0xaa, 0x5d, 0xee, 0x54, 0x84, 0xeb, 0xcb, 0xab, 0xf8, 0xbf, 0x18, + 0xa9, 0xf2, 0xbb, 0xe4, 0x36, 0x3e, 0xc8, 0x2d, 0x5c, 0x8e, 0x24, 0x2b, + 0xf5, 0xe7, 0x9a, 0x52, 0x36, 0xf0, 0x35, 0x4e, 0xaa, 0x60, 0xa5, 0x51, + 0x39, 0x7b, 0xae, 0xa1, 0x3c, 0x77, 0x42, 0xb3, 0xfd, 0xba, 0x6e, 0xb8, + 0xcc, 0x7e, 0xc4, 0xbf, 0x49, 0xbc, 0x7d, 0x99, 0xc7, 0x58, 0xd4, 0x18, + 0x6f, 0x26, 0xaf, 0x68, 0x74, 0xad, 0x05, 0xa5, 0x39, 0xc0, 0x42, 0xa3, + 0x26, 0x4a, 0xad, 0x8d, 0x89, 0xaa, 0x8b, 0x48, 0xbc, 0x2d, 0xe2, 0xc9, + 0xc2, 0xe9, 0x73, 0x32, 0x0f, 0xb5, 0xb6, 0x02, 0xa5, 0xa4, 0x18, 0x1b, + 0x70, 0xf7, 0x6f, 0x96, 0xa4, 0xa9, 0xfe, 0x87, 0x1a, 0x45, 0x08, 0x71, + 0x12, 0xba, 0xad, 0xc9, 0x82, 0x54, 0x83, 0xb2, 0xf8, 0x42, 0xe8, 0xb2, + 0x63, 0x89, 0xbd, 0xe8, 0xb6, 0xfa, 0xa2, 0xc0, 0x4f, 0xd2, 0xcb, 0xa1, + 0xd3, 0x0f, 0x31, 0xb0, 0x3c, 0xf6, 0xc2, 0x16, 0x37, 0x44, 0x30, 0xff, + 0x4d, 0x00, 0xb9, 0x6f, 0xbb, 0xf5, 0xd9, 0x8d, 0x60, 0xce, 0xa0, 0x33, + 0x7f, 0xef, 0x4c, 0xd8, 0xb8, 0xc8, 0xdc, 0x9b, 0x1e, 0x01, 0x91, 0x9d, + 0x4d, 0xe0, 0x4e, 0x6b, 0x9d, 0x36, 0xa7, 0x42, 0x86, 0xce, 0x97, 0xbc, + 0x3b, 0x63, 0xa5, 0x9f, 0xc6, 0xff, 0x22, 0x08, 0x7d, 0xe3, 0x59, 0xb2, + 0x25, 0xa0, 0x5d, 0xf1, 0x3b, 0x22, 0xbd, 0x7a, 0xf2, 0x5a, 0xb7, 0x31, + 0x72, 0x4f, 0x58, 0x3b, 0x1f, 0x37, 0xa6, 0xd4, 0x1f, 0xa1, 0x24, 0x4c, + 0x8d, 0x3f, 0xbb, 0xf5, 0x28, 0xbf, 0x8a, 0x7e, 0x64, 0xcb, 0x14, 0x03, + 0x7c, 0x32, 0x72, 0xd7, 0x71, 0xc3, 0x27, 0xfe, 0xdb, 0x9e, 0x29, 0x64, + 0xa7, 0xd0, 0x52, 0x88, 0xa8, 0x35, 0x9f, 0xc2, 0x92, 0x17, 0xd8, 0x6e, + 0x5e, 0x1b, 0xe5, 0xa3, 0xdf, 0xee, 0xc6, 0x00, 0xf3, 0xdc, 0xba, 0x49, + 0x7c, 0xb4, 0x4e, 0x69, 0xee, 0x9e, 0x05, 0x6a, 0x6c, 0xdd, 0xfb, 0x90, + 0xa2, 0xb7, 0x84, 0xea, 0x32, 0xca, 0xec, 0x15, 0x1c, 0x24, 0xd6, 0x60, + 0xef, 0xf2, 0x2f, 0x4b, 0x87, 0x46, 0xeb, 0xe1, 0x89, 0x78, 0xba, 0xd0, + 0xcd, 0xd7, 0x41, 0x01, 0xf9, 0xb4, 0x2c, 0x33, 0x34, 0xb1, 0x07, 0xe5, + 0x9f, 0x0e, 0xb3, 0xc9, 0x60, 0xc1, 0x3c, 0xbd, 0x4e, 0x58, 0x08, 0x05, + 0x3b, 0xf8, 0x29, 0x0c, 0x26, 0x68, 0x18, 0xaf, 0x84, 0x55, 0xc0, 0xd1, + 0xfb, 0x08, 0xac, 0x40, 0x04, 0xf8, 0xd0, 0x73, 0x54, 0x64, 0x0f, 0xbd, + 0x1f, 0x40, 0xd8, 0x16, 0xa4, 0x0b, 0x9a, 0xd9, 0x4e, 0x0e, 0xcb, 0x14, + 0xe4, 0xec, 0xa3, 0xb0, 0xef, 0x45, 0xeb, 0x03, 0xdf, 0x62, 0x9f, 0x5d, + 0x4a, 0x84, 0x12, 0x78, 0xb0, 0x3b, 0x94, 0x69, 0xd0, 0x5f, 0x99, 0x40, + 0x1e, 0x82, 0x0b, 0x9e, 0x73, 0x45, 0xd9, 0x6d, 0xc3, 0x92, 0x22, 0x3e, + 0x3e, 0x93, 0x41, 0x46, 0x64, 0xcd, 0xe8, 0x09, 0x8f, 0xe1, 0x13, 0xfb, + 0xd5, 0xe4, 0xbc, 0x70, 0xdd, 0x6c, 0x98, 0xe8}; + +const size_t kGoldenDataSize = sizeof(kGoldenDataSize); + +} // namespace + +std::vector DataSource::Get(size_t size) { + std::vector buffer(size); + + size_t write_head = 0; + + while (write_head < size) { + const size_t remaining_read = kGoldenDataSize - read_head_; + const size_t remaining_write = size - write_head; + + // Figure out how much more we can write. If we are nearing the end of the + // read buffer, we have to cut short our read so that we can loop back to + // the start of the read buffer. + size_t next_write = std::min(remaining_read, remaining_write); + memcpy(buffer.data() + write_head, kGoldenData + read_head_, next_write); + + // Move both the read and write heads forward. Make sure to loop the read + // head back to the start so that we we can restart reading when necessary. + write_head += next_write; + read_head_ = (read_head_ + next_write) % kGoldenDataSize; + } + + return buffer; +} + +} // namespace widevine diff --git a/benchmarking/data_source.h b/benchmarking/data_source.h new file mode 100644 index 0000000..5729a8f --- /dev/null +++ b/benchmarking/data_source.h @@ -0,0 +1,23 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#ifndef WHITEBOX_BENCHMARKING_DATA_SOURCE_H_ +#define WHITEBOX_BENCHMARKING_DATA_SOURCE_H_ + +#include +#include + +#include + +namespace widevine { + +class DataSource { + public: + std::vector Get(size_t size); + + private: + size_t read_head_ = 0; +}; + +} // namespace widevine + +#endif // WHITEBOX_BENCHMARKING_DATA_SOURCE_H_ diff --git a/benchmarking/measurements.cc b/benchmarking/measurements.cc new file mode 100644 index 0000000..ea23565 --- /dev/null +++ b/benchmarking/measurements.cc @@ -0,0 +1,78 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#include "benchmarking/measurements.h" + +#include + +#include "base/logging.h" + +namespace widevine { +namespace { +size_t RoundUp(size_t num, size_t denum) { + return (num + denum - 1) / denum; +} +} // namespace + +void Timer::Reset() { + start_ = Clock::now(); +} + +Period Timer::Get() const { + const auto now = Clock::now(); + return std::chrono::duration_cast(now - start_); +} + +Throughput::Throughput() : bytes(0), microseconds(0), bits_per_second(0) {} + +Throughput::Throughput(const Period& duration, size_t bytes) { + this->bytes = bytes; + microseconds = duration.count(); + + bits_per_second = 0; + + if (microseconds > 0) { + // Make sure to only do one division to ensure that we are not loosing too + // much information as we are avoiding floating-point values. + bits_per_second = (bytes * 8 * 1000 * 1000) / microseconds; + } +} + +const Period& Sampler::Percentile::Get(size_t percentile) const { + // Nearest-rank method: + // https://en.wikipedia.org/wiki/Percentile#The_nearest-rank_method + const size_t index = RoundUp(samples_.size() * percentile, 100); + + // The index will be from 1 to N, but we need it to be 0 to N-1. + return samples_[index - 1]; +} + +void Sampler::Push(const Period& period) { + samples_.push_back(period); +} + +void PrettyPrint(const std::string& title, + const Throughput& throughput, + size_t bytes_per_call) { + LOG(INFO) << title; + LOG(INFO) << " bytes per call: " << bytes_per_call; + LOG(INFO) << " bytes: " << throughput.bytes; + LOG(INFO) << " microseconds: " << throughput.microseconds; + LOG(INFO) << " bits per second: " << throughput.bits_per_second; +} + +void PrettyPrint(const std::string& title, + const Sampler& samples, + size_t per_sample_size) { + const auto& percentiles = samples.Percentiles(); + + LOG(INFO) << title; + LOG(INFO) << " Sample Size: " << samples.SampleSize() << " (" + << per_sample_size << " bytes)"; + LOG(INFO) << " Min (0%): " << percentiles.Get(0).count() << " us"; + LOG(INFO) << " 25%: " << percentiles.Get(25).count() << " us"; + LOG(INFO) << " Median (50%): " << percentiles.Get(50).count() << " us"; + LOG(INFO) << " 75%: " << percentiles.Get(75).count() << " us"; + LOG(INFO) << " Max (100%): " << percentiles.Get(100).count() << " us"; +} + +} // namespace widevine diff --git a/benchmarking/measurements.h b/benchmarking/measurements.h new file mode 100644 index 0000000..502d788 --- /dev/null +++ b/benchmarking/measurements.h @@ -0,0 +1,72 @@ +// Copyright 2020 Google LLC. All Rights Reserved. + +#ifndef WHITEBOX_BENCHMARKING_MEASUREMENT_H_ +#define WHITEBOX_BENCHMARKING_MEASUREMENT_H_ + +#include +#include + +#include +#include +#include +#include + +namespace widevine { + +using Clock = std::chrono::high_resolution_clock; +using Period = std::chrono::microseconds; + +class Timer { + public: + void Reset(); + + Period Get() const; + + private: + Clock::time_point start_; +}; + +struct Throughput { + size_t bytes; + uint64_t microseconds; + uint64_t bits_per_second; + + Throughput(); + Throughput(const Period& duration, size_t bytes); +}; + +class Sampler { + public: + class Percentile { + public: + Percentile(const std::vector& samples) : samples_(samples) { + std::sort(samples_.begin(), samples_.end()); + }; + + const Period& Get(size_t percentile) const; + + private: + std::vector samples_; + }; + + void Push(const Period& period); + + Percentile Percentiles() const { return Percentile(samples_); } + + size_t SampleSize() const { return samples_.size(); } + + private: + std::vector samples_; +}; + +void PrettyPrint(const std::string& title, + const Throughput& throughput, + size_t bytes_per_call); + +void PrettyPrint(const std::string& title, + const Sampler& samples, + size_t sample_input_size); + +} // namespace widevine + +#endif // WHITEBOX_BENCHMARKING_MEASUREMENT_H_ diff --git a/impl/reference/BUILD b/impl/reference/BUILD index 6ef1918..837636a 100644 --- a/impl/reference/BUILD +++ b/impl/reference/BUILD @@ -59,6 +59,16 @@ cc_test( ], ) +cc_test( + name = "aead_whitebox_benchmark", + size = "small", + deps = [ + ":aead_whitebox", + ":test_data", + "//api:aead_whitebox_benchmark", + ], +) + cc_test( name = "license_whitebox_test", size = "small", @@ -69,6 +79,16 @@ cc_test( ], ) +cc_test( + name = "license_whitebox_benchmark", + size = "small", + deps = [ + ":license_whitebox", + ":test_data", + "//api:license_whitebox_benchmark", + ], +) + cc_library( name = "memory_util", srcs = ["memory_util.cc"], diff --git a/impl/reference/license_whitebox_impl.cc b/impl/reference/license_whitebox_impl.cc index 49a32dc..cf426f1 100644 --- a/impl/reference/license_whitebox_impl.cc +++ b/impl/reference/license_whitebox_impl.cc @@ -54,31 +54,14 @@ struct WB_License_Whitebox { }; namespace { -// Secret string value. For simplicity the pattern will just be 0xAAAAAAAA which -// is xor'd to the buffer, and removed by xor'ing it a second time. +// For simplicity we use a basic pad but we use a different byte for each +// position as we need to support abirtary indexes and we want to make sure that +// a wrong index will actually trigger a failure. const uint8_t kSecretStringPattern[] = { - 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, - 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, }; const size_t kSecretStringPatternSize = sizeof(kSecretStringPattern); -static_assert(kSecretStringPatternSize == AES_BLOCK_SIZE, - "Secret string must be AES_BLOCK_SIZE."); - -void ApplyPattern(const uint8_t* input_buffer, - size_t input_buffer_size, - const uint8_t* pattern, - size_t pattern_size, - uint8_t* output_buffer) { - DCHECK(input_buffer); - DCHECK(pattern); - DCHECK(output_buffer); - DCHECK_GT(pattern_size, 0u); - DCHECK_EQ(input_buffer_size % pattern_size, 0u); - - for (size_t i = 0; i < input_buffer_size; ++i) { - output_buffer[i] = input_buffer[i] ^ pattern[i % pattern_size]; - } -} const ContentKey* FindKey(const WB_License_Whitebox* whitebox, const uint8_t* id, @@ -681,22 +664,31 @@ WB_Result WB_License_MaskedDecrypt(const WB_License_Whitebox* whitebox, // Trivial implementation that simply takes the decrypted output and XORs it // with a fixed pattern. |output|'s size is based on |masked_output_data| so - // we shouldn't need to worry about overflow. - ApplyPattern(output.data(), output.size(), kSecretStringPattern, - kSecretStringPatternSize, masked_output_data); + // we shouldn't need to worry about overflow. This logic must be mirrored in + // Unmask(). + const uint8_t* mask = kSecretStringPattern; + const size_t mask_size = kSecretStringPatternSize; + for (size_t i = 0; i < output.size(); ++i) { + masked_output_data[i] = output[i] ^ mask[i % mask_size]; + } return WB_RESULT_OK; } -void WB_License_Unmask(const uint8_t* secret_string, +void WB_License_Unmask(const uint8_t* masked_data, + size_t offset, + size_t size, + const uint8_t* secret_string, size_t secret_string_size, - uint8_t* buffer, - size_t buffer_size) { + uint8_t* unmasked_data) { // No return code, so only check if parameters are valid. + DCHECK(masked_data); DCHECK(secret_string); - DCHECK(secret_string_size); - DCHECK(buffer); - DCHECK(buffer_size); + DCHECK(unmasked_data); - ApplyPattern(buffer, buffer_size, secret_string, secret_string_size, buffer); + for (size_t local_i = 0; local_i < size; local_i++) { + const size_t global_i = offset + local_i; + const uint8_t mask = secret_string[global_i % secret_string_size]; + unmasked_data[local_i] = masked_data[global_i] ^ mask; + } }