Commit 4a98a3cc authored by Philip Pfaffe's avatar Philip Pfaffe Committed by Commit Bot

Implement protocol::Binary to/from base64 conversion

Supporting WebAssembly evaluator modules requires support for passing
binary data as a parameter to CDP methods. Currently, the required base64
conversions are not implemented.

Bug: chromium:1020120
Change-Id: Ie74f93ee5accfa369aac428e5c5b5f882c921c52
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2152645
Commit-Queue: Philip Pfaffe <pfaffe@chromium.org>
Reviewed-by: 's avatarSimon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/master@{#67490}
parent fc03e548
...@@ -28,7 +28,7 @@ class String16 { ...@@ -28,7 +28,7 @@ class String16 {
String16(String16&&) V8_NOEXCEPT = default; String16(String16&&) V8_NOEXCEPT = default;
String16(const UChar* characters, size_t size); String16(const UChar* characters, size_t size);
V8_EXPORT String16(const UChar* characters); // NOLINT(runtime/explicit) V8_EXPORT String16(const UChar* characters); // NOLINT(runtime/explicit)
String16(const char* characters); // NOLINT(runtime/explicit) V8_EXPORT String16(const char* characters); // NOLINT(runtime/explicit)
String16(const char* characters, size_t size); String16(const char* characters, size_t size);
explicit String16(const std::basic_string<UChar>& impl); explicit String16(const std::basic_string<UChar>& impl);
explicit String16(std::basic_string<UChar>&& impl); explicit String16(std::basic_string<UChar>&& impl);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <cinttypes> #include <cinttypes>
#include <cmath> #include <cmath>
#include <cstddef>
#include "src/base/platform/platform.h" #include "src/base/platform/platform.h"
#include "src/inspector/protocol/Protocol.h" #include "src/inspector/protocol/Protocol.h"
...@@ -13,6 +14,91 @@ ...@@ -13,6 +14,91 @@
namespace v8_inspector { namespace v8_inspector {
namespace protocol {
namespace {
std::pair<uint8_t, uint8_t> SplitByte(uint8_t byte, uint8_t split) {
return {byte >> split, (byte & ((1 << split) - 1)) << (6 - split)};
}
v8::Maybe<uint8_t> DecodeByte(char byte) {
if ('A' <= byte && byte <= 'Z') return v8::Just<uint8_t>(byte - 'A');
if ('a' <= byte && byte <= 'z') return v8::Just<uint8_t>(byte - 'a' + 26);
if ('0' <= byte && byte <= '9')
return v8::Just<uint8_t>(byte - '0' + 26 + 26);
if (byte == '+') return v8::Just<uint8_t>(62);
if (byte == '/') return v8::Just<uint8_t>(63);
return v8::Nothing<uint8_t>();
}
} // namespace
String Binary::toBase64() const {
const char* table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if (size() == 0) return {};
std::basic_string<UChar> result;
result.reserve(4 * ((size() + 2) / 3));
uint8_t last = 0;
for (size_t n = 0; n < size();) {
auto split = SplitByte((*bytes_)[n], 2 + 2 * (n % 3));
result.push_back(table[split.first | last]);
++n;
if (n < size() && n % 3 == 0) {
result.push_back(table[split.second]);
last = 0;
} else {
last = split.second;
}
}
result.push_back(table[last]);
while (result.size() % 4 > 0) result.push_back('=');
return String16(std::move(result));
}
/* static */
Binary Binary::fromBase64(const String& base64, bool* success) {
if (base64.isEmpty()) {
*success = true;
return Binary::fromSpan(nullptr, 0);
}
*success = false;
// Fail if the length is invalid or decoding would overflow.
if (base64.length() % 4 != 0 || base64.length() + 4 < base64.length()) {
return Binary::fromSpan(nullptr, 0);
}
std::vector<uint8_t> result;
result.reserve(3 * base64.length() / 4);
char pad = '=';
// Iterate groups of four
for (size_t i = 0; i < base64.length(); i += 4) {
uint8_t a = 0, b = 0, c = 0, d = 0;
if (!DecodeByte(base64[i + 0]).To(&a)) return Binary::fromSpan(nullptr, 0);
if (!DecodeByte(base64[i + 1]).To(&b)) return Binary::fromSpan(nullptr, 0);
if (!DecodeByte(base64[i + 2]).To(&c)) {
// Padding is allowed only in the group on the last two positions
if (i + 4 < base64.length() || base64[i + 2] != pad ||
base64[i + 3] != pad) {
return Binary::fromSpan(nullptr, 0);
}
}
if (!DecodeByte(base64[i + 3]).To(&d)) {
// Padding is allowed only in the group on the last two positions
if (i + 4 < base64.length() || base64[i + 3] != pad) {
return Binary::fromSpan(nullptr, 0);
}
}
result.push_back((a << 2) | (b >> 4));
if (base64[i + 2] != '=') result.push_back((0xFF & (b << 4)) | (c >> 2));
if (base64[i + 3] != '=') result.push_back((0xFF & (c << 6)) | d);
}
*success = true;
return Binary(std::make_shared<std::vector<uint8_t>>(std::move(result)));
}
} // namespace protocol
v8::Local<v8::String> toV8String(v8::Isolate* isolate, const String16& string) { v8::Local<v8::String> toV8String(v8::Isolate* isolate, const String16& string) {
if (string.isEmpty()) return v8::String::Empty(isolate); if (string.isEmpty()) return v8::String::Empty(isolate);
DCHECK_GT(v8::String::kMaxLength, string.length()); DCHECK_GT(v8::String::kMaxLength, string.length());
......
...@@ -41,19 +41,14 @@ class StringUtil { ...@@ -41,19 +41,14 @@ class StringUtil {
}; };
// A read-only sequence of uninterpreted bytes with reference-counted storage. // A read-only sequence of uninterpreted bytes with reference-counted storage.
// Though the templates for generating the protocol bindings reference class V8_EXPORT Binary {
// this type, js_protocol.pdl doesn't have a field of type 'binary', so
// therefore it's unnecessary to provide an implementation here.
class Binary {
public: public:
Binary() = default; Binary() = default;
const uint8_t* data() const { return bytes_->data(); } const uint8_t* data() const { return bytes_->data(); }
size_t size() const { return bytes_->size(); } size_t size() const { return bytes_->size(); }
String toBase64() const { UNIMPLEMENTED(); } String toBase64() const;
static Binary fromBase64(const String& base64, bool* success) { static Binary fromBase64(const String& base64, bool* success);
UNIMPLEMENTED();
}
static Binary fromSpan(const uint8_t* data, size_t size) { static Binary fromSpan(const uint8_t* data, size_t size) {
return Binary(std::make_shared<std::vector<uint8_t>>(data, data + size)); return Binary(std::make_shared<std::vector<uint8_t>>(data, data + size));
} }
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
#include <memory> #include <memory>
#include "test/cctest/cctest.h"
#include "include/v8-inspector.h" #include "include/v8-inspector.h"
#include "include/v8.h" #include "include/v8.h"
#include "src/inspector/protocol/Runtime.h" #include "src/inspector/protocol/Runtime.h"
#include "src/inspector/string-util.h"
#include "test/cctest/cctest.h"
using v8_inspector::StringBuffer; using v8_inspector::StringBuffer;
using v8_inspector::StringView; using v8_inspector::StringView;
...@@ -63,3 +63,108 @@ TEST(WrapInsideWrapOnInterrupt) { ...@@ -63,3 +63,108 @@ TEST(WrapInsideWrapOnInterrupt) {
isolate->RequestInterrupt(&WrapOnInterrupt, session.get()); isolate->RequestInterrupt(&WrapOnInterrupt, session.get());
session->wrapObject(env.local(), v8::Null(isolate), object_group_view, false); session->wrapObject(env.local(), v8::Null(isolate), object_group_view, false);
} }
TEST(BinaryFromBase64) {
auto checkBinary = [](const v8_inspector::protocol::Binary& binary,
const std::vector<uint8_t>& values) {
std::vector<uint8_t> binary_vector(binary.data(),
binary.data() + binary.size());
CHECK_EQ(binary_vector, values);
};
{
bool success;
auto binary = v8_inspector::protocol::Binary::fromBase64("", &success);
CHECK(success);
checkBinary(binary, {});
}
{
bool success;
auto binary = v8_inspector::protocol::Binary::fromBase64("YQ==", &success);
CHECK(success);
checkBinary(binary, {'a'});
}
{
bool success;
auto binary = v8_inspector::protocol::Binary::fromBase64("YWI=", &success);
CHECK(success);
checkBinary(binary, {'a', 'b'});
}
{
bool success;
auto binary = v8_inspector::protocol::Binary::fromBase64("YWJj", &success);
CHECK(success);
checkBinary(binary, {'a', 'b', 'c'});
}
{
bool success;
// Wrong input length:
auto binary = v8_inspector::protocol::Binary::fromBase64("Y", &success);
CHECK(!success);
}
{
bool success;
// Invalid space:
auto binary = v8_inspector::protocol::Binary::fromBase64("=AAA", &success);
CHECK(!success);
}
{
bool success;
// Invalid space in a non-final block of four:
auto binary =
v8_inspector::protocol::Binary::fromBase64("AAA=AAAA", &success);
CHECK(!success);
}
{
bool success;
// Invalid invalid space in second to last position:
auto binary = v8_inspector::protocol::Binary::fromBase64("AA=A", &success);
CHECK(!success);
}
{
bool success;
// Invalid character:
auto binary = v8_inspector::protocol::Binary::fromBase64(" ", &success);
CHECK(!success);
}
}
TEST(BinaryToBase64) {
uint8_t input[] = {'a', 'b', 'c'};
{
auto binary = v8_inspector::protocol::Binary::fromSpan(input, 0);
v8_inspector::protocol::String base64 = binary.toBase64();
CHECK_EQ(base64.utf8(), "");
}
{
auto binary = v8_inspector::protocol::Binary::fromSpan(input, 1);
v8_inspector::protocol::String base64 = binary.toBase64();
CHECK_EQ(base64.utf8(), "YQ==");
}
{
auto binary = v8_inspector::protocol::Binary::fromSpan(input, 2);
v8_inspector::protocol::String base64 = binary.toBase64();
CHECK_EQ(base64.utf8(), "YWI=");
}
{
auto binary = v8_inspector::protocol::Binary::fromSpan(input, 3);
v8_inspector::protocol::String base64 = binary.toBase64();
CHECK_EQ(base64.utf8(), "YWJj");
}
}
TEST(BinaryBase64RoundTrip) {
std::array<uint8_t, 256> values;
for (uint16_t b = 0x0; b <= 0xFF; ++b) values[b] = b;
auto binary =
v8_inspector::protocol::Binary::fromSpan(values.data(), values.size());
v8_inspector::protocol::String base64 = binary.toBase64();
bool success = false;
auto roundtrip_binary =
v8_inspector::protocol::Binary::fromBase64(base64, &success);
CHECK(success);
CHECK_EQ(values.size(), roundtrip_binary.size());
for (size_t i = 0; i < values.size(); ++i) {
CHECK_EQ(values[i], roundtrip_binary.data()[i]);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment