Commit c3b381ee authored by Kenton Varda's avatar Kenton Varda

Add framework for capability membranes.

parent cbd18b28
...@@ -161,6 +161,7 @@ includecapnp_HEADERS = \ ...@@ -161,6 +161,7 @@ includecapnp_HEADERS = \
src/capnp/any.h \ src/capnp/any.h \
src/capnp/message.h \ src/capnp/message.h \
src/capnp/capability.h \ src/capnp/capability.h \
src/capnp/membrane.h \
src/capnp/schema.capnp.h \ src/capnp/schema.capnp.h \
src/capnp/schema-lite.h \ src/capnp/schema-lite.h \
src/capnp/schema.h \ src/capnp/schema.h \
...@@ -255,6 +256,7 @@ libcapnp_rpc_la_LDFLAGS = -release $(SO_VERSION) -no-undefined ...@@ -255,6 +256,7 @@ libcapnp_rpc_la_LDFLAGS = -release $(SO_VERSION) -no-undefined
libcapnp_rpc_la_SOURCES= \ libcapnp_rpc_la_SOURCES= \
src/capnp/serialize-async.c++ \ src/capnp/serialize-async.c++ \
src/capnp/capability.c++ \ src/capnp/capability.c++ \
src/capnp/membrane.c++ \
src/capnp/dynamic-capability.c++ \ src/capnp/dynamic-capability.c++ \
src/capnp/rpc.c++ \ src/capnp/rpc.c++ \
src/capnp/rpc.capnp.c++ \ src/capnp/rpc.capnp.c++ \
...@@ -373,6 +375,7 @@ heavy_tests = \ ...@@ -373,6 +375,7 @@ heavy_tests = \
src/kj/parse/char-test.c++ \ src/kj/parse/char-test.c++ \
src/kj/std/iostream-test.c++ \ src/kj/std/iostream-test.c++ \
src/capnp/capability-test.c++ \ src/capnp/capability-test.c++ \
src/capnp/membrane-test.c++ \
src/capnp/schema-test.c++ \ src/capnp/schema-test.c++ \
src/capnp/schema-loader-test.c++ \ src/capnp/schema-loader-test.c++ \
src/capnp/schema-parser-test.c++ \ src/capnp/schema-parser-test.c++ \
......
...@@ -36,6 +36,7 @@ set(capnp_headers ...@@ -36,6 +36,7 @@ set(capnp_headers
any.h any.h
message.h message.h
capability.h capability.h
membrane.h
dynamic.h dynamic.h
schema.h schema.h
schema.capnp.h schema.capnp.h
...@@ -62,6 +63,7 @@ install(FILES ${capnp_headers} ${capnp_schemas} DESTINATION "${INCLUDE_INSTALL_D ...@@ -62,6 +63,7 @@ install(FILES ${capnp_headers} ${capnp_schemas} DESTINATION "${INCLUDE_INSTALL_D
set(capnp-rpc_sources set(capnp-rpc_sources
serialize-async.c++ serialize-async.c++
capability.c++ capability.c++
membrane.c++
dynamic-capability.c++ dynamic-capability.c++
rpc.c++ rpc.c++
rpc.capnp.c++ rpc.capnp.c++
...@@ -209,6 +211,7 @@ if(BUILD_TESTING) ...@@ -209,6 +211,7 @@ if(BUILD_TESTING)
add_executable(capnp-heavy-tests add_executable(capnp-heavy-tests
endian-reverse-test.c++ endian-reverse-test.c++
capability-test.c++ capability-test.c++
membrane-test.c++
schema-test.c++ schema-test.c++
schema-loader-test.c++ schema-loader-test.c++
schema-parser-test.c++ schema-parser-test.c++
......
...@@ -139,6 +139,7 @@ private: ...@@ -139,6 +139,7 @@ private:
template <typename, typename> template <typename, typename>
friend class Request; friend class Request;
friend class ResponseHook;
}; };
class Capability::Client { class Capability::Client {
...@@ -488,6 +489,11 @@ class ResponseHook { ...@@ -488,6 +489,11 @@ class ResponseHook {
public: public:
virtual ~ResponseHook() noexcept(false); virtual ~ResponseHook() noexcept(false);
// Just here to make sure the type is dynamic. // Just here to make sure the type is dynamic.
template <typename T>
inline static kj::Own<ResponseHook> from(Response<T>&& response) {
return kj::mv(response.hook);
}
}; };
// class PipelineHook is declared in any.h because it is needed there. // class PipelineHook is declared in any.h because it is needed there.
......
...@@ -2440,6 +2440,10 @@ kj::Maybe<Arena&> PointerReader::getArena() const { ...@@ -2440,6 +2440,10 @@ kj::Maybe<Arena&> PointerReader::getArena() const {
return segment == nullptr ? nullptr : segment->getArena(); return segment == nullptr ? nullptr : segment->getArena();
} }
CapTableReader* PointerReader::getCapTable() {
return capTable;
}
PointerReader PointerReader::imbue(CapTableReader* capTable) const { PointerReader PointerReader::imbue(CapTableReader* capTable) const {
auto result = *this; auto result = *this;
result.capTable = capTable; result.capTable = capTable;
......
...@@ -411,6 +411,9 @@ public: ...@@ -411,6 +411,9 @@ public:
kj::Maybe<Arena&> getArena() const; kj::Maybe<Arena&> getArena() const;
// Get the arena containing this pointer. // Get the arena containing this pointer.
CapTableReader* getCapTable();
// Gets the capability context in which this object is operating.
PointerReader imbue(CapTableReader* capTable) const; PointerReader imbue(CapTableReader* capTable) const;
// Return a copy of this reader except using the given capability context. // Return a copy of this reader except using the given capability context.
......
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "membrane.h"
#include <kj/test.h>
#include "test-util.h"
#include <kj/function.h>
#include <kj/async-io.h>
#include "rpc-twoparty.h"
namespace capnp {
namespace _ {
namespace {
using Thing = test::TestMembrane::Thing;
class ThingImpl: public Thing::Server {
public:
ThingImpl(kj::StringPtr text): text(text) {}
protected:
kj::Promise<void> passThrough(PassThroughContext context) override {
context.getResults().setText(text);
return kj::READY_NOW;
}
kj::Promise<void> intercept(InterceptContext context) override {
context.getResults().setText(text);
return kj::READY_NOW;
}
private:
kj::StringPtr text;
};
class TestMembraneImpl: public test::TestMembrane::Server {
protected:
kj::Promise<void> makeThing(MakeThingContext context) override {
context.getResults().setThing(kj::heap<ThingImpl>("inside"));
return kj::READY_NOW;
}
kj::Promise<void> callPassThrough(CallPassThroughContext context) override {
auto params = context.getParams();
auto req = params.getThing().passThroughRequest();
if (params.getTailCall()) {
return context.tailCall(kj::mv(req));
} else {
return req.send().then([context](Response<test::TestMembrane::Result>&& result) mutable {
context.setResults(result);
});
}
}
kj::Promise<void> callIntercept(CallInterceptContext context) override {
auto params = context.getParams();
auto req = params.getThing().interceptRequest();
if (params.getTailCall()) {
return context.tailCall(kj::mv(req));
} else {
return req.send().then([context](Response<test::TestMembrane::Result>&& result) mutable {
context.setResults(result);
});
}
}
kj::Promise<void> loopback(LoopbackContext context) override {
context.getResults().setThing(context.getParams().getThing());
return kj::READY_NOW;
}
};
class MembranePolicyImpl: public MembranePolicy, public kj::Refcounted {
public:
kj::Maybe<Capability::Client> inboundCall(uint64_t interfaceId, uint16_t methodId) override {
if (interfaceId == capnp::typeId<Thing>() && methodId == 1) {
return Capability::Client(kj::heap<ThingImpl>("inbound"));
} else {
return nullptr;
}
}
kj::Maybe<Capability::Client> outboundCall(uint64_t interfaceId, uint16_t methodId) override {
if (interfaceId == capnp::typeId<Thing>() && methodId == 1) {
return Capability::Client(kj::heap<ThingImpl>("outbound"));
} else {
return nullptr;
}
}
kj::Own<MembranePolicy> addRef() override {
return kj::addRef(*this);
}
};
void testThingImpl(kj::WaitScope& waitScope, test::TestMembrane::Client membraned,
kj::Function<Thing::Client()> makeThing,
kj::StringPtr localPassThrough, kj::StringPtr localIntercept,
kj::StringPtr remotePassThrough, kj::StringPtr remoteIntercept) {
KJ_EXPECT(makeThing().passThroughRequest().send().wait(waitScope).getText() == localPassThrough);
KJ_EXPECT(makeThing().interceptRequest().send().wait(waitScope).getText() == localIntercept);
{
auto req = membraned.callPassThroughRequest();
req.setThing(makeThing());
req.setTailCall(false);
KJ_EXPECT(req.send().wait(waitScope).getText() == remotePassThrough);
}
{
auto req = membraned.callInterceptRequest();
req.setThing(makeThing());
req.setTailCall(false);
KJ_EXPECT(req.send().wait(waitScope).getText() == remoteIntercept);
}
{
auto req = membraned.callPassThroughRequest();
req.setThing(makeThing());
req.setTailCall(true);
KJ_EXPECT(req.send().wait(waitScope).getText() == remotePassThrough);
}
{
auto req = membraned.callInterceptRequest();
req.setThing(makeThing());
req.setTailCall(true);
KJ_EXPECT(req.send().wait(waitScope).getText() == remoteIntercept);
}
}
struct TestEnv {
kj::EventLoop loop;
kj::WaitScope waitScope;
test::TestMembrane::Client membraned;
TestEnv()
: waitScope(loop),
membraned(membrane(kj::heap<TestMembraneImpl>(), kj::refcounted<MembranePolicyImpl>())) {}
void testThing(kj::Function<Thing::Client()> makeThing,
kj::StringPtr localPassThrough, kj::StringPtr localIntercept,
kj::StringPtr remotePassThrough, kj::StringPtr remoteIntercept) {
testThingImpl(waitScope, membraned, kj::mv(makeThing),
localPassThrough, localIntercept, remotePassThrough, remoteIntercept);
}
};
KJ_TEST("call local object inside membrane") {
TestEnv env;
env.testThing([&]() {
return env.membraned.makeThingRequest().send().wait(env.waitScope).getThing();
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call local promise inside membrane") {
TestEnv env;
env.testThing([&]() {
return env.membraned.makeThingRequest().send().getThing();
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call local resolved promise inside membrane") {
TestEnv env;
env.testThing([&]() {
auto thing = env.membraned.makeThingRequest().send().getThing();
thing.whenResolved().wait(env.waitScope);
return thing;
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call local object outside membrane") {
TestEnv env;
env.testThing([&]() {
return kj::heap<ThingImpl>("outside");
}, "outside", "outside", "outside", "outbound");
}
KJ_TEST("call local capability that has passed into and back out of membrane") {
TestEnv env;
env.testThing([&]() {
auto req = env.membraned.loopbackRequest();
req.setThing(kj::heap<ThingImpl>("outside"));
return req.send().wait(env.waitScope).getThing();
}, "outside", "outside", "outside", "outbound");
}
KJ_TEST("call local promise pointing into membrane that eventually resolves to outside") {
TestEnv env;
env.testThing([&]() {
auto req = env.membraned.loopbackRequest();
req.setThing(kj::heap<ThingImpl>("outside"));
return req.send().getThing();
}, "outside", "outside", "outside", "outbound");
}
struct TestRpcEnv {
kj::AsyncIoContext io;
kj::TwoWayPipe pipe;
TwoPartyClient client;
TwoPartyClient server;
test::TestMembrane::Client membraned;
TestRpcEnv()
: io(kj::setupAsyncIo()),
pipe(io.provider->newTwoWayPipe()),
client(*pipe.ends[0]),
server(*pipe.ends[1],
membrane(kj::heap<TestMembraneImpl>(), kj::refcounted<MembranePolicyImpl>()),
rpc::twoparty::Side::SERVER),
membraned(client.bootstrap().castAs<test::TestMembrane>()) {}
void testThing(kj::Function<Thing::Client()> makeThing,
kj::StringPtr localPassThrough, kj::StringPtr localIntercept,
kj::StringPtr remotePassThrough, kj::StringPtr remoteIntercept) {
testThingImpl(io.waitScope, membraned, kj::mv(makeThing),
localPassThrough, localIntercept, remotePassThrough, remoteIntercept);
}
};
KJ_TEST("call remote object inside membrane") {
TestRpcEnv env;
env.testThing([&]() {
return env.membraned.makeThingRequest().send().wait(env.io.waitScope).getThing();
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call remote promise inside membrane") {
TestRpcEnv env;
env.testThing([&]() {
return env.membraned.makeThingRequest().send().getThing();
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call remote resolved promise inside membrane") {
TestEnv env;
env.testThing([&]() {
auto thing = env.membraned.makeThingRequest().send().getThing();
thing.whenResolved().wait(env.waitScope);
return thing;
}, "inside", "inbound", "inside", "inside");
}
KJ_TEST("call remote object outside membrane") {
TestRpcEnv env;
env.testThing([&]() {
return kj::heap<ThingImpl>("outside");
}, "outside", "outside", "outside", "outbound");
}
KJ_TEST("call remote capability that has passed into and back out of membrane") {
TestRpcEnv env;
env.testThing([&]() {
auto req = env.membraned.loopbackRequest();
req.setThing(kj::heap<ThingImpl>("outside"));
return req.send().wait(env.io.waitScope).getThing();
}, "outside", "outside", "outside", "outbound");
}
KJ_TEST("call remote promise pointing into membrane that eventually resolves to outside") {
TestRpcEnv env;
env.testThing([&]() {
auto req = env.membraned.loopbackRequest();
req.setThing(kj::heap<ThingImpl>("outside"));
return req.send().getThing();
}, "outside", "outside", "outside", "outbound");
}
} // namespace
} // namespace _
} // namespace capnp
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "membrane.h"
#include <kj/debug.h>
namespace capnp {
namespace {
static const char DUMMY = 0;
static constexpr const void* MEMBRANE_BRAND = &DUMMY;
kj::Own<ClientHook> membrane(kj::Own<ClientHook> inner, MembranePolicy& policy, bool reverse);
class MembraneCapTableReader final: public _::CapTableReader {
public:
MembraneCapTableReader(MembranePolicy& policy, bool reverse)
: policy(policy), reverse(reverse) {}
AnyPointer::Reader imbue(AnyPointer::Reader reader) {
KJ_REQUIRE(inner == nullptr, "can only call this once");
auto pointerReader = _::PointerHelpers<AnyPointer>::getInternalReader(kj::mv(reader));
inner = pointerReader.getCapTable();
return AnyPointer::Reader(pointerReader.imbue(this));
}
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) override {
// The underlying message is inside the membrane, and we're pulling a cap out of it. Therefore,
// we want to wrap the extracted capability in the membrane.
return inner->extractCap(index).map([this](kj::Own<ClientHook>&& cap) {
return membrane(kj::mv(cap), policy, reverse);
});
}
private:
_::CapTableReader* inner = nullptr;
MembranePolicy& policy;
bool reverse;
};
class MembraneCapTableBuilder final: public _::CapTableBuilder {
public:
MembraneCapTableBuilder(MembranePolicy& policy, bool reverse)
: policy(policy), reverse(reverse) {}
AnyPointer::Builder imbue(AnyPointer::Builder builder) {
KJ_REQUIRE(inner == nullptr, "can only call this once");
auto pointerBuilder = _::PointerHelpers<AnyPointer>::getInternalBuilder(kj::mv(builder));
inner = pointerBuilder.getCapTable();
return AnyPointer::Builder(pointerBuilder.imbue(this));
}
AnyPointer::Builder unimbue(AnyPointer::Builder builder) {
auto pointerBuilder = _::PointerHelpers<AnyPointer>::getInternalBuilder(kj::mv(builder));
KJ_REQUIRE(pointerBuilder.getCapTable() == this);
return AnyPointer::Builder(pointerBuilder.imbue(inner));
}
kj::Maybe<kj::Own<ClientHook>> extractCap(uint index) override {
// The underlying message is inside the membrane, and we're pulling a cap out of it. Therefore,
// we want to wrap the extracted capability in the membrane.
return inner->extractCap(index).map([this](kj::Own<ClientHook>&& cap) {
return membrane(kj::mv(cap), policy, reverse);
});
}
uint injectCap(kj::Own<ClientHook>&& cap) override {
// The underlying message is inside the membrane, and we're inserting a cap from outside into
// it. Therefore we want to add a reverse membrane.
return inner->injectCap(membrane(kj::mv(cap), policy, !reverse));
}
void dropCap(uint index) override {
inner->dropCap(index);
}
private:
_::CapTableBuilder* inner = nullptr;
MembranePolicy& policy;
bool reverse;
};
class MembranePipelineHook final: public PipelineHook, public kj::Refcounted {
public:
MembranePipelineHook(
kj::Own<PipelineHook>&& inner, kj::Own<MembranePolicy>&& policy, bool reverse)
: inner(kj::mv(inner)), policy(kj::mv(policy)), reverse(reverse) {}
kj::Own<PipelineHook> addRef() override {
return kj::addRef(*this);
}
kj::Own<ClientHook> getPipelinedCap(kj::ArrayPtr<const PipelineOp> ops) override {
return membrane(inner->getPipelinedCap(ops), *policy, reverse);
}
kj::Own<ClientHook> getPipelinedCap(kj::Array<PipelineOp>&& ops) override {
return membrane(inner->getPipelinedCap(kj::mv(ops)), *policy, reverse);
}
private:
kj::Own<PipelineHook> inner;
kj::Own<MembranePolicy> policy;
bool reverse;
};
class MembraneResponseHook final: public ResponseHook {
public:
MembraneResponseHook(
kj::Own<ResponseHook>&& inner, kj::Own<MembranePolicy>&& policy, bool reverse)
: inner(kj::mv(inner)), policy(kj::mv(policy)), capTable(*this->policy, reverse) {}
AnyPointer::Reader imbue(AnyPointer::Reader reader) { return capTable.imbue(reader); }
private:
kj::Own<ResponseHook> inner;
kj::Own<MembranePolicy> policy;
MembraneCapTableReader capTable;
};
class MembraneRequestHook final: public RequestHook {
public:
MembraneRequestHook(kj::Own<RequestHook>&& inner, kj::Own<MembranePolicy>&& policy, bool reverse)
: inner(kj::mv(inner)), policy(kj::mv(policy)),
reverse(reverse), capTable(*this->policy, reverse) {}
static Request<AnyPointer, AnyPointer> wrap(
Request<AnyPointer, AnyPointer>&& inner, MembranePolicy& policy, bool reverse) {
AnyPointer::Builder builder = inner;
auto innerHook = RequestHook::from(kj::mv(inner));
if (innerHook->getBrand() == MEMBRANE_BRAND) {
auto& otherMembrane = kj::downcast<MembraneRequestHook>(*innerHook);
if (otherMembrane.policy.get() == &policy && otherMembrane.reverse == !reverse) {
// Request that passed across the membrane one way is now passing back the other way.
// Unwrap it rather than double-wrap it.
builder = otherMembrane.capTable.unimbue(builder);
return { builder, kj::mv(otherMembrane.inner) };
}
}
auto newHook = kj::heap<MembraneRequestHook>(kj::mv(innerHook), policy.addRef(), reverse);
builder = newHook->capTable.imbue(builder);
return { builder, kj::mv(newHook) };
}
static kj::Own<RequestHook> wrap(
kj::Own<RequestHook>&& inner, MembranePolicy& policy, bool reverse) {
if (inner->getBrand() == MEMBRANE_BRAND) {
auto& otherMembrane = kj::downcast<MembraneRequestHook>(*inner);
if (otherMembrane.policy.get() == &policy && otherMembrane.reverse == !reverse) {
// Request that passed across the membrane one way is now passing back the other way.
// Unwrap it rather than double-wrap it.
return kj::mv(otherMembrane.inner);
}
}
return kj::heap<MembraneRequestHook>(kj::mv(inner), policy.addRef(), reverse);
}
RemotePromise<AnyPointer> send() override {
auto promise = inner->send();
auto newPipeline = AnyPointer::Pipeline(kj::refcounted<MembranePipelineHook>(
PipelineHook::from(kj::mv(promise)), policy->addRef(), reverse));
bool reverse = this->reverse; // for capture
auto newPromise = promise.then(kj::mvCapture(policy,
[reverse](kj::Own<MembranePolicy>&& policy, Response<AnyPointer>&& response) {
AnyPointer::Reader reader = response;
auto newRespHook = kj::heap<MembraneResponseHook>(
ResponseHook::from(kj::mv(response)), policy->addRef(), reverse);
reader = newRespHook->imbue(reader);
return Response<AnyPointer>(reader, kj::mv(newRespHook));
}));
return RemotePromise<AnyPointer>(kj::mv(newPromise), kj::mv(newPipeline));
}
const void* getBrand() override {
return MEMBRANE_BRAND;
}
private:
kj::Own<RequestHook> inner;
kj::Own<MembranePolicy> policy;
bool reverse;
MembraneCapTableBuilder capTable;
};
class MembraneCallContextHook final: public CallContextHook, public kj::Refcounted {
public:
MembraneCallContextHook(kj::Own<CallContextHook>&& inner,
kj::Own<MembranePolicy>&& policy, bool reverse)
: inner(kj::mv(inner)), policy(kj::mv(policy)), reverse(reverse),
paramsCapTable(*this->policy, reverse),
resultsCapTable(*this->policy, reverse) {}
AnyPointer::Reader getParams() override {
KJ_REQUIRE(!releasedParams);
KJ_IF_MAYBE(p, params) {
return *p;
} else {
auto result = paramsCapTable.imbue(inner->getParams());
params = result;
return result;
}
}
void releaseParams() override {
KJ_REQUIRE(!releasedParams);
releasedParams = true;
inner->releaseParams();
}
AnyPointer::Builder getResults(kj::Maybe<MessageSize> sizeHint) override {
KJ_IF_MAYBE(r, results) {
return *r;
} else {
auto result = resultsCapTable.imbue(inner->getResults(sizeHint));
results = result;
return result;
}
}
kj::Promise<void> tailCall(kj::Own<RequestHook>&& request) override {
return inner->tailCall(MembraneRequestHook::wrap(kj::mv(request), *policy, !reverse));
}
void allowCancellation() override {
inner->allowCancellation();
}
kj::Promise<AnyPointer::Pipeline> onTailCall() override {
return inner->onTailCall().then([this](AnyPointer::Pipeline&& innerPipeline) {
return AnyPointer::Pipeline(kj::refcounted<MembranePipelineHook>(
PipelineHook::from(kj::mv(innerPipeline)), policy->addRef(), reverse));
});
}
ClientHook::VoidPromiseAndPipeline directTailCall(kj::Own<RequestHook>&& request) override {
auto pair = inner->directTailCall(
MembraneRequestHook::wrap(kj::mv(request), *policy, !reverse));
return {
kj::mv(pair.promise),
kj::refcounted<MembranePipelineHook>(kj::mv(pair.pipeline), policy->addRef(), reverse)
};
}
kj::Own<CallContextHook> addRef() override {
return kj::addRef(*this);
}
private:
kj::Own<CallContextHook> inner;
kj::Own<MembranePolicy> policy;
bool reverse;
MembraneCapTableReader paramsCapTable;
kj::Maybe<AnyPointer::Reader> params;
bool releasedParams = false;
MembraneCapTableBuilder resultsCapTable;
kj::Maybe<AnyPointer::Builder> results;
};
class MembraneHook final: public ClientHook, public kj::Refcounted {
public:
MembraneHook(kj::Own<ClientHook>&& inner, kj::Own<MembranePolicy>&& policy, bool reverse)
: inner(kj::mv(inner)), policy(kj::mv(policy)), reverse(reverse) {}
static kj::Own<ClientHook> wrap(ClientHook& cap, MembranePolicy& policy, bool reverse) {
if (cap.getBrand() == MEMBRANE_BRAND) {
auto& otherMembrane = kj::downcast<MembraneHook>(cap);
if (otherMembrane.policy.get() == &policy && otherMembrane.reverse == !reverse) {
// Capability that passed across the membrane one way is now passing back the other way.
// Unwrap it rather than double-wrap it.
return otherMembrane.inner->addRef();
}
}
return kj::refcounted<MembraneHook>(cap.addRef(), policy.addRef(), reverse);
}
static kj::Own<ClientHook> wrap(kj::Own<ClientHook> cap, MembranePolicy& policy, bool reverse) {
if (cap->getBrand() == MEMBRANE_BRAND) {
auto& otherMembrane = kj::downcast<MembraneHook>(*cap);
if (otherMembrane.policy.get() == &policy && otherMembrane.reverse == !reverse) {
// Capability that passed across the membrane one way is now passing back the other way.
// Unwrap it rather than double-wrap it.
return otherMembrane.inner->addRef();
}
}
return kj::refcounted<MembraneHook>(kj::mv(cap), policy.addRef(), reverse);
}
Request<AnyPointer, AnyPointer> newCall(
uint64_t interfaceId, uint16_t methodId, kj::Maybe<MessageSize> sizeHint) override {
KJ_IF_MAYBE(r, resolved) {
return r->get()->newCall(interfaceId, methodId, sizeHint);
}
auto redirect = reverse ? policy->outboundCall(interfaceId, methodId)
: policy->inboundCall(interfaceId, methodId);
KJ_IF_MAYBE(r, redirect) {
// The policy says that *if* this capability points into the membrane, then we want to
// redirect the call. However, if this capability is a promise, then it could resolve to
// something outside the membrane later. We have to wait before we actually redirect,
// otherwise behavior will differ depending on whether the promise is resolved.
KJ_IF_MAYBE(p, whenMoreResolved()) {
return newLocalPromiseClient(kj::mv(*p))->newCall(interfaceId, methodId, sizeHint);
}
return ClientHook::from(kj::mv(*r))->newCall(interfaceId, methodId, sizeHint);
} else {
// For pass-through calls, we don't worry about promises, because if the capability resolves
// to something outside the membrane, then the call will pass back out of the membrane too.
return MembraneRequestHook::wrap(
inner->newCall(interfaceId, methodId, sizeHint), *policy, reverse);
}
}
VoidPromiseAndPipeline call(uint64_t interfaceId, uint16_t methodId,
kj::Own<CallContextHook>&& context) override {
KJ_IF_MAYBE(r, resolved) {
return r->get()->call(interfaceId, methodId, kj::mv(context));
}
auto redirect = reverse ? policy->outboundCall(interfaceId, methodId)
: policy->inboundCall(interfaceId, methodId);
KJ_IF_MAYBE(r, redirect) {
// The policy says that *if* this capability points into the membrane, then we want to
// redirect the call. However, if this capability is a promise, then it could resolve to
// something outside the membrane later. We have to wait before we actually redirect,
// otherwise behavior will differ depending on whether the promise is resolved.
KJ_IF_MAYBE(p, whenMoreResolved()) {
return newLocalPromiseClient(kj::mv(*p))->call(interfaceId, methodId, kj::mv(context));
}
return ClientHook::from(kj::mv(*r))->call(interfaceId, methodId, kj::mv(context));
} else {
// !reverse because calls to the CallContext go in the opposite direction.
auto result = inner->call(interfaceId, methodId,
kj::refcounted<MembraneCallContextHook>(kj::mv(context), policy->addRef(), !reverse));
return {
kj::mv(result.promise),
kj::refcounted<MembranePipelineHook>(kj::mv(result.pipeline), policy->addRef(), reverse)
};
}
}
kj::Maybe<ClientHook&> getResolved() override {
KJ_IF_MAYBE(r, resolved) {
return **r;
}
KJ_IF_MAYBE(newInner, inner->getResolved()) {
kj::Own<ClientHook> newResolved = wrap(*newInner, *policy, reverse);
ClientHook& result = *newResolved;
resolved = kj::mv(newResolved);
return result;
} else {
return nullptr;
}
}
kj::Maybe<kj::Promise<kj::Own<ClientHook>>> whenMoreResolved() override {
KJ_IF_MAYBE(r, resolved) {
return kj::Promise<kj::Own<ClientHook>>(r->get()->addRef());
}
KJ_IF_MAYBE(promise, inner->whenMoreResolved()) {
return promise->then([this](kj::Own<ClientHook>&& newInner) {
kj::Own<ClientHook> newResolved = wrap(*newInner, *policy, reverse);
if (resolved == nullptr) {
resolved = newResolved->addRef();
}
return newResolved;
});
} else {
return nullptr;
}
}
kj::Own<ClientHook> addRef() override {
return kj::addRef(*this);
}
const void* getBrand() override {
return MEMBRANE_BRAND;
}
private:
kj::Own<ClientHook> inner;
kj::Own<MembranePolicy> policy;
bool reverse;
kj::Maybe<kj::Own<ClientHook>> resolved;
};
kj::Own<ClientHook> membrane(kj::Own<ClientHook> inner, MembranePolicy& policy, bool reverse) {
return MembraneHook::wrap(kj::mv(inner), policy, reverse);
}
} // namespace
Capability::Client membrane(Capability::Client inner, kj::Own<MembranePolicy> policy) {
return Capability::Client(membrane(
ClientHook::from(kj::mv(inner)), *policy, false));
}
Capability::Client reverseMembrane(Capability::Client inner, kj::Own<MembranePolicy> policy) {
return Capability::Client(membrane(
ClientHook::from(kj::mv(inner)), *policy, true));
}
} // namespace capnp
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef CAPNP_MEMBRANE_H_
#define CAPNP_MEMBRANE_H_
// In capability theory, a "membrane" is a wrapper around a capability which (usually) forwards
// calls but recursively wraps capabilities in those calls in the same membrane. The purpose of a
// membrane is to enforce a barrier between two capabilities that cannot be bypassed by merely
// introducing new objects.
//
// The most common use case for a membrane is revocation: Say Alice wants to give Bob a capability
// to access Carol, but wants to be able to revoke this capability later. Alice can accomplish this
// by wrapping Carol in a revokable wrapper which passes through calls until such a time as Alice
// indicates it should be revoked, after which all calls through the wrapper will throw exceptions.
// However, a naive wrapper approach has a problem: if Bob makes a call to Carol and sends a new
// capability in that call, or if Carol returns a capability to Bob in the response to a call, then
// the two are now able to communicate using this new capability, which Alice cannot revoke. In
// order to avoid this problem, Alice must use not just a wrapper but a "membrane", which
// recursively wraps all objects that pass through it in either direction. Thus, all connections
// formed between Bob and Carol (originating from Alice's original introduction) can be revoked
// together by revoking the membrane.
//
// Note that when a capability is passed into a membrane and then passed back out, the result is
// the original capability, not a double-membraned capability. This means that in our revocation
// example, if Bob uses his capability to Carol to obtain another capability from her, then send
// it back to her, the capability Carol receives back will NOT be revoked when Bob's access to
// Carol is revoked. Thus Bob can create long-term irrevocable connections. In most practical use
// cases, this is what you want. APIs commonly rely on the fact that a capability obtained and then
// passed back can be recognized as the original capability.
//
// Mark Miller on membranes: http://www.eros-os.org/pipermail/e-lang/2003-January/008434.html
#include "capability.h"
namespace capnp {
class MembranePolicy {
// Applications may implement this interface to define a membrane policy, which allows some
// calls crossing the membrane to be blocked or redirected.
public:
virtual kj::Maybe<Capability::Client> inboundCall(uint64_t interfaceId, uint16_t methodId) = 0;
// Given an inbound call (a call originating "outside" the membrane destined for an object
// "inside" the membrane), decides what to do with it. The policy may:
//
// - Return null to indicate that the call should proceed to the destination. All capabilities
// in the parameters or result will be properly wrapped in the same membrane.
// - Return a capability to have the call redirected to that capability. Note that the redirect
// capability will be treated as outside the membrane, so the params and results will not be
// auto-wrapped; however, the callee can easily wrap the returned capability in the membrane
// itself before returning to achieve this effect.
// - Throw an exception to cause the call to fail with that exception.
virtual kj::Maybe<Capability::Client> outboundCall(uint64_t interfaceId, uint16_t methodId) = 0;
// Like `inboundCall()`, but applies to calls originating *inside* the membrane and terminating
// outside.
//
// Note: It is strongly recommended that `outboundCall()` returns null in exactly the same cases
// that `inboundCall()` return null. Conversely, for any case where `inboundCall()` would
// redirect or throw, `outboundCall()` should also redirect or throw. Otherwise, you can run
// into inconsistent behavion when a promise is returned across a membrane, and that promise
// later resolves to a capability on the other side of the membrane: calls on the promise
// will enter and then exit the membrane, but calls on the eventual resolution will not cross
// the membrane at all, so it is important that these two cases behave the same.
virtual kj::Own<MembranePolicy> addRef() = 0;
// Return a new owned pointer to the same policy.
//
// Typically an implementation of MembranePolicy should also inherit kj::Refcounted and implement
// `addRef()` as `return kj::addRef(*this);`.
//
// Note that the membraning system considers two membranes created with the same MembranePolicy
// object actually to be the *same* membrane. This is relevant when an object passes into the
// membrane and then back out (or out and then back in): instead of double-wrapping the object,
// the wrapping will be removed.
};
Capability::Client membrane(Capability::Client inner, kj::Own<MembranePolicy> policy);
// Wrap `inner` in a membrane specified by `filter`. `inner` is considered "inside" the membrane,
// while the returned capability should only be called from outside the membrane.
Capability::Client reverseMembrane(Capability::Client outer, kj::Own<MembranePolicy> policy);
// Like `membrane` but treat the input capability as "outside" the membrane, and return a
// capability appropriate for use inside.
//
// Applications typically won't use this directly; the membraning code automatically sets up
// reverse membranes where needed.
template <typename ClientType>
ClientType membrane(ClientType inner, kj::Own<MembranePolicy> policy);
template <typename ClientType>
ClientType reverseMembrane(ClientType inner, kj::Own<MembranePolicy> policy);
// Convenience templates which return the same interface type as the input.
template <typename ServerType>
typename ServerType::Serves::Client membrane(
kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy);
template <typename ServerType>
typename ServerType::Serves::Client reverseMembrane(
kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy);
// Convenience templates which input a capability server type and return the appropriate client
// type.
// =======================================================================================
// inline implementation details
template <typename ClientType>
ClientType membrane(ClientType inner, kj::Own<MembranePolicy> policy) {
return membrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
.castAs<typename ClientType::Calls>();
}
template <typename ClientType>
ClientType reverseMembrane(ClientType inner, kj::Own<MembranePolicy> policy) {
return reverseMembrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
.castAs<typename ClientType::Calls>();
}
template <typename ServerType>
typename ServerType::Serves::Client membrane(
kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy) {
return membrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
.castAs<typename ServerType::Serves::Client>();
}
template <typename ServerType>
typename ServerType::Serves::Client reverseMembrane(
kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy) {
return reverseMembrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
.castAs<typename ServerType::Serves::Client>();
}
} // namespace capnp
#endif // CAPNP_MEMBRANE_H_
...@@ -182,14 +182,17 @@ TwoPartyClient::TwoPartyClient(kj::AsyncIoStream& connection) ...@@ -182,14 +182,17 @@ TwoPartyClient::TwoPartyClient(kj::AsyncIoStream& connection)
TwoPartyClient::TwoPartyClient(kj::AsyncIoStream& connection, TwoPartyClient::TwoPartyClient(kj::AsyncIoStream& connection,
Capability::Client bootstrapInterface) Capability::Client bootstrapInterface,
: network(connection, rpc::twoparty::Side::CLIENT), rpc::twoparty::Side side)
: network(connection, side),
rpcSystem(network, bootstrapInterface) {} rpcSystem(network, bootstrapInterface) {}
Capability::Client TwoPartyClient::bootstrap() { Capability::Client TwoPartyClient::bootstrap() {
MallocMessageBuilder message(4); MallocMessageBuilder message(4);
auto vatId = message.getRoot<rpc::twoparty::VatId>(); auto vatId = message.getRoot<rpc::twoparty::VatId>();
vatId.setSide(rpc::twoparty::Side::SERVER); vatId.setSide(network.getSide() == rpc::twoparty::Side::CLIENT
? rpc::twoparty::Side::SERVER
: rpc::twoparty::Side::CLIENT);
return rpcSystem.bootstrap(vatId); return rpcSystem.bootstrap(vatId);
} }
......
...@@ -59,6 +59,8 @@ public: ...@@ -59,6 +59,8 @@ public:
kj::Promise<void> onDisconnect() { return disconnectPromise.addBranch(); } kj::Promise<void> onDisconnect() { return disconnectPromise.addBranch(); }
// Returns a promise that resolves when the peer disconnects. // Returns a promise that resolves when the peer disconnects.
rpc::twoparty::Side getSide() { return side; }
// implements VatNetwork ----------------------------------------------------- // implements VatNetwork -----------------------------------------------------
kj::Maybe<kj::Own<TwoPartyVatNetworkBase::Connection>> connect( kj::Maybe<kj::Own<TwoPartyVatNetworkBase::Connection>> connect(
...@@ -136,7 +138,8 @@ class TwoPartyClient { ...@@ -136,7 +138,8 @@ class TwoPartyClient {
public: public:
explicit TwoPartyClient(kj::AsyncIoStream& connection); explicit TwoPartyClient(kj::AsyncIoStream& connection);
TwoPartyClient(kj::AsyncIoStream& connection, Capability::Client bootstrapInterface); TwoPartyClient(kj::AsyncIoStream& connection, Capability::Client bootstrapInterface,
rpc::twoparty::Side side = rpc::twoparty::Side::CLIENT);
Capability::Client bootstrap(); Capability::Client bootstrap();
// Get the server's bootstrap interface. // Get the server's bootstrap interface.
......
...@@ -803,6 +803,22 @@ interface TestMoreStuff extends(TestCallOrder) { ...@@ -803,6 +803,22 @@ interface TestMoreStuff extends(TestCallOrder) {
# this can be used to test garbage collection. # this can be used to test garbage collection.
} }
interface TestMembrane {
makeThing @0 () -> (thing :Thing);
callPassThrough @1 (thing :Thing, tailCall :Bool) -> Result;
callIntercept @2 (thing :Thing, tailCall :Bool) -> Result;
loopback @3 (thing :Thing) -> (thing :Thing);
interface Thing {
passThrough @0 () -> Result;
intercept @1 () -> Result;
}
struct Result {
text @0 :Text;
}
}
struct TestTransferCap { struct TestTransferCap {
list @0 :List(Element); list @0 :List(Element);
struct Element { struct Element {
......
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