diff --git a/fml/base32.cc b/fml/base32.cc index f988dabce3b62..1cd2992825ea5 100644 --- a/fml/base32.cc +++ b/fml/base32.cc @@ -24,22 +24,52 @@ std::pair Base32Encode(std::string_view input) { const size_t encoded_length = (input.size() * 8 + 4) / 5; output.reserve(encoded_length); - uint16_t bit_stream = (static_cast(input[0]) << 8); + Base32EncodeConverter converter; + converter.Append(input[0]); size_t next_byte_index = 1; - int free_bits = 8; - while (free_bits < 16) { - output.push_back(kEncoding[(bit_stream & 0xf800) >> 11]); - bit_stream <<= 5; - free_bits += 5; - - if (free_bits >= 8 && next_byte_index < input.size()) { - free_bits -= 8; - bit_stream += static_cast(input[next_byte_index++]) << free_bits; + while (converter.CanExtract()) { + output.push_back(kEncoding[converter.Extract()]); + if (converter.CanAppend() && next_byte_index < input.size()) { + converter.Append(static_cast(input[next_byte_index++])); } } + if (converter.BitsAvailable() > 0) { + output.push_back(kEncoding[converter.Peek()]); + } + return {true, output}; } +static constexpr signed char kDecodeMap[] = { + // starting from ASCII 50 '2' + 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -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}; + +static constexpr int kDecodeMapSize = + sizeof(kDecodeMap) / sizeof(kDecodeMap[0]); + +std::pair Base32Decode(const std::string& input) { + std::string result; + Base32DecodeConverter converter; + for (char c : input) { + int map_index = c - '2'; + if (map_index < 0 || map_index >= kDecodeMapSize || + kDecodeMap[map_index] == -1) { + return {false, result}; + } + converter.Append(kDecodeMap[map_index]); + if (converter.CanExtract()) { + result.push_back(converter.Extract()); + } + } + if (converter.Peek() != 0) { + // The padding should always be zero. Return false if not. + return {false, result}; + } + return {true, result}; +} + } // namespace fml diff --git a/fml/base32.h b/fml/base32.h index 0b570b2c04556..bd8199b116381 100644 --- a/fml/base32.h +++ b/fml/base32.h @@ -8,9 +8,49 @@ #include #include +#include "flutter/fml/logging.h" + namespace fml { +template +class BitConverter { + public: + void Append(int bits) { + FML_DCHECK(bits < (1 << from_length)); + FML_DCHECK(CanAppend()); + lower_free_bits_ -= from_length; + buffer_ |= (bits << lower_free_bits_); + } + + int Extract() { + FML_DCHECK(CanExtract()); + int result = Peek(); + buffer_ = (buffer_ << to_length) & mask_; + lower_free_bits_ += to_length; + return result; + } + + int Peek() const { return (buffer_ >> (buffer_length - to_length)); } + int BitsAvailable() const { return buffer_length - lower_free_bits_; } + bool CanAppend() const { return lower_free_bits_ >= from_length; } + bool CanExtract() const { return BitsAvailable() >= to_length; } + + private: + static_assert(buffer_length >= 2 * from_length); + static_assert(buffer_length >= 2 * to_length); + static_assert(buffer_length < sizeof(int) * 8); + + static constexpr int mask_ = (1 << buffer_length) - 1; + + int buffer_ = 0; + int lower_free_bits_ = buffer_length; +}; + +using Base32DecodeConverter = BitConverter<5, 8, 16>; +using Base32EncodeConverter = BitConverter<8, 5, 16>; + std::pair Base32Encode(std::string_view input); +std::pair Base32Decode(const std::string& input); } // namespace fml diff --git a/fml/base32_unittest.cc b/fml/base32_unittest.cc index efd2543396ae0..fc4a46a29e5e1 100644 --- a/fml/base32_unittest.cc +++ b/fml/base32_unittest.cc @@ -5,6 +5,8 @@ #include "flutter/fml/base32.h" #include "gtest/gtest.h" +#include + TEST(Base32Test, CanEncode) { { auto result = fml::Base32Encode("hello"); @@ -36,3 +38,49 @@ TEST(Base32Test, CanEncode) { ASSERT_EQ(result.second, "NBSWYTDP"); } } + +TEST(Base32Test, CanEncodeDecodeStrings) { + std::vector strings = {"hello", "helLo", "", "1", "\0"}; + for (size_t i = 0; i < strings.size(); i += 1) { + auto encode_result = fml::Base32Encode(strings[i]); + ASSERT_TRUE(encode_result.first); + auto decode_result = fml::Base32Decode(encode_result.second); + ASSERT_TRUE(decode_result.first); + const std::string& decoded = decode_result.second; + std::string decoded_string(decoded.data(), decoded.size()); + ASSERT_EQ(strings[i], decoded_string); + } +} + +TEST(Base32Test, DecodeReturnsFalseForInvalideInput) { + // "B" is invalid because it has a non-zero padding. + std::vector invalid_inputs = {"a", "1", "9", "B"}; + for (const std::string& input : invalid_inputs) { + auto decode_result = fml::Base32Decode(input); + if (decode_result.first) { + std::cout << "Base32Decode should return false on " << input << std::endl; + } + ASSERT_FALSE(decode_result.first); + } +} + +TEST(Base32Test, CanDecodeSkSLKeys) { + std::vector inputs = { + "CAZAAAACAAAAADQAAAABKAAAAAJQAAIA7777777777776EYAAEAP777777777777AAAAAABA" + "ABTAAAAAAAAAAAAAAABAAAAAGQAGGAA", + "CAZAAAICAAAAAAAAAAADOAAAAAJQAAIA777777Y4AAKAAEYAAEAP777777777777EAAGMAAA" + "AAAAAAAAAAACQACNAAAAAAAAAAAAAAACAAAAAPAAMMAA", + "CAZACAACAAAABAYACAAAAAAAAAJQAAIADQABIAH777777777777RQAAOAAAAAAAAAAAAAABE" + "AANQAAAAAAAAAAAAAAYAAJYAAAAAAAANAAAQAAAAAAAEAAAHAAAAAAAAAAAAAAANAAAQAAAA" + "AAAFIADKAAAAAAAAAAAAAAACAAAAAZAAMMAA"}; + for (const std::string& input : inputs) { + auto decode_result = fml::Base32Decode(input); + if (!decode_result.first) { + std::cout << "Base32Decode should return true on " << input << std::endl; + } + ASSERT_TRUE(decode_result.first); + auto encode_result = fml::Base32Encode(decode_result.second); + ASSERT_TRUE(encode_result.first); + ASSERT_EQ(encode_result.second, input); + } +}