blob: dab0fd80f2969194568e463b6c59e12005702bec [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/parcel.h"
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <sstream>
#include <string>
#include <utility>
#include "ipcz/node_link.h"
#include "ipcz/node_link_memory.h"
#include "third_party/abseil-cpp/absl/base/macros.h"
#include "third_party/abseil-cpp/absl/types/span.h"
namespace ipcz {
Parcel::Parcel() = default;
Parcel::Parcel(SequenceNumber sequence_number)
: sequence_number_(sequence_number) {}
// Note: We do not use default move construction or assignment because we want
// to explicitly clear the data and object views of the moved-from Parcel.
Parcel::Parcel(Parcel&& other)
: sequence_number_(other.sequence_number_),
remote_source_(std::move(other.remote_source_)),
inlined_data_(std::move(other.inlined_data_)),
data_fragment_(std::exchange(other.data_fragment_, {})),
data_fragment_memory_(
std::exchange(other.data_fragment_memory_, nullptr)),
objects_(std::move(other.objects_)),
data_view_(std::exchange(other.data_view_, {})),
objects_view_(std::exchange(other.objects_view_, {})) {}
Parcel& Parcel::operator=(Parcel&& other) {
sequence_number_ = other.sequence_number_;
remote_source_ = std::move(other.remote_source_);
inlined_data_ = std::move(other.inlined_data_);
data_fragment_ = std::exchange(other.data_fragment_, {});
data_fragment_memory_ = std::move(other.data_fragment_memory_);
objects_ = std::move(other.objects_);
data_view_ = std::exchange(other.data_view_, {});
objects_view_ = std::exchange(other.objects_view_, {});
return *this;
}
Parcel::~Parcel() {
for (Ref<APIObject>& object : objects_) {
if (object) {
object->Close();
}
}
if (!data_fragment_.is_null()) {
ABSL_ASSERT(data_fragment_memory_);
data_fragment_memory_->FreeFragment(data_fragment_);
}
}
void Parcel::SetInlinedData(std::vector<uint8_t> data) {
inlined_data_ = std::move(data);
data_view_ = absl::MakeSpan(inlined_data_);
}
void Parcel::AllocateData(size_t num_bytes,
bool allow_partial,
NodeLinkMemory* memory) {
// This should never be called on a Parcel that already has data.
ABSL_ASSERT(inlined_data_.empty());
ABSL_ASSERT(data_fragment_.is_null());
ABSL_ASSERT(data_view_.empty());
Fragment fragment;
if (num_bytes > 0 && memory && memory->allow_parcel_data_allocation()) {
const size_t requested_fragment_size = num_bytes + sizeof(FragmentHeader);
if (allow_partial) {
fragment = memory->AllocateFragmentBestEffort(requested_fragment_size);
} else {
fragment = memory->AllocateFragment(requested_fragment_size);
}
}
if (fragment.is_null()) {
inlined_data_.resize(num_bytes);
data_view_ = absl::MakeSpan(inlined_data_);
return;
}
// The smallest possible Fragment we could allocate above is still
// subsantially larger than a FragmentHeader.
ABSL_ASSERT(fragment.size() > sizeof(FragmentHeader));
// Leave room for a FragmentHeader at the start of the fragment. This header
// is not written until CommitData().
const size_t data_size =
std::min(num_bytes, fragment.size() - sizeof(FragmentHeader));
data_fragment_ = fragment;
data_fragment_memory_ = WrapRefCounted(memory);
data_view_ =
fragment.mutable_bytes().subspan(sizeof(FragmentHeader), data_size);
}
bool Parcel::AdoptDataFragment(Ref<NodeLinkMemory> memory,
const Fragment& fragment) {
// This should never be called on a Parcel that already has data.
ABSL_ASSERT(inlined_data_.empty());
ABSL_ASSERT(data_fragment_.is_null());
ABSL_ASSERT(data_view_.empty());
if (!fragment.is_addressable() || fragment.size() <= sizeof(FragmentHeader)) {
return false;
}
// This load-acquire is balanced by a store-release in CommitData() by the
// producer of this data.
const auto& header =
*reinterpret_cast<const FragmentHeader*>(fragment.bytes().data());
const uint32_t data_size = header.size.load(std::memory_order_acquire);
const size_t max_data_size = fragment.size() - sizeof(FragmentHeader);
if (data_size > max_data_size) {
return false;
}
data_fragment_ = fragment;
data_fragment_memory_ = std::move(memory);
data_view_ =
fragment.mutable_bytes().subspan(sizeof(FragmentHeader), data_size);
return true;
}
void Parcel::SetObjects(std::vector<Ref<APIObject>> objects) {
objects_ = std::move(objects);
objects_view_ = absl::MakeSpan(objects_);
}
void Parcel::CommitData(size_t num_bytes) {
data_view_ = data_view_.first(num_bytes);
if (data_fragment_.is_null()) {
return;
}
ABSL_ASSERT(data_fragment_.is_addressable());
ABSL_ASSERT(num_bytes <= data_fragment_.size() + sizeof(FragmentHeader));
auto& header =
*reinterpret_cast<FragmentHeader*>(data_fragment_.mutable_bytes().data());
header.reserved = 0;
// This store-release is balanced by the load-acquire in AdoptDataFragment()
// by the eventual consumer of this data.
//
// A static_cast is fine here: we do not allocate 4 GB+ fragments.
header.size.store(static_cast<uint32_t>(num_bytes),
std::memory_order_release);
}
void Parcel::ReleaseDataFragment() {
ABSL_ASSERT(!data_fragment_.is_null());
data_fragment_ = {};
data_fragment_memory_.reset();
data_view_ = {};
}
void Parcel::Consume(size_t num_bytes, absl::Span<IpczHandle> out_handles) {
auto data = data_view();
auto objects = objects_view();
ABSL_ASSERT(num_bytes <= data.size());
ABSL_ASSERT(out_handles.size() <= objects.size());
for (size_t i = 0; i < out_handles.size(); ++i) {
out_handles[i] = APIObject::ReleaseAsHandle(std::move(objects[i]));
}
data_view_.remove_prefix(num_bytes);
objects_view_.remove_prefix(out_handles.size());
}
std::string Parcel::Describe() const {
std::stringstream ss;
ss << "parcel " << sequence_number() << " (";
if (!data_view().empty()) {
// Cheesy heuristic: if the first character is an ASCII letter or number,
// assume the parcel data is human-readable and print a few characters.
if (std::isalnum(data_view()[0])) {
const absl::Span<const uint8_t> preview = data_view().subspan(0, 8);
ss << "\"" << std::string(preview.begin(), preview.end());
if (preview.size() < data_size()) {
ss << "...\", " << data_size() << " bytes";
} else {
ss << '"';
}
}
} else {
ss << "no data";
}
if (!objects_view().empty()) {
ss << ", " << num_objects() << " handles";
}
ss << ")";
return ss.str();
}
} // namespace ipcz