blob: 8bd0d2bad32c1245879623f0c9f7a9e7ebdce073 [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.
#ifndef IPCZ_SRC_IPCZ_PARCEL_H_
#define IPCZ_SRC_IPCZ_PARCEL_H_
#include <algorithm>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include "ipcz/api_object.h"
#include "ipcz/fragment.h"
#include "ipcz/ipcz.h"
#include "ipcz/message.h"
#include "ipcz/node_link.h"
#include "ipcz/node_link_memory.h"
#include "ipcz/sequence_number.h"
#include "third_party/abseil-cpp/absl/base/macros.h"
#include "third_party/abseil-cpp/absl/container/inlined_vector.h"
#include "third_party/abseil-cpp/absl/types/span.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "util/ref_counted.h"
namespace ipcz {
class NodeLink;
class NodeLinkMemory;
// Represents a parcel queued within a portal, either for inbound retrieval or
// outgoing transfer.
class Parcel {
public:
// Arbitrary hard cap on the number of subparcels which can be embedded within
// a parcel, to mitigate the potential for abuse.
static constexpr size_t kMaxSubparcelsPerParcel = 1024;
Parcel();
explicit Parcel(SequenceNumber sequence_number);
Parcel(const Parcel& other) = delete;
Parcel& operator=(const Parcel& other) = delete;
~Parcel();
void set_sequence_number(SequenceNumber n) { sequence_number_ = n; }
SequenceNumber sequence_number() const { return sequence_number_; }
void set_num_subparcels(size_t num_subparcels) {
num_subparcels_ = num_subparcels;
}
size_t num_subparcels() const { return num_subparcels_; }
void set_subparcel_index(size_t index) { subparcel_index_ = index; }
size_t subparcel_index() const { return subparcel_index_; }
// Indicates whether this Parcel is empty, meaning its data and objects have
// been fully consumed.
bool empty() const { return data_view().empty() && objects_view().empty(); }
// Sets this Parcel's data to the contents of `data_view`, backed by a subset
// of the memory within `buffer`. Any prior data in the Parcel is discarded.
void SetDataFromMessage(Message::ReceivedDataBuffer buffer,
absl::Span<uint8_t> data_view);
// Attaches the given set of `objects` to this Parcel.
void SetObjects(std::vector<Ref<APIObject>> objects);
// Allocates `num_bytes` of storage for this Parcel's data. If `memory` is
// non-null then its fragment pool is the preferred allocation source.
// Otherwise memory is allocated and zero-initialized on the heap, and the
// data placed therein will be inlined within any message that transmits this
// parcel.
//
// If `memory` is non-null and `allow_partial` is true, this may allocate less
// memory than requested if some reasonable amount of space is still available
// within `memory`.
//
// Upon return, data_view() references the allocated memory wherever it
// resides.
//
// If the Parcel had any data attached prior to this call, it is discarded and
// replaced with the newly allocated storage.
void AllocateData(size_t num_bytes,
bool allow_partial,
NodeLinkMemory* memory);
// Configures this Parcel to adopt its data fragment from the `fragment`
// belonging to `memory`. `fragment` must be addressable and must have a valid
// FragmentHeader at the start describing the data which follows. Otherwise
// this returns false.
bool AdoptDataFragment(Ref<NodeLinkMemory> memory, const Fragment& fragment);
void set_remote_source(Ref<NodeLink> source) {
remote_source_ = std::move(source);
}
const Ref<NodeLink>& remote_source() const { return remote_source_; }
absl::Span<uint8_t> data_view() { return data_.view; }
absl::Span<const uint8_t> data_view() const { return data_.view; }
size_t data_size() const { return data_view().size(); }
bool has_data_fragment() const {
return absl::holds_alternative<DataFragment>(data_.storage);
}
const Fragment& data_fragment() const {
ABSL_ASSERT(has_data_fragment());
return absl::get<DataFragment>(data_.storage).fragment();
}
const Ref<NodeLinkMemory>& data_fragment_memory() const {
ABSL_ASSERT(has_data_fragment());
return absl::get<DataFragment>(data_.storage).memory();
}
absl::Span<Ref<APIObject>> objects_view() const { return objects_.view; }
size_t num_objects() const { return objects_view().size(); }
// Commits `num_bytes` of data to this Parcel's data fragment. This MUST be
// called after populating the Parcel's data, and it must be called by the
// same thread that populated the data. If the parcel's data is inlined rather
// than stored in a fragment, this is a no-op.
void CommitData(size_t num_bytes);
// Relinquishes ownership of this Parcel's data fragment, if applicable. This
// prevents the fragment from being freed upon Parcel destruction.
void ReleaseDataFragment();
// Filling `out_handles` (of size N) with handles to the first N APIObjects in
// objects_view(). The front of objects_view() is also advanced by N,
// effectively removing the objects from this parcel.
//
// Note that the size of `out_handles` must not be larger than the size of
// objects_view().
void ConsumeHandles(absl::Span<IpczHandle> out_handles);
void SetEnvelope(DriverObject envelope);
// Produces a log-friendly description of the Parcel, useful for various
// debugging log messages.
std::string Describe() const;
private:
// When Parcel data is in a shared memory fragment, this header sits at the
// front of the fragment.
struct IPCZ_ALIGN(8) FragmentHeader {
// The size in bytes of the parcel data which immediately follows this
// header. Must not extend beyond the bounds of the fragment itself.
//
// Access to this atomic is also used to synchronize access to the parcel
// data. Writes to the fragment must be finalized by calling CommitData(),
// and any node receiving this parcel must adopt the fragment by calling
// AdoptDataFragment().
std::atomic<uint32_t> size;
// Reserved padding for 8-byte parcel data alignment.
std::atomic<uint32_t> reserved;
};
// Holds a Fragment and a reference to its backing memory. Used when a
// Parcel's data lives in shared memory. This implements exclusive ownership
// of the underlying fragment, and upon destruction it releases the fragment
// back into the memory pool.
class DataFragment {
public:
DataFragment() = default;
DataFragment(Ref<NodeLinkMemory> memory, const Fragment& fragment)
: memory_(std::move(memory)), fragment_(fragment) {
// Parcels can only be given data fragments which are already addressable.
ABSL_ASSERT(is_valid());
}
DataFragment(const DataFragment& other) = delete;
DataFragment& operator=(const DataFragment& other) = delete;
~DataFragment();
bool is_valid() const { return memory_ && fragment_.is_addressable(); }
const Fragment& fragment() const { return fragment_; }
const Ref<NodeLinkMemory>& memory() const { return memory_; }
[[nodiscard]] Fragment release();
void reset();
private:
Ref<NodeLinkMemory> memory_;
Fragment fragment_;
};
// A variant backing type for the parcel's data. Data may be in shared memory,
// heap-allocated and initialized from within the Parcel, or heap-allocated by
// a received Message and moved into the Parcel from there.
using DataStorage = absl::variant<absl::monostate,
DataFragment,
std::vector<uint8_t>,
Message::ReceivedDataBuffer>;
// Groups a DataStorage with a view into its data. This defines its own move
// construction and assignment operators to ensure that moved-from data is
// cleared. Note that the view may reference only a subset of the data within
// the DataStorage. This subset is considered the Parcel's data.
struct DataStorageWithView {
DataStorageWithView() = default;
DataStorageWithView(const DataStorageWithView& other) = delete;
DataStorageWithView& operator=(const DataStorageWithView& other) = delete;
~DataStorageWithView() = default;
DataStorage storage;
absl::Span<uint8_t> view;
};
// Groups a vector of object attachments along with a view into that vector.
// Note that the view may reference only a subset of the elements within the
// vector if some objects have been removed or not-yet-attached.
struct ObjectStorageWithView {
ObjectStorageWithView() = default;
ObjectStorageWithView(const ObjectStorageWithView&) = delete;
ObjectStorageWithView& operator=(const ObjectStorageWithView&) = delete;
~ObjectStorageWithView() = default;
std::vector<Ref<APIObject>> storage;
absl::Span<Ref<APIObject>> view;
};
SequenceNumber sequence_number_{0};
// If this Parcel was received from a remote node, this tracks the NodeLink
// which received it.
Ref<NodeLink> remote_source_;
// Concrete storage for the parcel's data, along with a view the data not yet
// consumed.
DataStorageWithView data_;
// The set of APIObjects attached to this parcel, and a view of the objects
// not yet consumed from it.
ObjectStorageWithView objects_;
// By default, all parcels have a single subparcel (theirself) at index 0. On
// any Parcel that exists as a subparcel of another, these fields will be
// updated by the containing Parcel as needed.
size_t num_subparcels_ = 1;
size_t subparcel_index_ = 0;
DriverObject envelope_;
};
} // namespace ipcz
#endif // IPCZ_SRC_IPCZ_PARCEL_H_