Skip to content

Commit

Permalink
Implement Base32Decode (#12253)
Browse files Browse the repository at this point in the history
For #32170

This is to enable reading back SkSL persistent cache filenames
and decode them as SkData.
  • Loading branch information
liyuqian committed Sep 16, 2019
1 parent aac33d1 commit 8a8610a
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 10 deletions.
50 changes: 40 additions & 10 deletions fml/base32.cc
Expand Up @@ -24,22 +24,52 @@ std::pair<bool, std::string> 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<bool, std::string> 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
40 changes: 40 additions & 0 deletions fml/base32.h
Expand Up @@ -8,9 +8,49 @@
#include <string_view>
#include <utility>

#include "flutter/fml/logging.h"

namespace fml {

template <int from_length, int to_length, int buffer_length>
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<bool, std::string> Base32Encode(std::string_view input);
std::pair<bool, std::string> Base32Decode(const std::string& input);

} // namespace fml

Expand Down
48 changes: 48 additions & 0 deletions fml/base32_unittest.cc
Expand Up @@ -5,6 +5,8 @@
#include "flutter/fml/base32.h"
#include "gtest/gtest.h"

#include <iostream>

TEST(Base32Test, CanEncode) {
{
auto result = fml::Base32Encode("hello");
Expand Down Expand Up @@ -36,3 +38,49 @@ TEST(Base32Test, CanEncode) {
ASSERT_EQ(result.second, "NBSWYTDP");
}
}

TEST(Base32Test, CanEncodeDecodeStrings) {
std::vector<std::string> 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<std::string> 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<std::string> 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);
}
}

0 comments on commit 8a8610a

Please sign in to comment.