membrane-test.c++ (13663B)
1 // Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #include "membrane.h" 23 #include <kj/test.h> 24 #include "test-util.h" 25 #include <kj/function.h> 26 #include <kj/async-io.h> 27 #include "rpc-twoparty.h" 28 29 namespace capnp { 30 namespace _ { 31 namespace { 32 33 using Thing = test::TestMembrane::Thing; 34 35 class ThingImpl final: public Thing::Server { 36 public: 37 ThingImpl(kj::StringPtr text): text(text) {} 38 39 protected: 40 kj::Promise<void> passThrough(PassThroughContext context) override { 41 context.getResults().setText(text); 42 return kj::READY_NOW; 43 } 44 45 kj::Promise<void> intercept(InterceptContext context) override { 46 context.getResults().setText(text); 47 return kj::READY_NOW; 48 } 49 50 private: 51 kj::StringPtr text; 52 }; 53 54 class TestMembraneImpl final: public test::TestMembrane::Server { 55 protected: 56 kj::Promise<void> makeThing(MakeThingContext context) override { 57 context.getResults().setThing(kj::heap<ThingImpl>("inside")); 58 return kj::READY_NOW; 59 } 60 61 kj::Promise<void> callPassThrough(CallPassThroughContext context) override { 62 auto params = context.getParams(); 63 auto req = params.getThing().passThroughRequest(); 64 if (params.getTailCall()) { 65 return context.tailCall(kj::mv(req)); 66 } else { 67 return req.send().then( 68 [KJ_CPCAP(context)](Response<test::TestMembrane::Result>&& result) mutable { 69 context.setResults(result); 70 }); 71 } 72 } 73 74 kj::Promise<void> callIntercept(CallInterceptContext context) override { 75 auto params = context.getParams(); 76 auto req = params.getThing().interceptRequest(); 77 if (params.getTailCall()) { 78 return context.tailCall(kj::mv(req)); 79 } else { 80 return req.send().then( 81 [KJ_CPCAP(context)](Response<test::TestMembrane::Result>&& result) mutable { 82 context.setResults(result); 83 }); 84 } 85 } 86 87 kj::Promise<void> loopback(LoopbackContext context) override { 88 context.getResults().setThing(context.getParams().getThing()); 89 return kj::READY_NOW; 90 } 91 92 kj::Promise<void> waitForever(WaitForeverContext context) override { 93 context.allowCancellation(); 94 return kj::NEVER_DONE; 95 } 96 }; 97 98 class MembranePolicyImpl: public MembranePolicy, public kj::Refcounted { 99 public: 100 MembranePolicyImpl() = default; 101 MembranePolicyImpl(kj::Maybe<kj::Promise<void>> revokePromise) 102 : revokePromise(revokePromise.map([](kj::Promise<void>& p) { return p.fork(); })) {} 103 104 kj::Maybe<Capability::Client> inboundCall(uint64_t interfaceId, uint16_t methodId, 105 Capability::Client target) override { 106 if (interfaceId == capnp::typeId<Thing>() && methodId == 1) { 107 return Capability::Client(kj::heap<ThingImpl>("inbound")); 108 } else { 109 return nullptr; 110 } 111 } 112 113 kj::Maybe<Capability::Client> outboundCall(uint64_t interfaceId, uint16_t methodId, 114 Capability::Client target) override { 115 if (interfaceId == capnp::typeId<Thing>() && methodId == 1) { 116 return Capability::Client(kj::heap<ThingImpl>("outbound")); 117 } else { 118 return nullptr; 119 } 120 } 121 122 kj::Own<MembranePolicy> addRef() override { 123 return kj::addRef(*this); 124 } 125 126 kj::Maybe<kj::Promise<void>> onRevoked() override { 127 return revokePromise.map([](kj::ForkedPromise<void>& fork) { 128 return fork.addBranch(); 129 }); 130 } 131 132 private: 133 kj::Maybe<kj::ForkedPromise<void>> revokePromise; 134 }; 135 136 void testThingImpl(kj::WaitScope& waitScope, test::TestMembrane::Client membraned, 137 kj::Function<Thing::Client()> makeThing, 138 kj::StringPtr localPassThrough, kj::StringPtr localIntercept, 139 kj::StringPtr remotePassThrough, kj::StringPtr remoteIntercept) { 140 KJ_EXPECT(makeThing().passThroughRequest().send().wait(waitScope).getText() == localPassThrough); 141 KJ_EXPECT(makeThing().interceptRequest().send().wait(waitScope).getText() == localIntercept); 142 143 { 144 auto req = membraned.callPassThroughRequest(); 145 req.setThing(makeThing()); 146 req.setTailCall(false); 147 KJ_EXPECT(req.send().wait(waitScope).getText() == remotePassThrough); 148 } 149 { 150 auto req = membraned.callInterceptRequest(); 151 req.setThing(makeThing()); 152 req.setTailCall(false); 153 KJ_EXPECT(req.send().wait(waitScope).getText() == remoteIntercept); 154 } 155 { 156 auto req = membraned.callPassThroughRequest(); 157 req.setThing(makeThing()); 158 req.setTailCall(true); 159 KJ_EXPECT(req.send().wait(waitScope).getText() == remotePassThrough); 160 } 161 { 162 auto req = membraned.callInterceptRequest(); 163 req.setThing(makeThing()); 164 req.setTailCall(true); 165 KJ_EXPECT(req.send().wait(waitScope).getText() == remoteIntercept); 166 } 167 } 168 169 struct TestEnv { 170 kj::EventLoop loop; 171 kj::WaitScope waitScope; 172 kj::Own<MembranePolicyImpl> policy; 173 test::TestMembrane::Client membraned; 174 175 TestEnv() 176 : waitScope(loop), 177 policy(kj::refcounted<MembranePolicyImpl>()), 178 membraned(membrane(kj::heap<TestMembraneImpl>(), policy->addRef())) {} 179 180 void testThing(kj::Function<Thing::Client()> makeThing, 181 kj::StringPtr localPassThrough, kj::StringPtr localIntercept, 182 kj::StringPtr remotePassThrough, kj::StringPtr remoteIntercept) { 183 testThingImpl(waitScope, membraned, kj::mv(makeThing), 184 localPassThrough, localIntercept, remotePassThrough, remoteIntercept); 185 } 186 }; 187 188 KJ_TEST("call local object inside membrane") { 189 TestEnv env; 190 env.testThing([&]() { 191 return env.membraned.makeThingRequest().send().wait(env.waitScope).getThing(); 192 }, "inside", "inbound", "inside", "inside"); 193 } 194 195 KJ_TEST("call local promise inside membrane") { 196 TestEnv env; 197 env.testThing([&]() { 198 return env.membraned.makeThingRequest().send().getThing(); 199 }, "inside", "inbound", "inside", "inside"); 200 } 201 202 KJ_TEST("call local resolved promise inside membrane") { 203 TestEnv env; 204 env.testThing([&]() { 205 auto thing = env.membraned.makeThingRequest().send().getThing(); 206 thing.whenResolved().wait(env.waitScope); 207 return thing; 208 }, "inside", "inbound", "inside", "inside"); 209 } 210 211 KJ_TEST("call local object outside membrane") { 212 TestEnv env; 213 env.testThing([&]() { 214 return kj::heap<ThingImpl>("outside"); 215 }, "outside", "outside", "outside", "outbound"); 216 } 217 218 KJ_TEST("call local capability that has passed into and back out of membrane") { 219 TestEnv env; 220 env.testThing([&]() { 221 auto req = env.membraned.loopbackRequest(); 222 req.setThing(kj::heap<ThingImpl>("outside")); 223 return req.send().wait(env.waitScope).getThing(); 224 }, "outside", "outside", "outside", "outbound"); 225 } 226 227 KJ_TEST("call local promise pointing into membrane that eventually resolves to outside") { 228 TestEnv env; 229 env.testThing([&]() { 230 auto req = env.membraned.loopbackRequest(); 231 req.setThing(kj::heap<ThingImpl>("outside")); 232 return req.send().getThing(); 233 }, "outside", "outside", "outside", "outbound"); 234 } 235 236 KJ_TEST("apply membrane using copyOutOfMembrane() on struct") { 237 TestEnv env; 238 239 env.testThing([&]() { 240 MallocMessageBuilder outsideBuilder; 241 auto root = outsideBuilder.initRoot<test::TestContainMembrane>(); 242 root.setCap(kj::heap<ThingImpl>("inside")); 243 MallocMessageBuilder insideBuilder; 244 insideBuilder.adoptRoot(copyOutOfMembrane( 245 root.asReader(), insideBuilder.getOrphanage(), env.policy->addRef())); 246 return insideBuilder.getRoot<test::TestContainMembrane>().getCap(); 247 }, "inside", "inbound", "inside", "inside"); 248 } 249 250 KJ_TEST("apply membrane using copyOutOfMembrane() on list") { 251 TestEnv env; 252 253 env.testThing([&]() { 254 MallocMessageBuilder outsideBuilder; 255 auto list = outsideBuilder.initRoot<test::TestContainMembrane>().initList(1); 256 list.set(0, kj::heap<ThingImpl>("inside")); 257 MallocMessageBuilder insideBuilder; 258 insideBuilder.initRoot<test::TestContainMembrane>().adoptList(copyOutOfMembrane( 259 list.asReader(), insideBuilder.getOrphanage(), env.policy->addRef())); 260 return insideBuilder.getRoot<test::TestContainMembrane>().getList()[0]; 261 }, "inside", "inbound", "inside", "inside"); 262 } 263 264 KJ_TEST("apply membrane using copyOutOfMembrane() on AnyPointer") { 265 TestEnv env; 266 267 env.testThing([&]() { 268 MallocMessageBuilder outsideBuilder; 269 auto ptr = outsideBuilder.initRoot<test::TestAnyPointer>().getAnyPointerField(); 270 ptr.setAs<test::TestMembrane::Thing>(kj::heap<ThingImpl>("inside")); 271 MallocMessageBuilder insideBuilder; 272 insideBuilder.initRoot<test::TestAnyPointer>().getAnyPointerField().adopt(copyOutOfMembrane( 273 ptr.asReader(), insideBuilder.getOrphanage(), env.policy->addRef())); 274 return insideBuilder.getRoot<test::TestAnyPointer>().getAnyPointerField() 275 .getAs<test::TestMembrane::Thing>(); 276 }, "inside", "inbound", "inside", "inside"); 277 } 278 279 struct TestRpcEnv { 280 kj::EventLoop loop; 281 kj::WaitScope waitScope; 282 kj::TwoWayPipe pipe; 283 TwoPartyClient client; 284 TwoPartyClient server; 285 test::TestMembrane::Client membraned; 286 287 TestRpcEnv(kj::Maybe<kj::Promise<void>> revokePromise = nullptr) 288 : waitScope(loop), 289 pipe(kj::newTwoWayPipe()), 290 client(*pipe.ends[0]), 291 server(*pipe.ends[1], 292 membrane(kj::heap<TestMembraneImpl>(), 293 kj::refcounted<MembranePolicyImpl>(kj::mv(revokePromise))), 294 rpc::twoparty::Side::SERVER), 295 membraned(client.bootstrap().castAs<test::TestMembrane>()) {} 296 297 void testThing(kj::Function<Thing::Client()> makeThing, 298 kj::StringPtr localPassThrough, kj::StringPtr localIntercept, 299 kj::StringPtr remotePassThrough, kj::StringPtr remoteIntercept) { 300 testThingImpl(waitScope, membraned, kj::mv(makeThing), 301 localPassThrough, localIntercept, remotePassThrough, remoteIntercept); 302 } 303 }; 304 305 KJ_TEST("call remote object inside membrane") { 306 TestRpcEnv env; 307 env.testThing([&]() { 308 return env.membraned.makeThingRequest().send().wait(env.waitScope).getThing(); 309 }, "inside", "inbound", "inside", "inside"); 310 } 311 312 KJ_TEST("call remote promise inside membrane") { 313 TestRpcEnv env; 314 env.testThing([&]() { 315 return env.membraned.makeThingRequest().send().getThing(); 316 }, "inside", "inbound", "inside", "inside"); 317 } 318 319 KJ_TEST("call remote resolved promise inside membrane") { 320 TestEnv env; 321 env.testThing([&]() { 322 auto thing = env.membraned.makeThingRequest().send().getThing(); 323 thing.whenResolved().wait(env.waitScope); 324 return thing; 325 }, "inside", "inbound", "inside", "inside"); 326 } 327 328 KJ_TEST("call remote object outside membrane") { 329 TestRpcEnv env; 330 env.testThing([&]() { 331 return kj::heap<ThingImpl>("outside"); 332 }, "outside", "outside", "outside", "outbound"); 333 } 334 335 KJ_TEST("call remote capability that has passed into and back out of membrane") { 336 TestRpcEnv env; 337 env.testThing([&]() { 338 auto req = env.membraned.loopbackRequest(); 339 req.setThing(kj::heap<ThingImpl>("outside")); 340 return req.send().wait(env.waitScope).getThing(); 341 }, "outside", "outside", "outside", "outbound"); 342 } 343 344 KJ_TEST("call remote promise pointing into membrane that eventually resolves to outside") { 345 TestRpcEnv env; 346 env.testThing([&]() { 347 auto req = env.membraned.loopbackRequest(); 348 req.setThing(kj::heap<ThingImpl>("outside")); 349 return req.send().getThing(); 350 }, "outside", "outside", "outside", "outbound"); 351 } 352 353 KJ_TEST("revoke membrane") { 354 auto paf = kj::newPromiseAndFulfiller<void>(); 355 356 TestRpcEnv env(kj::mv(paf.promise)); 357 358 auto thing = env.membraned.makeThingRequest().send().wait(env.waitScope).getThing(); 359 360 auto callPromise = env.membraned.waitForeverRequest().send(); 361 362 KJ_EXPECT(!callPromise.poll(env.waitScope)); 363 364 paf.fulfiller->reject(KJ_EXCEPTION(DISCONNECTED, "foobar")); 365 366 // TRICKY: We need to use .ignoreResult().wait() below because when compiling with 367 // -fno-exceptions, void waits throw recoverable exceptions while non-void waits necessarily 368 // throw fatal exceptions... but testing for fatal exceptions when exceptions are disabled 369 // involves fork()ing the process to run the code so if it has side effects on file descriptors 370 // then we'll get in a bad state... 371 372 KJ_ASSERT(callPromise.poll(env.waitScope)); 373 KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("foobar", callPromise.ignoreResult().wait(env.waitScope)); 374 375 KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("foobar", 376 env.membraned.makeThingRequest().send().ignoreResult().wait(env.waitScope)); 377 378 KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("foobar", 379 thing.passThroughRequest().send().ignoreResult().wait(env.waitScope)); 380 } 381 382 } // namespace 383 } // namespace _ 384 } // namespace capnp