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; + } }