capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

membrane.h (15500B)


      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 #pragma once
     23 // In capability theory, a "membrane" is a wrapper around a capability which (usually) forwards
     24 // calls but recursively wraps capabilities in those calls in the same membrane. The purpose of a
     25 // membrane is to enforce a barrier between two capabilities that cannot be bypassed by merely
     26 // introducing new objects.
     27 //
     28 // The most common use case for a membrane is revocation: Say Alice wants to give Bob a capability
     29 // to access Carol, but wants to be able to revoke this capability later. Alice can accomplish this
     30 // by wrapping Carol in a revokable wrapper which passes through calls until such a time as Alice
     31 // indicates it should be revoked, after which all calls through the wrapper will throw exceptions.
     32 // However, a naive wrapper approach has a problem: if Bob makes a call to Carol and sends a new
     33 // capability in that call, or if Carol returns a capability to Bob in the response to a call, then
     34 // the two are now able to communicate using this new capability, which Alice cannot revoke. In
     35 // order to avoid this problem, Alice must use not just a wrapper but a "membrane", which
     36 // recursively wraps all objects that pass through it in either direction. Thus, all connections
     37 // formed between Bob and Carol (originating from Alice's original introduction) can be revoked
     38 // together by revoking the membrane.
     39 //
     40 // Note that when a capability is passed into a membrane and then passed back out, the result is
     41 // the original capability, not a double-membraned capability. This means that in our revocation
     42 // example, if Bob uses his capability to Carol to obtain another capability from her, then send
     43 // it back to her, the capability Carol receives back will NOT be revoked when Bob's access to
     44 // Carol is revoked. Thus Bob can create long-term irrevocable connections. In most practical use
     45 // cases, this is what you want. APIs commonly rely on the fact that a capability obtained and then
     46 // passed back can be recognized as the original capability.
     47 //
     48 // Mark Miller on membranes: http://www.eros-os.org/pipermail/e-lang/2003-January/008434.html
     49 
     50 #include "capability.h"
     51 
     52 namespace capnp {
     53 
     54 class MembranePolicy {
     55   // Applications may implement this interface to define a membrane policy, which allows some
     56   // calls crossing the membrane to be blocked or redirected.
     57 
     58 public:
     59   virtual kj::Maybe<Capability::Client> inboundCall(
     60       uint64_t interfaceId, uint16_t methodId, Capability::Client target) = 0;
     61   // Given an inbound call (a call originating "outside" the membrane destined for an object
     62   // "inside" the membrane), decides what to do with it. The policy may:
     63   //
     64   // - Return null to indicate that the call should proceed to the destination. All capabilities
     65   //   in the parameters or result will be properly wrapped in the same membrane.
     66   // - Return a capability to have the call redirected to that capability. Note that the redirect
     67   //   capability will be treated as outside the membrane, so the params and results will not be
     68   //   auto-wrapped; however, the callee can easily wrap the returned capability in the membrane
     69   //   itself before returning to achieve this effect.
     70   // - Throw an exception to cause the call to fail with that exception.
     71   //
     72   // `target` is the underlying capability (*inside* the membrane) for which the call is destined.
     73   // Generally, the only way you should use `target` is to wrap it in some capability which you
     74   // return as a redirect. The redirect capability may modify the call in some way and send it to
     75   // `target`. Be careful to use `copyIntoMembrane()` and `copyOutOfMembrane()` as appropriate when
     76   // copying parameters or results across the membrane.
     77   //
     78   // Note that since `target` is inside the capability, if you were to directly return it (rather
     79   // than return null), the effect would be that the membrane would be broken: the call would
     80   // proceed directly and any new capabilities introduced through it would not be membraned. You
     81   // generally should not do that.
     82 
     83   virtual kj::Maybe<Capability::Client> outboundCall(
     84       uint64_t interfaceId, uint16_t methodId, Capability::Client target) = 0;
     85   // Like `inboundCall()`, but applies to calls originating *inside* the membrane and terminating
     86   // outside.
     87   //
     88   // Note: It is strongly recommended that `outboundCall()` returns null in exactly the same cases
     89   //   that `inboundCall()` return null. Conversely, for any case where `inboundCall()` would
     90   //   redirect or throw, `outboundCall()` should also redirect or throw. Otherwise, you can run
     91   //   into inconsistent behavion when a promise is returned across a membrane, and that promise
     92   //   later resolves to a capability on the other side of the membrane: calls on the promise
     93   //   will enter and then exit the membrane, but calls on the eventual resolution will not cross
     94   //   the membrane at all, so it is important that these two cases behave the same.
     95 
     96   virtual kj::Own<MembranePolicy> addRef() = 0;
     97   // Return a new owned pointer to the same policy.
     98   //
     99   // Typically an implementation of MembranePolicy should also inherit kj::Refcounted and implement
    100   // `addRef()` as `return kj::addRef(*this);`.
    101   //
    102   // Note that the membraning system considers two membranes created with the same MembranePolicy
    103   // object actually to be the *same* membrane. This is relevant when an object passes into the
    104   // membrane and then back out (or out and then back in): instead of double-wrapping the object,
    105   // the wrapping will be removed.
    106 
    107   virtual kj::Maybe<kj::Promise<void>> onRevoked() { return nullptr; }
    108   // If this returns non-null, then it is a promise that will reject (throw an exception) when the
    109   // membrane should be revoked. On revocation, all capabilities pointing across the membrane will
    110   // be dropped and all outstanding calls canceled. The exception thrown by the promise will be
    111   // propagated to all these calls. It is an error for the promise to resolve without throwing.
    112   //
    113   // After the revocation promise has rejected, inboundCall() and outboundCall() will still be
    114   // invoked for new calls, but the `target` passed to them will be a capability that always
    115   // rethrows the revocation exception.
    116 
    117   virtual bool shouldResolveBeforeRedirecting() { return true; }
    118   // If this returns true, then when inboundCall() or outboundCall() returns a redirect, but the
    119   // original target is a promise, then the membrane will discard the redirect and instead wait
    120   // for the promise to become more resolved and try again.
    121   //
    122   // This behavior is important in particular when implementing a membrane that wants to intercept
    123   // calls that would otherwise terminate inside the membrane, but needs to be careful not to
    124   // intercept calls that might be reflected back out of the membrane. If the promise eventually
    125   // resolves to a capability outside the membrane, then the call will be forwarded to that
    126   // capability without applying the policy at all.
    127   //
    128   // However, some membranes don't need this behavior, and may be negatively impacted by the
    129   // unnecessary waiting. Such membranes should override this to return false.
    130   //
    131   // TODO(cleanup): Consider a backwards-incompatible revamp of the MembranePolicy API with a
    132   //   better design here. Maybe we should more carefully distinguish between MembranePolicies
    133   //   which are reversible vs. those which are one-way?
    134 
    135   // ---------------------------------------------------------------------------
    136   // Control over importing and exporting.
    137   //
    138   // Most membranes should not override these methods. The default behavior is that a capability
    139   // that crosses the membrane is wrapped in it, and if the wrapped version crosses back the other
    140   // way, it is unwrapped.
    141 
    142   virtual Capability::Client importExternal(Capability::Client external);
    143   // An external capability is crossing into the membrane. Returns the capability that should
    144   // substitute for it when called from the inside.
    145   //
    146   // The default implementation creates a capability that invokes this MembranePolicy. E.g. all
    147   // calls will invoke outboundCall().
    148   //
    149   // Note that reverseMembrane(cap, policy) normally calls policy->importExternal(cap), unless
    150   // `cap` itself was originally returned by the default implementation of exportInternal(), in
    151   // which case importInternal() is called instead.
    152 
    153   virtual Capability::Client exportInternal(Capability::Client internal);
    154   // An internal capability is crossing out of the membrane. Returns the capability that should
    155   // substitute for it when called from the outside.
    156   //
    157   // The default implementation creates a capability that invokes this MembranePolicy. E.g. all
    158   // calls will invoke inboundCall().
    159   //
    160   // Note that membrane(cap, policy) normally calls policy->exportInternal(cap), unless `cap`
    161   // itself was originally returned by the default implementation of exportInternal(), in which
    162   // case importInternal() is called instead.
    163 
    164   virtual MembranePolicy& rootPolicy() { return *this; }
    165   // If two policies return the same value for rootPolicy(), then a capability imported through
    166   // one can be exported through the other, and vice versa. `importInternal()` and
    167   // `exportExternal()` will always be called on the root policy, passing the two child policies
    168   // as parameters. If you don't override rootPolicy(), then the policy references passed to
    169   // importInternal() and exportExternal() will always be references to *this.
    170 
    171   virtual Capability::Client importInternal(
    172       Capability::Client internal, MembranePolicy& exportPolicy, MembranePolicy& importPolicy);
    173   // An internal capability which was previously exported is now being re-imported, i.e. a
    174   // capability passed out of the membrane and then back in.
    175   //
    176   // The default implementation simply returns `internal`.
    177 
    178   virtual Capability::Client exportExternal(
    179       Capability::Client external, MembranePolicy& importPolicy, MembranePolicy& exportPolicy);
    180   // An external capability which was previously imported is now being re-exported, i.e. a
    181   // capability passed into the membrane and then back out.
    182   //
    183   // The default implementation simply returns `external`.
    184 };
    185 
    186 Capability::Client membrane(Capability::Client inner, kj::Own<MembranePolicy> policy);
    187 // Wrap `inner` in a membrane specified by `policy`. `inner` is considered "inside" the membrane,
    188 // while the returned capability should only be called from outside the membrane.
    189 
    190 Capability::Client reverseMembrane(Capability::Client outer, kj::Own<MembranePolicy> policy);
    191 // Like `membrane` but treat the input capability as "outside" the membrane, and return a
    192 // capability appropriate for use inside.
    193 //
    194 // Applications typically won't use this directly; the membraning code automatically sets up
    195 // reverse membranes where needed.
    196 
    197 template <typename ClientType>
    198 ClientType membrane(ClientType inner, kj::Own<MembranePolicy> policy);
    199 template <typename ClientType>
    200 ClientType reverseMembrane(ClientType inner, kj::Own<MembranePolicy> policy);
    201 // Convenience templates which return the same interface type as the input.
    202 
    203 template <typename ServerType>
    204 typename ServerType::Serves::Client membrane(
    205     kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy);
    206 template <typename ServerType>
    207 typename ServerType::Serves::Client reverseMembrane(
    208     kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy);
    209 // Convenience templates which input a capability server type and return the appropriate client
    210 // type.
    211 
    212 template <typename Reader>
    213 Orphan<typename kj::Decay<Reader>::Reads> copyIntoMembrane(
    214     Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy);
    215 // Copy a Cap'n Proto object (e.g. struct or list), adding the given membrane to any capabilities
    216 // found within it. `from` is interpreted as "outside" the membrane while `to` is "inside".
    217 
    218 template <typename Reader>
    219 Orphan<typename kj::Decay<Reader>::Reads> copyOutOfMembrane(
    220     Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy);
    221 // Like copyIntoMembrane() except that `from` is "inside" the membrane and `to` is "outside".
    222 
    223 // =======================================================================================
    224 // inline implementation details
    225 
    226 template <typename ClientType>
    227 ClientType membrane(ClientType inner, kj::Own<MembranePolicy> policy) {
    228   return membrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
    229       .castAs<typename ClientType::Calls>();
    230 }
    231 template <typename ClientType>
    232 ClientType reverseMembrane(ClientType inner, kj::Own<MembranePolicy> policy) {
    233   return reverseMembrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
    234       .castAs<typename ClientType::Calls>();
    235 }
    236 
    237 template <typename ServerType>
    238 typename ServerType::Serves::Client membrane(
    239     kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy) {
    240   return membrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
    241       .castAs<typename ServerType::Serves>();
    242 }
    243 template <typename ServerType>
    244 typename ServerType::Serves::Client reverseMembrane(
    245     kj::Own<ServerType> inner, kj::Own<MembranePolicy> policy) {
    246   return reverseMembrane(Capability::Client(kj::mv(inner)), kj::mv(policy))
    247       .castAs<typename ServerType::Serves>();
    248 }
    249 
    250 namespace _ {  // private
    251 
    252 OrphanBuilder copyOutOfMembrane(PointerReader from, Orphanage to,
    253                                 kj::Own<MembranePolicy> policy, bool reverse);
    254 OrphanBuilder copyOutOfMembrane(StructReader from, Orphanage to,
    255                                 kj::Own<MembranePolicy> policy, bool reverse);
    256 OrphanBuilder copyOutOfMembrane(ListReader from, Orphanage to,
    257                                 kj::Own<MembranePolicy> policy, bool reverse);
    258 
    259 }  // namespace _ (private)
    260 
    261 template <typename Reader>
    262 Orphan<typename kj::Decay<Reader>::Reads> copyIntoMembrane(
    263     Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy) {
    264   return _::copyOutOfMembrane(
    265       _::PointerHelpers<typename kj::Decay<Reader>::Reads>::getInternalReader(from),
    266       to, kj::mv(policy), true);
    267 }
    268 
    269 template <typename Reader>
    270 Orphan<typename kj::Decay<Reader>::Reads> copyOutOfMembrane(
    271     Reader&& from, Orphanage to, kj::Own<MembranePolicy> policy) {
    272   return _::copyOutOfMembrane(
    273       _::PointerHelpers<typename kj::Decay<Reader>::Reads>::getInternalReader(from),
    274       to, kj::mv(policy), false);
    275 }
    276 
    277 } // namespace capnp