blob: a514dae0bd3401bb1ed270370a1f1cec4eb1b388 [file] [log] [blame]
// 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/udp_socket.h"
#include "base/barrier_callback.h"
#include "base/metrics/histogram_functions.h"
#include "net/base/net_errors.h"
#include "third_party/blink/public/mojom/direct_sockets/direct_sockets.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_udp_socket_open_info.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_udp_socket_options.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/streams/writable_stream.h"
#include "third_party/blink/renderer/modules/direct_sockets/stream_wrapper.h"
#include "third_party/blink/renderer/modules/direct_sockets/udp_readable_stream_wrapper.h"
#include "third_party/blink/renderer/modules/direct_sockets/udp_writable_stream_wrapper.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
namespace {
constexpr char kUDPNetworkFailuresHistogramName[] =
"DirectSockets.UDPNetworkFailures";
constexpr uint32_t readableStreamDefaultBufferSize = 32;
bool CheckSendReceiveReadableStreamBufferSize(const UDPSocketOptions* options,
ExceptionState& exception_state) {
if (options->hasSendBufferSize() && options->sendBufferSize() == 0) {
exception_state.ThrowTypeError("sendBufferSize must be greater than zero.");
return false;
}
if (options->hasReceiveBufferSize() && options->receiveBufferSize() == 0) {
exception_state.ThrowTypeError(
"receiverBufferSize must be greater than zero.");
return false;
}
if (options->hasReadableStreamBufferSize() &&
options->readableStreamBufferSize() == 0) {
exception_state.ThrowTypeError(
"readableStreamBufferSize must be greater than zero.");
return false;
}
return true;
}
mojom::blink::DirectSocketOptionsPtr CreateUDPSocketOptions(
const String& address,
const uint16_t port,
const UDPSocketOptions* options,
ExceptionState& exception_state) {
auto socket_options = mojom::blink::DirectSocketOptions::New();
socket_options->remote_hostname = address;
socket_options->remote_port = port;
if (!CheckSendReceiveReadableStreamBufferSize(options, exception_state)) {
return {};
}
if (options->hasSendBufferSize()) {
socket_options->send_buffer_size = options->sendBufferSize();
}
if (options->hasReceiveBufferSize()) {
socket_options->receive_buffer_size = options->receiveBufferSize();
}
return socket_options;
}
} // namespace
// static
UDPSocket* UDPSocket::Create(ScriptState* script_state,
const String& address,
const uint16_t port,
const UDPSocketOptions* options,
ExceptionState& exception_state) {
if (!Socket::CheckContextAndPermissions(script_state, exception_state)) {
return nullptr;
}
auto* socket = MakeGarbageCollected<UDPSocket>(script_state);
if (!socket->Open(address, port, options, exception_state)) {
return nullptr;
}
return socket;
}
UDPSocket::UDPSocket(ScriptState* script_state)
: Socket(script_state),
udp_socket_(
MakeGarbageCollected<UDPSocketMojoRemote>(GetExecutionContext())),
socket_listener_{this, GetExecutionContext()} {}
UDPSocket::~UDPSocket() = default;
bool UDPSocket::Open(const String& address,
const uint16_t port,
const UDPSocketOptions* options,
ExceptionState& exception_state) {
auto open_udp_socket_options =
CreateUDPSocketOptions(address, port, options, exception_state);
if (exception_state.HadException()) {
return false;
}
if (options->hasReadableStreamBufferSize()) {
readable_stream_buffer_size_ = options->readableStreamBufferSize();
}
ConnectService();
service_->get()->OpenUdpSocket(
std::move(open_udp_socket_options), GetUDPSocketReceiver(),
GetUDPSocketListener(),
WTF::Bind(&UDPSocket::Init, WrapPersistent(this)));
return true;
}
void UDPSocket::Init(int32_t result,
const absl::optional<net::IPEndPoint>& local_addr,
const absl::optional<net::IPEndPoint>& peer_addr) {
if (result == net::OK && peer_addr) {
auto close_callback = base::BarrierCallback<bool>(
/*num_callbacks=*/2,
WTF::Bind(&UDPSocket::OnBothStreamsClosed, WrapWeakPersistent(this)));
readable_stream_wrapper_ = MakeGarbageCollected<UDPReadableStreamWrapper>(
script_state_, close_callback, udp_socket_,
readable_stream_buffer_size_.value_or(readableStreamDefaultBufferSize));
writable_stream_wrapper_ = MakeGarbageCollected<UDPWritableStreamWrapper>(
script_state_, close_callback, udp_socket_);
auto* open_info = UDPSocketOpenInfo::Create();
open_info->setReadable(readable_stream_wrapper_->Readable());
open_info->setWritable(writable_stream_wrapper_->Writable());
open_info->setRemoteAddress(String{peer_addr->ToStringWithoutPort()});
open_info->setRemotePort(peer_addr->port());
open_info->setLocalAddress(String{local_addr->ToStringWithoutPort()});
open_info->setLocalPort(local_addr->port());
opened_resolver_->Resolve(open_info);
} else {
if (result != net::OK) {
// Error codes are negative.
base::UmaHistogramSparse(kUDPNetworkFailuresHistogramName, -result);
}
opened_resolver_->Reject(CreateDOMExceptionFromNetErrorCode(result));
CloseServiceAndResetFeatureHandle();
closed_resolver_->Reject();
}
opened_resolver_ = nullptr;
}
mojo::PendingReceiver<blink::mojom::blink::DirectUDPSocket>
UDPSocket::GetUDPSocketReceiver() {
return udp_socket_->get().BindNewPipeAndPassReceiver(
GetExecutionContext()->GetTaskRunner(TaskType::kNetworking));
}
mojo::PendingRemote<network::mojom::blink::UDPSocketListener>
UDPSocket::GetUDPSocketListener() {
auto pending_remote = socket_listener_.BindNewPipeAndPassRemote(
GetExecutionContext()->GetTaskRunner(TaskType::kNetworking));
socket_listener_.set_disconnect_handler(
WTF::Bind(&UDPSocket::OnSocketConnectionError, WrapPersistent(this)));
return pending_remote;
}
// Invoked when data is received.
// - When UDPSocket is used with Bind() (i.e. when localAddress/localPort in
// options)
// On success, |result| is net::OK. |src_addr| indicates the address of the
// sender. |data| contains the received data.
// On failure, |result| is a negative network error code. |data| is null.
// |src_addr| might be null.
// - When UDPSocket is used with Connect():
// |src_addr| is always null. Data are always received from the remote
// address specified in Connect().
// On success, |result| is net::OK. |data| contains the received data.
// On failure, |result| is a negative network error code. |data| is null.
//
// Note that in both cases, |data| can be an empty buffer when |result| is
// net::OK, which indicates a zero-byte payload.
// For further details please refer to the
// services/network/public/mojom/udp_socket.mojom file.
void UDPSocket::OnReceived(int32_t result,
const absl::optional<::net::IPEndPoint>& src_addr,
absl::optional<::base::span<const ::uint8_t>> data) {
if (result != net::Error::OK) {
CloseOnError();
return;
}
readable_stream_wrapper_->Push(*data, src_addr);
}
bool UDPSocket::HasPendingActivity() const {
return Socket::HasPendingActivity();
}
void UDPSocket::Trace(Visitor* visitor) const {
visitor->Trace(udp_socket_);
visitor->Trace(socket_listener_);
ScriptWrappable::Trace(visitor);
Socket::Trace(visitor);
ActiveScriptWrappable::Trace(visitor);
}
void UDPSocket::OnServiceConnectionError() {
if (opened_resolver_) {
Init(net::ERR_UNEXPECTED, absl::nullopt, absl::nullopt);
}
}
void UDPSocket::OnSocketConnectionError() {
CloseOnError();
}
void UDPSocket::CloseOnError() {
if (!Initialized()) {
return;
}
readable_stream_wrapper_->ErrorStream(net::ERR_CONNECTION_ABORTED);
writable_stream_wrapper_->ErrorStream(net::ERR_CONNECTION_ABORTED);
}
void UDPSocket::OnBothStreamsClosed(std::vector<bool> args) {
DCHECK_EQ(args.size(), 2U);
// At least one callback was invoked with error = true.
bool error = base::ranges::any_of(args, base::identity());
CloseServiceAndResetFeatureHandle();
ResolveOrRejectClosed(error);
socket_listener_.reset();
// Close the socket.
udp_socket_->Close();
}
} // namespace blink