Base64: Pre-allocate size of input and output

Problem:
- Output of decode and encode grow as the decode and encode
  happen. This is inefficient because a large size will require many
  reallocations.
- `const` qualifiers are missing on variables which are not intended
  to change.

Solution:
- Since the size of the decoded or encoded message is known prior to
  starting, calculate the size and set the output to that size
  immediately. All appends will not incur the reallocation overhead.
- Add `const` qualifiers to show intent.
This commit is contained in:
Lenny Maiorani 2020-10-13 10:48:48 -04:00 committed by Andreas Kling
parent 8f535435dc
commit 2983215fb1
Notes: sideshowbarker 2024-07-19 01:54:34 +09:00
3 changed files with 41 additions and 22 deletions

View file

@ -62,9 +62,19 @@ static constexpr auto make_lookup_table()
return table; return table;
} }
size_t calculate_base64_decoded_length(const StringView& input)
{
return input.length() * 3 / 4;
}
size_t calculate_base64_encoded_length(ReadonlyBytes input)
{
return ((4 * input.size() / 3) + 3) & ~3;
}
ByteBuffer decode_base64(const StringView& input) ByteBuffer decode_base64(const StringView& input)
{ {
auto get = [&](size_t offset, bool* is_padding = nullptr) -> u8 { auto get = [&](const size_t offset, bool* is_padding = nullptr) -> u8 {
constexpr auto table = make_lookup_table(); constexpr auto table = make_lookup_table();
if (offset >= input.length()) if (offset >= input.length())
return 0; return 0;
@ -77,19 +87,20 @@ ByteBuffer decode_base64(const StringView& input)
}; };
Vector<u8> output; Vector<u8> output;
output.ensure_capacity(calculate_base64_decoded_length(input));
for (size_t i = 0; i < input.length(); i += 4) { for (size_t i = 0; i < input.length(); i += 4) {
bool in2_is_padding = false; bool in2_is_padding = false;
bool in3_is_padding = false; bool in3_is_padding = false;
u8 in0 = get(i); const u8 in0 = get(i);
u8 in1 = get(i + 1); const u8 in1 = get(i + 1);
u8 in2 = get(i + 2, &in2_is_padding); const u8 in2 = get(i + 2, &in2_is_padding);
u8 in3 = get(i + 3, &in3_is_padding); const u8 in3 = get(i + 3, &in3_is_padding);
u8 out0 = (in0 << 2) | ((in1 >> 4) & 3); const u8 out0 = (in0 << 2) | ((in1 >> 4) & 3);
u8 out1 = ((in1 & 0xf) << 4) | ((in2 >> 2) & 0xf); const u8 out1 = ((in1 & 0xf) << 4) | ((in2 >> 2) & 0xf);
u8 out2 = ((in2 & 0x3) << 6) | in3; const u8 out2 = ((in2 & 0x3) << 6) | in3;
output.append(out0); output.append(out0);
if (!in2_is_padding) if (!in2_is_padding)
@ -104,9 +115,9 @@ ByteBuffer decode_base64(const StringView& input)
String encode_base64(ReadonlyBytes input) String encode_base64(ReadonlyBytes input)
{ {
constexpr auto alphabet = make_alphabet(); constexpr auto alphabet = make_alphabet();
StringBuilder output; StringBuilder output(calculate_base64_decoded_length(input));
auto get = [&](size_t offset, bool* need_padding = nullptr) -> u8 { auto get = [&](const size_t offset, bool* need_padding = nullptr) -> u8 {
if (offset >= input.size()) { if (offset >= input.size()) {
if (need_padding) if (need_padding)
*need_padding = true; *need_padding = true;
@ -119,19 +130,19 @@ String encode_base64(ReadonlyBytes input)
bool is_8bit = false; bool is_8bit = false;
bool is_16bit = false; bool is_16bit = false;
u8 in0 = get(i); const u8 in0 = get(i);
u8 in1 = get(i + 1, &is_16bit); const u8 in1 = get(i + 1, &is_16bit);
u8 in2 = get(i + 2, &is_8bit); const u8 in2 = get(i + 2, &is_8bit);
u8 index0 = (in0 >> 2) & 0x3f; const u8 index0 = (in0 >> 2) & 0x3f;
u8 index1 = ((in0 << 4) | (in1 >> 4)) & 0x3f; const u8 index1 = ((in0 << 4) | (in1 >> 4)) & 0x3f;
u8 index2 = ((in1 << 2) | (in2 >> 6)) & 0x3f; const u8 index2 = ((in1 << 2) | (in2 >> 6)) & 0x3f;
u8 index3 = in2 & 0x3f; const u8 index3 = in2 & 0x3f;
u8 out0 = alphabet[index0]; const u8 out0 = alphabet[index0];
u8 out1 = alphabet[index1]; const u8 out1 = alphabet[index1];
u8 out2 = is_16bit ? '=' : alphabet[index2]; const u8 out2 = is_16bit ? '=' : alphabet[index2];
u8 out3 = is_8bit ? '=' : alphabet[index3]; const u8 out3 = is_8bit ? '=' : alphabet[index3];
output.append(out0); output.append(out0);
output.append(out1); output.append(out1);

View file

@ -26,11 +26,17 @@
#pragma once #pragma once
#include <AK/Forward.h> #include <AK/ByteBuffer.h>
#include <AK/Span.h> #include <AK/Span.h>
#include <AK/String.h>
#include <AK/StringView.h>
namespace AK { namespace AK {
size_t calculate_base64_decoded_length(const StringView&);
size_t calculate_base64_encoded_length(ReadonlyBytes);
ByteBuffer decode_base64(const StringView&); ByteBuffer decode_base64(const StringView&);
String encode_base64(ReadonlyBytes); String encode_base64(ReadonlyBytes);

View file

@ -35,6 +35,7 @@ TEST_CASE(test_decode)
auto decode_equal = [&](const char* input, const char* expected) { auto decode_equal = [&](const char* input, const char* expected) {
auto decoded = decode_base64(StringView(input)); auto decoded = decode_base64(StringView(input));
EXPECT(String::copy(decoded) == String(expected)); EXPECT(String::copy(decoded) == String(expected));
EXPECT(StringView(expected).length() <= calculate_base64_decoded_length(StringView(input).bytes()));
}; };
decode_equal("", ""); decode_equal("", "");
@ -51,6 +52,7 @@ TEST_CASE(test_encode)
auto encode_equal = [&](const char* input, const char* expected) { auto encode_equal = [&](const char* input, const char* expected) {
auto encoded = encode_base64({ input, strlen(input) }); auto encoded = encode_base64({ input, strlen(input) });
EXPECT(encoded == String(expected)); EXPECT(encoded == String(expected));
EXPECT_EQ(StringView(expected).length(), calculate_base64_encoded_length(StringView(input).bytes()));
}; };
encode_equal("", ""); encode_equal("", "");