EDK: Add Dispatcher::SupportsEntrypointClass().

But don't actually use it yet.

The problem we need to solve is: With rights, we'd like to do the rights
check in a common place (in Core) for each entrypoint. However, in the
case that the rights check fails (i.e., the handle doesn't have the
required right(s)), we prefer to say "invalid argument" -- rather than
"permission denied" -- if the dispatcher does not support that
entrypoint.

So what we do is the following, if an entrypoint |Foo()| is called:
* Get the dispatcher |d|. (If this fails, then "invalid argument".)
* Do the rights check.
* If the rights check passes:
  * Call |d->Foo()|.
  * If |d| supports |Foo()|, it does its thing (and yields whatever
    result).
  * If not, then |d->Foo()| must return "invalid argument".
* If the rights check fails, then:
  * Check |d->SupportsEntrypointClass(<class of Foo()>)|.
  * If |Foo()|'s class is supported, then "permission denied".
  * Else "invalid argument".

Note that we could of course call |d->SupportsEntrypointClass()| even
when the rights check passes. However, this is an additional call, and
penalizes the common/important (non-error) case, so we prefer to just
call |d->Foo()|.

R=azani@chromium.org

Review URL: https://codereview.chromium.org/1915153002 .
diff --git a/mojo/edk/system/core_test_base.cc b/mojo/edk/system/core_test_base.cc
index 542eadb..d73706d 100644
--- a/mojo/edk/system/core_test_base.cc
+++ b/mojo/edk/system/core_test_base.cc
@@ -32,9 +32,14 @@
     return AdoptRef(new MockDispatcher(info));
   }
 
-  // |Dispatcher| private methods:
+  // |Dispatcher| public methods:
   Type GetType() const override { return Type::UNKNOWN; }
 
+  bool SupportsEntrypointClass(
+      EntrypointClass entrypoint_class) const override {
+    return true;
+  }
+
  private:
   explicit MockDispatcher(CoreTestBase::MockHandleInfo* info) : info_(info) {
     CHECK(info_);
diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.cc b/mojo/edk/system/data_pipe_consumer_dispatcher.cc
index 2b3aed0..521456e 100644
--- a/mojo/edk/system/data_pipe_consumer_dispatcher.cc
+++ b/mojo/edk/system/data_pipe_consumer_dispatcher.cc
@@ -27,6 +27,11 @@
   return Type::DATA_PIPE_CONSUMER;
 }
 
+bool DataPipeConsumerDispatcher::SupportsEntrypointClass(
+    EntrypointClass entrypoint_class) const {
+  return (entrypoint_class == EntrypointClass::DATA_PIPE_CONSUMER);
+}
+
 // static
 RefPtr<DataPipeConsumerDispatcher> DataPipeConsumerDispatcher::Deserialize(
     Channel* channel,
diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.h b/mojo/edk/system/data_pipe_consumer_dispatcher.h
index 7b69568..ba69a35 100644
--- a/mojo/edk/system/data_pipe_consumer_dispatcher.h
+++ b/mojo/edk/system/data_pipe_consumer_dispatcher.h
@@ -29,6 +29,7 @@
 
   // |Dispatcher| public methods:
   Type GetType() const override;
+  bool SupportsEntrypointClass(EntrypointClass entrypoint_class) const override;
 
   // The "opposite" of |SerializeAndClose()|. (Typically this is called by
   // |Dispatcher::Deserialize()|.)
diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.cc b/mojo/edk/system/data_pipe_producer_dispatcher.cc
index febe217..bfa7646 100644
--- a/mojo/edk/system/data_pipe_producer_dispatcher.cc
+++ b/mojo/edk/system/data_pipe_producer_dispatcher.cc
@@ -27,6 +27,11 @@
   return Type::DATA_PIPE_PRODUCER;
 }
 
+bool DataPipeProducerDispatcher::SupportsEntrypointClass(
+    EntrypointClass entrypoint_class) const {
+  return (entrypoint_class == EntrypointClass::DATA_PIPE_PRODUCER);
+}
+
 // static
 RefPtr<DataPipeProducerDispatcher> DataPipeProducerDispatcher::Deserialize(
     Channel* channel,
diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.h b/mojo/edk/system/data_pipe_producer_dispatcher.h
index 5f3e197..51675e7 100644
--- a/mojo/edk/system/data_pipe_producer_dispatcher.h
+++ b/mojo/edk/system/data_pipe_producer_dispatcher.h
@@ -29,6 +29,7 @@
 
   // |Dispatcher| public methods:
   Type GetType() const override;
+  bool SupportsEntrypointClass(EntrypointClass entrypoint_class) const override;
 
   // The "opposite" of |SerializeAndClose()|. (Typically this is called by
   // |Dispatcher::Deserialize()|.)
diff --git a/mojo/edk/system/dispatcher.h b/mojo/edk/system/dispatcher.h
index 2206848..e21a650 100644
--- a/mojo/edk/system/dispatcher.h
+++ b/mojo/edk/system/dispatcher.h
@@ -60,6 +60,9 @@
 // |mutex()| method).
 class Dispatcher : public util::RefCountedThreadSafe<Dispatcher> {
  public:
+  // Types of dispatchers. Note that these are not necessarily a one-to-one with
+  // implementations of |Dispatcher|: multiple implementations may share the
+  // same type.
   enum class Type {
     UNKNOWN = 0,
     MESSAGE_PIPE,
@@ -70,8 +73,41 @@
     // "Private" types (not exposed via the public interface):
     PLATFORM_HANDLE = -1
   };
+
+  // Classes of "entrypoints"/"syscalls": Each dispatcher should support entire
+  // classes of methods (and if they don't support a given class, they should
+  // return |MOJO_RESULT_INVALID_ARGUMENT| for all the methods in that class).
+  // Warning: A method may be called even if |SupportsEntrypointClass()|
+  // indicates that the method's class is not supported (see below).
+  enum class EntrypointClass {
+    // |ReadMessage()|, |WriteMessage()|:
+    MESSAGE_PIPE,
+
+    // |SetDataPipeProducerOptions()|, |GetDataPipeProducerOptions()|,
+    // |WriteData()|, |BeginWriteData()|, |EndWriteData()|:
+    DATA_PIPE_PRODUCER,
+
+    // |SetDataPipeConsumerOptions()|, |GetDataPipeConsumerOptions()|,
+    // |ReadData()|, |BeginReadData()|, |EndReadData()|:
+    DATA_PIPE_CONSUMER,
+
+    // |DuplicateBufferHandle()|, |GetBufferInformation()|, |MapBuffer()|:
+    BUFFER,
+  };
+
+  // Gets the type of the dispatcher; see |Type| above.
   virtual Type GetType() const = 0;
 
+  // Gets whether the given entrypoint class is supported; see |EntrypointClass|
+  // above. This is ONLY called when a rights check has failed, to determine
+  // whether |MOJO_RESULT_PERMISSION_DENIED| (if the entrypoint class is
+  // supported) or |MOJO_RESULT_INVALID_ARGUMENT| (if not) should be returned.
+  // In the case that the rights check passes, |Core| will proceed immediately
+  // to call the method (so if the method is not supported, it must still return
+  // |MOJO_RESULT_INVALID_ARGUMENT|).
+  virtual bool SupportsEntrypointClass(
+      EntrypointClass entrypoint_class) const = 0;
+
   // These methods implement the various primitives named |Mojo...()|. These
   // take |mutex_| and handle races with |Close()|. Then they call out to
   // subclasses' |...ImplNoLock()| methods (still under |mutex_|), which
@@ -81,6 +117,7 @@
   // possible. If this becomes an issue, we can rethink this.
   MojoResult Close();
 
+  // |EntrypointClass::MESSAGE_PIPE|:
   // |transports| may be non-null if and only if there are handles to be
   // written; not that |this| must not be in |transports|. On success, all the
   // dispatchers in |transports| must have been moved to a closed state; on
@@ -98,6 +135,7 @@
                          uint32_t* num_dispatchers,
                          MojoReadMessageFlags flags);
 
+  // |EntrypointClass::DATA_PIPE_PRODUCER|:
   MojoResult SetDataPipeProducerOptions(
       UserPointer<const MojoDataPipeProducerOptions> options);
   MojoResult GetDataPipeProducerOptions(
@@ -110,6 +148,8 @@
                             UserPointer<uint32_t> buffer_num_bytes,
                             MojoWriteDataFlags flags);
   MojoResult EndWriteData(uint32_t num_bytes_written);
+
+  // |EntrypointClass::DATA_PIPE_CONSUMER|:
   MojoResult SetDataPipeConsumerOptions(
       UserPointer<const MojoDataPipeConsumerOptions> options);
   MojoResult GetDataPipeConsumerOptions(
@@ -123,6 +163,7 @@
                            MojoReadDataFlags flags);
   MojoResult EndReadData(uint32_t num_bytes_read);
 
+  // |EntrypointClass::BUFFER|:
   // |options| may be null. |new_dispatcher| must not be null, but
   // |*new_dispatcher| should be null (and will contain the dispatcher for the
   // new handle on success).
diff --git a/mojo/edk/system/message_pipe_dispatcher.cc b/mojo/edk/system/message_pipe_dispatcher.cc
index 7028cbe..005b094 100644
--- a/mojo/edk/system/message_pipe_dispatcher.cc
+++ b/mojo/edk/system/message_pipe_dispatcher.cc
@@ -71,6 +71,11 @@
   return Type::MESSAGE_PIPE;
 }
 
+bool MessagePipeDispatcher::SupportsEntrypointClass(
+    EntrypointClass entrypoint_class) const {
+  return (entrypoint_class == EntrypointClass::MESSAGE_PIPE);
+}
+
 // static
 RefPtr<MessagePipeDispatcher> MessagePipeDispatcher::CreateRemoteMessagePipe(
     RefPtr<ChannelEndpoint>* channel_endpoint) {
diff --git a/mojo/edk/system/message_pipe_dispatcher.h b/mojo/edk/system/message_pipe_dispatcher.h
index 054fe0f..be86377 100644
--- a/mojo/edk/system/message_pipe_dispatcher.h
+++ b/mojo/edk/system/message_pipe_dispatcher.h
@@ -47,6 +47,7 @@
 
   // |Dispatcher| public methods:
   Type GetType() const override;
+  bool SupportsEntrypointClass(EntrypointClass entrypoint_class) const override;
 
   // Creates a |MessagePipe| with a local endpoint (at port 0) and a proxy
   // endpoint, and creates/initializes a |MessagePipeDispatcher| (attached to
diff --git a/mojo/edk/system/message_pipe_dispatcher_unittest.cc b/mojo/edk/system/message_pipe_dispatcher_unittest.cc
index 171c84c..4bae645 100644
--- a/mojo/edk/system/message_pipe_dispatcher_unittest.cc
+++ b/mojo/edk/system/message_pipe_dispatcher_unittest.cc
@@ -38,10 +38,6 @@
 namespace system {
 namespace {
 
-const MojoHandleSignals kAllSignals = MOJO_HANDLE_SIGNAL_READABLE |
-                                      MOJO_HANDLE_SIGNAL_WRITABLE |
-                                      MOJO_HANDLE_SIGNAL_PEER_CLOSED;
-
 TEST(MessagePipeDispatcherTest, Basic) {
   Stopwatch stopwatch;
   int32_t buffer[1];
@@ -70,7 +66,9 @@
     EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS,
               d0->AddAwakable(&w, MOJO_HANDLE_SIGNAL_WRITABLE, 0, &hss));
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
-    EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+    EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                  MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+              hss.satisfiable_signals);
     // Shouldn't need to remove the waiter (it was not added).
 
     // Add a readable waiter to |d0|, then make it readable (by writing to
@@ -90,7 +88,9 @@
     d0->RemoveAwakable(&w, &hss);
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
               hss.satisfied_signals);
-    EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+    EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                  MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+              hss.satisfiable_signals);
 
     // Try adding a readable waiter when already readable (from above).
     w.Init();
@@ -99,7 +99,9 @@
               d0->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 2, &hss));
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
               hss.satisfied_signals);
-    EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+    EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                  MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+              hss.satisfiable_signals);
     // Shouldn't need to remove the waiter (it was not added).
 
     // Make |d0| no longer readable (by reading from it).
@@ -122,7 +124,9 @@
     hss = HandleSignalsState();
     d0->RemoveAwakable(&w, &hss);
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
-    EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+    EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                  MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+              hss.satisfiable_signals);
 
     // Wait for non-zero, finite time for readability on |d0| (will time out).
     w.Init();
@@ -137,7 +141,9 @@
     hss = HandleSignalsState();
     d0->RemoveAwakable(&w, &hss);
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals);
-    EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+    EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                  MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+              hss.satisfiable_signals);
 
     // Check the peer closed signal.
     w.Init();
@@ -159,6 +165,35 @@
   }
 }
 
+TEST(MessagePipeDispatcherTest, SupportsEntrypointClass) {
+  auto d = MessagePipeDispatcher::Create(
+      MessagePipeDispatcher::kDefaultCreateOptions);
+  ASSERT_TRUE(d);
+
+  // We need to initialize |d|.
+  {
+    auto d_peer = MessagePipeDispatcher::Create(
+        MessagePipeDispatcher::kDefaultCreateOptions);
+    auto mp = MessagePipe::CreateLocalLocal();
+    d->Init(mp.Clone(), 0);
+    d_peer->Init(std::move(mp), 1);
+    EXPECT_EQ(MOJO_RESULT_OK, d_peer->Close());
+  }
+
+  EXPECT_TRUE(
+      d->SupportsEntrypointClass(Dispatcher::EntrypointClass::MESSAGE_PIPE));
+  EXPECT_FALSE(d->SupportsEntrypointClass(
+      Dispatcher::EntrypointClass::DATA_PIPE_PRODUCER));
+  EXPECT_FALSE(d->SupportsEntrypointClass(
+      Dispatcher::EntrypointClass::DATA_PIPE_CONSUMER));
+  EXPECT_FALSE(d->SupportsEntrypointClass(Dispatcher::EntrypointClass::BUFFER));
+
+  // TODO(vtl): Check that it actually returns |MOJO_RESULT_INVALID_ARGUMENT|
+  // for methods in unsupported entrypoint classes.
+
+  EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+}
+
 TEST(MessagePipeDispatcherTest, InvalidParams) {
   char buffer[1];
 
@@ -261,7 +296,9 @@
               d0->AddAwakable(&w, MOJO_HANDLE_SIGNAL_READABLE, 0, &hss));
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
               hss.satisfied_signals);
-    EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+    EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                  MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+              hss.satisfiable_signals);
 
     // Try reading from |d1|; should fail (nothing to read).
     buffer[0] = 0;
@@ -394,7 +431,9 @@
     EXPECT_EQ(1u, context);
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
               hss.satisfied_signals);
-    EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+    EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                  MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+              hss.satisfiable_signals);
 
     // Now |d1| is already readable. Try waiting for it again.
     {
@@ -409,7 +448,9 @@
     EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, result);
     EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE,
               hss.satisfied_signals);
-    EXPECT_EQ(kAllSignals, hss.satisfiable_signals);
+    EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE |
+                  MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+              hss.satisfiable_signals);
 
     // Consume what we wrote to |d0|.
     buffer[0] = 0;
diff --git a/mojo/edk/system/mock_simple_dispatcher.cc b/mojo/edk/system/mock_simple_dispatcher.cc
index 03828f6..bd8b283 100644
--- a/mojo/edk/system/mock_simple_dispatcher.cc
+++ b/mojo/edk/system/mock_simple_dispatcher.cc
@@ -50,6 +50,11 @@
   return Type::UNKNOWN;
 }
 
+bool MockSimpleDispatcher::SupportsEntrypointClass(
+    EntrypointClass entrypoint_class) const {
+  return false;
+}
+
 MockSimpleDispatcher::MockSimpleDispatcher(
     MojoHandleSignals satisfied_signals,
     MojoHandleSignals satisfiable_signals)
diff --git a/mojo/edk/system/mock_simple_dispatcher.h b/mojo/edk/system/mock_simple_dispatcher.h
index 4f616b0..9776bd6 100644
--- a/mojo/edk/system/mock_simple_dispatcher.h
+++ b/mojo/edk/system/mock_simple_dispatcher.h
@@ -22,6 +22,7 @@
   // Note: Use |MakeRefCounted<MockSimpleDispatcher>()|.
 
   Type GetType() const override;
+  bool SupportsEntrypointClass(EntrypointClass entrypoint_class) const override;
 
   void SetSatisfiedSignals(MojoHandleSignals new_satisfied_signals);
   void SetSatisfiableSignals(MojoHandleSignals new_satisfiable_signals);
diff --git a/mojo/edk/system/platform_handle_dispatcher.cc b/mojo/edk/system/platform_handle_dispatcher.cc
index aab08d7..f2c6e72 100644
--- a/mojo/edk/system/platform_handle_dispatcher.cc
+++ b/mojo/edk/system/platform_handle_dispatcher.cc
@@ -35,6 +35,11 @@
   return Type::PLATFORM_HANDLE;
 }
 
+bool PlatformHandleDispatcher::SupportsEntrypointClass(
+    EntrypointClass entrypoint_class) const {
+  return false;
+}
+
 // static
 RefPtr<PlatformHandleDispatcher> PlatformHandleDispatcher::Deserialize(
     Channel* channel,
@@ -73,8 +78,7 @@
     ScopedPlatformHandle platform_handle)
     : platform_handle_(platform_handle.Pass()) {}
 
-PlatformHandleDispatcher::~PlatformHandleDispatcher() {
-}
+PlatformHandleDispatcher::~PlatformHandleDispatcher() {}
 
 void PlatformHandleDispatcher::CloseImplNoLock() {
   mutex().AssertHeld();
diff --git a/mojo/edk/system/platform_handle_dispatcher.h b/mojo/edk/system/platform_handle_dispatcher.h
index 426f0c3..5190927 100644
--- a/mojo/edk/system/platform_handle_dispatcher.h
+++ b/mojo/edk/system/platform_handle_dispatcher.h
@@ -29,6 +29,7 @@
 
   // |Dispatcher| public methods:
   Type GetType() const override;
+  bool SupportsEntrypointClass(EntrypointClass entrypoint_class) const override;
 
   // The "opposite" of |SerializeAndClose()|. (Typically this is called by
   // |Dispatcher::Deserialize()|.)
diff --git a/mojo/edk/system/platform_handle_dispatcher_unittest.cc b/mojo/edk/system/platform_handle_dispatcher_unittest.cc
index a930c47..e57c511 100644
--- a/mojo/edk/system/platform_handle_dispatcher_unittest.cc
+++ b/mojo/edk/system/platform_handle_dispatcher_unittest.cc
@@ -60,6 +60,34 @@
   EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close());
 }
 
+TEST(PlatformHandleDispatcher, SupportsEntrypointClass) {
+  test::ScopedTestDir test_dir;
+
+  util::ScopedFILE fp(test_dir.CreateFile());
+  ASSERT_TRUE(fp);
+
+  ScopedPlatformHandle h(PlatformHandleFromFILE(std::move(fp)));
+  EXPECT_FALSE(fp);
+  ASSERT_TRUE(h.is_valid());
+
+  auto d = PlatformHandleDispatcher::Create(h.Pass());
+  ASSERT_TRUE(d);
+  EXPECT_FALSE(h.is_valid());
+
+  EXPECT_FALSE(
+      d->SupportsEntrypointClass(Dispatcher::EntrypointClass::MESSAGE_PIPE));
+  EXPECT_FALSE(d->SupportsEntrypointClass(
+      Dispatcher::EntrypointClass::DATA_PIPE_PRODUCER));
+  EXPECT_FALSE(d->SupportsEntrypointClass(
+      Dispatcher::EntrypointClass::DATA_PIPE_CONSUMER));
+  EXPECT_FALSE(d->SupportsEntrypointClass(Dispatcher::EntrypointClass::BUFFER));
+
+  // TODO(vtl): Check that it actually returns |MOJO_RESULT_INVALID_ARGUMENT|
+  // for methods in unsupported entrypoint classes.
+
+  EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+}
+
 TEST(PlatformHandleDispatcherTest, CreateEquivalentDispatcherAndClose) {
   test::ScopedTestDir test_dir;
 
diff --git a/mojo/edk/system/shared_buffer_dispatcher.cc b/mojo/edk/system/shared_buffer_dispatcher.cc
index 4474c70..3ea46d80 100644
--- a/mojo/edk/system/shared_buffer_dispatcher.cc
+++ b/mojo/edk/system/shared_buffer_dispatcher.cc
@@ -97,6 +97,11 @@
   return Type::SHARED_BUFFER;
 }
 
+bool SharedBufferDispatcher::SupportsEntrypointClass(
+    EntrypointClass entrypoint_class) const {
+  return (entrypoint_class == EntrypointClass::BUFFER);
+}
+
 // static
 RefPtr<SharedBufferDispatcher> SharedBufferDispatcher::Deserialize(
     Channel* channel,
diff --git a/mojo/edk/system/shared_buffer_dispatcher.h b/mojo/edk/system/shared_buffer_dispatcher.h
index 3ba0fff..3f2f6e7 100644
--- a/mojo/edk/system/shared_buffer_dispatcher.h
+++ b/mojo/edk/system/shared_buffer_dispatcher.h
@@ -56,6 +56,7 @@
 
   // |Dispatcher| public methods:
   Type GetType() const override;
+  bool SupportsEntrypointClass(EntrypointClass entrypoint_class) const override;
 
   // The "opposite" of |SerializeAndClose()|. (Typically this is called by
   // |Dispatcher::Deserialize()|.)
diff --git a/mojo/edk/system/shared_buffer_dispatcher_unittest.cc b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc
index b0cc9a4..276a0b4 100644
--- a/mojo/edk/system/shared_buffer_dispatcher_unittest.cc
+++ b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc
@@ -183,6 +183,28 @@
   EXPECT_EQ('y', static_cast<char*>(mapping1->GetBase())[kSize / 2 + 1]);
 }
 
+TEST_F(SharedBufferDispatcherTest, SupportsEntrypointClass) {
+  MojoResult result = MOJO_RESULT_INTERNAL;
+  auto d = SharedBufferDispatcher::Create(
+      platform_support(), SharedBufferDispatcher::kDefaultCreateOptions, 100u,
+      &result);
+  ASSERT_TRUE(d);
+  EXPECT_EQ(MOJO_RESULT_OK, result);
+
+  EXPECT_FALSE(
+      d->SupportsEntrypointClass(Dispatcher::EntrypointClass::MESSAGE_PIPE));
+  EXPECT_FALSE(d->SupportsEntrypointClass(
+      Dispatcher::EntrypointClass::DATA_PIPE_PRODUCER));
+  EXPECT_FALSE(d->SupportsEntrypointClass(
+      Dispatcher::EntrypointClass::DATA_PIPE_CONSUMER));
+  EXPECT_TRUE(d->SupportsEntrypointClass(Dispatcher::EntrypointClass::BUFFER));
+
+  // TODO(vtl): Check that it actually returns |MOJO_RESULT_INVALID_ARGUMENT|
+  // for methods in unsupported entrypoint classes.
+
+  EXPECT_EQ(MOJO_RESULT_OK, d->Close());
+}
+
 TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandle) {
   const uint64_t kSize = 100u;