blob: d9a5b3bcdf173048f59e26a305ccc825b59eecce [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ipcz/portal.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "ipcz/api_object.h"
#include "ipcz/local_router_link.h"
#include "ipcz/operation_context.h"
#include "ipcz/router.h"
#include "ipcz/trap_event_dispatcher.h"
#include "third_party/abseil-cpp/absl/types/span.h"
#include "util/log.h"
#include "util/overloaded.h"
#include "util/ref_counted.h"
namespace ipcz {
namespace {
bool ValidateAndAcquireObjectsForTransitFrom(
Portal& sender,
absl::Span<const IpczHandle> handles,
std::vector<Ref<APIObject>>& objects) {
objects.resize(handles.size());
for (size_t i = 0; i < handles.size(); ++i) {
auto* object = APIObject::FromHandle(handles[i]);
if (!object || !object->CanSendFrom(sender)) {
return false;
}
objects[i] = WrapRefCounted(object);
}
return true;
}
} // namespace
Portal::Portal(Ref<Node> node, Ref<Router> router)
: node_(std::move(node)), router_(std::move(router)) {}
Portal::~Portal() = default;
// static
Portal::Pair Portal::CreatePair(Ref<Node> node) {
Router::Pair routers{MakeRefCounted<Router>(), MakeRefCounted<Router>()};
DVLOG(5) << "Created new portal pair with routers " << routers.first.get()
<< " and " << routers.second.get();
const OperationContext context{OperationContext::kAPICall};
auto links = LocalRouterLink::CreatePair(LinkType::kCentral, routers,
LocalRouterLink::kStable);
routers.first->SetOutwardLink(context, std::move(links.first));
routers.second->SetOutwardLink(context, std::move(links.second));
return {MakeRefCounted<Portal>(node, std::move(routers.first)),
MakeRefCounted<Portal>(node, std::move(routers.second))};
}
IpczResult Portal::Close() {
router_->CloseRoute();
return IPCZ_RESULT_OK;
}
bool Portal::CanSendFrom(Portal& sender) {
return &sender != this && !sender.router()->HasLocalPeer(*router_);
}
IpczResult Portal::QueryStatus(IpczPortalStatus& status) {
router_->QueryStatus(status);
return IPCZ_RESULT_OK;
}
IpczResult Portal::Merge(Portal& other) {
return router_->MergeRoute(other.router());
}
IpczResult Portal::Put(absl::Span<const uint8_t> data,
absl::Span<const IpczHandle> handles) {
std::vector<Ref<APIObject>> objects;
if (!ValidateAndAcquireObjectsForTransitFrom(*this, handles, objects)) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
if (router_->IsPeerClosed()) {
return IPCZ_RESULT_NOT_FOUND;
}
Parcel parcel;
const IpczResult allocate_result = router_->AllocateOutboundParcel(
data.size(), /*allow_partial=*/false, parcel);
if (allocate_result != IPCZ_RESULT_OK) {
return allocate_result;
}
if (!data.empty()) {
memcpy(parcel.data_view().data(), data.data(), data.size());
}
parcel.CommitData(data.size());
parcel.SetObjects(std::move(objects));
const IpczResult result = router_->SendOutboundParcel(parcel);
if (result == IPCZ_RESULT_OK) {
// If the parcel was sent, the sender relinquishes handle ownership and
// therefore implicitly releases its ref to each object.
for (IpczHandle handle : handles) {
std::ignore = APIObject::TakeFromHandle(handle);
}
}
return result;
}
IpczResult Portal::BeginPut(IpczBeginPutFlags flags,
size_t& num_data_bytes,
void*& data) {
const bool allow_partial = (flags & IPCZ_BEGIN_PUT_ALLOW_PARTIAL) != 0;
if (router_->IsPeerClosed()) {
return IPCZ_RESULT_NOT_FOUND;
}
// Always request a non-zero size for two-phase puts so that we always have
// a non-null data address upon which to key the operation in EndPut().
const size_t num_bytes_to_request = num_data_bytes ? num_data_bytes : 1;
Parcel parcel;
const IpczResult allocation_result = router_->AllocateOutboundParcel(
num_bytes_to_request, allow_partial, parcel);
absl::MutexLock lock(&mutex_);
if (allocation_result != IPCZ_RESULT_OK) {
return allocation_result;
}
num_data_bytes = parcel.data_view().size();
data = parcel.data_view().data();
absl::visit(Overloaded{[&](absl::monostate) {
pending_parcels_.emplace<Parcel>(std::move(parcel));
},
[&](Parcel& first_parcel) {
const void* first_key =
first_parcel.data_view().data();
PendingParcelMap parcels;
parcels[first_key] = std::move(first_parcel);
parcels[data] = std::move(parcel);
pending_parcels_.emplace<PendingParcelMap>(
std::move(parcels));
},
[&](PendingParcelMap& parcels) {
parcels[data] = std::move(parcel);
}},
pending_parcels_);
return IPCZ_RESULT_OK;
}
IpczResult Portal::CommitPut(const void* data,
size_t num_data_bytes_produced,
absl::Span<const IpczHandle> handles) {
std::vector<Ref<APIObject>> objects;
if (!ValidateAndAcquireObjectsForTransitFrom(*this, handles, objects)) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
Parcel parcel;
{
absl::MutexLock lock(&mutex_);
const bool is_request_valid = absl::visit(
Overloaded{
[&](absl::monostate) { return false; },
[&](Parcel& first_parcel) {
if (first_parcel.data_view().data() != data ||
num_data_bytes_produced > first_parcel.data_view().size()) {
return false;
}
parcel = std::move(first_parcel);
pending_parcels_ = absl::monostate{};
return true;
},
[&](PendingParcelMap& parcels) {
auto it = parcels.find(data);
if (it == parcels.end() ||
num_data_bytes_produced > it->second.data_view().size()) {
return false;
}
parcel = std::move(it->second);
parcels.erase(it);
return true;
}},
pending_parcels_);
if (!is_request_valid) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
}
parcel.CommitData(num_data_bytes_produced);
parcel.SetObjects(std::move(objects));
IpczResult result = router_->SendOutboundParcel(parcel);
if (result == IPCZ_RESULT_OK) {
// If the parcel was sent, the sender relinquishes handle ownership and
// therefore implicitly releases its ref to each object.
for (IpczHandle handle : handles) {
APIObject::TakeFromHandle(handle);
}
}
return result;
}
IpczResult Portal::AbortPut(const void* data) {
absl::MutexLock lock(&mutex_);
const bool is_request_valid =
absl::visit(Overloaded{[&](absl::monostate) { return false; },
[&](Parcel& first_parcel) {
if (first_parcel.data_view().data() != data) {
return false;
}
pending_parcels_ = absl::monostate{};
return true;
},
[&](PendingParcelMap& parcels) {
auto it = parcels.find(data);
if (it == parcels.end()) {
return false;
}
parcels.erase(it);
return true;
}},
pending_parcels_);
if (!is_request_valid) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
return IPCZ_RESULT_OK;
}
IpczResult Portal::Get(IpczGetFlags flags,
void* data,
size_t* num_data_bytes,
IpczHandle* handles,
size_t* num_handles,
IpczHandle* parcel) {
return router_->GetNextInboundParcel(flags, data, num_data_bytes, handles,
num_handles, parcel);
}
IpczResult Portal::BeginGet(const void** data,
size_t* num_data_bytes,
size_t* num_handles) {
absl::MutexLock lock(&mutex_);
if (in_two_phase_get_) {
return IPCZ_RESULT_ALREADY_EXISTS;
}
if (router_->IsRouteDead()) {
return IPCZ_RESULT_NOT_FOUND;
}
const IpczResult result =
router_->BeginGetNextIncomingParcel(data, num_data_bytes, num_handles);
if (result == IPCZ_RESULT_OK) {
in_two_phase_get_ = true;
}
return result;
}
IpczResult Portal::CommitGet(size_t num_data_bytes_consumed,
absl::Span<IpczHandle> handles) {
TrapEventDispatcher dispatcher;
absl::MutexLock lock(&mutex_);
if (!in_two_phase_get_) {
return IPCZ_RESULT_FAILED_PRECONDITION;
}
IpczResult result = router_->CommitGetNextIncomingParcel(
num_data_bytes_consumed, handles, dispatcher);
if (result == IPCZ_RESULT_OK) {
in_two_phase_get_ = false;
}
return result;
}
IpczResult Portal::AbortGet() {
absl::MutexLock lock(&mutex_);
if (!in_two_phase_get_) {
return IPCZ_RESULT_FAILED_PRECONDITION;
}
in_two_phase_get_ = false;
return IPCZ_RESULT_OK;
}
} // namespace ipcz