123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 |
- From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
- From: Cheng Zhao <[email protected]>
- Date: Thu, 4 Oct 2018 14:57:02 -0700
- Subject: fix: data race on NodeChannel destruction
- [1081874] [High] [CVE-2020-6575]: Double free on NodeChannel
- Backport https://chromium.googlesource.com/chromium/src/+/5e61913985df0cc621bf72f7fa75e76759ffde15.
- diff --git a/mojo/core/BUILD.gn b/mojo/core/BUILD.gn
- index e6fcb96256f6fdb867a362588547a7829f28efe8..6282eed9158c691a9ca49d1ec9a57cedc4392741 100644
- --- a/mojo/core/BUILD.gn
- +++ b/mojo/core/BUILD.gn
- @@ -18,6 +18,7 @@ component("embedder_internal") {
- ":test_sources",
- "//mojo:*",
- "//mojo/core/embedder",
- + "//mojo/core/test:test_support",
- ]
- }
-
- @@ -57,6 +58,7 @@ template("core_impl_source_set") {
- "handle_table.h",
- "invitation_dispatcher.h",
- "message_pipe_dispatcher.h",
- + "node_channel.h",
- "node_controller.h",
- "options_validation.h",
- "platform_handle_dispatcher.h",
- @@ -86,7 +88,6 @@ template("core_impl_source_set") {
- "invitation_dispatcher.cc",
- "message_pipe_dispatcher.cc",
- "node_channel.cc",
- - "node_channel.h",
- "node_controller.cc",
- "platform_handle_dispatcher.cc",
- "platform_handle_in_transit.cc",
- @@ -289,6 +290,7 @@ source_set("test_sources") {
- "handle_table_unittest.cc",
- "message_pipe_unittest.cc",
- "message_unittest.cc",
- + "node_channel_unittest.cc",
- "options_validation_unittest.cc",
- "platform_handle_dispatcher_unittest.cc",
- "quota_unittest.cc",
- @@ -387,6 +389,7 @@ fuzzer_test("mojo_core_node_channel_fuzzer") {
- deps = [
- ":core_impl_for_fuzzers",
- "//base",
- + "//mojo/core/test:test_support",
- "//mojo/public/cpp/platform",
- ]
- }
- diff --git a/mojo/core/embedder/embedder.cc b/mojo/core/embedder/embedder.cc
- index ec68e37d09888f672759f79961e3e6e4d18f0c5c..f70c73be4df529cce09cb503dfce28f2a04e7e1a 100644
- --- a/mojo/core/embedder/embedder.cc
- +++ b/mojo/core/embedder/embedder.cc
- @@ -35,7 +35,7 @@ void SetDefaultProcessErrorCallback(ProcessErrorCallback callback) {
- Core::Get()->SetDefaultProcessErrorCallback(std::move(callback));
- }
-
- -scoped_refptr<base::TaskRunner> GetIOTaskRunner() {
- +scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner() {
- return Core::Get()->GetNodeController()->io_task_runner();
- }
-
- diff --git a/mojo/core/embedder/embedder.h b/mojo/core/embedder/embedder.h
- index 5d65400987728940a296ba2a84d40158f52d6d34..96dc44c0a78e0e6cb6e5aa511bd2fd4f1252cf16 100644
- --- a/mojo/core/embedder/embedder.h
- +++ b/mojo/core/embedder/embedder.h
- @@ -13,7 +13,7 @@
- #include "base/component_export.h"
- #include "base/memory/ref_counted.h"
- #include "base/process/process_handle.h"
- -#include "base/task_runner.h"
- +#include "base/single_thread_task_runner.h"
- #include "build/build_config.h"
- #include "mojo/core/embedder/configuration.h"
-
- @@ -42,9 +42,10 @@ void SetDefaultProcessErrorCallback(ProcessErrorCallback callback);
-
- // Initialialization/shutdown for interprocess communication (IPC) -------------
-
- -// Retrieves the TaskRunner used for IPC I/O, as set by ScopedIPCSupport.
- +// Retrieves the SequencedTaskRunner used for IPC I/O, as set by
- +// ScopedIPCSupport.
- COMPONENT_EXPORT(MOJO_CORE_EMBEDDER)
- -scoped_refptr<base::TaskRunner> GetIOTaskRunner();
- +scoped_refptr<base::SingleThreadTaskRunner> GetIOTaskRunner();
-
- } // namespace core
- } // namespace mojo
- diff --git a/mojo/core/node_channel.cc b/mojo/core/node_channel.cc
- index e898b044286e019b5e423b941502030ce3094582..061ea1026e95d1b1f80a762ce377aebdd97e1b42 100644
- --- a/mojo/core/node_channel.cc
- +++ b/mojo/core/node_channel.cc
- @@ -228,7 +228,7 @@ void NodeChannel::NotifyBadMessage(const std::string& error) {
- }
-
- void NodeChannel::SetRemoteProcessHandle(ScopedProcessHandle process_handle) {
- - DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- + DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
- {
- base::AutoLock lock(channel_lock_);
- if (channel_)
- @@ -253,7 +253,7 @@ ScopedProcessHandle NodeChannel::CloneRemoteProcessHandle() {
- }
-
- void NodeChannel::SetRemoteNodeName(const ports::NodeName& name) {
- - DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- + DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
- remote_node_name_ = name;
- }
-
- @@ -468,15 +468,15 @@ NodeChannel::NodeChannel(
- Channel::HandlePolicy channel_handle_policy,
- scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
- const ProcessErrorCallback& process_error_callback)
- - : delegate_(delegate),
- - io_task_runner_(io_task_runner),
- + : base::RefCountedDeleteOnSequence<NodeChannel>(io_task_runner),
- + delegate_(delegate),
- process_error_callback_(process_error_callback)
- #if !defined(OS_NACL_SFI)
- ,
- channel_(Channel::Create(this,
- std::move(connection_params),
- channel_handle_policy,
- - io_task_runner_))
- + std::move(io_task_runner)))
- #endif
- {
- }
- @@ -499,15 +499,10 @@ void NodeChannel::CreateAndBindLocalBrokerHost(
- void NodeChannel::OnChannelMessage(const void* payload,
- size_t payload_size,
- std::vector<PlatformHandle> handles) {
- - DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- + DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
-
- RequestContext request_context(RequestContext::Source::SYSTEM);
-
- - // Ensure this NodeChannel stays alive through the extent of this method. The
- - // delegate may have the only other reference to this object and it may choose
- - // to drop it here in response to, e.g., a malformed message.
- - scoped_refptr<NodeChannel> keepalive = this;
- -
- if (payload_size <= sizeof(Header)) {
- delegate_->OnChannelError(remote_node_name_, this);
- return;
- @@ -739,7 +734,7 @@ void NodeChannel::OnChannelMessage(const void* payload,
- }
-
- void NodeChannel::OnChannelError(Channel::Error error) {
- - DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
- + DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
-
- RequestContext request_context(RequestContext::Source::SYSTEM);
-
- diff --git a/mojo/core/node_channel.h b/mojo/core/node_channel.h
- index ea91f927049befa258884b13e7f7966c09518687..04501da0fb6cd36df1b332135282104dd442b41e 100644
- --- a/mojo/core/node_channel.h
- +++ b/mojo/core/node_channel.h
- @@ -11,7 +11,7 @@
- #include "base/callback.h"
- #include "base/containers/queue.h"
- #include "base/macros.h"
- -#include "base/memory/ref_counted.h"
- +#include "base/memory/ref_counted_delete_on_sequence.h"
- #include "base/process/process_handle.h"
- #include "base/single_thread_task_runner.h"
- #include "base/synchronization/lock.h"
- @@ -21,13 +21,15 @@
- #include "mojo/core/embedder/process_error_callback.h"
- #include "mojo/core/ports/name.h"
- #include "mojo/core/scoped_process_handle.h"
- +#include "mojo/core/system_impl_export.h"
-
- namespace mojo {
- namespace core {
-
- // Wraps a Channel to send and receive Node control messages.
- -class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
- - public Channel::Delegate {
- +class MOJO_SYSTEM_IMPL_EXPORT NodeChannel
- + : public base::RefCountedDeleteOnSequence<NodeChannel>,
- + public Channel::Delegate {
- public:
- class Delegate {
- public:
- @@ -92,8 +94,6 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
- void** data,
- size_t* num_data_bytes);
-
- - Channel* channel() const { return channel_.get(); }
- -
- // Start receiving messages.
- void Start();
-
- @@ -155,7 +155,8 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
- #endif
-
- private:
- - friend class base::RefCountedThreadSafe<NodeChannel>;
- + friend class base::RefCountedDeleteOnSequence<NodeChannel>;
- + friend class base::DeleteHelper<NodeChannel>;
-
- using PendingMessageQueue = base::queue<Channel::MessagePtr>;
- using PendingRelayMessageQueue =
- @@ -181,13 +182,12 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>,
- void WriteChannelMessage(Channel::MessagePtr message);
-
- Delegate* const delegate_;
- - const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
- const ProcessErrorCallback process_error_callback_;
-
- base::Lock channel_lock_;
- - scoped_refptr<Channel> channel_;
- + scoped_refptr<Channel> channel_ GUARDED_BY(channel_lock_);
-
- - // Must only be accessed from |io_task_runner_|'s thread.
- + // Must only be accessed from the owning task runner's thread.
- ports::NodeName remote_node_name_;
-
- base::Lock remote_process_handle_lock_;
- diff --git a/mojo/core/node_channel_fuzzer.cc b/mojo/core/node_channel_fuzzer.cc
- index 99047c000dbe6be90f1d28b4b6817e608f9853a6..54fe757e0dec8aa138af96aa1a0afe596563c26c 100644
- --- a/mojo/core/node_channel_fuzzer.cc
- +++ b/mojo/core/node_channel_fuzzer.cc
- @@ -14,6 +14,7 @@
- #include "mojo/core/connection_params.h"
- #include "mojo/core/entrypoints.h"
- #include "mojo/core/node_channel.h" // nogncheck
- +#include "mojo/core/test/mock_node_channel_delegate.h"
- #include "mojo/public/cpp/platform/platform_channel.h"
-
- #if defined(OS_WIN)
- @@ -24,60 +25,6 @@ using mojo::core::Channel;
- using mojo::core::ConnectionParams;
- using mojo::core::ports::NodeName;
-
- -// Implementation of NodeChannel::Delegate which does nothing. All of the
- -// interesting NodeChannel control message message parsing is done by
- -// NodeChannel by the time any of the delegate methods are invoked, so there's
- -// no need for this to do any work.
- -class FakeNodeChannelDelegate : public mojo::core::NodeChannel::Delegate {
- - public:
- - FakeNodeChannelDelegate() = default;
- - ~FakeNodeChannelDelegate() override = default;
- -
- - void OnAcceptInvitee(const NodeName& from_node,
- - const NodeName& inviter_name,
- - const NodeName& token) override {}
- - void OnAcceptInvitation(const NodeName& from_node,
- - const NodeName& token,
- - const NodeName& invitee_name) override {}
- - void OnAddBrokerClient(const NodeName& from_node,
- - const NodeName& client_name,
- - base::ProcessHandle process_handle) override {}
- - void OnBrokerClientAdded(const NodeName& from_node,
- - const NodeName& client_name,
- - mojo::PlatformHandle broker_channel) override {}
- - void OnAcceptBrokerClient(const NodeName& from_node,
- - const NodeName& broker_name,
- - mojo::PlatformHandle broker_channel) override {}
- - void OnEventMessage(const NodeName& from_node,
- - Channel::MessagePtr message) override {}
- - void OnRequestPortMerge(
- - const NodeName& from_node,
- - const mojo::core::ports::PortName& connector_port_name,
- - const std::string& token) override {}
- - void OnRequestIntroduction(const NodeName& from_node,
- - const NodeName& name) override {}
- - void OnIntroduce(const NodeName& from_node,
- - const NodeName& name,
- - mojo::PlatformHandle channel_handle) override {}
- - void OnBroadcast(const NodeName& from_node,
- - Channel::MessagePtr message) override {}
- -#if defined(OS_WIN)
- - void OnRelayEventMessage(const NodeName& from_node,
- - base::ProcessHandle from_process,
- - const NodeName& destination,
- - Channel::MessagePtr message) override {}
- - void OnEventMessageFromRelay(const NodeName& from_node,
- - const NodeName& source_node,
- - Channel::MessagePtr message) override {}
- -#endif
- - void OnAcceptPeer(const NodeName& from_node,
- - const NodeName& token,
- - const NodeName& peer_name,
- - const mojo::core::ports::PortName& port_name) override {}
- - void OnChannelError(const NodeName& node,
- - mojo::core::NodeChannel* channel) override {}
- -};
- -
- // A fake delegate for the sending Channel endpoint. The sending Channel is not
- // being fuzzed and won't receive any interesting messages, so this doesn't need
- // to do anything.
- @@ -109,7 +56,7 @@ extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) {
- // used to carry messages between processes.
- mojo::PlatformChannel channel;
-
- - FakeNodeChannelDelegate receiver_delegate;
- + mojo::core::MockNodeChannelDelegate receiver_delegate;
- auto receiver = mojo::core::NodeChannel::Create(
- &receiver_delegate, ConnectionParams(channel.TakeLocalEndpoint()),
- Channel::HandlePolicy::kRejectHandles,
- diff --git a/mojo/core/node_channel_unittest.cc b/mojo/core/node_channel_unittest.cc
- new file mode 100644
- index 0000000000000000000000000000000000000000..13c46f13fea6342316534a7a843debbf7586108d
- --- /dev/null
- +++ b/mojo/core/node_channel_unittest.cc
- @@ -0,0 +1,72 @@
- +// Copyright 2020 The Chromium Authors. All rights reserved.
- +// Use of this source code is governed by a BSD-style license that can be
- +// found in the LICENSE file.
- +
- +#include "mojo/core/node_channel.h"
- +
- +#include "base/bind_helpers.h"
- +#include "base/memory/scoped_refptr.h"
- +#include "base/message_loop/message_pump_type.h"
- +#include "base/test/task_environment.h"
- +#include "base/threading/thread.h"
- +#include "mojo/core/embedder/embedder.h"
- +#include "mojo/core/test/mock_node_channel_delegate.h"
- +#include "mojo/public/cpp/platform/platform_channel.h"
- +#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
- +#include "testing/gtest/include/gtest/gtest.h"
- +
- +namespace mojo {
- +namespace core {
- +namespace {
- +
- +using NodeChannelTest = testing::Test;
- +using ports::NodeName;
- +
- +scoped_refptr<NodeChannel> CreateNodeChannel(NodeChannel::Delegate* delegate,
- + PlatformChannelEndpoint endpoint) {
- + return NodeChannel::Create(delegate, ConnectionParams(std::move(endpoint)),
- + Channel::HandlePolicy::kAcceptHandles,
- + GetIOTaskRunner(), base::NullCallback());
- +}
- +
- +TEST_F(NodeChannelTest, DestructionIsSafe) {
- + // Regression test for https://crbug.com/1081874.
- + base::test::TaskEnvironment task_environment;
- +
- + PlatformChannel channel;
- + MockNodeChannelDelegate local_delegate;
- + auto local_channel =
- + CreateNodeChannel(&local_delegate, channel.TakeLocalEndpoint());
- + local_channel->Start();
- + MockNodeChannelDelegate remote_delegate;
- + auto remote_channel =
- + CreateNodeChannel(&remote_delegate, channel.TakeRemoteEndpoint());
- + remote_channel->Start();
- +
- + // Verify end-to-end operation
- + const NodeName kRemoteNodeName{123, 456};
- + const NodeName kToken{987, 654};
- + base::RunLoop loop;
- + EXPECT_CALL(local_delegate,
- + OnAcceptInvitee(ports::kInvalidNodeName, kRemoteNodeName, kToken))
- + .WillRepeatedly([&] { loop.Quit(); });
- + remote_channel->AcceptInvitee(kRemoteNodeName, kToken);
- + loop.Run();
- +
- + // Now send another message to the local endpoint but tear it down
- + // immediately. This will race with the message being received on the IO
- + // thread, and although the corresponding delegate call may or may not
- + // dispatch as a result, the race should still be memory-safe.
- + remote_channel->AcceptInvitee(kRemoteNodeName, kToken);
- +
- + base::RunLoop error_loop;
- + EXPECT_CALL(remote_delegate, OnChannelError).WillOnce([&] {
- + error_loop.Quit();
- + });
- + local_channel.reset();
- + error_loop.Run();
- +}
- +
- +} // namespace
- +} // namespace core
- +} // namespace mojo
- diff --git a/mojo/core/test/BUILD.gn b/mojo/core/test/BUILD.gn
- index 1abadfc503d5176d2af1dcecfde153f17488546d..9429c61853059e10ca2430ad79ec8d9a39d90906 100644
- --- a/mojo/core/test/BUILD.gn
- +++ b/mojo/core/test/BUILD.gn
- @@ -7,6 +7,8 @@ import("//third_party/protobuf/proto_library.gni")
- static_library("test_support") {
- testonly = true
- sources = [
- + "mock_node_channel_delegate.cc",
- + "mock_node_channel_delegate.h",
- "mojo_test_base.cc",
- "mojo_test_base.h",
- "test_utils.h",
- @@ -27,8 +29,10 @@ static_library("test_support") {
- public_deps = [
- "//base",
- "//base/test:test_support",
- + "//mojo/core:embedder_internal",
- "//mojo/core/embedder",
- "//mojo/public/cpp/system",
- + "//testing/gmock",
- "//testing/gtest",
- ]
- }
- diff --git a/mojo/core/test/mock_node_channel_delegate.cc b/mojo/core/test/mock_node_channel_delegate.cc
- new file mode 100644
- index 0000000000000000000000000000000000000000..d257c3e1dc03857f91e94807328a0dc176f332f4
- --- /dev/null
- +++ b/mojo/core/test/mock_node_channel_delegate.cc
- @@ -0,0 +1,15 @@
- +// Copyright 2020 The Chromium Authors. All rights reserved.
- +// Use of this source code is governed by a BSD-style license that can be
- +// found in the LICENSE file.
- +
- +#include "mojo/core/test/mock_node_channel_delegate.h"
- +
- +namespace mojo {
- +namespace core {
- +
- +MockNodeChannelDelegate::MockNodeChannelDelegate() = default;
- +
- +MockNodeChannelDelegate::~MockNodeChannelDelegate() = default;
- +
- +} // namespace core
- +} // namespace mojo
- diff --git a/mojo/core/test/mock_node_channel_delegate.h b/mojo/core/test/mock_node_channel_delegate.h
- new file mode 100644
- index 0000000000000000000000000000000000000000..06ca96857b9472682d577a802c68a56a7af0aacd
- --- /dev/null
- +++ b/mojo/core/test/mock_node_channel_delegate.h
- @@ -0,0 +1,114 @@
- +// Copyright 2020 The Chromium Authors. All rights reserved.
- +// Use of this source code is governed by a BSD-style license that can be
- +// found in the LICENSE file.
- +
- +#ifndef MOJO_CORE_TEST_MOCK_NODE_CHANNEL_DELEGATE_H_
- +#define MOJO_CORE_TEST_MOCK_NODE_CHANNEL_DELEGATE_H_
- +
- +#include "build/build_config.h"
- +#include "mojo/core/node_channel.h"
- +#include "testing/gmock/include/gmock/gmock.h"
- +
- +namespace mojo {
- +namespace core {
- +
- +// A NodeChannel Delegate implementation which can be used by NodeChannel unit
- +// tests and fuzzers.
- +class MockNodeChannelDelegate
- + : public testing::NiceMock<NodeChannel::Delegate> {
- + public:
- + using NodeName = ports::NodeName;
- + using PortName = ports::PortName;
- +
- + MockNodeChannelDelegate();
- + MockNodeChannelDelegate(const MockNodeChannelDelegate&) = delete;
- + MockNodeChannelDelegate& operator=(const MockNodeChannelDelegate&) = delete;
- + ~MockNodeChannelDelegate() override;
- +
- + // testing::NiceMock<NodeChannel::Delegate> implementation:
- + MOCK_METHOD(void,
- + OnAcceptInvitee,
- + (const NodeName& from_node,
- + const NodeName& inviter_name,
- + const NodeName& token),
- + (override));
- + MOCK_METHOD(void,
- + OnAcceptInvitation,
- + (const NodeName& from_node,
- + const NodeName& token,
- + const NodeName& invitee_name),
- + (override));
- + MOCK_METHOD(void,
- + OnAddBrokerClient,
- + (const NodeName& from_node,
- + const NodeName& client_name,
- + base::ProcessHandle process_handle),
- + (override));
- + MOCK_METHOD(void,
- + OnBrokerClientAdded,
- + (const NodeName& from_node,
- + const NodeName& client_name,
- + PlatformHandle broker_channel),
- + (override));
- + MOCK_METHOD(void,
- + OnAcceptBrokerClient,
- + (const NodeName& from_node,
- + const NodeName& broker_name,
- + PlatformHandle broker_channel),
- + (override));
- + MOCK_METHOD(void,
- + OnEventMessage,
- + (const NodeName& from_node, Channel::MessagePtr message),
- + (override));
- + MOCK_METHOD(void,
- + OnRequestPortMerge,
- + (const NodeName& from_node,
- + const PortName& connector_port_name,
- + const std::string& token),
- + (override));
- + MOCK_METHOD(void,
- + OnRequestIntroduction,
- + (const NodeName& from_node, const NodeName& name),
- + (override));
- + MOCK_METHOD(void,
- + OnIntroduce,
- + (const NodeName& from_node,
- + const NodeName& name,
- + PlatformHandle channel_handle),
- + (override));
- + MOCK_METHOD(void,
- + OnBroadcast,
- + (const NodeName& from_node, Channel::MessagePtr message),
- + (override));
- +#if defined(OS_WIN)
- + MOCK_METHOD(void,
- + OnRelayEventMessage,
- + (const NodeName& from_node,
- + base::ProcessHandle from_process,
- + const NodeName& destination,
- + Channel::MessagePtr message),
- + (override));
- + MOCK_METHOD(void,
- + OnEventMessageFromRelay,
- + (const NodeName& from_node,
- + const NodeName& source_node,
- + Channel::MessagePtr message),
- + (override));
- +#endif
- + MOCK_METHOD(void,
- + OnAcceptPeer,
- + (const NodeName& from_node,
- + const NodeName& token,
- + const NodeName& peer_name,
- + const PortName& port_name),
- + (override));
- + MOCK_METHOD(void,
- + OnChannelError,
- + (const NodeName& node, NodeChannel* channel),
- + (override));
- +};
- +
- +} // namespace core
- +} // namespace mojo
- +
- +#endif // MOJO_CORE_TEST_MOCK_NODE_CHANNEL_DELEGATE_H_
|