blob: 23994d30c1660eed223b018ec13d820c4fc1e0b7 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/public/common/messaging/string_message_codec.h"
#include <string>
#include <variant>
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/task_environment.h"
#include "mojo/public/cpp/base/big_buffer.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "v8/include/v8.h"
namespace blink {
namespace {
WebMessagePayload DecodeWithV8(const TransferableMessage& message) {
base::test::TaskEnvironment task_environment;
WebMessagePayload result;
v8::Isolate::CreateParams params;
params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::Isolate* isolate = v8::Isolate::New(params);
{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope scope(isolate);
v8::TryCatch try_catch(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope lock(context);
v8::ValueDeserializer deserializer(isolate, message.encoded_message.data(),
message.encoded_message.size());
deserializer.SetSupportsLegacyWireFormat(true);
if (message.array_buffer_contents_array.size() == 1) {
// Prepare to transfer ArrayBuffer first. This does not necessary mean the
// result type is ArrayBuffer.
mojo_base::BigBuffer& big_buffer =
message.array_buffer_contents_array[0]->contents;
v8::Local<v8::ArrayBuffer> message_as_array_buffer =
v8::ArrayBuffer::New(isolate, big_buffer.size());
UNSAFE_TODO(memcpy(message_as_array_buffer->GetBackingStore()->Data(),
big_buffer.data(), big_buffer.size()));
deserializer.TransferArrayBuffer(0, message_as_array_buffer);
}
EXPECT_TRUE(deserializer.ReadHeader(context).ToChecked());
v8::Local<v8::Value> value =
deserializer.ReadValue(context).ToLocalChecked();
if (value->IsString()) {
v8::Local<v8::String> js_str = value->ToString(context).ToLocalChecked();
std::u16string str;
str.resize(js_str->Length());
js_str->WriteV2(isolate, 0, str.size(),
reinterpret_cast<uint16_t*>(&str[0]));
result = str;
}
if (value->IsArrayBuffer()) {
auto js_array_buffer = value.As<v8::ArrayBuffer>()->GetBackingStore();
std::vector<uint8_t> array_buffer_contents;
array_buffer_contents.resize(js_array_buffer->ByteLength());
UNSAFE_TODO(memcpy(array_buffer_contents.data(), js_array_buffer->Data(),
js_array_buffer->ByteLength()));
result = WebMessageArrayBufferPayload::CreateForTesting(
std::move(array_buffer_contents));
}
}
isolate->Dispose();
delete params.array_buffer_allocator;
return result;
}
TransferableMessage EncodeWithV8(const WebMessagePayload& message,
const bool transferable = false) {
TransferableMessage transferable_message;
base::test::TaskEnvironment task_environment;
std::vector<uint8_t> result;
v8::Isolate::CreateParams params;
params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::Isolate* isolate = v8::Isolate::New(params);
{
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope scope(isolate);
v8::TryCatch try_catch(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope lock(context);
v8::ValueSerializer serializer(isolate);
serializer.WriteHeader();
std::visit(
absl::Overload{
[&](const std::u16string& str) {
v8::Local<v8::String> message_as_value =
v8::String::NewFromTwoByte(
isolate, reinterpret_cast<const uint16_t*>(str.data()),
v8::NewStringType::kNormal, str.size())
.ToLocalChecked();
EXPECT_TRUE(
serializer.WriteValue(context, message_as_value).ToChecked());
},
[&](const std::unique_ptr<WebMessageArrayBufferPayload>&
array_buffer) {
// Create a new JS ArrayBuffer, then transfer into serializer.
v8::Local<v8::ArrayBuffer> message_as_array_buffer =
v8::ArrayBuffer::New(isolate, array_buffer->GetLength());
array_buffer->CopyInto(UNSAFE_TODO(base::span(
reinterpret_cast<uint8_t*>(message_as_array_buffer->Data()),
message_as_array_buffer->ByteLength())));
if (transferable) {
serializer.TransferArrayBuffer(0, message_as_array_buffer);
// Copy data into a new array_buffer_contents_array slot.
mojo_base::BigBuffer big_buffer(array_buffer->GetLength());
array_buffer->CopyInto(big_buffer);
constexpr bool is_resizable_by_user_js = false;
constexpr size_t max_byte_length = 0;
transferable_message.array_buffer_contents_array.push_back(
mojom::SerializedArrayBufferContents::New(
std::move(big_buffer), is_resizable_by_user_js,
max_byte_length));
}
EXPECT_TRUE(
serializer.WriteValue(context, message_as_array_buffer)
.ToChecked());
}},
message);
std::pair<uint8_t*, size_t> buffer = serializer.Release();
result = std::vector<uint8_t>(buffer.first,
UNSAFE_TODO(buffer.first + buffer.second));
free(buffer.first);
}
isolate->Dispose();
delete params.array_buffer_allocator;
transferable_message.owned_encoded_message = std::move(result);
transferable_message.encoded_message =
transferable_message.owned_encoded_message;
return transferable_message;
}
void CheckStringEQ(const std::optional<WebMessagePayload>& optional_payload,
const std::u16string& str) {
EXPECT_TRUE(optional_payload);
auto& payload = optional_payload.value();
EXPECT_TRUE(std::holds_alternative<std::u16string>(payload));
EXPECT_EQ(str, std::get<std::u16string>(payload));
}
void CheckVectorEQ(const std::optional<WebMessagePayload>& optional_payload,
const std::vector<uint8_t>& buffer) {
EXPECT_TRUE(optional_payload);
auto& payload = optional_payload.value();
EXPECT_TRUE(
std::holds_alternative<std::unique_ptr<WebMessageArrayBufferPayload>>(
payload));
auto& array_buffer =
std::get<std::unique_ptr<WebMessageArrayBufferPayload>>(payload);
EXPECT_EQ(buffer.size(), array_buffer->GetLength());
auto span = array_buffer->GetAsSpanIfPossible();
if (span) {
// GetAsSpan is supported, check it is the same as the original buffer.
EXPECT_EQ(std::vector<uint8_t>(span->begin(), span->end()), buffer);
}
std::vector<uint8_t> temp(array_buffer->GetLength());
array_buffer->CopyInto(base::span(temp));
EXPECT_EQ(temp, buffer);
}
TEST(StringMessageCodecTest, SelfTest_ASCII) {
std::u16string message = u"hello";
CheckStringEQ(DecodeToWebMessagePayload(
EncodeWebMessagePayload(WebMessagePayload(message))),
message);
}
TEST(StringMessageCodecTest, SelfTest_Latin1) {
std::u16string message = u"hello \u00E7";
CheckStringEQ(DecodeToWebMessagePayload(
EncodeWebMessagePayload(WebMessagePayload(message))),
message);
}
TEST(StringMessageCodecTest, SelfTest_TwoByte) {
std::u16string message = u"hello \u263A";
CheckStringEQ(DecodeToWebMessagePayload(
EncodeWebMessagePayload(WebMessagePayload(message))),
message);
}
TEST(StringMessageCodecTest, SelfTest_TwoByteLongEnoughToForcePadding) {
std::u16string message(200, 0x263A);
CheckStringEQ(DecodeToWebMessagePayload(
EncodeWebMessagePayload(WebMessagePayload(message))),
message);
}
TEST(StringMessageCodecTest, SelfTest_ArrayBuffer) {
std::vector<uint8_t> message(200, 0xFF);
CheckVectorEQ(DecodeToWebMessagePayload(EncodeWebMessagePayload(
WebMessageArrayBufferPayload::CreateForTesting(message))),
message);
}
TEST(StringMessageCodecTest, SelfToV8Test_ASCII) {
std::u16string message = u"hello";
CheckStringEQ(
DecodeWithV8(EncodeWebMessagePayload(WebMessagePayload(message))),
message);
}
TEST(StringMessageCodecTest, SelfToV8Test_Latin1) {
std::u16string message = u"hello \u00E7";
CheckStringEQ(
DecodeWithV8(EncodeWebMessagePayload(WebMessagePayload(message))),
message);
}
TEST(StringMessageCodecTest, SelfToV8Test_TwoByte) {
std::u16string message = u"hello \u263A";
CheckStringEQ(
DecodeWithV8(EncodeWebMessagePayload(WebMessagePayload(message))),
message);
}
TEST(StringMessageCodecTest, SelfToV8Test_TwoByteLongEnoughToForcePadding) {
std::u16string message(200, 0x263A);
CheckStringEQ(
DecodeWithV8(EncodeWebMessagePayload(WebMessagePayload(message))),
message);
}
TEST(StringMessageCodecTest, SelfToV8Test_ArrayBuffer) {
std::vector<uint8_t> message(200, 0xFF);
CheckVectorEQ(DecodeWithV8(EncodeWebMessagePayload(
WebMessageArrayBufferPayload::CreateForTesting(message))),
message);
}
TEST(StringMessageCodecTest, V8ToSelfTest_ASCII) {
std::u16string message = u"hello";
CheckStringEQ(DecodeToWebMessagePayload(EncodeWithV8(message)), message);
}
TEST(StringMessageCodecTest, V8ToSelfTest_Latin1) {
std::u16string message = u"hello \u00E7";
CheckStringEQ(DecodeToWebMessagePayload(EncodeWithV8(message)), message);
}
TEST(StringMessageCodecTest, V8ToSelfTest_TwoByte) {
std::u16string message = u"hello \u263A";
CheckStringEQ(DecodeToWebMessagePayload(EncodeWithV8(message)), message);
}
TEST(StringMessageCodecTest, V8ToSelfTest_TwoByteLongEnoughToForcePadding) {
std::u16string message(200, 0x263A);
CheckStringEQ(DecodeToWebMessagePayload(EncodeWithV8(message)), message);
}
TEST(StringMessageCodecTest, V8ToSelfTest_ArrayBuffer) {
std::vector<uint8_t> message(200, 0xFF);
CheckVectorEQ(DecodeToWebMessagePayload(EncodeWithV8(
WebMessageArrayBufferPayload::CreateForTesting(message))),
message);
}
TEST(StringMessageCodecTest, V8ToSelfTest_ArrayBuffer_transferrable) {
std::vector<uint8_t> message(200, 0xFF);
CheckVectorEQ(
DecodeToWebMessagePayload(EncodeWithV8(
WebMessageArrayBufferPayload::CreateForTesting(message), true)),
message);
}
TransferableMessage TransferableMessageFromRawData(std::vector<uint8_t> data) {
TransferableMessage message;
message.owned_encoded_message = std::move(data);
message.encoded_message = message.owned_encoded_message;
return message;
}
TEST(StringMessageCodecTest, Overflow) {
const std::vector<uint8_t> kOverflowOneByteData{'"', 0xff, 0xff, 0xff, 0x7f};
EXPECT_FALSE(DecodeToWebMessagePayload(
TransferableMessageFromRawData(kOverflowOneByteData)));
const std::vector<uint8_t> kOverflowTwoByteData{'c', 0xff, 0xff, 0xff, 0x7f};
EXPECT_FALSE(DecodeToWebMessagePayload(
TransferableMessageFromRawData(kOverflowTwoByteData)));
}
TEST(StringMessageCodecTest, InvalidDecode) {
auto decode_from_raw = [](std::vector<uint8_t> data) {
return DecodeToWebMessagePayload(
TransferableMessageFromRawData(std::move(data)));
};
EXPECT_FALSE(decode_from_raw({})) << "no data";
EXPECT_FALSE(decode_from_raw({0xff, 0x01})) << "only one version";
EXPECT_FALSE(decode_from_raw({0xff, 0x80}))
<< "end of buffer during first version";
EXPECT_FALSE(decode_from_raw({0xff, 0x01, 0xff, 0x01}))
<< "only two versions";
EXPECT_FALSE(decode_from_raw({0xff, 0x10, 0xff, 0x80}))
<< "end of buffer during second version";
EXPECT_FALSE(decode_from_raw({0xff, 0x15, 0xfe, 0xff, 0x01, '"', 0x01, 'a'}))
<< "end of buffer during trailer offset";
EXPECT_FALSE(decode_from_raw({0xff, 0x15, 0x7f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xff, 0x10, '"', 0x01, 'a'}))
<< "unrecognized trailer offset tag";
// Confirm that aside from the specific errors above, this encoding is
// generally correct.
auto valid_payload = decode_from_raw(
{0xff, 0x15, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x10, '"', 0x01, 'a'});
ASSERT_TRUE(valid_payload.has_value());
ASSERT_TRUE(std::holds_alternative<std::u16string>(*valid_payload));
EXPECT_EQ(std::get<std::u16string>(*valid_payload), u"a");
}
} // namespace
} // namespace blink