Commit 5413038b authored by Kenton Varda's avatar Kenton Varda

Refactor how messages are imbued with a capability table.

**The problem**

The methods MessageReader::initCapTable() and MessageBuilder::getCapTable() always felt rather hacky. initCapTable() in particular feels like something that should be handled by the constructor. However, in practice, the cap table is often initialized based on a table encoded within the message itself. That is, an RPC message contains a "payload" which includes both the application-level message structure and a table of capabilities. The cap table has to be processed first, then initCapTable() is called on the overall message, before the application structure can safely be read.

The really weird part about this is that even though the cap table only applies to one branch of the message (the payload), it is set on the *whole* MessageReader. This implies, for example, that it would be impossible to have a message that contains multiple payloads. We haven't had any need for such a thing, but an implemnetation that has such artificial limitations feels very wrong.

MessageBuilder has similar issues going in the opposite direction.

All of this ugliness potentially gets worse when we introduce "membranes". We want a way to intercept capabilities as they are being read from or written to an RPC payload. Currently, the only plausible way to do that is, again, to apply a transformation to all capabilities in the message. In practice it seems like this would work out OK, but it again feels wrong -- we really want to take a single Reader or Builder and "wrap" it so that transformations are applied on capabilities read/written through it.

**The solution**

This change fixes the problem by adding a new pointer to each struct/list Reader/Builder that tracks the current cap table. So, now a Reader or Builder for a particular sub-object can be "imbued" with a cap table without affecting any other existing Readers/Builders pointing into the same message. The cap table is inherited by child Readers/Builders obtained through the original one.

This approach matches up nicely with membranes, which should make their implementation nice and clean.

This change unfortunately means that Readers and Builders are now bigger, possibly with some performance impact.
parent 9fc1590b
...@@ -138,6 +138,7 @@ struct AnyPointer { ...@@ -138,6 +138,7 @@ struct AnyPointer {
friend struct AnyPointer; friend struct AnyPointer;
friend class Orphanage; friend class Orphanage;
friend class CapReaderContext; friend class CapReaderContext;
friend class _::PointerHelpers<AnyPointer>;
}; };
class Builder { class Builder {
...@@ -254,6 +255,7 @@ struct AnyPointer { ...@@ -254,6 +255,7 @@ struct AnyPointer {
_::PointerBuilder builder; _::PointerBuilder builder;
friend class Orphanage; friend class Orphanage;
friend class CapBuilderContext; friend class CapBuilderContext;
friend class _::PointerHelpers<AnyPointer>;
}; };
#if !CAPNP_LITE #if !CAPNP_LITE
...@@ -888,6 +890,12 @@ struct PointerHelpers<AnyPointer, Kind::OTHER> { ...@@ -888,6 +890,12 @@ struct PointerHelpers<AnyPointer, Kind::OTHER> {
static inline Orphan<AnyPointer> disown(PointerBuilder builder) { static inline Orphan<AnyPointer> disown(PointerBuilder builder) {
return Orphan<AnyPointer>(builder.disown()); return Orphan<AnyPointer>(builder.disown());
} }
static inline _::PointerReader getInternalReader(const AnyPointer::Reader& reader) {
return reader.reader;
}
static inline _::PointerBuilder getInternalBuilder(AnyPointer::Builder&& builder) {
return builder.builder;
}
}; };
template <> template <>
......
...@@ -108,16 +108,6 @@ void ReaderArena::reportReadLimitReached() { ...@@ -108,16 +108,6 @@ void ReaderArena::reportReadLimitReached() {
} }
} }
#if !CAPNP_LITE
kj::Maybe<kj::Own<ClientHook>> ReaderArena::extractCap(uint index) {
if (index < capTable.size()) {
return capTable[index].map([](kj::Own<ClientHook>& cap) { return cap->addRef(); });
} else {
return nullptr;
}
}
#endif // !CAPNP_LITE
// ======================================================================================= // =======================================================================================
BuilderArena::BuilderArena(MessageBuilder* message) BuilderArena::BuilderArena(MessageBuilder* message)
...@@ -294,7 +284,7 @@ void BuilderArena::reportReadLimitReached() { ...@@ -294,7 +284,7 @@ void BuilderArena::reportReadLimitReached() {
} }
#if !CAPNP_LITE #if !CAPNP_LITE
kj::Maybe<kj::Own<ClientHook>> BuilderArena::extractCap(uint index) { kj::Maybe<kj::Own<ClientHook>> BuilderArena::LocalCapTable::extractCap(uint index) {
if (index < capTable.size()) { if (index < capTable.size()) {
return capTable[index].map([](kj::Own<ClientHook>& cap) { return cap->addRef(); }); return capTable[index].map([](kj::Own<ClientHook>& cap) { return cap->addRef(); });
} else { } else {
...@@ -302,15 +292,13 @@ kj::Maybe<kj::Own<ClientHook>> BuilderArena::extractCap(uint index) { ...@@ -302,15 +292,13 @@ kj::Maybe<kj::Own<ClientHook>> BuilderArena::extractCap(uint index) {
} }
} }
uint BuilderArena::injectCap(kj::Own<ClientHook>&& cap) { uint BuilderArena::LocalCapTable::injectCap(kj::Own<ClientHook>&& cap) {
// TODO(perf): Detect if the cap is already on the table and reuse the index? Perhaps this
// doesn't happen enough to be worth the effort.
uint result = capTable.size(); uint result = capTable.size();
capTable.add(kj::mv(cap)); capTable.add(kj::mv(cap));
return result; return result;
} }
void BuilderArena::dropCap(uint index) { void BuilderArena::LocalCapTable::dropCap(uint index) {
KJ_ASSERT(index < capTable.size(), "Invalid capability descriptor in message.") { KJ_ASSERT(index < capTable.size(), "Invalid capability descriptor in message.") {
return; return;
} }
......
...@@ -205,11 +205,6 @@ public: ...@@ -205,11 +205,6 @@ public:
// Called to report that the read limit has been reached. See ReadLimiter, below. This invokes // Called to report that the read limit has been reached. See ReadLimiter, below. This invokes
// the VALIDATE_INPUT() macro which may throw an exception; if it returns normally, the caller // the VALIDATE_INPUT() macro which may throw an exception; if it returns normally, the caller
// will need to continue with default values. // will need to continue with default values.
#if !CAPNP_LITE
virtual kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) = 0;
// Extract the capability at the given index. If the index is invalid, returns null.
#endif // !CAPNP_LITE
}; };
class ReaderArena final: public Arena { class ReaderArena final: public Arena {
...@@ -218,28 +213,13 @@ public: ...@@ -218,28 +213,13 @@ public:
~ReaderArena() noexcept(false); ~ReaderArena() noexcept(false);
KJ_DISALLOW_COPY(ReaderArena); KJ_DISALLOW_COPY(ReaderArena);
#if !CAPNP_LITE
inline void initCapTable(kj::Array<kj::Maybe<kj::Own<ClientHook>>> capTable) {
// Imbues the arena with a capability table. This is not passed to the constructor because the
// table itself may be built based on some other part of the message (as is the case with the
// RPC protocol).
this->capTable = kj::mv(capTable);
}
#endif // !CAPNP_LITE
// implements Arena ------------------------------------------------ // implements Arena ------------------------------------------------
SegmentReader* tryGetSegment(SegmentId id) override; SegmentReader* tryGetSegment(SegmentId id) override;
void reportReadLimitReached() override; void reportReadLimitReached() override;
#if !CAPNP_LITE
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index);
#endif // !CAPNP_LITE
private: private:
MessageReader* message; MessageReader* message;
ReadLimiter readLimiter; ReadLimiter readLimiter;
#if !CAPNP_LITE
kj::Array<kj::Maybe<kj::Own<ClientHook>>> capTable;
#endif // !CAPNP_LITE
// Optimize for single-segment messages so that small messages are handled quickly. // Optimize for single-segment messages so that small messages are handled quickly.
SegmentReader segment0; SegmentReader segment0;
...@@ -272,8 +252,22 @@ public: ...@@ -272,8 +252,22 @@ public:
// not-yet-allocated space. // not-yet-allocated space.
#if !CAPNP_LITE #if !CAPNP_LITE
inline kj::ArrayPtr<kj::Maybe<kj::Own<ClientHook>>> getCapTable() { return capTable; } inline CapTableBuilder* getLocalCapTable() {
// Return the capability table. // Return a CapTableBuilder that merely implements local loopback. That is, you can set
// capabilities, then read the same capabilities back, but there is no intent ever to transmit
// these capabilities. A MessageBuilder that isn't imbued with some other CapTable uses this
// by default.
//
// TODO(cleanup): It's sort of a hack that this exists. In theory, perhaps, unimbued
// MessageBuilders should throw exceptions on any attempt to access capability fields, like
// unimbued MessageReaders do. However, lots of code exists which uses MallocMessageBuilder
// as a temporary holder for data to be copied in and out (without being serialized), and it
// is expected that such data can include capabilities, which is admittedly reasonable.
// Therefore, all MessageBuilders must have a cap table by default. Arguably we should
// deprecate this usage and instead define a new helper type for this exact purpose.
return &localCapTable;
}
#endif // !CAPNP_LITE #endif // !CAPNP_LITE
SegmentBuilder* getSegment(SegmentId id); SegmentBuilder* getSegment(SegmentId id);
...@@ -301,28 +295,26 @@ public: ...@@ -301,28 +295,26 @@ public:
// from disk (until the message itself is written out). `Orphanage` provides the public API for // from disk (until the message itself is written out). `Orphanage` provides the public API for
// this feature. // this feature.
#if !CAPNP_LITE
uint injectCap(kj::Own<ClientHook>&& cap);
// Add the capability to the message and return its index. If the same ClientHook is injected
// twice, this may return the same index both times, but in this case dropCap() needs to be
// called an equal number of times to actually remove the cap.
#endif // !CAPNP_LITE
void dropCap(uint index);
// Remove a capability injected earlier. Called when the pointer is overwritten or zero'd out.
// implements Arena ------------------------------------------------ // implements Arena ------------------------------------------------
SegmentReader* tryGetSegment(SegmentId id) override; SegmentReader* tryGetSegment(SegmentId id) override;
void reportReadLimitReached() override; void reportReadLimitReached() override;
#if !CAPNP_LITE
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index);
#endif // !CAPNP_LITE
private: private:
MessageBuilder* message; MessageBuilder* message;
ReadLimiter dummyLimiter; ReadLimiter dummyLimiter;
#if !CAPNP_LITE #if !CAPNP_LITE
kj::Vector<kj::Maybe<kj::Own<ClientHook>>> capTable; class LocalCapTable: public CapTableBuilder {
public:
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) override;
uint injectCap(kj::Own<ClientHook>&& cap) override;
void dropCap(uint index) override;
private:
kj::Vector<kj::Maybe<kj::Own<ClientHook>>> capTable;
};
LocalCapTable localCapTable;
#endif // !CAPNP_LITE #endif // !CAPNP_LITE
SegmentBuilder segment0; SegmentBuilder segment0;
......
...@@ -65,11 +65,6 @@ void* ClientHook::getLocalServer(_::CapabilityServerSetBase& capServerSet) { ...@@ -65,11 +65,6 @@ void* ClientHook::getLocalServer(_::CapabilityServerSetBase& capServerSet) {
return nullptr; return nullptr;
} }
void MessageReader::initCapTable(kj::Array<kj::Maybe<kj::Own<ClientHook>>> capTable) {
setGlobalBrokenCapFactoryForLayoutCpp(brokenCapFactory);
arena()->initCapTable(kj::mv(capTable));
}
// ======================================================================================= // =======================================================================================
Capability::Client::Client(decltype(nullptr)) Capability::Client::Client(decltype(nullptr))
...@@ -655,6 +650,47 @@ Request<AnyPointer, AnyPointer> newBrokenRequest( ...@@ -655,6 +650,47 @@ Request<AnyPointer, AnyPointer> newBrokenRequest(
return Request<AnyPointer, AnyPointer>(root, kj::mv(hook)); return Request<AnyPointer, AnyPointer>(root, kj::mv(hook));
} }
// =======================================================================================
ReaderCapabilityTable::ReaderCapabilityTable(
kj::Array<kj::Maybe<kj::Own<ClientHook>>> table)
: table(kj::mv(table)) {
setGlobalBrokenCapFactoryForLayoutCpp(brokenCapFactory);
}
kj::Maybe<kj::Own<ClientHook>> ReaderCapabilityTable::extractCap(uint index) {
if (index < table.size()) {
return table[index].map([](kj::Own<ClientHook>& cap) { return cap->addRef(); });
} else {
return nullptr;
}
}
BuilderCapabilityTable::BuilderCapabilityTable() {
setGlobalBrokenCapFactoryForLayoutCpp(brokenCapFactory);
}
kj::Maybe<kj::Own<ClientHook>> BuilderCapabilityTable::extractCap(uint index) {
if (index < table.size()) {
return table[index].map([](kj::Own<ClientHook>& cap) { return cap->addRef(); });
} else {
return nullptr;
}
}
uint BuilderCapabilityTable::injectCap(kj::Own<ClientHook>&& cap) {
uint result = table.size();
table.add(kj::mv(cap));
return result;
}
void BuilderCapabilityTable::dropCap(uint index) {
KJ_ASSERT(index < table.size(), "Invalid capability descriptor in message.") {
return;
}
table[index] = nullptr;
}
// ======================================================================================= // =======================================================================================
// CapabilityServerSet // CapabilityServerSet
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#endif #endif
#include <kj/async.h> #include <kj/async.h>
#include <kj/vector.h>
#include "any.h" #include "any.h"
#include "pointer-helpers.h" #include "pointer-helpers.h"
...@@ -91,6 +92,7 @@ struct Capability { ...@@ -91,6 +92,7 @@ struct Capability {
}; };
// ======================================================================================= // =======================================================================================
// Capability clients
class RequestHook; class RequestHook;
class ResponseHook; class ResponseHook;
...@@ -230,7 +232,7 @@ private: ...@@ -230,7 +232,7 @@ private:
}; };
// ======================================================================================= // =======================================================================================
// Local capabilities // Capability servers
class CallContextHook; class CallContextHook;
...@@ -360,6 +362,62 @@ private: ...@@ -360,6 +362,62 @@ private:
// ======================================================================================= // =======================================================================================
class ReaderCapabilityTable: private _::CapTableReader {
// Class which imbues Readers with the ability to read capabilities.
//
// In Cap'n Proto format, the encoding of a capability pointer is simply an integer index into
// an external table. Since these pointers fundamentally point outside the message, a
// MessageReader by default has no idea what they point at, and therefore reading capabilities
// from such a reader will throw exceptions.
//
// In order to be able to read capabilities, you must first attach a capability table, using
// this class. By "imbuing" a Reader, you get a new Reader which will interpret capability
// pointers by treating them as indexes into the ReaderCapabilityTable.
//
// Note that when using Cap'n Proto's RPC system, this is handled automatically.
public:
explicit ReaderCapabilityTable(kj::Array<kj::Maybe<kj::Own<ClientHook>>> table);
KJ_DISALLOW_COPY(ReaderCapabilityTable);
template <typename T>
T imbue(T reader);
// Return a reader equivalent to `reader` except that when reading capability-valued fields,
// the capabilities are looked up in this table.
private:
kj::Array<kj::Maybe<kj::Own<ClientHook>>> table;
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) override;
};
class BuilderCapabilityTable: private _::CapTableBuilder {
// Class which imbues Builders with the ability to read and write capabilities.
//
// This is much like ReaderCapabilityTable, except for builders. The table starts out empty,
// but capabilities can be added to it over time.
public:
BuilderCapabilityTable();
KJ_DISALLOW_COPY(BuilderCapabilityTable);
inline kj::ArrayPtr<kj::Maybe<kj::Own<ClientHook>>> getTable() { return table; }
template <typename T>
T imbue(T builder);
// Return a builder equivalent to `builder` except that when reading capability-valued fields,
// the capabilities are looked up in this table.
private:
kj::Vector<kj::Maybe<kj::Own<ClientHook>>> table;
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) override;
uint injectCap(kj::Own<ClientHook>&& cap) override;
void dropCap(uint index) override;
};
// =======================================================================================
namespace _ { // private namespace _ { // private
class CapabilityServerSetBase { class CapabilityServerSetBase {
...@@ -766,6 +824,16 @@ Capability::Client Capability::Server::thisCap() { ...@@ -766,6 +824,16 @@ Capability::Client Capability::Server::thisCap() {
return Client(thisHook->addRef()); return Client(thisHook->addRef());
} }
template <typename T>
T ReaderCapabilityTable::imbue(T reader) {
return T(_::PointerHelpers<FromReader<T>>::getInternalReader(reader).imbue(this));
}
template <typename T>
T BuilderCapabilityTable::imbue(T builder) {
return T(_::PointerHelpers<FromBuilder<T>>::getInternalBuilder(kj::mv(builder)).imbue(this));
}
template <typename T> template <typename T>
typename T::Client CapabilityServerSet<T>::add(kj::Own<typename T::Server>&& server) { typename T::Client CapabilityServerSet<T>::add(kj::Own<typename T::Server>&& server) {
void* ptr = reinterpret_cast<void*>(server.get()); void* ptr = reinterpret_cast<void*>(server.get());
......
...@@ -1939,16 +1939,16 @@ Orphan<DynamicCapability> Orphan<AnyPointer>::releaseAs<DynamicCapability>( ...@@ -1939,16 +1939,16 @@ Orphan<DynamicCapability> Orphan<AnyPointer>::releaseAs<DynamicCapability>(
Orphan<DynamicStruct> Orphanage::newOrphan(StructSchema schema) const { Orphan<DynamicStruct> Orphanage::newOrphan(StructSchema schema) const {
return Orphan<DynamicStruct>( return Orphan<DynamicStruct>(
schema, _::OrphanBuilder::initStruct(arena, structSizeFromSchema(schema))); schema, _::OrphanBuilder::initStruct(arena, capTable, structSizeFromSchema(schema)));
} }
Orphan<DynamicList> Orphanage::newOrphan(ListSchema schema, uint size) const { Orphan<DynamicList> Orphanage::newOrphan(ListSchema schema, uint size) const {
if (schema.whichElementType() == schema::Type::STRUCT) { if (schema.whichElementType() == schema::Type::STRUCT) {
return Orphan<DynamicList>(schema, _::OrphanBuilder::initStructList( return Orphan<DynamicList>(schema, _::OrphanBuilder::initStructList(
arena, size * ELEMENTS, structSizeFromSchema(schema.getStructElementType()))); arena, capTable, size * ELEMENTS, structSizeFromSchema(schema.getStructElementType())));
} else { } else {
return Orphan<DynamicList>(schema, _::OrphanBuilder::initList( return Orphan<DynamicList>(schema, _::OrphanBuilder::initList(
arena, size * ELEMENTS, elementSizeFor(schema.whichElementType()))); arena, capTable, size * ELEMENTS, elementSizeFor(schema.whichElementType())));
} }
} }
......
...@@ -1044,20 +1044,21 @@ template <> ...@@ -1044,20 +1044,21 @@ template <>
inline Orphan<DynamicStruct> Orphanage::newOrphanCopy<DynamicStruct::Reader>( inline Orphan<DynamicStruct> Orphanage::newOrphanCopy<DynamicStruct::Reader>(
const DynamicStruct::Reader& copyFrom) const { const DynamicStruct::Reader& copyFrom) const {
return Orphan<DynamicStruct>( return Orphan<DynamicStruct>(
copyFrom.getSchema(), _::OrphanBuilder::copy(arena, copyFrom.reader)); copyFrom.getSchema(), _::OrphanBuilder::copy(arena, capTable, copyFrom.reader));
} }
template <> template <>
inline Orphan<DynamicList> Orphanage::newOrphanCopy<DynamicList::Reader>( inline Orphan<DynamicList> Orphanage::newOrphanCopy<DynamicList::Reader>(
const DynamicList::Reader& copyFrom) const { const DynamicList::Reader& copyFrom) const {
return Orphan<DynamicList>(copyFrom.getSchema(), _::OrphanBuilder::copy(arena, copyFrom.reader)); return Orphan<DynamicList>(copyFrom.getSchema(),
_::OrphanBuilder::copy(arena, capTable, copyFrom.reader));
} }
template <> template <>
inline Orphan<DynamicCapability> Orphanage::newOrphanCopy<DynamicCapability::Client>( inline Orphan<DynamicCapability> Orphanage::newOrphanCopy<DynamicCapability::Client>(
DynamicCapability::Client& copyFrom) const { DynamicCapability::Client& copyFrom) const {
return Orphan<DynamicCapability>( return Orphan<DynamicCapability>(
copyFrom.getSchema(), _::OrphanBuilder::copy(arena, copyFrom.hook->addRef())); copyFrom.getSchema(), _::OrphanBuilder::copy(arena, capTable, copyFrom.hook->addRef()));
} }
template <> template <>
......
...@@ -282,7 +282,7 @@ TEST(WireFormat, StructRoundTrip_OneSegment) { ...@@ -282,7 +282,7 @@ TEST(WireFormat, StructRoundTrip_OneSegment) {
SegmentBuilder* segment = allocation.segment; SegmentBuilder* segment = allocation.segment;
word* rootLocation = allocation.words; word* rootLocation = allocation.words;
StructBuilder builder = PointerBuilder::getRoot(segment, rootLocation) StructBuilder builder = PointerBuilder::getRoot(segment, nullptr, rootLocation)
.initStruct(StructSize(2 * WORDS, 4 * POINTERS)); .initStruct(StructSize(2 * WORDS, 4 * POINTERS));
setupStruct(builder); setupStruct(builder);
...@@ -309,7 +309,8 @@ TEST(WireFormat, StructRoundTrip_OneSegment) { ...@@ -309,7 +309,8 @@ TEST(WireFormat, StructRoundTrip_OneSegment) {
checkStruct(builder); checkStruct(builder);
checkStruct(builder.asReader()); checkStruct(builder.asReader());
checkStruct(PointerReader::getRootUnchecked(segment->getStartPtr()).getStruct(nullptr)); checkStruct(PointerReader::getRootUnchecked(segment->getStartPtr()).getStruct(nullptr));
checkStruct(PointerReader::getRoot(segment, segment->getStartPtr(), 4).getStruct(nullptr)); checkStruct(PointerReader::getRoot(segment, nullptr, segment->getStartPtr(), 4)
.getStruct(nullptr));
} }
TEST(WireFormat, StructRoundTrip_OneSegmentPerAllocation) { TEST(WireFormat, StructRoundTrip_OneSegmentPerAllocation) {
...@@ -319,7 +320,7 @@ TEST(WireFormat, StructRoundTrip_OneSegmentPerAllocation) { ...@@ -319,7 +320,7 @@ TEST(WireFormat, StructRoundTrip_OneSegmentPerAllocation) {
SegmentBuilder* segment = allocation.segment; SegmentBuilder* segment = allocation.segment;
word* rootLocation = allocation.words; word* rootLocation = allocation.words;
StructBuilder builder = PointerBuilder::getRoot(segment, rootLocation) StructBuilder builder = PointerBuilder::getRoot(segment, nullptr, rootLocation)
.initStruct(StructSize(2 * WORDS, 4 * POINTERS)); .initStruct(StructSize(2 * WORDS, 4 * POINTERS));
setupStruct(builder); setupStruct(builder);
...@@ -347,7 +348,8 @@ TEST(WireFormat, StructRoundTrip_OneSegmentPerAllocation) { ...@@ -347,7 +348,8 @@ TEST(WireFormat, StructRoundTrip_OneSegmentPerAllocation) {
checkStruct(builder); checkStruct(builder);
checkStruct(builder.asReader()); checkStruct(builder.asReader());
checkStruct(PointerReader::getRoot(segment, segment->getStartPtr(), 4).getStruct(nullptr)); checkStruct(PointerReader::getRoot(segment, nullptr, segment->getStartPtr(), 4)
.getStruct(nullptr));
} }
TEST(WireFormat, StructRoundTrip_MultipleSegmentsWithMultipleAllocations) { TEST(WireFormat, StructRoundTrip_MultipleSegmentsWithMultipleAllocations) {
...@@ -357,7 +359,7 @@ TEST(WireFormat, StructRoundTrip_MultipleSegmentsWithMultipleAllocations) { ...@@ -357,7 +359,7 @@ TEST(WireFormat, StructRoundTrip_MultipleSegmentsWithMultipleAllocations) {
SegmentBuilder* segment = allocation.segment; SegmentBuilder* segment = allocation.segment;
word* rootLocation = allocation.words; word* rootLocation = allocation.words;
StructBuilder builder = PointerBuilder::getRoot(segment, rootLocation) StructBuilder builder = PointerBuilder::getRoot(segment, nullptr, rootLocation)
.initStruct(StructSize(2 * WORDS, 4 * POINTERS)); .initStruct(StructSize(2 * WORDS, 4 * POINTERS));
setupStruct(builder); setupStruct(builder);
...@@ -376,7 +378,8 @@ TEST(WireFormat, StructRoundTrip_MultipleSegmentsWithMultipleAllocations) { ...@@ -376,7 +378,8 @@ TEST(WireFormat, StructRoundTrip_MultipleSegmentsWithMultipleAllocations) {
checkStruct(builder); checkStruct(builder);
checkStruct(builder.asReader()); checkStruct(builder.asReader());
checkStruct(PointerReader::getRoot(segment, segment->getStartPtr(), 4).getStruct(nullptr)); checkStruct(PointerReader::getRoot(segment, nullptr, segment->getStartPtr(), 4)
.getStruct(nullptr));
} }
inline bool isNan(float f) { return f != f; } inline bool isNan(float f) { return f != f; }
......
This diff is collapsed.
This diff is collapsed.
...@@ -32,6 +32,18 @@ ...@@ -32,6 +32,18 @@
namespace capnp { namespace capnp {
namespace {
class DummyCapTableReader: public _::CapTableReader {
public:
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) override {
return nullptr;
}
};
static constexpr DummyCapTableReader dummyCapTableReader = DummyCapTableReader();
} // namespace
MessageReader::MessageReader(ReaderOptions options): options(options), allocatedArena(false) {} MessageReader::MessageReader(ReaderOptions options): options(options), allocatedArena(false) {}
MessageReader::~MessageReader() noexcept(false) { MessageReader::~MessageReader() noexcept(false) {
if (allocatedArena) { if (allocatedArena) {
...@@ -55,8 +67,10 @@ AnyPointer::Reader MessageReader::getRootInternal() { ...@@ -55,8 +67,10 @@ AnyPointer::Reader MessageReader::getRootInternal() {
return AnyPointer::Reader(); return AnyPointer::Reader();
} }
// const_cast here is safe because dummyCapTableReader has no state.
return AnyPointer::Reader(_::PointerReader::getRoot( return AnyPointer::Reader(_::PointerReader::getRoot(
segment, segment->getStartPtr(), options.nestingLimit)); segment, const_cast<DummyCapTableReader*>(&dummyCapTableReader),
segment->getStartPtr(), options.nestingLimit));
} }
// ------------------------------------------------------------------- // -------------------------------------------------------------------
...@@ -97,7 +111,7 @@ _::SegmentBuilder* MessageBuilder::getRootSegment() { ...@@ -97,7 +111,7 @@ _::SegmentBuilder* MessageBuilder::getRootSegment() {
AnyPointer::Builder MessageBuilder::getRootInternal() { AnyPointer::Builder MessageBuilder::getRootInternal() {
_::SegmentBuilder* rootSegment = getRootSegment(); _::SegmentBuilder* rootSegment = getRootSegment();
return AnyPointer::Builder(_::PointerBuilder::getRoot( return AnyPointer::Builder(_::PointerBuilder::getRoot(
rootSegment, rootSegment->getPtrUnchecked(0 * WORDS))); rootSegment, arena()->getLocalCapTable(), rootSegment->getPtrUnchecked(0 * WORDS)));
} }
kj::ArrayPtr<const kj::ArrayPtr<const word>> MessageBuilder::getSegmentsForOutput() { kj::ArrayPtr<const kj::ArrayPtr<const word>> MessageBuilder::getSegmentsForOutput() {
...@@ -108,22 +122,12 @@ kj::ArrayPtr<const kj::ArrayPtr<const word>> MessageBuilder::getSegmentsForOutpu ...@@ -108,22 +122,12 @@ kj::ArrayPtr<const kj::ArrayPtr<const word>> MessageBuilder::getSegmentsForOutpu
} }
} }
#if !CAPNP_LITE
kj::ArrayPtr<kj::Maybe<kj::Own<ClientHook>>> MessageBuilder::getCapTable() {
if (allocatedArena) {
return arena()->getCapTable();
} else {
return nullptr;
}
}
#endif // !CAPNP_LITE
Orphanage MessageBuilder::getOrphanage() { Orphanage MessageBuilder::getOrphanage() {
// We must ensure that the arena and root pointer have been allocated before the Orphanage // We must ensure that the arena and root pointer have been allocated before the Orphanage
// can be used. // can be used.
if (!allocatedArena) getRootSegment(); if (!allocatedArena) getRootSegment();
return Orphanage(arena()); return Orphanage(arena(), arena()->getLocalCapTable());
} }
// ======================================================================================= // =======================================================================================
......
...@@ -120,18 +120,6 @@ public: ...@@ -120,18 +120,6 @@ public:
// RootType in this case must be DynamicStruct, and you must #include <capnp/dynamic.h> to // RootType in this case must be DynamicStruct, and you must #include <capnp/dynamic.h> to
// use this. // use this.
#if !CAPNP_LITE
void initCapTable(kj::Array<kj::Maybe<kj::Own<ClientHook>>> capTable);
// Sets the table of capabilities embedded in this message. Capability pointers found in the
// message content contain indexes into this table. You must call this before attempting to
// read any capability pointers (interface pointers) from the message. The table is not passed
// to the constructor because often (as in the RPC system) the cap table is actually constructed
// based on a list read from the message itself.
//
// You must link against libcapnp-rpc to call this method (the rest of MessageBuilder is in
// regular libcapnp).
#endif // !CAPNP_LITE
private: private:
ReaderOptions options; ReaderOptions options;
...@@ -232,14 +220,6 @@ public: ...@@ -232,14 +220,6 @@ public:
kj::ArrayPtr<const kj::ArrayPtr<const word>> getSegmentsForOutput(); kj::ArrayPtr<const kj::ArrayPtr<const word>> getSegmentsForOutput();
// Get the raw data that makes up the message. // Get the raw data that makes up the message.
#if !CAPNP_LITE
kj::ArrayPtr<kj::Maybe<kj::Own<ClientHook>>> getCapTable();
// Get the table of capabilities (interface pointers) that have been added to this message.
// When you later parse this message, you must call `initCapTable()` on the `MessageReader` and
// give it an equivalent set of capabilities, otherwise cap pointers in the message will be
// unusable.
#endif // !CAPNP_LITE
Orphanage getOrphanage(); Orphanage getOrphanage();
private: private:
......
...@@ -166,8 +166,10 @@ public: ...@@ -166,8 +166,10 @@ public:
private: private:
_::BuilderArena* arena; _::BuilderArena* arena;
_::CapTableBuilder* capTable;
inline explicit Orphanage(_::BuilderArena* arena): arena(arena) {} inline explicit Orphanage(_::BuilderArena* arena, _::CapTableBuilder* capTable)
: arena(arena), capTable(capTable) {}
template <typename T, Kind = CAPNP_KIND(T)> template <typename T, Kind = CAPNP_KIND(T)>
struct GetInnerBuilder; struct GetInnerBuilder;
...@@ -317,45 +319,52 @@ struct Orphanage::GetInnerBuilder<T, Kind::LIST> { ...@@ -317,45 +319,52 @@ struct Orphanage::GetInnerBuilder<T, Kind::LIST> {
template <typename BuilderType> template <typename BuilderType>
Orphanage Orphanage::getForMessageContaining(BuilderType builder) { Orphanage Orphanage::getForMessageContaining(BuilderType builder) {
return Orphanage(GetInnerBuilder<FromBuilder<BuilderType>>::apply(builder).getArena()); auto inner = GetInnerBuilder<FromBuilder<BuilderType>>::apply(builder);
return Orphanage(inner.getArena(), inner.getCapTable());
} }
template <typename RootType> template <typename RootType>
Orphan<RootType> Orphanage::newOrphan() const { Orphan<RootType> Orphanage::newOrphan() const {
return Orphan<RootType>(_::OrphanBuilder::initStruct(arena, _::structSize<RootType>())); return Orphan<RootType>(_::OrphanBuilder::initStruct(arena, capTable, _::structSize<RootType>()));
} }
template <typename T, Kind k> template <typename T, Kind k>
struct Orphanage::NewOrphanListImpl<List<T, k>> { struct Orphanage::NewOrphanListImpl<List<T, k>> {
static inline _::OrphanBuilder apply(_::BuilderArena* arena, uint size) { static inline _::OrphanBuilder apply(
return _::OrphanBuilder::initList(arena, size * ELEMENTS, _::ElementSizeForType<T>::value); _::BuilderArena* arena, _::CapTableBuilder* capTable, uint size) {
return _::OrphanBuilder::initList(
arena, capTable, size * ELEMENTS, _::ElementSizeForType<T>::value);
} }
}; };
template <typename T> template <typename T>
struct Orphanage::NewOrphanListImpl<List<T, Kind::STRUCT>> { struct Orphanage::NewOrphanListImpl<List<T, Kind::STRUCT>> {
static inline _::OrphanBuilder apply(_::BuilderArena* arena, uint size) { static inline _::OrphanBuilder apply(
return _::OrphanBuilder::initStructList(arena, size * ELEMENTS, _::structSize<T>()); _::BuilderArena* arena, _::CapTableBuilder* capTable, uint size) {
return _::OrphanBuilder::initStructList(
arena, capTable, size * ELEMENTS, _::structSize<T>());
} }
}; };
template <> template <>
struct Orphanage::NewOrphanListImpl<Text> { struct Orphanage::NewOrphanListImpl<Text> {
static inline _::OrphanBuilder apply(_::BuilderArena* arena, uint size) { static inline _::OrphanBuilder apply(
return _::OrphanBuilder::initText(arena, size * BYTES); _::BuilderArena* arena, _::CapTableBuilder* capTable, uint size) {
return _::OrphanBuilder::initText(arena, capTable, size * BYTES);
} }
}; };
template <> template <>
struct Orphanage::NewOrphanListImpl<Data> { struct Orphanage::NewOrphanListImpl<Data> {
static inline _::OrphanBuilder apply(_::BuilderArena* arena, uint size) { static inline _::OrphanBuilder apply(
return _::OrphanBuilder::initData(arena, size * BYTES); _::BuilderArena* arena, _::CapTableBuilder* capTable, uint size) {
return _::OrphanBuilder::initData(arena, capTable, size * BYTES);
} }
}; };
template <typename RootType> template <typename RootType>
Orphan<RootType> Orphanage::newOrphan(uint size) const { Orphan<RootType> Orphanage::newOrphan(uint size) const {
return Orphan<RootType>(NewOrphanListImpl<RootType>::apply(arena, size)); return Orphan<RootType>(NewOrphanListImpl<RootType>::apply(arena, capTable, size));
} }
template <typename T> template <typename T>
...@@ -382,7 +391,7 @@ struct Orphanage::GetInnerReader<T, Kind::BLOB> { ...@@ -382,7 +391,7 @@ struct Orphanage::GetInnerReader<T, Kind::BLOB> {
template <typename Reader> template <typename Reader>
inline Orphan<FromReader<Reader>> Orphanage::newOrphanCopy(const Reader& copyFrom) const { inline Orphan<FromReader<Reader>> Orphanage::newOrphanCopy(const Reader& copyFrom) const {
return Orphan<FromReader<Reader>>(_::OrphanBuilder::copy( return Orphan<FromReader<Reader>>(_::OrphanBuilder::copy(
arena, GetInnerReader<FromReader<Reader>>::apply(copyFrom))); arena, capTable, GetInnerReader<FromReader<Reader>>::apply(copyFrom)));
} }
template <typename Reader> template <typename Reader>
inline Orphan<FromReader<Reader>> Orphanage::newOrphanCopy(Reader& copyFrom) const { inline Orphan<FromReader<Reader>> Orphanage::newOrphanCopy(Reader& copyFrom) const {
......
...@@ -244,10 +244,6 @@ public: ...@@ -244,10 +244,6 @@ public:
return message.getRoot<AnyPointer>(); return message.getRoot<AnyPointer>();
} }
void initCapTable(kj::Array<kj::Maybe<kj::Own<ClientHook>>>&& capTable) override {
message.initCapTable(kj::mv(capTable));
}
kj::Array<word> data; kj::Array<word> data;
FlatArrayMessageReader message; FlatArrayMessageReader message;
}; };
...@@ -263,10 +259,6 @@ public: ...@@ -263,10 +259,6 @@ public:
return message.getRoot<AnyPointer>(); return message.getRoot<AnyPointer>();
} }
kj::ArrayPtr<kj::Maybe<kj::Own<ClientHook>>> getCapTable() override {
return message.getCapTable();
}
void send() override { void send() override {
if (connection.networkException != nullptr) { if (connection.networkException != nullptr) {
return; return;
......
...@@ -81,10 +81,6 @@ public: ...@@ -81,10 +81,6 @@ public:
return message.getRoot<AnyPointer>(); return message.getRoot<AnyPointer>();
} }
kj::ArrayPtr<kj::Maybe<kj::Own<ClientHook>>> getCapTable() override {
return message.getCapTable();
}
void send() override { void send() override {
network.previousWrite = KJ_ASSERT_NONNULL(network.previousWrite, "already shut down") network.previousWrite = KJ_ASSERT_NONNULL(network.previousWrite, "already shut down")
.then([&]() { .then([&]() {
...@@ -112,10 +108,6 @@ public: ...@@ -112,10 +108,6 @@ public:
return message->getRoot<AnyPointer>(); return message->getRoot<AnyPointer>();
} }
void initCapTable(kj::Array<kj::Maybe<kj::Own<ClientHook>>>&& capTable) override {
message->initCapTable(kj::mv(capTable));
}
private: private:
kj::Own<MessageReader> message; kj::Own<MessageReader> message;
}; };
......
...@@ -1322,7 +1322,7 @@ private: ...@@ -1322,7 +1322,7 @@ private:
firstSegmentSize(sizeHint, messageSizeHint<rpc::Call>() + firstSegmentSize(sizeHint, messageSizeHint<rpc::Call>() +
sizeInWords<rpc::Payload>() + MESSAGE_TARGET_SIZE_HINT))), sizeInWords<rpc::Payload>() + MESSAGE_TARGET_SIZE_HINT))),
callBuilder(message->getBody().getAs<rpc::Message>().initCall()), callBuilder(message->getBody().getAs<rpc::Message>().initCall()),
paramsBuilder(callBuilder.getParams().getContent()) {} paramsBuilder(capTable.imbue(callBuilder.getParams().getContent())) {}
inline AnyPointer::Builder getRoot() { inline AnyPointer::Builder getRoot() {
return paramsBuilder; return paramsBuilder;
...@@ -1417,6 +1417,7 @@ private: ...@@ -1417,6 +1417,7 @@ private:
kj::Own<RpcClient> target; kj::Own<RpcClient> target;
kj::Own<OutgoingRpcMessage> message; kj::Own<OutgoingRpcMessage> message;
BuilderCapabilityTable capTable;
rpc::Call::Builder callBuilder; rpc::Call::Builder callBuilder;
AnyPointer::Builder paramsBuilder; AnyPointer::Builder paramsBuilder;
...@@ -1428,7 +1429,7 @@ private: ...@@ -1428,7 +1429,7 @@ private:
SendInternalResult sendInternal(bool isTailCall) { SendInternalResult sendInternal(bool isTailCall) {
// Build the cap table. // Build the cap table.
auto exports = connectionState->writeDescriptors( auto exports = connectionState->writeDescriptors(
message->getCapTable(), callBuilder.getParams()); capTable.getTable(), callBuilder.getParams());
// Init the question table. Do this after writing descriptors to avoid interference. // Init the question table. Do this after writing descriptors to avoid interference.
QuestionId questionId; QuestionId questionId;
...@@ -1560,10 +1561,12 @@ private: ...@@ -1560,10 +1561,12 @@ private:
RpcResponseImpl(RpcConnectionState& connectionState, RpcResponseImpl(RpcConnectionState& connectionState,
kj::Own<QuestionRef>&& questionRef, kj::Own<QuestionRef>&& questionRef,
kj::Own<IncomingRpcMessage>&& message, kj::Own<IncomingRpcMessage>&& message,
kj::Array<kj::Maybe<kj::Own<ClientHook>>> capTableArray,
AnyPointer::Reader results) AnyPointer::Reader results)
: connectionState(kj::addRef(connectionState)), : connectionState(kj::addRef(connectionState)),
message(kj::mv(message)), message(kj::mv(message)),
reader(results), capTable(kj::mv(capTableArray)),
reader(capTable.imbue(results)),
questionRef(kj::mv(questionRef)) {} questionRef(kj::mv(questionRef)) {}
AnyPointer::Reader getResults() override { AnyPointer::Reader getResults() override {
...@@ -1577,6 +1580,7 @@ private: ...@@ -1577,6 +1580,7 @@ private:
private: private:
kj::Own<RpcConnectionState> connectionState; kj::Own<RpcConnectionState> connectionState;
kj::Own<IncomingRpcMessage> message; kj::Own<IncomingRpcMessage> message;
ReaderCapabilityTable capTable;
AnyPointer::Reader reader; AnyPointer::Reader reader;
kj::Own<QuestionRef> questionRef; kj::Own<QuestionRef> questionRef;
}; };
...@@ -1599,7 +1603,7 @@ private: ...@@ -1599,7 +1603,7 @@ private:
payload(payload) {} payload(payload) {}
AnyPointer::Builder getResultsBuilder() override { AnyPointer::Builder getResultsBuilder() override {
return payload.getContent(); return capTable.imbue(payload.getContent());
} }
kj::Maybe<kj::Array<ExportId>> send() { kj::Maybe<kj::Array<ExportId>> send() {
...@@ -1607,7 +1611,7 @@ private: ...@@ -1607,7 +1611,7 @@ private:
// (Could return a non-null empty array if there were caps but none of them were exports.) // (Could return a non-null empty array if there were caps but none of them were exports.)
// Build the cap table. // Build the cap table.
auto capTable = message->getCapTable(); auto capTable = this->capTable.getTable();
auto exports = connectionState.writeDescriptors(capTable, payload); auto exports = connectionState.writeDescriptors(capTable, payload);
// Capabilities that we are returning are subject to embargos. See `Disembargo` in rpc.capnp. // Capabilities that we are returning are subject to embargos. See `Disembargo` in rpc.capnp.
...@@ -1632,6 +1636,7 @@ private: ...@@ -1632,6 +1636,7 @@ private:
private: private:
RpcConnectionState& connectionState; RpcConnectionState& connectionState;
kj::Own<OutgoingRpcMessage> message; kj::Own<OutgoingRpcMessage> message;
BuilderCapabilityTable capTable;
rpc::Payload::Builder payload; rpc::Payload::Builder payload;
}; };
...@@ -1661,12 +1666,15 @@ private: ...@@ -1661,12 +1666,15 @@ private:
class RpcCallContext final: public CallContextHook, public kj::Refcounted { class RpcCallContext final: public CallContextHook, public kj::Refcounted {
public: public:
RpcCallContext(RpcConnectionState& connectionState, AnswerId answerId, RpcCallContext(RpcConnectionState& connectionState, AnswerId answerId,
kj::Own<IncomingRpcMessage>&& request, const AnyPointer::Reader& params, kj::Own<IncomingRpcMessage>&& request,
kj::Array<kj::Maybe<kj::Own<ClientHook>>> capTableArray,
const AnyPointer::Reader& params,
bool redirectResults, kj::Own<kj::PromiseFulfiller<void>>&& cancelFulfiller) bool redirectResults, kj::Own<kj::PromiseFulfiller<void>>&& cancelFulfiller)
: connectionState(kj::addRef(connectionState)), : connectionState(kj::addRef(connectionState)),
answerId(answerId), answerId(answerId),
request(kj::mv(request)), request(kj::mv(request)),
params(params), paramsCapTable(kj::mv(capTableArray)),
params(paramsCapTable.imbue(params)),
returnMessage(nullptr), returnMessage(nullptr),
redirectResults(redirectResults), redirectResults(redirectResults),
cancelFulfiller(kj::mv(cancelFulfiller)) {} cancelFulfiller(kj::mv(cancelFulfiller)) {}
...@@ -1881,6 +1889,7 @@ private: ...@@ -1881,6 +1889,7 @@ private:
// Request --------------------------------------------- // Request ---------------------------------------------
kj::Maybe<kj::Own<IncomingRpcMessage>> request; kj::Maybe<kj::Own<IncomingRpcMessage>> request;
ReaderCapabilityTable paramsCapTable;
AnyPointer::Reader params; AnyPointer::Reader params;
// Response -------------------------------------------- // Response --------------------------------------------
...@@ -2117,13 +2126,14 @@ private: ...@@ -2117,13 +2126,14 @@ private:
cap = bootstrapFactory.baseCreateFor(conn.baseGetPeerVatId()); cap = bootstrapFactory.baseCreateFor(conn.baseGetPeerVatId());
} }
BuilderCapabilityTable capTable;
auto payload = ret.initResults(); auto payload = ret.initResults();
payload.getContent().setAs<Capability>(kj::mv(cap)); capTable.imbue(payload.getContent()).setAs<Capability>(kj::mv(cap));
auto capTable = response->getCapTable(); auto capTableArray = capTable.getTable();
KJ_DASSERT(capTable.size() == 1); KJ_DASSERT(capTableArray.size() == 1);
resultExports = writeDescriptors(capTable, payload); resultExports = writeDescriptors(capTableArray, payload);
capHook = KJ_ASSERT_NONNULL(capTable[0])->addRef(); capHook = KJ_ASSERT_NONNULL(capTableArray[0])->addRef();
})) { })) {
fromException(*exception, ret.initException()); fromException(*exception, ret.initException());
capHook = newBrokenCap(kj::mv(*exception)); capHook = newBrokenCap(kj::mv(*exception));
...@@ -2167,13 +2177,13 @@ private: ...@@ -2167,13 +2177,13 @@ private:
} }
auto payload = call.getParams(); auto payload = call.getParams();
message->initCapTable(receiveCaps(payload.getCapTable())); auto capTableArray = receiveCaps(payload.getCapTable());
auto cancelPaf = kj::newPromiseAndFulfiller<void>(); auto cancelPaf = kj::newPromiseAndFulfiller<void>();
AnswerId answerId = call.getQuestionId(); AnswerId answerId = call.getQuestionId();
auto context = kj::refcounted<RpcCallContext>( auto context = kj::refcounted<RpcCallContext>(
*this, answerId, kj::mv(message), payload.getContent(), *this, answerId, kj::mv(message), kj::mv(capTableArray), payload.getContent(),
redirectResults, kj::mv(cancelPaf.fulfiller)); redirectResults, kj::mv(cancelPaf.fulfiller));
// No more using `call` after this point, as it now belongs to the context. // No more using `call` after this point, as it now belongs to the context.
...@@ -2334,9 +2344,10 @@ private: ...@@ -2334,9 +2344,10 @@ private:
} }
auto payload = ret.getResults(); auto payload = ret.getResults();
message->initCapTable(receiveCaps(payload.getCapTable())); auto capTableArray = receiveCaps(payload.getCapTable());
questionRef->fulfill(kj::refcounted<RpcResponseImpl>( questionRef->fulfill(kj::refcounted<RpcResponseImpl>(
*this, kj::addRef(*questionRef), kj::mv(message), payload.getContent())); *this, kj::addRef(*questionRef), kj::mv(message),
kj::mv(capTableArray), payload.getContent()));
break; break;
} }
......
...@@ -243,9 +243,6 @@ public: ...@@ -243,9 +243,6 @@ public:
// Get the message body, which the caller may fill in any way it wants. (The standard RPC // Get the message body, which the caller may fill in any way it wants. (The standard RPC
// implementation initializes it as a Message as defined in rpc.capnp.) // implementation initializes it as a Message as defined in rpc.capnp.)
virtual kj::ArrayPtr<kj::Maybe<kj::Own<ClientHook>>> getCapTable() = 0;
// Calls getCapTable() on the underlying MessageBuilder.
virtual void send() = 0; virtual void send() = 0;
// Send the message, or at least put it in a queue to be sent later. Note that the builder // Send the message, or at least put it in a queue to be sent later. Note that the builder
// returned by `getBody()` remains valid at least until the `OutgoingRpcMessage` is destroyed. // returned by `getBody()` remains valid at least until the `OutgoingRpcMessage` is destroyed.
...@@ -258,9 +255,6 @@ public: ...@@ -258,9 +255,6 @@ public:
virtual AnyPointer::Reader getBody() = 0; virtual AnyPointer::Reader getBody() = 0;
// Get the message body, to be interpreted by the caller. (The standard RPC implementation // Get the message body, to be interpreted by the caller. (The standard RPC implementation
// interprets it as a Message as defined in rpc.capnp.) // interprets it as a Message as defined in rpc.capnp.)
virtual void initCapTable(kj::Array<kj::Maybe<kj::Own<ClientHook>>>&& capTable) = 0;
// Calls initCapTable() on the underlying MessageReader.
}; };
template <typename VatId, typename ProvisionId, typename RecipientId, template <typename VatId, typename ProvisionId, typename RecipientId,
......
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