// Copyright 2015 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/edk/system/message_pipe_dispatcher.h" #include <limits> #include <memory> #include "base/logging.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "mojo/edk/embedder/embedder_internal.h" #include "mojo/edk/system/core.h" #include "mojo/edk/system/message_for_transit.h" #include "mojo/edk/system/node_controller.h" #include "mojo/edk/system/ports/message_filter.h" #include "mojo/edk/system/ports_message.h" #include "mojo/edk/system/request_context.h" namespace mojo { namespace edk { namespace { using DispatcherHeader = MessageForTransit::DispatcherHeader; using MessageHeader = MessageForTransit::MessageHeader; #pragma pack(push, 1) struct SerializedState { uint64_t pipe_id; int8_t endpoint; char padding[7]; }; static_assert(sizeof(SerializedState) % 8 == 0, "Invalid SerializedState size."); #pragma pack(pop) } // namespace // A PortObserver which forwards to a MessagePipeDispatcher. This owns a // reference to the MPD to ensure it lives as long as the observed port. class MessagePipeDispatcher::PortObserverThunk : public NodeController::PortObserver { public: explicit PortObserverThunk(scoped_refptr<MessagePipeDispatcher> dispatcher) : dispatcher_(dispatcher) {} private: ~PortObserverThunk() override {} // NodeController::PortObserver: void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } scoped_refptr<MessagePipeDispatcher> dispatcher_; DISALLOW_COPY_AND_ASSIGN(PortObserverThunk); }; // A MessageFilter used by ReadMessage to determine whether a message should // actually be consumed yet. class ReadMessageFilter : public ports::MessageFilter { public: // Creates a new ReadMessageFilter which captures and potentially modifies // various (unowned) local state within MessagePipeDispatcher::ReadMessage. ReadMessageFilter(bool read_any_size, bool may_discard, uint32_t* num_bytes, uint32_t* num_handles, bool* no_space, bool* invalid_message) : read_any_size_(read_any_size), may_discard_(may_discard), num_bytes_(num_bytes), num_handles_(num_handles), no_space_(no_space), invalid_message_(invalid_message) {} ~ReadMessageFilter() override {} // ports::MessageFilter: bool Match(const ports::Message& m) override { const PortsMessage& message = static_cast<const PortsMessage&>(m); if (message.num_payload_bytes() < sizeof(MessageHeader)) { *invalid_message_ = true; return true; } const MessageHeader* header = static_cast<const MessageHeader*>(message.payload_bytes()); if (header->header_size > message.num_payload_bytes()) { *invalid_message_ = true; return true; } uint32_t bytes_to_read = 0; uint32_t bytes_available = static_cast<uint32_t>(message.num_payload_bytes()) - header->header_size; if (num_bytes_) { bytes_to_read = std::min(*num_bytes_, bytes_available); *num_bytes_ = bytes_available; } uint32_t handles_to_read = 0; uint32_t handles_available = header->num_dispatchers; if (num_handles_) { handles_to_read = std::min(*num_handles_, handles_available); *num_handles_ = handles_available; } if (handles_to_read < handles_available || (!read_any_size_ && bytes_to_read < bytes_available)) { *no_space_ = true; return may_discard_; } return true; } private: const bool read_any_size_; const bool may_discard_; uint32_t* const num_bytes_; uint32_t* const num_handles_; bool* const no_space_; bool* const invalid_message_; DISALLOW_COPY_AND_ASSIGN(ReadMessageFilter); }; #if DCHECK_IS_ON() // A MessageFilter which never matches a message. Used to peek at the size of // the next available message on a port, for debug logging only. class PeekSizeMessageFilter : public ports::MessageFilter { public: PeekSizeMessageFilter() {} ~PeekSizeMessageFilter() override {} // ports::MessageFilter: bool Match(const ports::Message& message) override { message_size_ = message.num_payload_bytes(); return false; } size_t message_size() const { return message_size_; } private: size_t message_size_ = 0; DISALLOW_COPY_AND_ASSIGN(PeekSizeMessageFilter); }; #endif // DCHECK_IS_ON() MessagePipeDispatcher::MessagePipeDispatcher(NodeController* node_controller, const ports::PortRef& port, uint64_t pipe_id, int endpoint) : node_controller_(node_controller), port_(port), pipe_id_(pipe_id), endpoint_(endpoint) { DVLOG(2) << "Creating new MessagePipeDispatcher for port " << port.name() << " [pipe_id=" << pipe_id << "; endpoint=" << endpoint << "]"; node_controller_->SetPortObserver( port_, make_scoped_refptr(new PortObserverThunk(this))); } bool MessagePipeDispatcher::Fuse(MessagePipeDispatcher* other) { node_controller_->SetPortObserver(port_, nullptr); node_controller_->SetPortObserver(other->port_, nullptr); ports::PortRef port0; { base::AutoLock lock(signal_lock_); port0 = port_; port_closed_.Set(true); awakables_.CancelAll(); } ports::PortRef port1; { base::AutoLock lock(other->signal_lock_); port1 = other->port_; other->port_closed_.Set(true); other->awakables_.CancelAll(); } // Both ports are always closed by this call. int rv = node_controller_->MergeLocalPorts(port0, port1); return rv == ports::OK; } Dispatcher::Type MessagePipeDispatcher::GetType() const { return Type::MESSAGE_PIPE; } MojoResult MessagePipeDispatcher::Close() { base::AutoLock lock(signal_lock_); DVLOG(2) << "Closing message pipe " << pipe_id_ << " endpoint " << endpoint_ << " [port=" << port_.name() << "]"; return CloseNoLock(); } MojoResult MessagePipeDispatcher::Watch(MojoHandleSignals signals, const Watcher::WatchCallback& callback, uintptr_t context) { base::AutoLock lock(signal_lock_); if (port_closed_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; return awakables_.AddWatcher( signals, callback, context, GetHandleSignalsStateNoLock()); } MojoResult MessagePipeDispatcher::CancelWatch(uintptr_t context) { base::AutoLock lock(signal_lock_); if (port_closed_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; return awakables_.RemoveWatcher(context); } MojoResult MessagePipeDispatcher::WriteMessage( std::unique_ptr<MessageForTransit> message, MojoWriteMessageFlags flags) { if (port_closed_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; size_t num_bytes = message->num_bytes(); int rv = node_controller_->SendMessage(port_, message->TakePortsMessage()); DVLOG(4) << "Sent message on pipe " << pipe_id_ << " endpoint " << endpoint_ << " [port=" << port_.name() << "; rv=" << rv << "; num_bytes=" << num_bytes << "]"; if (rv != ports::OK) { if (rv == ports::ERROR_PORT_UNKNOWN || rv == ports::ERROR_PORT_STATE_UNEXPECTED || rv == ports::ERROR_PORT_CANNOT_SEND_PEER) { return MOJO_RESULT_INVALID_ARGUMENT; } else if (rv == ports::ERROR_PORT_PEER_CLOSED) { return MOJO_RESULT_FAILED_PRECONDITION; } NOTREACHED(); return MOJO_RESULT_UNKNOWN; } return MOJO_RESULT_OK; } MojoResult MessagePipeDispatcher::ReadMessage( std::unique_ptr<MessageForTransit>* message, uint32_t* num_bytes, MojoHandle* handles, uint32_t* num_handles, MojoReadMessageFlags flags, bool read_any_size) { // We can't read from a port that's closed or in transit! if (port_closed_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; bool no_space = false; bool may_discard = flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD; bool invalid_message = false; // Grab a message if the provided handles buffer is large enough. If the input // |num_bytes| is provided and |read_any_size| is false, we also ensure // that it specifies a size at least as large as the next available payload. // // If |read_any_size| is true, the input value of |*num_bytes| is ignored. // This flag exists to support both new and old API behavior. ports::ScopedMessage ports_message; ReadMessageFilter filter(read_any_size, may_discard, num_bytes, num_handles, &no_space, &invalid_message); int rv = node_controller_->node()->GetMessage(port_, &ports_message, &filter); if (invalid_message) return MOJO_RESULT_UNKNOWN; if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) { if (rv == ports::ERROR_PORT_UNKNOWN || rv == ports::ERROR_PORT_STATE_UNEXPECTED) return MOJO_RESULT_INVALID_ARGUMENT; NOTREACHED(); return MOJO_RESULT_UNKNOWN; // TODO: Add a better error code here? } if (no_space) { // |*num_handles| (and/or |*num_bytes| if |read_any_size| is false) wasn't // sufficient to hold this message's data. The message will still be in // queue unless MOJO_READ_MESSAGE_FLAG_MAY_DISCARD was set. return MOJO_RESULT_RESOURCE_EXHAUSTED; } if (!ports_message) { // No message was available in queue. if (rv == ports::OK) return MOJO_RESULT_SHOULD_WAIT; // Peer is closed and there are no more messages to read. DCHECK_EQ(rv, ports::ERROR_PORT_PEER_CLOSED); return MOJO_RESULT_FAILED_PRECONDITION; } // Alright! We have a message and the caller has provided sufficient storage // in which to receive it. std::unique_ptr<PortsMessage> msg( static_cast<PortsMessage*>(ports_message.release())); const MessageHeader* header = static_cast<const MessageHeader*>(msg->payload_bytes()); const DispatcherHeader* dispatcher_headers = reinterpret_cast<const DispatcherHeader*>(header + 1); if (header->num_dispatchers > std::numeric_limits<uint16_t>::max()) return MOJO_RESULT_UNKNOWN; // Deserialize dispatchers. if (header->num_dispatchers > 0) { CHECK(handles); std::vector<DispatcherInTransit> dispatchers(header->num_dispatchers); size_t data_payload_index = sizeof(MessageHeader) + header->num_dispatchers * sizeof(DispatcherHeader); if (data_payload_index > header->header_size) return MOJO_RESULT_UNKNOWN; const char* dispatcher_data = reinterpret_cast<const char*>( dispatcher_headers + header->num_dispatchers); size_t port_index = 0; size_t platform_handle_index = 0; ScopedPlatformHandleVectorPtr msg_handles = msg->TakeHandles(); const size_t num_msg_handles = msg_handles ? msg_handles->size() : 0; for (size_t i = 0; i < header->num_dispatchers; ++i) { const DispatcherHeader& dh = dispatcher_headers[i]; Type type = static_cast<Type>(dh.type); size_t next_payload_index = data_payload_index + dh.num_bytes; if (msg->num_payload_bytes() < next_payload_index || next_payload_index < data_payload_index) { return MOJO_RESULT_UNKNOWN; } size_t next_port_index = port_index + dh.num_ports; if (msg->num_ports() < next_port_index || next_port_index < port_index) return MOJO_RESULT_UNKNOWN; size_t next_platform_handle_index = platform_handle_index + dh.num_platform_handles; if (num_msg_handles < next_platform_handle_index || next_platform_handle_index < platform_handle_index) { return MOJO_RESULT_UNKNOWN; } PlatformHandle* out_handles = num_msg_handles ? msg_handles->data() + platform_handle_index : nullptr; dispatchers[i].dispatcher = Dispatcher::Deserialize( type, dispatcher_data, dh.num_bytes, msg->ports() + port_index, dh.num_ports, out_handles, dh.num_platform_handles); if (!dispatchers[i].dispatcher) return MOJO_RESULT_UNKNOWN; dispatcher_data += dh.num_bytes; data_payload_index = next_payload_index; port_index = next_port_index; platform_handle_index = next_platform_handle_index; } if (!node_controller_->core()->AddDispatchersFromTransit(dispatchers, handles)) return MOJO_RESULT_UNKNOWN; } CHECK(msg); *message = MessageForTransit::WrapPortsMessage(std::move(msg)); return MOJO_RESULT_OK; } HandleSignalsState MessagePipeDispatcher::GetHandleSignalsState() const { base::AutoLock lock(signal_lock_); return GetHandleSignalsStateNoLock(); } MojoResult MessagePipeDispatcher::AddAwakable( Awakable* awakable, MojoHandleSignals signals, uintptr_t context, HandleSignalsState* signals_state) { base::AutoLock lock(signal_lock_); if (port_closed_ || in_transit_) { if (signals_state) *signals_state = HandleSignalsState(); return MOJO_RESULT_INVALID_ARGUMENT; } HandleSignalsState state = GetHandleSignalsStateNoLock(); DVLOG(2) << "Getting signal state for pipe " << pipe_id_ << " endpoint " << endpoint_ << " [awakable=" << awakable << "; port=" << port_.name() << "; signals=" << signals << "; satisfied=" << state.satisfied_signals << "; satisfiable=" << state.satisfiable_signals << "]"; if (state.satisfies(signals)) { if (signals_state) *signals_state = state; DVLOG(2) << "Signals already set for " << port_.name(); return MOJO_RESULT_ALREADY_EXISTS; } if (!state.can_satisfy(signals)) { if (signals_state) *signals_state = state; DVLOG(2) << "Signals impossible to satisfy for " << port_.name(); return MOJO_RESULT_FAILED_PRECONDITION; } DVLOG(2) << "Adding awakable to pipe " << pipe_id_ << " endpoint " << endpoint_ << " [awakable=" << awakable << "; port=" << port_.name() << "; signals=" << signals << "]"; awakables_.Add(awakable, signals, context); return MOJO_RESULT_OK; } void MessagePipeDispatcher::RemoveAwakable(Awakable* awakable, HandleSignalsState* signals_state) { base::AutoLock lock(signal_lock_); if (port_closed_ || in_transit_) { if (signals_state) *signals_state = HandleSignalsState(); } else if (signals_state) { *signals_state = GetHandleSignalsStateNoLock(); } DVLOG(2) << "Removing awakable from pipe " << pipe_id_ << " endpoint " << endpoint_ << " [awakable=" << awakable << "; port=" << port_.name() << "]"; awakables_.Remove(awakable); } void MessagePipeDispatcher::StartSerialize(uint32_t* num_bytes, uint32_t* num_ports, uint32_t* num_handles) { *num_bytes = static_cast<uint32_t>(sizeof(SerializedState)); *num_ports = 1; *num_handles = 0; } bool MessagePipeDispatcher::EndSerialize(void* destination, ports::PortName* ports, PlatformHandle* handles) { SerializedState* state = static_cast<SerializedState*>(destination); state->pipe_id = pipe_id_; state->endpoint = static_cast<int8_t>(endpoint_); memset(state->padding, 0, sizeof(state->padding)); ports[0] = port_.name(); return true; } bool MessagePipeDispatcher::BeginTransit() { base::AutoLock lock(signal_lock_); if (in_transit_ || port_closed_) return false; in_transit_.Set(true); return in_transit_; } void MessagePipeDispatcher::CompleteTransitAndClose() { node_controller_->SetPortObserver(port_, nullptr); base::AutoLock lock(signal_lock_); port_transferred_ = true; in_transit_.Set(false); CloseNoLock(); } void MessagePipeDispatcher::CancelTransit() { base::AutoLock lock(signal_lock_); in_transit_.Set(false); // Something may have happened while we were waiting for potential transit. awakables_.AwakeForStateChange(GetHandleSignalsStateNoLock()); } // static scoped_refptr<Dispatcher> MessagePipeDispatcher::Deserialize( const void* data, size_t num_bytes, const ports::PortName* ports, size_t num_ports, PlatformHandle* handles, size_t num_handles) { if (num_ports != 1 || num_handles || num_bytes != sizeof(SerializedState)) return nullptr; const SerializedState* state = static_cast<const SerializedState*>(data); ports::PortRef port; CHECK_EQ( ports::OK, internal::g_core->GetNodeController()->node()->GetPort(ports[0], &port)); return new MessagePipeDispatcher(internal::g_core->GetNodeController(), port, state->pipe_id, state->endpoint); } MessagePipeDispatcher::~MessagePipeDispatcher() { DCHECK(port_closed_ && !in_transit_); } MojoResult MessagePipeDispatcher::CloseNoLock() { signal_lock_.AssertAcquired(); if (port_closed_ || in_transit_) return MOJO_RESULT_INVALID_ARGUMENT; port_closed_.Set(true); awakables_.CancelAll(); if (!port_transferred_) { base::AutoUnlock unlock(signal_lock_); node_controller_->ClosePort(port_); } return MOJO_RESULT_OK; } HandleSignalsState MessagePipeDispatcher::GetHandleSignalsStateNoLock() const { HandleSignalsState rv; ports::PortStatus port_status; if (node_controller_->node()->GetStatus(port_, &port_status) != ports::OK) { CHECK(in_transit_ || port_transferred_ || port_closed_); return HandleSignalsState(); } if (port_status.has_messages) { rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_READABLE; rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; } if (port_status.receiving_messages) rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; if (!port_status.peer_closed) { rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; } else { rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; } rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; return rv; } void MessagePipeDispatcher::OnPortStatusChanged() { DCHECK(RequestContext::current()); base::AutoLock lock(signal_lock_); // We stop observing our port as soon as it's transferred, but this can race // with events which are raised right before that happens. This is fine to // ignore. if (port_transferred_) return; #if DCHECK_IS_ON() ports::PortStatus port_status; if (node_controller_->node()->GetStatus(port_, &port_status) == ports::OK) { if (port_status.has_messages) { ports::ScopedMessage unused; PeekSizeMessageFilter filter; node_controller_->node()->GetMessage(port_, &unused, &filter); DVLOG(4) << "New message detected on message pipe " << pipe_id_ << " endpoint " << endpoint_ << " [port=" << port_.name() << "; size=" << filter.message_size() << "]"; } if (port_status.peer_closed) { DVLOG(2) << "Peer closure detected on message pipe " << pipe_id_ << " endpoint " << endpoint_ << " [port=" << port_.name() << "]"; } } #endif awakables_.AwakeForStateChange(GetHandleSignalsStateNoLock()); } } // namespace edk } // namespace mojo