Skip to content

Commit 8a8610a

Browse files
authored
Implement Base32Decode (flutter#12253)
For flutter#32170 This is to enable reading back SkSL persistent cache filenames and decode them as SkData.
1 parent aac33d1 commit 8a8610a

File tree

3 files changed

+128
-10
lines changed

3 files changed

+128
-10
lines changed

fml/base32.cc

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,52 @@ std::pair<bool, std::string> Base32Encode(std::string_view input) {
2424
const size_t encoded_length = (input.size() * 8 + 4) / 5;
2525
output.reserve(encoded_length);
2626

27-
uint16_t bit_stream = (static_cast<uint8_t>(input[0]) << 8);
27+
Base32EncodeConverter converter;
28+
converter.Append(input[0]);
2829
size_t next_byte_index = 1;
29-
int free_bits = 8;
3030

31-
while (free_bits < 16) {
32-
output.push_back(kEncoding[(bit_stream & 0xf800) >> 11]);
33-
bit_stream <<= 5;
34-
free_bits += 5;
35-
36-
if (free_bits >= 8 && next_byte_index < input.size()) {
37-
free_bits -= 8;
38-
bit_stream += static_cast<uint8_t>(input[next_byte_index++]) << free_bits;
31+
while (converter.CanExtract()) {
32+
output.push_back(kEncoding[converter.Extract()]);
33+
if (converter.CanAppend() && next_byte_index < input.size()) {
34+
converter.Append(static_cast<uint8_t>(input[next_byte_index++]));
3935
}
4036
}
4137

38+
if (converter.BitsAvailable() > 0) {
39+
output.push_back(kEncoding[converter.Peek()]);
40+
}
41+
4242
return {true, output};
4343
}
4444

45+
static constexpr signed char kDecodeMap[] = {
46+
// starting from ASCII 50 '2'
47+
26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1,
48+
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
49+
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};
50+
51+
static constexpr int kDecodeMapSize =
52+
sizeof(kDecodeMap) / sizeof(kDecodeMap[0]);
53+
54+
std::pair<bool, std::string> Base32Decode(const std::string& input) {
55+
std::string result;
56+
Base32DecodeConverter converter;
57+
for (char c : input) {
58+
int map_index = c - '2';
59+
if (map_index < 0 || map_index >= kDecodeMapSize ||
60+
kDecodeMap[map_index] == -1) {
61+
return {false, result};
62+
}
63+
converter.Append(kDecodeMap[map_index]);
64+
if (converter.CanExtract()) {
65+
result.push_back(converter.Extract());
66+
}
67+
}
68+
if (converter.Peek() != 0) {
69+
// The padding should always be zero. Return false if not.
70+
return {false, result};
71+
}
72+
return {true, result};
73+
}
74+
4575
} // namespace fml

fml/base32.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,49 @@
88
#include <string_view>
99
#include <utility>
1010

11+
#include "flutter/fml/logging.h"
12+
1113
namespace fml {
1214

15+
template <int from_length, int to_length, int buffer_length>
16+
class BitConverter {
17+
public:
18+
void Append(int bits) {
19+
FML_DCHECK(bits < (1 << from_length));
20+
FML_DCHECK(CanAppend());
21+
lower_free_bits_ -= from_length;
22+
buffer_ |= (bits << lower_free_bits_);
23+
}
24+
25+
int Extract() {
26+
FML_DCHECK(CanExtract());
27+
int result = Peek();
28+
buffer_ = (buffer_ << to_length) & mask_;
29+
lower_free_bits_ += to_length;
30+
return result;
31+
}
32+
33+
int Peek() const { return (buffer_ >> (buffer_length - to_length)); }
34+
int BitsAvailable() const { return buffer_length - lower_free_bits_; }
35+
bool CanAppend() const { return lower_free_bits_ >= from_length; }
36+
bool CanExtract() const { return BitsAvailable() >= to_length; }
37+
38+
private:
39+
static_assert(buffer_length >= 2 * from_length);
40+
static_assert(buffer_length >= 2 * to_length);
41+
static_assert(buffer_length < sizeof(int) * 8);
42+
43+
static constexpr int mask_ = (1 << buffer_length) - 1;
44+
45+
int buffer_ = 0;
46+
int lower_free_bits_ = buffer_length;
47+
};
48+
49+
using Base32DecodeConverter = BitConverter<5, 8, 16>;
50+
using Base32EncodeConverter = BitConverter<8, 5, 16>;
51+
1352
std::pair<bool, std::string> Base32Encode(std::string_view input);
53+
std::pair<bool, std::string> Base32Decode(const std::string& input);
1454

1555
} // namespace fml
1656

fml/base32_unittest.cc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include "flutter/fml/base32.h"
66
#include "gtest/gtest.h"
77

8+
#include <iostream>
9+
810
TEST(Base32Test, CanEncode) {
911
{
1012
auto result = fml::Base32Encode("hello");
@@ -36,3 +38,49 @@ TEST(Base32Test, CanEncode) {
3638
ASSERT_EQ(result.second, "NBSWYTDP");
3739
}
3840
}
41+
42+
TEST(Base32Test, CanEncodeDecodeStrings) {
43+
std::vector<std::string> strings = {"hello", "helLo", "", "1", "\0"};
44+
for (size_t i = 0; i < strings.size(); i += 1) {
45+
auto encode_result = fml::Base32Encode(strings[i]);
46+
ASSERT_TRUE(encode_result.first);
47+
auto decode_result = fml::Base32Decode(encode_result.second);
48+
ASSERT_TRUE(decode_result.first);
49+
const std::string& decoded = decode_result.second;
50+
std::string decoded_string(decoded.data(), decoded.size());
51+
ASSERT_EQ(strings[i], decoded_string);
52+
}
53+
}
54+
55+
TEST(Base32Test, DecodeReturnsFalseForInvalideInput) {
56+
// "B" is invalid because it has a non-zero padding.
57+
std::vector<std::string> invalid_inputs = {"a", "1", "9", "B"};
58+
for (const std::string& input : invalid_inputs) {
59+
auto decode_result = fml::Base32Decode(input);
60+
if (decode_result.first) {
61+
std::cout << "Base32Decode should return false on " << input << std::endl;
62+
}
63+
ASSERT_FALSE(decode_result.first);
64+
}
65+
}
66+
67+
TEST(Base32Test, CanDecodeSkSLKeys) {
68+
std::vector<std::string> inputs = {
69+
"CAZAAAACAAAAADQAAAABKAAAAAJQAAIA7777777777776EYAAEAP777777777777AAAAAABA"
70+
"ABTAAAAAAAAAAAAAAABAAAAAGQAGGAA",
71+
"CAZAAAICAAAAAAAAAAADOAAAAAJQAAIA777777Y4AAKAAEYAAEAP777777777777EAAGMAAA"
72+
"AAAAAAAAAAACQACNAAAAAAAAAAAAAAACAAAAAPAAMMAA",
73+
"CAZACAACAAAABAYACAAAAAAAAAJQAAIADQABIAH777777777777RQAAOAAAAAAAAAAAAAABE"
74+
"AANQAAAAAAAAAAAAAAYAAJYAAAAAAAANAAAQAAAAAAAEAAAHAAAAAAAAAAAAAAANAAAQAAAA"
75+
"AAAFIADKAAAAAAAAAAAAAAACAAAAAZAAMMAA"};
76+
for (const std::string& input : inputs) {
77+
auto decode_result = fml::Base32Decode(input);
78+
if (!decode_result.first) {
79+
std::cout << "Base32Decode should return true on " << input << std::endl;
80+
}
81+
ASSERT_TRUE(decode_result.first);
82+
auto encode_result = fml::Base32Encode(decode_result.second);
83+
ASSERT_TRUE(encode_result.first);
84+
ASSERT_EQ(encode_result.second, input);
85+
}
86+
}

0 commit comments

Comments
 (0)