Skip to content

Commit

Permalink
Simplify base64.cpp and fix 0 byte ending issue
Browse files Browse the repository at this point in the history
Uses `EVP_EncodedLength` to avoid a local implementation, and uses
`EVP_DecodeBase64` to handle a corner case around null bytes at the end
of input data for base64 encoding.

Counterpart to https://android-review.googlesource.com/c/device/google/cuttlefish/+/3481535

Bug: b/395180112
Test: bazel test :utils_test
  • Loading branch information
Databean committed Feb 8, 2025
1 parent 0740473 commit 6f328cb
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 57 deletions.
2 changes: 2 additions & 0 deletions base/cvd/cuttlefish/common/libs/utils/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ clang_tidy_test(
cc_test(
name = "utils_test",
srcs = [
"base64_test.cpp",
"files_test.cpp",
"files_test_helper.cpp",
"files_test_helper.h",
Expand All @@ -155,6 +156,7 @@ cc_test(
":utils",
"//cuttlefish/common/libs/fs",
"//libbase",
"@boringssl//:crypto",
"@googletest//:gtest",
"@googletest//:gtest_main",
"@libxml2//:libxml2",
Expand Down
69 changes: 12 additions & 57 deletions base/cvd/cuttlefish/common/libs/utils/base64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,56 +18,20 @@

#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>

#include <openssl/evp.h>
#include <openssl/base64.h>

namespace cuttlefish {

namespace {

// EVP_EncodedLength is boringssl specific so it can't be used outside of
// android.
std::optional<size_t> EncodedLength(size_t len) {
if (len + 2 < len) {
return std::nullopt;
}
len += 2;
len /= 3;

if (((len << 2) >> 2) != len) {
return std::nullopt;
}
len <<= 2;

if (len + 1 < len) {
return std::nullopt;
}
len++;

return {len};
}

// EVP_DecodedLength is boringssl specific so it can't be used outside of
// android.
std::optional<size_t> DecodedLength(size_t len) {
if (len % 4 != 0) {
return std::nullopt;
}

return {(len / 4) * 3};
}

} // namespace

bool EncodeBase64(const void *data, std::size_t size, std::string *out) {
auto len_res = EncodedLength(size);
if (!len_res) {
std::size_t max_length = 0;
if (EVP_EncodedLength(&max_length, size) == 0) {
return false;
}
out->resize(*len_res);

out->resize(max_length);
auto enc_res =
EVP_EncodeBlock(reinterpret_cast<std::uint8_t *>(out->data()),
reinterpret_cast<const std::uint8_t *>(data), size);
Expand All @@ -79,24 +43,15 @@ bool EncodeBase64(const void *data, std::size_t size, std::string *out) {
}

bool DecodeBase64(const std::string &data, std::vector<std::uint8_t> *buffer) {
auto len_res = DecodedLength(data.size());
if (!len_res) {
buffer->resize(data.size());
std::size_t actual_len = 0;
int success = EVP_DecodeBase64(buffer->data(), &actual_len, buffer->size(),
reinterpret_cast<const uint8_t *>(data.data()),
data.size());
if (success != 1) {
return false;
}
auto out_len = *len_res;
buffer->resize(out_len);
auto actual_len = EVP_DecodeBlock(buffer->data(),
reinterpret_cast<const uint8_t *>(data.data()),
data.size());
if (actual_len < 0) {
return false;
}

// DecodeBlock leaves null characters at the end of the buffer when the
// decoded message is not a multiple of 3.
while (!buffer->empty() && buffer->back() == '\0') {
buffer->pop_back();
}
buffer->resize(actual_len);

return true;
}
Expand Down
97 changes: 97 additions & 0 deletions base/cvd/cuttlefish/common/libs/utils/base64_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <string>
#include <vector>

#include <gtest/gtest.h>

#include "common/libs/utils/base64.h"

namespace cuttlefish {

TEST(Base64Test, EncodeMult3) {
std::string in = "foobar";
std::string expected("Zm9vYmFy");
std::string out;
ASSERT_TRUE(EncodeBase64(in.c_str(), in.size(), &out));
ASSERT_EQ(out.size(), expected.size());
ASSERT_EQ(out, expected);
}

TEST(Base64Test, EncodeNonMult3) {
std::string in = "foobar1";
std::string expected("Zm9vYmFyMQ==");
std::string out;
ASSERT_TRUE(EncodeBase64(in.c_str(), in.size(), &out));
ASSERT_EQ(out.size(), expected.size());
ASSERT_EQ(out, expected);
}

TEST(Base64Test, DecodeMult3) {
std::string in = "Zm9vYmFy";
std::vector<uint8_t> expected{'f','o','o','b','a','r'};
std::vector<uint8_t> out;
ASSERT_TRUE(DecodeBase64(in, &out));
ASSERT_EQ(out.size(), expected.size());
ASSERT_EQ(out, expected);
}

TEST(Base64Test, DecodeNonMult3) {
std::string in = "Zm9vYmFyMQ==";
std::vector<uint8_t> expected{'f','o','o','b','a','r','1'};
std::vector<uint8_t> out;
ASSERT_TRUE(DecodeBase64(in, &out));
ASSERT_EQ(out.size(), expected.size());
ASSERT_EQ(out, expected);
}

TEST(Base64Test, EncodeOneZero) {
std::vector<uint8_t> in = {0};
std::string string_encoding;

ASSERT_TRUE(EncodeBase64(in.data(), in.size(), &string_encoding));

std::vector<uint8_t> out;
ASSERT_TRUE(DecodeBase64(string_encoding, &out));

ASSERT_EQ(in, out);
}

TEST(Base64Test, EncodeTwoZeroes) {
std::vector<uint8_t> in = {0, 0};
std::string string_encoding;

ASSERT_TRUE(EncodeBase64(in.data(), in.size(), &string_encoding));

std::vector<uint8_t> out;
ASSERT_TRUE(DecodeBase64(string_encoding, &out));

ASSERT_EQ(in, out);
}

TEST(Base64Test, EncodeThreeZeroes) {
std::vector<uint8_t> in = {0, 0, 0};
std::string string_encoding;

ASSERT_TRUE(EncodeBase64(in.data(), in.size(), &string_encoding));

std::vector<uint8_t> out;
ASSERT_TRUE(DecodeBase64(string_encoding, &out));

ASSERT_EQ(in, out);
}
}

0 comments on commit 6f328cb

Please sign in to comment.