diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index b4d58de3f1185..718d678c8c559 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -231,6 +231,7 @@ ../../../flutter/runtime/no_dart_plugin_registrant_unittests.cc ../../../flutter/runtime/type_conversions_unittests.cc ../../../flutter/shell/common/animator_unittests.cc +../../../flutter/shell/common/base64_unittests.cc ../../../flutter/shell/common/context_options_unittests.cc ../../../flutter/shell/common/dl_op_spy_unittests.cc ../../../flutter/shell/common/engine_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 84d3c9fd3f98a..3a9223f770030 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2246,6 +2246,8 @@ ORIGIN: ../../../flutter/runtime/test_font_data.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/runtime/test_font_data.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/common/animator.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/common/animator.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/common/base64.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/common/base64.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/common/context_options.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/common/context_options.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/common/dart_native_benchmarks.cc + ../../../flutter/LICENSE @@ -5005,6 +5007,8 @@ FILE: ../../../flutter/runtime/test_font_data.cc FILE: ../../../flutter/runtime/test_font_data.h FILE: ../../../flutter/shell/common/animator.cc FILE: ../../../flutter/shell/common/animator.h +FILE: ../../../flutter/shell/common/base64.cc +FILE: ../../../flutter/shell/common/base64.h FILE: ../../../flutter/shell/common/context_options.cc FILE: ../../../flutter/shell/common/context_options.h FILE: ../../../flutter/shell/common/dart_native_benchmarks.cc diff --git a/common/graphics/BUILD.gn b/common/graphics/BUILD.gn index 70fbc1ad5a614..61d410d4f864d 100644 --- a/common/graphics/BUILD.gn +++ b/common/graphics/BUILD.gn @@ -24,6 +24,7 @@ source_set("graphics") { "//flutter/assets", "//flutter/display_list", "//flutter/fml", + "//flutter/shell/common:base64", "//flutter/shell/version:version", "//third_party/boringssl", "//third_party/rapidjson", diff --git a/common/graphics/persistent_cache.cc b/common/graphics/persistent_cache.cc index 50d09bb8fcbc6..0d24125d1f00b 100644 --- a/common/graphics/persistent_cache.cc +++ b/common/graphics/persistent_cache.cc @@ -18,11 +18,11 @@ #include "flutter/fml/mapping.h" #include "flutter/fml/paths.h" #include "flutter/fml/trace_event.h" +#include "flutter/shell/common/base64.h" #include "flutter/shell/version/version.h" #include "openssl/sha.h" #include "rapidjson/document.h" #include "third_party/skia/include/gpu/GrDirectContext.h" -#include "third_party/skia/include/utils/SkBase64.h" namespace flutter { @@ -169,21 +169,21 @@ sk_sp ParseBase32(const std::string& input) { } sk_sp ParseBase64(const std::string& input) { - SkBase64::Error error; + Base64::Error error; size_t output_len; - error = SkBase64::Decode(input.c_str(), input.length(), nullptr, &output_len); - if (error != SkBase64::Error::kNoError) { - FML_LOG(ERROR) << "Base64 decode error: " << error; + error = Base64::Decode(input.c_str(), input.length(), nullptr, &output_len); + if (error != Base64::Error::kNone) { + FML_LOG(ERROR) << "Base64 decode error: " << (int)error; FML_LOG(ERROR) << "Base64 can't decode: " << input; return nullptr; } sk_sp data = SkData::MakeUninitialized(output_len); void* output = data->writable_data(); - error = SkBase64::Decode(input.c_str(), input.length(), output, &output_len); - if (error != SkBase64::Error::kNoError) { - FML_LOG(ERROR) << "Base64 decode error: " << error; + error = Base64::Decode(input.c_str(), input.length(), output, &output_len); + if (error != Base64::Error::kNone) { + FML_LOG(ERROR) << "Base64 decode error: " << (int)error; FML_LOG(ERROR) << "Base64 can't decode: " << input; return nullptr; } diff --git a/flow/BUILD.gn b/flow/BUILD.gn index a75605749c7aa..492126c2a3086 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -188,6 +188,7 @@ if (enable_unittests) { "//flutter/common/graphics", "//flutter/display_list/testing:display_list_testing", "//flutter/fml", + "//flutter/shell/common:base64", "//flutter/testing:skia", "//flutter/testing:testing_lib", "//third_party/dart/runtime:libdart_jit", # for tracing diff --git a/flow/layers/performance_overlay_layer_unittests.cc b/flow/layers/performance_overlay_layer_unittests.cc index e1bbb29893268..0c4f7e11d3a2a 100644 --- a/flow/layers/performance_overlay_layer_unittests.cc +++ b/flow/layers/performance_overlay_layer_unittests.cc @@ -10,6 +10,7 @@ #include "flutter/flow/flow_test_utils.h" #include "flutter/flow/raster_cache.h" #include "flutter/flow/testing/layer_test.h" +#include "flutter/shell/common/base64.h" #include "flutter/testing/mock_canvas.h" #include "third_party/skia/include/core/SkData.h" #include "third_party/skia/include/core/SkImage.h" @@ -18,7 +19,6 @@ #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/core/SkTextBlob.h" #include "third_party/skia/include/encode/SkPngEncoder.h" -#include "third_party/skia/include/utils/SkBase64.h" namespace flutter { namespace testing { @@ -111,11 +111,13 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { wstream.write(snapshot_data->data(), snapshot_data->size()); wstream.flush(); + // TODO(kjlubick) We shouldn't need to call Encode once to pre-flight the + // encode length. It should be ceil(4/3 * sksl.value->size()). size_t b64_size = - SkBase64::Encode(snapshot_data->data(), snapshot_data->size(), nullptr); + Base64::Encode(snapshot_data->data(), snapshot_data->size(), nullptr); sk_sp b64_data = SkData::MakeUninitialized(b64_size + 1); char* b64_char = static_cast(b64_data->writable_data()); - SkBase64::Encode(snapshot_data->data(), snapshot_data->size(), b64_char); + Base64::Encode(snapshot_data->data(), snapshot_data->size(), b64_char); b64_char[b64_size] = 0; // make it null terminated for printing EXPECT_TRUE(golden_data_matches) diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index c5a4db91e4333..31d89012a58d3 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -143,6 +143,7 @@ source_set("common") { "//flutter/fml", "//flutter/lib/ui", "//flutter/runtime", + "//flutter/shell/common:base64", "//flutter/shell/profiling", "//third_party/dart/runtime:dart_api", "//third_party/skia", @@ -158,6 +159,15 @@ source_set("common") { } } +# These are in their own source_set to avoid a dependency cycle with //common/graphics +source_set("base64") { + sources = [ + "base64.cc", + "base64.h", + ] + deps = [ "//flutter/fml" ] +} + template("shell_host_executable") { executable(target_name) { testonly = true @@ -293,6 +303,7 @@ if (enable_unittests) { sources = [ "animator_unittests.cc", + "base64_unittests.cc", "context_options_unittests.cc", "dl_op_spy_unittests.cc", "engine_unittests.cc", @@ -312,6 +323,7 @@ if (enable_unittests) { ":shell_unittests_fixtures", "//flutter/assets", "//flutter/common/graphics", + "//flutter/shell/common:base64", "//flutter/shell/profiling:profiling_unittests", "//flutter/shell/version", "//flutter/testing:fixture_test", diff --git a/shell/common/base64.cc b/shell/common/base64.cc new file mode 100644 index 0000000000000..f026f9b8fc2bb --- /dev/null +++ b/shell/common/base64.cc @@ -0,0 +1,157 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/common/base64.h" + +#include "flutter/fml/logging.h" + +#include + +#define DecodePad -2 +#define EncodePad 64 + +static const char kDefaultEncode[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/="; + +static const signed char kDecodeData[] = { + 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, + -1, -1, DecodePad, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51}; + +namespace flutter { + +Base64::Error Base64::Decode(const void* srcv, + size_t srcLength, + void* dstv, + size_t* dstLength) { + const unsigned char* src = static_cast(srcv); + unsigned char* dst = static_cast(dstv); + + int i = 0; + bool padTwo = false; + bool padThree = false; + char unsigned const* const end = src + srcLength; + while (src < end) { + unsigned char bytes[4] = {0, 0, 0, 0}; + int byte = 0; + do { + unsigned char srcByte = *src++; + if (srcByte == 0) { + *dstLength = i; + return Error::kNone; + } + if (srcByte <= ' ') { + continue; // treat as white space + } + if (srcByte < '+' || srcByte > 'z') { + return Error::kBadChar; + } + signed char decoded = kDecodeData[srcByte - '+']; + bytes[byte] = decoded; + if (decoded != DecodePad) { + if (decoded < 0) { + return Error::kBadChar; + } + byte++; + if (*src) { + continue; + } + if (byte == 0) { + *dstLength = i; + return Error::kNone; + } + if (byte == 4) { + break; + } + } + // As an optimization, if we find an equals sign + // we assume all future bytes to read are the + // appropriate number of padding equals signs. + if (byte < 2) { + return Error::kBadPadding; + } + padThree = true; + if (byte == 2) { + padTwo = true; + } + break; + } while (byte < 4); + int two = 0; + int three = 0; + if (dst) { + int one = (uint8_t)(bytes[0] << 2); + two = bytes[1]; + one |= two >> 4; + two = (uint8_t)((two << 4) & 0xFF); + three = bytes[2]; + two |= three >> 2; + three = (uint8_t)((three << 6) & 0xFF); + three |= bytes[3]; + FML_DCHECK(one < 256 && two < 256 && three < 256); + dst[i] = (unsigned char)one; + } + i++; + if (padTwo) { + break; + } + if (dst) { + dst[i] = (unsigned char)two; + } + i++; + if (padThree) { + break; + } + if (dst) { + dst[i] = (unsigned char)three; + } + i++; + } + *dstLength = i; + return Error::kNone; +} + +size_t Base64::Encode(const void* srcv, size_t length, void* dstv) { + const unsigned char* src = static_cast(srcv); + unsigned char* dst = static_cast(dstv); + + const char* encode = kDefaultEncode; + if (dst) { + size_t remainder = length % 3; + char unsigned const* const end = &src[length - remainder]; + while (src < end) { + unsigned a = *src++; + unsigned b = *src++; + unsigned c = *src++; + int d = c & 0x3F; + c = (c >> 6 | b << 2) & 0x3F; + b = (b >> 4 | a << 4) & 0x3F; + a = a >> 2; + *dst++ = encode[a]; + *dst++ = encode[b]; + *dst++ = encode[c]; + *dst++ = encode[d]; + } + if (remainder > 0) { + int k1 = 0; + int k2 = EncodePad; + int a = (uint8_t)*src++; + if (remainder == 2) { + int b = *src++; + k1 = b >> 4; + k2 = (b << 2) & 0x3F; + } + *dst++ = encode[a >> 2]; + *dst++ = encode[(k1 | a << 4) & 0x3F]; + *dst++ = encode[k2]; + *dst++ = encode[EncodePad]; + } + } + return (length + 2) / 3 * 4; +} + +} // namespace flutter diff --git a/shell/common/base64.h b/shell/common/base64.h new file mode 100644 index 0000000000000..9e24cea437887 --- /dev/null +++ b/shell/common/base64.h @@ -0,0 +1,53 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_COMMON_BASE64_H_ +#define FLUTTER_SHELL_COMMON_BASE64_H_ + +#include + +namespace flutter { + +struct Base64 { + public: + enum class Error { + kNone, + kBadPadding, + kBadChar, + }; + + /** + Base64 encodes src into dst. + + Normally this is called once with 'dst' nullptr to get the required size, + then again with an allocated 'dst' pointer to do the actual encoding. + + @param dst nullptr or a pointer to a buffer large enough to receive the + result + + @return the required length of dst for encoding. + */ + static size_t Encode(const void* src, size_t length, void* dst); + + /** + Base64 decodes src into dst. + + Normally this is called once with 'dst' nullptr to get the required size, + then again with an allocated 'dst' pointer to do the actual encoding. + + @param dst nullptr or a pointer to a buffer large enough to receive the + result + + @param dstLength assigned the length dst is required to be. Must not be + nullptr. + */ + [[nodiscard]] static Error Decode(const void* src, + size_t srcLength, + void* dst, + size_t* dstLength); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_COMMON_BASE64_H_ diff --git a/shell/common/base64_unittests.cc b/shell/common/base64_unittests.cc new file mode 100644 index 0000000000000..89ae026d034c1 --- /dev/null +++ b/shell/common/base64_unittests.cc @@ -0,0 +1,120 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/common/base64.h" + +#include "fml/logging.h" +#include "gtest/gtest.h" + +#include + +namespace flutter { +namespace testing { + +TEST(Base64, EncodeStrings) { + auto test = [](const std::string& input, const std::string& output) { + char buffer[256]; + size_t len = Base64::Encode(input.c_str(), input.length(), &buffer); + FML_CHECK(len <= 256); + std::string actual(buffer, len); + ASSERT_STREQ(actual.c_str(), output.c_str()); + }; + // Some arbitrary strings + test("apple", "YXBwbGU="); + test("BANANA", "QkFOQU5B"); + test("Cherry Pie", "Q2hlcnJ5IFBpZQ=="); + test("fLoCcInAuCiNiHiLiPiLiFiCaTiOn", + "ZkxvQ2NJbkF1Q2lOaUhpTGlQaUxpRmlDYVRpT24="); + test("", ""); +} + +TEST(Base64, EncodeBytes) { + auto test = [](const uint8_t input[], size_t num, const std::string& output) { + char buffer[512]; + size_t len = Base64::Encode(input, num, &buffer); + FML_CHECK(len <= 512); + std::string actual(buffer, len); + ASSERT_STREQ(actual.c_str(), output.c_str()); + }; + // Some arbitrary raw bytes + uint8_t e[] = {0x02, 0x71, 0x82, 0x81, 0x82, 0x84, 0x59}; + test(e, sizeof(e), "AnGCgYKEWQ=="); + + uint8_t pi[] = {0x03, 0x24, 0x3F, 0x6A, 0x88, 0x85}; + test(pi, sizeof(pi), "AyQ/aoiF"); + + uint8_t bytes[256]; + for (int i = 0; i < 256; i++) { + bytes[i] = i; + } + test(bytes, sizeof(bytes), + "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gIS" + "IjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFV" + "WV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJ" + "iouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8v" + "b6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8P" + "Hy8/T19vf4+fr7/P3+/w=="); +} + +TEST(Base64, DecodeStringsSuccess) { + auto test = [](const std::string& input, const std::string& output) { + char buffer[256]; + size_t len = 0; + auto err = Base64::Decode(input.c_str(), input.length(), &buffer, &len); + ASSERT_EQ(err, Base64::Error::kNone); + FML_CHECK(len <= 256); + std::string actual(buffer, len); + ASSERT_STREQ(actual.c_str(), output.c_str()); + }; + // Some arbitrary strings + test("ZGF0ZQ==", "date"); + test("RWdncGxhbnQ=", "Eggplant"); + test("RmlzaCAmIENoaXBz", "Fish & Chips"); + test("U3VQZVJjQWxJZlJhR2lMaVN0SWNFeFBpQWxJZE9jSW9Vcw==", + "SuPeRcAlIfRaGiLiStIcExPiAlIdOcIoUs"); + + // Spaces are ignored + test("Y X Bwb GU=", "apple"); +} + +TEST(Base64, DecodeStringsHasErrors) { + auto test = [](const std::string& input, Base64::Error expectedError) { + char buffer[256]; + size_t len = 0; + auto err = Base64::Decode(input.c_str(), input.length(), &buffer, &len); + ASSERT_EQ(err, expectedError) << input; + }; + + test("Nuts&Bolts", Base64::Error::kBadChar); + test("Error!", Base64::Error::kBadChar); + test(":", Base64::Error::kBadChar); + + test("RmlzaCAmIENoaXBz=", Base64::Error::kBadPadding); + // Some cases of bad padding may be ignored due to an internal optimization + // test("ZGF0ZQ=", Base64::Error::kBadPadding); + // test("RWdncGxhbnQ", Base64::Error::kBadPadding); +} + +TEST(Base64, DecodeBytes) { + auto test = [](const std::string& input, const uint8_t output[], size_t num) { + char buffer[256]; + size_t len = 0; + auto err = Base64::Decode(input.c_str(), input.length(), &buffer, &len); + ASSERT_EQ(err, Base64::Error::kNone); + FML_CHECK(len <= 256); + ASSERT_EQ(num, len) << input; + for (int i = 0; i < int(len); i++) { + ASSERT_EQ(uint8_t(buffer[i]), output[i]) << input << i; + } + }; + // Some arbitrary raw bytes, same as the byte output above + uint8_t e[] = {0x02, 0x71, 0x82, 0x81, 0x82, 0x84, 0x59}; + test("AnGCgYKEWQ==", e, sizeof(e)); + + uint8_t pi[] = {0x03, 0x24, 0x3F, 0x6A, 0x88, 0x85}; + test("AyQ/aoiF", pi, sizeof(pi)); +} + +} // namespace testing +} // namespace flutter \ No newline at end of file diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 7ab349d353f37..10c6429304ed5 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -14,6 +14,7 @@ #include "flutter/flow/layers/offscreen_surface.h" #include "flutter/fml/time/time_delta.h" #include "flutter/fml/time/time_point.h" +#include "flutter/shell/common/base64.h" #include "flutter/shell/common/serialization_callbacks.h" #include "fml/make_copyable.h" #include "third_party/skia/include/core/SkColorSpace.h" @@ -32,7 +33,6 @@ #include "third_party/skia/include/gpu/GrDirectContext.h" #include "third_party/skia/include/gpu/GrTypes.h" #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" -#include "third_party/skia/include/utils/SkBase64.h" namespace flutter { @@ -910,9 +910,11 @@ Rasterizer::Screenshot Rasterizer::ScreenshotLastLayerTree( } if (base64_encode) { - size_t b64_size = SkBase64::Encode(data->data(), data->size(), nullptr); + // TODO(kjlubick) We shouldn't need to call Encode once to pre-flight the + // encode length. It should be ceil(4/3 * sksl.value->size()). + size_t b64_size = Base64::Encode(data->data(), data->size(), nullptr); auto b64_data = SkData::MakeUninitialized(b64_size); - SkBase64::Encode(data->data(), data->size(), b64_data->writable_data()); + Base64::Encode(data->data(), data->size(), b64_data->writable_data()); return Rasterizer::Screenshot{b64_data, layer_tree->frame_size(), format}; } diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 44e5d2a65e110..20fcebeeb7d70 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -23,6 +23,7 @@ #include "flutter/fml/paths.h" #include "flutter/fml/trace_event.h" #include "flutter/runtime/dart_vm.h" +#include "flutter/shell/common/base64.h" #include "flutter/shell/common/engine.h" #include "flutter/shell/common/skia_event_tracer_impl.h" #include "flutter/shell/common/switches.h" @@ -39,7 +40,6 @@ #include "third_party/skia/include/codec/SkWbmpDecoder.h" #include "third_party/skia/include/codec/SkWebpDecoder.h" #include "third_party/skia/include/core/SkGraphics.h" -#include "third_party/skia/include/utils/SkBase64.h" #include "third_party/tonic/common/log.h" namespace flutter { @@ -1840,11 +1840,13 @@ bool Shell::OnServiceProtocolGetSkSLs( PersistentCache* persistent_cache = PersistentCache::GetCacheForProcess(); std::vector sksls = persistent_cache->LoadSkSLs(); for (const auto& sksl : sksls) { + // TODO(kjlubick) We shouldn't need to call Encode once to pre-flight the + // encode length. It should be ceil(4/3 * sksl.value->size()). size_t b64_size = - SkBase64::Encode(sksl.value->data(), sksl.value->size(), nullptr); + Base64::Encode(sksl.value->data(), sksl.value->size(), nullptr); sk_sp b64_data = SkData::MakeUninitialized(b64_size + 1); char* b64_char = static_cast(b64_data->writable_data()); - SkBase64::Encode(sksl.value->data(), sksl.value->size(), b64_char); + Base64::Encode(sksl.value->data(), sksl.value->size(), b64_char); b64_char[b64_size] = 0; // make it null terminated for printing rapidjson::Value shader_value(b64_char, response->GetAllocator()); std::string_view key_view(reinterpret_cast(sksl.key->data()),