Commit de0b805e authored by Song Fangzhen's avatar Song Fangzhen Committed by Chromium LUCI CQ

Direct Sockets: Add ReadableStream and WritableStream for TCPSocket.

Add TCPReadableStreamWrapper and TCPWritableStreamWrapper which
provide stream implementations for TCPSocket.

TCPReadableWrapper and TCPWritableWrapper watch the mojo datapipe,
create underlying queue strategy, and provide functions like
read/write data from/to network.

Unittests are also provided.

Below documents are from Eric Willigers <ericwilligers@chromium.org>.
Explainer: https://github.com/WICG/raw-sockets/blob/master/docs/explainer.md

Intent to Prototype:
https://groups.google.com/a/chromium.org/g/blink-dev/c/ARtkaw4e9T4/m/npjeMssPCAAJ

Design doc:
https://docs.google.com/document/d/1Xa5nFkIWxkL3hZHvDYWPhT8sZvNeFpCUKNuqIwZHxnE/edit?usp=sharing

Bug: 905818
Change-Id: I8a0da5d9391c543201e5e9620ca493d192166319
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2563340Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Commit-Queue: Ke He <kehe@chromium.org>
Cr-Commit-Position: refs/heads/master@{#836841}
parent a1e82124
...@@ -972,6 +972,7 @@ Simon La Macchia <smacchia@amazon.com> ...@@ -972,6 +972,7 @@ Simon La Macchia <smacchia@amazon.com>
Siva Kumar Gunturi <siva.gunturi@samsung.com> Siva Kumar Gunturi <siva.gunturi@samsung.com>
Sohan Jyoti Ghosh <sohan.jyoti@huawei.com> Sohan Jyoti Ghosh <sohan.jyoti@huawei.com>
Sohan Jyoti Ghosh <sohan.jyoti@samsung.com> Sohan Jyoti Ghosh <sohan.jyoti@samsung.com>
Song Fangzhen <songfangzhen@bytedance.com>
Song YeWen <ffmpeg@gmail.com> Song YeWen <ffmpeg@gmail.com>
Sooho Park <sooho1000@gmail.com> Sooho Park <sooho1000@gmail.com>
Soojung Choi <crystal2840@gmail.com> Soojung Choi <crystal2840@gmail.com>
......
...@@ -522,6 +522,10 @@ source_set("unit_tests") { ...@@ -522,6 +522,10 @@ source_set("unit_tests") {
"//v8", "//v8",
] ]
if (!is_android) {
deps += [ "//third_party/blink/renderer/modules/direct_sockets:unit_tests" ]
}
data_deps = [ data_deps = [
":accessibility_unittests_data", ":accessibility_unittests_data",
":mediastream_unittests_data", ":mediastream_unittests_data",
......
...@@ -8,10 +8,39 @@ blink_modules_sources("direct_sockets") { ...@@ -8,10 +8,39 @@ blink_modules_sources("direct_sockets") {
sources = [ sources = [
"navigator_socket.cc", "navigator_socket.cc",
"navigator_socket.h", "navigator_socket.h",
"tcp_readable_stream_wrapper.cc",
"tcp_readable_stream_wrapper.h",
"tcp_socket.cc", "tcp_socket.cc",
"tcp_socket.h", "tcp_socket.h",
"tcp_writable_stream_wrapper.cc",
"tcp_writable_stream_wrapper.h",
"udp_socket.cc", "udp_socket.cc",
"udp_socket.h", "udp_socket.h",
] ]
deps = [ "//net:net" ] deps = [ "//net:net" ]
} }
source_set("unit_tests") {
testonly = true
sources = [
"tcp_readable_stream_wrapper_unittest.cc",
"tcp_writable_stream_wrapper_unittest.cc",
]
configs += [
"//third_party/blink/renderer:config",
"//third_party/blink/renderer:inside_blink",
"//third_party/blink/renderer/core:blink_core_pch",
]
deps = [
"//base/test:test_support",
"//testing/gmock",
"//testing/gtest",
"//third_party/blink/renderer/controller:blink_bindings_test_sources",
"//third_party/blink/renderer/modules",
"//third_party/blink/renderer/platform",
"//third_party/blink/renderer/platform:test_support",
"//third_party/blink/renderer/platform/wtf",
]
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/direct_sockets/tcp_readable_stream_wrapper.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_array_buffer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h"
#include "third_party/blink/renderer/core/streams/underlying_source_base.h"
#include "third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
namespace blink {
// An implementation of UnderlyingSourceBase that forwards all operations to the
// TCPReadableStreamWrapper object that created it.
class TCPReadableStreamWrapper::UnderlyingSource final
: public UnderlyingSourceBase {
public:
UnderlyingSource(ScriptState* script_state, TCPReadableStreamWrapper* stream)
: UnderlyingSourceBase(script_state),
tcp_readable_stream_wrapper_(stream) {}
ScriptPromise Start(ScriptState* script_state) override {
DVLOG(1) << "TCPReadableStreamWrapper::UnderlyingSource::start() "
"tcp_readable_stream_wrapper_="
<< tcp_readable_stream_wrapper_;
tcp_readable_stream_wrapper_->controller_ = Controller();
return ScriptPromise::CastUndefined(script_state);
}
ScriptPromise pull(ScriptState* script_state) override {
DVLOG(1) << "TCPReadableStreamWrapper::UnderlyingSource::pull() "
"tcp_readable_stream_wrapper_="
<< tcp_readable_stream_wrapper_;
tcp_readable_stream_wrapper_->ReadFromPipeAndEnqueue();
return ScriptPromise::CastUndefined(script_state);
}
ScriptPromise Cancel(ScriptState* script_state, ScriptValue reason) override {
DVLOG(1) << "TCPReadableStreamWrapper::UnderlyingSource::Cancel() "
"tcp_readable_stream_wrapper_="
<< tcp_readable_stream_wrapper_;
tcp_readable_stream_wrapper_->AbortAndReset();
return ScriptPromise::CastUndefined(script_state);
}
void Trace(Visitor* visitor) const override {
visitor->Trace(tcp_readable_stream_wrapper_);
UnderlyingSourceBase::Trace(visitor);
}
private:
const Member<TCPReadableStreamWrapper> tcp_readable_stream_wrapper_;
};
TCPReadableStreamWrapper::TCPReadableStreamWrapper(
ScriptState* script_state,
base::OnceClosure on_abort,
mojo::ScopedDataPipeConsumerHandle handle)
: script_state_(script_state),
on_abort_(std::move(on_abort)),
data_pipe_(std::move(handle)),
read_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
close_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) {
read_watcher_.Watch(
data_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
WTF::BindRepeating(&TCPReadableStreamWrapper::OnHandleReady,
WrapWeakPersistent(this)));
close_watcher_.Watch(
data_pipe_.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
WTF::BindRepeating(&TCPReadableStreamWrapper::OnPeerClosed,
WrapWeakPersistent(this)));
// Set queuing strategy of default behavior with a high water mark of 1.
readable_ = ReadableStream::CreateWithCountQueueingStrategy(
script_state_,
MakeGarbageCollected<UnderlyingSource>(script_state_, this), 1);
}
TCPReadableStreamWrapper::~TCPReadableStreamWrapper() = default;
void TCPReadableStreamWrapper::Reset() {
DVLOG(1) << "TCPReadableStreamWrapper::Reset() this=" << this;
// We no longer need to call |on_abort_|.
on_abort_.Reset();
ErrorStreamAbortAndReset();
}
void TCPReadableStreamWrapper::Trace(Visitor* visitor) const {
visitor->Trace(script_state_);
visitor->Trace(readable_);
visitor->Trace(controller_);
}
void TCPReadableStreamWrapper::OnHandleReady(MojoResult result,
const mojo::HandleSignalsState&) {
DVLOG(1) << "TCPReadableStreamWrapper::OnHandleReady() this=" << this
<< " result=" << result;
switch (result) {
case MOJO_RESULT_OK:
ReadFromPipeAndEnqueue();
break;
case MOJO_RESULT_FAILED_PRECONDITION:
// Will be handled by |close_watcher_|.
break;
default:
NOTREACHED();
}
}
void TCPReadableStreamWrapper::OnPeerClosed(MojoResult result,
const mojo::HandleSignalsState&) {
DVLOG(1) << "TCPReadableStreamWrapper::OnPeerClosed() this=" << this
<< " result=" << result;
DCHECK_EQ(result, MOJO_RESULT_OK);
DCHECK_EQ(state_, State::kOpen);
state_ = State::kClosed;
ErrorStreamAbortAndReset();
}
void TCPReadableStreamWrapper::ReadFromPipeAndEnqueue() {
DVLOG(1) << "TCPReadableStreamWrapper::ReadFromPipeAndEnqueue() this=" << this
<< " in_two_phase_read_=" << in_two_phase_read_
<< " read_pending_=" << read_pending_;
// Protect against re-entrancy.
if (in_two_phase_read_) {
read_pending_ = true;
return;
}
DCHECK(!read_pending_);
const void* buffer = nullptr;
uint32_t buffer_num_bytes = 0;
auto result = data_pipe_->BeginReadData(&buffer, &buffer_num_bytes,
MOJO_BEGIN_READ_DATA_FLAG_NONE);
switch (result) {
case MOJO_RESULT_OK: {
in_two_phase_read_ = true;
// EnqueueBytes() may re-enter this method via pull().
EnqueueBytes(buffer, buffer_num_bytes);
data_pipe_->EndReadData(buffer_num_bytes);
in_two_phase_read_ = false;
if (read_pending_) {
read_pending_ = false;
// pull() will not be called when another pull() is in progress, so the
// maximum recursion depth is 1.
ReadFromPipeAndEnqueue();
}
break;
}
case MOJO_RESULT_SHOULD_WAIT:
read_watcher_.ArmOrNotify();
return;
case MOJO_RESULT_FAILED_PRECONDITION:
// This will be handled by close_watcher_.
return;
default:
NOTREACHED() << "Unexpected result: " << result;
return;
}
}
void TCPReadableStreamWrapper::EnqueueBytes(const void* source,
uint32_t byte_length) {
DVLOG(1) << "TCPReadableStreamWrapper::EnqueueBytes() this=" << this;
auto* buffer =
DOMUint8Array::Create(static_cast<const uint8_t*>(source), byte_length);
controller_->Enqueue(buffer);
}
ScriptValue TCPReadableStreamWrapper::CreateAbortException() {
DVLOG(1) << "TCPReadableStreamWrapper::CreateAbortException() this=" << this;
DOMExceptionCode code = DOMExceptionCode::kNetworkError;
String message = "The stream was aborted by the remote";
return ScriptValue(script_state_->GetIsolate(),
V8ThrowDOMException::CreateOrEmpty(
script_state_->GetIsolate(), code, message));
}
void TCPReadableStreamWrapper::ErrorStreamAbortAndReset() {
DVLOG(1) << "TCPReadableStreamWrapper::ErrorStreamAbortAndReset() this="
<< this;
if (script_state_->ContextIsValid()) {
ScriptState::Scope scope(script_state_);
if (controller_) {
controller_->Error(CreateAbortException());
}
}
controller_ = nullptr;
AbortAndReset();
}
void TCPReadableStreamWrapper::AbortAndReset() {
DVLOG(1) << "TCPReadableStreamWrapper::AbortAndReset() this=" << this;
state_ = State::kAborted;
if (on_abort_) {
std::move(on_abort_).Run();
}
ResetPipe();
}
void TCPReadableStreamWrapper::ResetPipe() {
DVLOG(1) << "TCPReadableStreamWrapper::ResetPipe() this=" << this;
read_watcher_.Cancel();
close_watcher_.Cancel();
data_pipe_.reset();
}
void TCPReadableStreamWrapper::Dispose() {
DVLOG(1) << "TCPReadableStreamWrapper::Dispose() this=" << this;
ResetPipe();
}
} // namespace blink
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_DIRECT_SOCKETS_TCP_READABLE_STREAM_WRAPPER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_DIRECT_SOCKETS_TCP_READABLE_STREAM_WRAPPER_H_
#include "base/callback.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/modules/modules_export.h"
namespace blink {
class ScriptState;
class ReadableStream;
class ReadableStreamDefaultControllerWithScriptScope;
class Visitor;
// Helper class to read from a mojo consumer handle
class MODULES_EXPORT TCPReadableStreamWrapper final
: public GarbageCollected<TCPReadableStreamWrapper> {
USING_PRE_FINALIZER(TCPReadableStreamWrapper, Dispose);
public:
enum class State {
kOpen,
kAborted,
kClosed,
};
TCPReadableStreamWrapper(ScriptState*,
base::OnceClosure on_abort,
mojo::ScopedDataPipeConsumerHandle);
~TCPReadableStreamWrapper();
ReadableStream* Readable() const {
DVLOG(1) << "TCPReadableStreamWrapper::readable() called";
return readable_;
}
ScriptState* GetScriptState() { return script_state_; }
void Reset();
State GetState() const { return state_; }
void Trace(Visitor*) const;
private:
class UnderlyingSource;
// Called when |data_pipe_| becomes readable or errored.
void OnHandleReady(MojoResult, const mojo::HandleSignalsState&);
// Called when |data_pipe_| is closed.
void OnPeerClosed(MojoResult, const mojo::HandleSignalsState&);
// Reads all the data currently in the pipe and enqueues it. If no data is
// currently available, triggers the |read_watcher_| and enqueues when data
// becomes available.
void ReadFromPipeAndEnqueue();
// Copies a sequence of bytes into an ArrayBuffer and enqueues it.
void EnqueueBytes(const void* source, uint32_t byte_length);
// Creates a DOMException indicating that the stream has been aborted.
ScriptValue CreateAbortException();
// Errors |readable_| and resets |data_pipe_|.
void ErrorStreamAbortAndReset();
// Resets the |data_pipe_|.
void AbortAndReset();
// Resets |data_pipe_| and clears the watchers.
void ResetPipe();
// Prepares the object for destruction.
void Dispose();
const Member<ScriptState> script_state_;
base::OnceClosure on_abort_;
mojo::ScopedDataPipeConsumerHandle data_pipe_;
// Only armed when we need to read something.
mojo::SimpleWatcher read_watcher_;
// Always armed to detect close.
mojo::SimpleWatcher close_watcher_;
Member<ReadableStream> readable_;
Member<ReadableStreamDefaultControllerWithScriptScope> controller_;
State state_ = State::kOpen;
// Indicates if we are currently performing a two-phase read from the pipe and
// so can't start another read.
bool in_two_phase_read_ = false;
// Indicates if we need to perform another read after the current one
// completes.
bool read_pending_ = false;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_DIRECT_SOCKETS_TCP_READABLE_STREAM_WRAPPER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/direct_sockets/tcp_readable_stream_wrapper.h"
#include "base/test/mock_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_iterator_result_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_uint8_array.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
namespace {
using ::testing::ElementsAre;
using ::testing::StrictMock;
// The purpose of this class is to ensure that the data pipe is reset before the
// V8TestingScope is destroyed, so that the TCPReadableStreamWrapper object
// doesn't try to create a DOMException after the ScriptState has gone away.
class StreamCreator {
STACK_ALLOCATED();
public:
StreamCreator() = default;
~StreamCreator() {
ClosePipe();
// Let the TCPReadableStreamWrapper object respond to the closure if it
// needs to.
test::RunPendingTasks();
}
// The default value of |capacity| means some sensible value selected by mojo.
TCPReadableStreamWrapper* Create(const V8TestingScope& scope,
uint32_t capacity = 0) {
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = capacity;
mojo::ScopedDataPipeConsumerHandle data_pipe_consumer;
MojoResult result = mojo::CreateDataPipe(&options, &data_pipe_producer_,
&data_pipe_consumer);
if (result != MOJO_RESULT_OK) {
ADD_FAILURE() << "CreateDataPipe() returned " << result;
}
auto* script_state = scope.GetScriptState();
auto* tcp_readable_stream_wrapper =
MakeGarbageCollected<TCPReadableStreamWrapper>(
script_state,
base::BindOnce(&StreamCreator::OnAbort, base::Unretained(this)),
std::move(data_pipe_consumer));
return tcp_readable_stream_wrapper;
}
void WriteToPipe(Vector<uint8_t> data) {
uint32_t num_bytes = data.size();
EXPECT_EQ(data_pipe_producer_->WriteData(data.data(), &num_bytes,
MOJO_WRITE_DATA_FLAG_ALL_OR_NONE),
MOJO_RESULT_OK);
EXPECT_EQ(num_bytes, data.size());
}
void ClosePipe() { data_pipe_producer_.reset(); }
// Copies the contents of a v8::Value containing a Uint8Array to a Vector.
static Vector<uint8_t> ToVector(const V8TestingScope& scope,
v8::Local<v8::Value> v8value) {
Vector<uint8_t> ret;
DOMUint8Array* value =
V8Uint8Array::ToImplWithTypeCheck(scope.GetIsolate(), v8value);
if (!value) {
ADD_FAILURE() << "chunk is not an Uint8Array";
return ret;
}
ret.Append(static_cast<uint8_t*>(value->Data()),
static_cast<wtf_size_t>(value->byteLength()));
return ret;
}
struct Iterator {
bool done = false;
Vector<uint8_t> value;
};
// Performs a single read from |reader|, converting the output to the
// Iterator type. Assumes that the readable stream is not errored.
// static Iterator Read(const V8TestingScope& scope,
Iterator Read(const V8TestingScope& scope,
ReadableStreamDefaultReader* reader) {
auto* script_state = scope.GetScriptState();
ScriptPromise read_promise =
reader->read(script_state, ASSERT_NO_EXCEPTION);
ScriptPromiseTester tester(script_state, read_promise);
tester.WaitUntilSettled();
EXPECT_TRUE(tester.IsFulfilled());
return IteratorFromReadResult(scope, tester.Value().V8Value());
}
static Iterator IteratorFromReadResult(const V8TestingScope& scope,
v8::Local<v8::Value> result) {
CHECK(result->IsObject());
Iterator ret;
v8::Local<v8::Value> v8value;
if (!V8UnpackIteratorResult(scope.GetScriptState(), result.As<v8::Object>(),
&ret.done)
.ToLocal(&v8value)) {
ADD_FAILURE() << "Couldn't unpack iterator";
return ret;
}
if (ret.done) {
EXPECT_TRUE(v8value->IsUndefined());
return ret;
}
ret.value = ToVector(scope, v8value);
return ret;
}
void OnAbort() { on_abort_called_ = true; }
bool HasAborted() const { return on_abort_called_; }
private:
bool on_abort_called_ = false;
mojo::ScopedDataPipeProducerHandle data_pipe_producer_;
};
TEST(TCPReadableStreamWrapperTest, Create) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_readable_stream_wrapper = stream_creator.Create(scope);
EXPECT_TRUE(tcp_readable_stream_wrapper->Readable());
}
TEST(TCPReadableStreamWrapperTest, ReadArrayBuffer) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_readable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* reader =
tcp_readable_stream_wrapper->Readable()->GetDefaultReaderForTesting(
script_state, ASSERT_NO_EXCEPTION);
stream_creator.WriteToPipe({'A'});
StreamCreator::Iterator result = stream_creator.Read(scope, reader);
EXPECT_FALSE(result.done);
EXPECT_THAT(result.value, ElementsAre('A'));
}
TEST(TCPReadableStreamWrapperTest, WriteToPipeWithPendingRead) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_readable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* reader =
tcp_readable_stream_wrapper->Readable()->GetDefaultReaderForTesting(
script_state, ASSERT_NO_EXCEPTION);
ScriptPromise read_promise = reader->read(script_state, ASSERT_NO_EXCEPTION);
ScriptPromiseTester tester(script_state, read_promise);
test::RunPendingTasks();
stream_creator.WriteToPipe({'A'});
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
StreamCreator::Iterator result =
stream_creator.IteratorFromReadResult(scope, tester.Value().V8Value());
EXPECT_FALSE(result.done);
EXPECT_THAT(result.value, ElementsAre('A'));
}
TEST(TCPReadableStreamWrapperTest, TriggerOnAborted) {
V8TestingScope scope;
StreamCreator stream_creator;
EXPECT_FALSE(stream_creator.HasAborted());
auto* tcp_readable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* reader =
tcp_readable_stream_wrapper->Readable()->GetDefaultReaderForTesting(
script_state, ASSERT_NO_EXCEPTION);
ScriptPromise read_promise = reader->read(script_state, ASSERT_NO_EXCEPTION);
ScriptPromiseTester tester(script_state, read_promise);
test::RunPendingTasks();
stream_creator.WriteToPipe({'A'});
// Trigger OnAborted() on purpose.
stream_creator.ClosePipe();
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
EXPECT_TRUE(stream_creator.HasAborted());
}
} // namespace
} // namespace blink
...@@ -50,9 +50,23 @@ void TCPSocket::Init(int32_t result, ...@@ -50,9 +50,23 @@ void TCPSocket::Init(int32_t result,
mojo::ScopedDataPipeConsumerHandle receive_stream, mojo::ScopedDataPipeConsumerHandle receive_stream,
mojo::ScopedDataPipeProducerHandle send_stream) { mojo::ScopedDataPipeProducerHandle send_stream) {
DCHECK(resolver_); DCHECK(resolver_);
DCHECK(!tcp_readable_stream_wrapper_);
DCHECK(!tcp_writable_stream_wrapper_);
if (result == net::Error::OK) { if (result == net::Error::OK) {
// TODO(crbug.com/905818): Finish initialization. local_addr_ = local_addr;
NOTIMPLEMENTED(); peer_addr_ = peer_addr;
tcp_readable_stream_wrapper_ =
MakeGarbageCollected<TCPReadableStreamWrapper>(
resolver_->GetScriptState(),
WTF::Bind(&TCPSocket::OnReadableStreamAbort,
WrapWeakPersistent(this)),
std::move(receive_stream));
tcp_writable_stream_wrapper_ =
MakeGarbageCollected<TCPWritableStreamWrapper>(
resolver_->GetScriptState(),
WTF::Bind(&TCPSocket::OnWritableStreamAbort,
WrapWeakPersistent(this)),
std::move(send_stream));
resolver_->Resolve(this); resolver_->Resolve(this);
} else { } else {
resolver_->Reject(MakeGarbageCollected<DOMException>( resolver_->Reject(MakeGarbageCollected<DOMException>(
...@@ -67,6 +81,26 @@ ScriptPromise TCPSocket::close(ScriptState*, ExceptionState&) { ...@@ -67,6 +81,26 @@ ScriptPromise TCPSocket::close(ScriptState*, ExceptionState&) {
return ScriptPromise(); return ScriptPromise();
} }
ReadableStream* TCPSocket::readable() const {
DCHECK(tcp_readable_stream_wrapper_);
return tcp_readable_stream_wrapper_->Readable();
}
WritableStream* TCPSocket::writable() const {
DCHECK(tcp_writable_stream_wrapper_);
return tcp_writable_stream_wrapper_->Writable();
}
String TCPSocket::remoteAddress() const {
DCHECK(peer_addr_);
return String::FromUTF8(peer_addr_->ToStringWithoutPort());
}
uint16_t TCPSocket::remotePort() const {
DCHECK(peer_addr_);
return peer_addr_->port();
}
void TCPSocket::OnReadError(int32_t net_error) { void TCPSocket::OnReadError(int32_t net_error) {
// TODO(crbug.com/905818): Implement error handling. // TODO(crbug.com/905818): Implement error handling.
NOTIMPLEMENTED(); NOTIMPLEMENTED();
...@@ -79,6 +113,8 @@ void TCPSocket::OnWriteError(int32_t net_error) { ...@@ -79,6 +113,8 @@ void TCPSocket::OnWriteError(int32_t net_error) {
void TCPSocket::Trace(Visitor* visitor) const { void TCPSocket::Trace(Visitor* visitor) const {
visitor->Trace(resolver_); visitor->Trace(resolver_);
visitor->Trace(tcp_readable_stream_wrapper_);
visitor->Trace(tcp_writable_stream_wrapper_);
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
} }
...@@ -87,4 +123,20 @@ void TCPSocket::OnSocketObserverConnectionError() { ...@@ -87,4 +123,20 @@ void TCPSocket::OnSocketObserverConnectionError() {
NOTIMPLEMENTED(); NOTIMPLEMENTED();
} }
void TCPSocket::OnReadableStreamAbort() {
if (tcp_writable_stream_wrapper_->GetState() ==
TCPWritableStreamWrapper::State::kAborted) {
return;
}
tcp_writable_stream_wrapper_->Reset();
}
void TCPSocket::OnWritableStreamAbort() {
if (tcp_readable_stream_wrapper_->GetState() ==
TCPReadableStreamWrapper::State::kAborted) {
return;
}
tcp_readable_stream_wrapper_->Reset();
}
} // namespace blink } // namespace blink
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
#include "mojo/public/cpp/system/data_pipe.h" #include "mojo/public/cpp/system/data_pipe.h"
#include "services/network/public/mojom/tcp_socket.mojom-blink.h" #include "services/network/public/mojom/tcp_socket.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/modules/direct_sockets/tcp_readable_stream_wrapper.h"
#include "third_party/blink/renderer/modules/direct_sockets/tcp_writable_stream_wrapper.h"
#include "third_party/blink/renderer/modules/modules_export.h" #include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
...@@ -51,6 +53,11 @@ class MODULES_EXPORT TCPSocket final ...@@ -51,6 +53,11 @@ class MODULES_EXPORT TCPSocket final
// Web-exposed function // Web-exposed function
ScriptPromise close(ScriptState*, ExceptionState&); ScriptPromise close(ScriptState*, ExceptionState&);
ReadableStream* readable() const;
WritableStream* writable() const;
String remoteAddress() const;
uint16_t remotePort() const;
// network::mojom::blink::SocketObserver: // network::mojom::blink::SocketObserver:
void OnReadError(int32_t net_error) override; void OnReadError(int32_t net_error) override;
void OnWriteError(int32_t net_error) override; void OnWriteError(int32_t net_error) override;
...@@ -61,6 +68,9 @@ class MODULES_EXPORT TCPSocket final ...@@ -61,6 +68,9 @@ class MODULES_EXPORT TCPSocket final
private: private:
void OnSocketObserverConnectionError(); void OnSocketObserverConnectionError();
void OnReadableStreamAbort();
void OnWritableStreamAbort();
Member<ScriptPromiseResolver> resolver_; Member<ScriptPromiseResolver> resolver_;
FrameOrWorkerScheduler::SchedulingAffectingFeatureHandle FrameOrWorkerScheduler::SchedulingAffectingFeatureHandle
feature_handle_for_scheduler_; feature_handle_for_scheduler_;
...@@ -68,6 +78,11 @@ class MODULES_EXPORT TCPSocket final ...@@ -68,6 +78,11 @@ class MODULES_EXPORT TCPSocket final
mojo::Remote<network::mojom::blink::TCPConnectedSocket> tcp_socket_; mojo::Remote<network::mojom::blink::TCPConnectedSocket> tcp_socket_;
mojo::Receiver<network::mojom::blink::SocketObserver> mojo::Receiver<network::mojom::blink::SocketObserver>
socket_observer_receiver_{this}; socket_observer_receiver_{this};
Member<TCPReadableStreamWrapper> tcp_readable_stream_wrapper_;
Member<TCPWritableStreamWrapper> tcp_writable_stream_wrapper_;
base::Optional<net::IPEndPoint> local_addr_;
base::Optional<net::IPEndPoint> peer_addr_;
}; };
} // namespace blink } // namespace blink
......
...@@ -14,4 +14,9 @@ ...@@ -14,4 +14,9 @@
[RaisesException, CallWith=ScriptState] [RaisesException, CallWith=ScriptState]
Promise<void> close(); Promise<void> close();
readonly attribute DOMString remoteAddress;
readonly attribute unsigned short remotePort;
readonly attribute ReadableStream readable;
readonly attribute WritableStream writable;
}; };
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_DIRECT_SOCKETS_TCP_WRITABLE_STREAM_WRAPPER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_DIRECT_SOCKETS_TCP_WRITABLE_STREAM_WRAPPER_H_
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/modules/modules_export.h"
namespace v8 {
class Isolate;
}
namespace blink {
class ScriptState;
class WritableStream;
class WritableStreamDefaultController;
// Helper class to write to a mojo producer handle
class MODULES_EXPORT TCPWritableStreamWrapper final
: public GarbageCollected<TCPWritableStreamWrapper> {
USING_PRE_FINALIZER(TCPWritableStreamWrapper, Dispose);
public:
enum class State {
kOpen,
kAborted,
kClosed,
};
TCPWritableStreamWrapper(ScriptState*,
base::OnceClosure on_abort,
mojo::ScopedDataPipeProducerHandle);
~TCPWritableStreamWrapper();
WritableStream* Writable() const {
DVLOG(1) << "TCPWritableStreamWrapper::writable() called";
return writable_;
}
ScriptState* GetScriptState() { return script_state_; }
void Reset();
State GetState() const { return state_; }
void Trace(Visitor*) const;
private:
class UnderlyingSink;
// Called when |data_pipe_| becomes writable or errored.
void OnHandleReady(MojoResult, const mojo::HandleSignalsState&);
// Called when |data_pipe_| is closed.
void OnPeerClosed(MojoResult, const mojo::HandleSignalsState&);
// Implements UnderlyingSink::write().
ScriptPromise SinkWrite(ScriptState*, ScriptValue chunk, ExceptionState&);
// Writes |data| to |data_pipe_|, possible saving unwritten data to
// |cached_data_|.
ScriptPromise WriteOrCacheData(ScriptState*, base::span<const uint8_t> data);
// Attempts to write some more of |cached_data_| to |data_pipe_|.
void WriteCachedData();
// Writes zero or more bytes of |data| synchronously to |data_pipe_|,
// returning the number of bytes that were written.
size_t WriteDataSynchronously(base::span<const uint8_t> data);
// Creates a DOMException indicating that the stream has been aborted.
ScriptValue CreateAbortException();
// Errors |writable_|, resolves |writing_aborted_| and resets |data_pipe_|.
void ErrorStreamAbortAndReset();
// Reset the |data_pipe_|.
void AbortAndReset();
// Resets |data_pipe_| and clears the watchers. Also discards |cached_data_|.
void ResetPipe();
// Prepares the object for destruction.
void Dispose();
class CachedDataBuffer {
public:
CachedDataBuffer(v8::Isolate* isolate, const uint8_t* data, size_t length);
~CachedDataBuffer();
size_t length() const { return length_; }
uint8_t* data() { return buffer_.get(); }
private:
// We need the isolate to call |AdjustAmountOfExternalAllocatedMemory| for
// the memory stored in |buffer_|.
v8::Isolate* isolate_;
size_t length_ = 0u;
struct OnFree {
void operator()(void* ptr) const {
WTF::Partitions::BufferPartition()->Free(ptr);
}
};
std::unique_ptr<uint8_t[], OnFree> buffer_;
};
const Member<ScriptState> script_state_;
base::OnceClosure on_abort_;
mojo::ScopedDataPipeProducerHandle data_pipe_;
// Only armed when we need to write something.
mojo::SimpleWatcher write_watcher_;
// Always armed to detect close.
mojo::SimpleWatcher close_watcher_;
// Data which has been passed to write() but still needs to be written
// asynchronously.
// Uses a custom CachedDataBuffer rather than a Vector because
// WTF::Vector is currently limited to 2GB.
std::unique_ptr<CachedDataBuffer> cached_data_;
// The offset into |cached_data_| of the first byte that still needs to be
// written.
size_t offset_ = 0;
Member<WritableStream> writable_;
Member<WritableStreamDefaultController> controller_;
// If an asynchronous write() on the underlying sink object is pending, this
// will be non-null.
Member<ScriptPromiseResolver> write_promise_resolver_;
State state_ = State::kOpen;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_DIRECT_SOCKETS_TCP_WRITABLE_STREAM_WRAPPER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/direct_sockets/tcp_writable_stream_wrapper.h"
#include "base/test/mock_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
namespace {
using ::testing::ElementsAre;
using ::testing::StrictMock;
// The purpose of this class is to ensure that the data pipe is reset before the
// V8TestingScope is destroyed, so that the TCPWritableStreamWrapper object
// doesn't try to create a DOMException after the ScriptState has gone away.
class StreamCreator {
STACK_ALLOCATED();
public:
StreamCreator() = default;
~StreamCreator() {
ClosePipe();
// Let the TCPWritableStreamWrapper object respond to the closure if it
// needs to.
test::RunPendingTasks();
}
// The default value of |capacity| means some sensible value selected by mojo.
TCPWritableStreamWrapper* Create(const V8TestingScope& scope,
uint32_t capacity = 0) {
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = capacity;
mojo::ScopedDataPipeProducerHandle data_pipe_producer;
MojoResult result = mojo::CreateDataPipe(&options, &data_pipe_producer,
&data_pipe_consumer_);
if (result != MOJO_RESULT_OK) {
ADD_FAILURE() << "CreateDataPipe() returned " << result;
}
auto* script_state = scope.GetScriptState();
auto* tcp_writable_stream_wrapper =
MakeGarbageCollected<TCPWritableStreamWrapper>(
script_state,
base::BindOnce(&StreamCreator::OnAbort, base::Unretained(this)),
std::move(data_pipe_producer));
return tcp_writable_stream_wrapper;
}
void ClosePipe() { data_pipe_consumer_.reset(); }
// Reads everything from |data_pipe_consumer_| and returns it in a vector.
Vector<uint8_t> ReadAllPendingData() {
Vector<uint8_t> data;
const void* buffer = nullptr;
uint32_t buffer_num_bytes = 0;
MojoResult result = data_pipe_consumer_->BeginReadData(
&buffer, &buffer_num_bytes, MOJO_BEGIN_READ_DATA_FLAG_NONE);
switch (result) {
case MOJO_RESULT_OK:
break;
case MOJO_RESULT_SHOULD_WAIT: // No more data yet.
return data;
default:
ADD_FAILURE() << "BeginReadData() failed: " << result;
return data;
}
data.Append(static_cast<const uint8_t*>(buffer), buffer_num_bytes);
data_pipe_consumer_->EndReadData(buffer_num_bytes);
return data;
}
void OnAbort() { on_abort_called_ = true; }
bool HasAborted() const { return on_abort_called_; }
private:
bool on_abort_called_ = false;
mojo::ScopedDataPipeConsumerHandle data_pipe_consumer_;
};
TEST(TCPWritableStreamWrapperTest, Create) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_writable_stream_wrapper = stream_creator.Create(scope);
EXPECT_TRUE(tcp_writable_stream_wrapper->Writable());
}
TEST(TCPWritableStreamWrapperTest, WriteArrayBuffer) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_writable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* writer = tcp_writable_stream_wrapper->Writable()->getWriter(
script_state, ASSERT_NO_EXCEPTION);
auto* chunk = DOMArrayBuffer::Create("A", 1);
ScriptPromise result =
writer->write(script_state, ScriptValue::From(script_state, chunk),
ASSERT_NO_EXCEPTION);
ScriptPromiseTester tester(scope.GetScriptState(), result);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
EXPECT_THAT(stream_creator.ReadAllPendingData(), ElementsAre('A'));
}
TEST(TCPWritableStreamWrapperTest, WriteArrayBufferView) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_writable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* writer = tcp_writable_stream_wrapper->Writable()->getWriter(
script_state, ASSERT_NO_EXCEPTION);
auto* buffer = DOMArrayBuffer::Create("*B", 2);
// Create a view into the buffer with offset 1, ie. "B".
auto* chunk = DOMUint8Array::Create(buffer, 1, 1);
ScriptPromise result =
writer->write(script_state, ScriptValue::From(script_state, chunk),
ASSERT_NO_EXCEPTION);
ScriptPromiseTester tester(scope.GetScriptState(), result);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
EXPECT_THAT(stream_creator.ReadAllPendingData(), ElementsAre('B'));
}
bool IsAllNulls(base::span<const uint8_t> data) {
return std::all_of(data.begin(), data.end(), [](uint8_t c) { return !c; });
}
TEST(TCPWritableStreamWrapperTest, AsyncWrite) {
V8TestingScope scope;
StreamCreator stream_creator;
// Set a large pipe capacity, so any platform-specific excess is dwarfed in
// size.
constexpr uint32_t kPipeCapacity = 512u * 1024u;
auto* tcp_writable_stream_wrapper =
stream_creator.Create(scope, kPipeCapacity);
auto* script_state = scope.GetScriptState();
auto* writer = tcp_writable_stream_wrapper->Writable()->getWriter(
script_state, ASSERT_NO_EXCEPTION);
// Write a chunk that definitely will not fit in the pipe.
const size_t kChunkSize = kPipeCapacity * 3;
auto* chunk = DOMArrayBuffer::Create(kChunkSize, 1);
ScriptPromise result =
writer->write(script_state, ScriptValue::From(script_state, chunk),
ASSERT_NO_EXCEPTION);
ScriptPromiseTester tester(scope.GetScriptState(), result);
// Let the first pipe write complete.
test::RunPendingTasks();
// Let microtasks run just in case write() returns prematurely.
v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
ASSERT_FALSE(tester.IsFulfilled());
// Read the first part of the data.
auto data1 = stream_creator.ReadAllPendingData();
EXPECT_LT(data1.size(), kChunkSize);
// Verify the data wasn't corrupted.
EXPECT_TRUE(IsAllNulls(data1));
// Allow the asynchronous pipe write to happen.
test::RunPendingTasks();
// Read the second part of the data.
auto data2 = stream_creator.ReadAllPendingData();
EXPECT_TRUE(IsAllNulls(data2));
test::RunPendingTasks();
// Read the final part of the data.
auto data3 = stream_creator.ReadAllPendingData();
EXPECT_TRUE(IsAllNulls(data3));
EXPECT_EQ(data1.size() + data2.size() + data3.size(), kChunkSize);
// Now the write() should settle.
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
// Nothing should be left to read.
EXPECT_THAT(stream_creator.ReadAllPendingData(), ElementsAre());
}
// Writing immediately followed by closing should not lose data.
TEST(TCPWritableStreamWrapperTest, WriteThenClose) {
V8TestingScope scope;
StreamCreator stream_creator;
auto* tcp_writable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* writer = tcp_writable_stream_wrapper->Writable()->getWriter(
script_state, ASSERT_NO_EXCEPTION);
auto* chunk = DOMArrayBuffer::Create("D", 1);
ScriptPromise write_promise =
writer->write(script_state, ScriptValue::From(script_state, chunk),
ASSERT_NO_EXCEPTION);
ScriptPromise close_promise =
writer->close(script_state, ASSERT_NO_EXCEPTION);
ScriptPromiseTester write_tester(scope.GetScriptState(), write_promise);
ScriptPromiseTester close_tester(scope.GetScriptState(), close_promise);
// Make sure that write() and close() both run before the event loop is
// serviced.
v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
write_tester.WaitUntilSettled();
ASSERT_TRUE(write_tester.IsFulfilled());
close_tester.WaitUntilSettled();
ASSERT_TRUE(close_tester.IsFulfilled());
EXPECT_THAT(stream_creator.ReadAllPendingData(), ElementsAre('D'));
}
TEST(TCPWritableStreamWrapperTest, TriggerHasAborted) {
V8TestingScope scope;
StreamCreator stream_creator;
EXPECT_FALSE(stream_creator.HasAborted());
auto* tcp_writable_stream_wrapper = stream_creator.Create(scope);
auto* script_state = scope.GetScriptState();
auto* writer = tcp_writable_stream_wrapper->Writable()->getWriter(
script_state, ASSERT_NO_EXCEPTION);
auto* chunk = DOMArrayBuffer::Create("D", 1);
ScriptPromise write_promise =
writer->write(script_state, ScriptValue::From(script_state, chunk),
ASSERT_NO_EXCEPTION);
ScriptPromiseTester write_tester(scope.GetScriptState(), write_promise);
// Trigger onAborted() on purpose.
stream_creator.ClosePipe();
write_tester.WaitUntilSettled();
EXPECT_TRUE(stream_creator.HasAborted());
}
} // namespace
} // namespace blink
...@@ -8285,6 +8285,10 @@ interface SyncManager ...@@ -8285,6 +8285,10 @@ interface SyncManager
method register method register
interface TCPSocket interface TCPSocket
attribute @@toStringTag attribute @@toStringTag
getter readable
getter remoteAddress
getter remotePort
getter writable
method close method close
method constructor method constructor
interface TaskAttributionTiming : PerformanceEntry interface TaskAttributionTiming : PerformanceEntry
......
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