| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "mojo/proxy/portal_proxy.h" |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/memory/platform_shared_memory_region.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/notreached.h" |
| #include "mojo/core/ipcz_driver/object.h" |
| #include "mojo/core/ipcz_driver/shared_buffer.h" |
| #include "mojo/core/ipcz_driver/wrapped_platform_handle.h" |
| #include "mojo/proxy/node_proxy.h" |
| #include "mojo/public/c/system/buffer.h" |
| #include "mojo/public/c/system/platform_handle.h" |
| #include "mojo/public/c/system/trap.h" |
| #include "mojo/public/c/system/types.h" |
| #include "mojo/public/cpp/platform/platform_handle.h" |
| #include "mojo/public/cpp/system/message_pipe.h" |
| #include "mojo/public/cpp/system/platform_handle.h" |
| #include "mojo/public/cpp/system/trap.h" |
| #include "third_party/ipcz/include/ipcz/ipcz.h" |
| |
| namespace mojo_proxy { |
| |
| using mojo::core::ScopedIpczHandle; |
| |
| PortalProxy::PortalProxy(const raw_ref<const IpczAPI> ipcz, |
| NodeProxy& node_proxy, |
| ScopedIpczHandle portal, |
| mojo::ScopedMessagePipeHandle pipe) |
| : ipcz_(ipcz), |
| node_proxy_(node_proxy), |
| portal_(std::move(portal)), |
| pipe_(std::move(pipe)) { |
| CHECK_EQ(mojo::CreateTrap(&OnMojoPipeActivity, &pipe_trap_), MOJO_RESULT_OK); |
| const MojoResult add_trigger_result = MojoAddTrigger( |
| pipe_trap_->value(), pipe_->value(), MOJO_HANDLE_SIGNAL_READABLE, |
| MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED, trap_context(), nullptr); |
| CHECK_EQ(add_trigger_result, MOJO_RESULT_OK); |
| } |
| |
| PortalProxy::~PortalProxy() = default; |
| |
| void PortalProxy::Start() { |
| CHECK(!disconnected_); |
| CHECK(!watching_portal_); |
| CHECK(!watching_pipe_); |
| |
| Flush(); |
| } |
| |
| void PortalProxy::Flush() { |
| CHECK(!in_flush_); |
| in_flush_ = true; |
| while (!disconnected_ && (!watching_portal_ || !watching_pipe_)) { |
| if (!disconnected_ && !watching_portal_) { |
| FlushAndWatchPortal(); |
| } |
| if (!disconnected_ && !watching_pipe_) { |
| FlushAndWatchPipe(); |
| } |
| } |
| in_flush_ = false; |
| |
| if (disconnected_) { |
| // Deletes `this`. |
| Die(); |
| } |
| } |
| |
| void PortalProxy::FlushAndWatchPortal() { |
| for (;;) { |
| std::vector<uint8_t> data; |
| size_t num_bytes = 0; |
| std::vector<IpczHandle> handles; |
| size_t num_handles = 0; |
| IpczResult result = |
| ipcz_->Get(portal_.get(), IPCZ_NO_FLAGS, nullptr, nullptr, &num_bytes, |
| nullptr, &num_handles, nullptr); |
| if (result == IPCZ_RESULT_OK) { |
| mojo::WriteMessageRaw(pipe_.get(), nullptr, 0, nullptr, 0, |
| MOJO_WRITE_MESSAGE_FLAG_NONE); |
| continue; |
| } |
| |
| if (result == IPCZ_RESULT_UNAVAILABLE) { |
| break; |
| } |
| |
| if (result != IPCZ_RESULT_RESOURCE_EXHAUSTED) { |
| disconnected_ = true; |
| return; |
| } |
| |
| data.resize(num_bytes); |
| handles.resize(num_handles); |
| result = ipcz_->Get(portal_.get(), IPCZ_NO_FLAGS, nullptr, data.data(), |
| &num_bytes, handles.data(), &num_handles, nullptr); |
| CHECK_EQ(result, IPCZ_RESULT_OK); |
| |
| std::vector<MojoHandle> mojo_handles; |
| mojo_handles.reserve(handles.size()); |
| for (IpczHandle handle : handles) { |
| mojo_handles.push_back(TranslateIpczToMojoHandle(ScopedIpczHandle(handle)) |
| .release() |
| .value()); |
| } |
| |
| mojo::WriteMessageRaw(pipe_.get(), data.data(), data.size(), |
| mojo_handles.data(), mojo_handles.size(), |
| MOJO_WRITE_MESSAGE_FLAG_NONE); |
| } |
| |
| IpczTrapConditionFlags flags; |
| const IpczTrapConditions trap_conditions{ |
| .size = sizeof(trap_conditions), |
| .flags = IPCZ_TRAP_ABOVE_MIN_LOCAL_PARCELS | IPCZ_TRAP_DEAD, |
| .min_local_parcels = 0, |
| }; |
| const IpczResult trap_result = |
| ipcz_->Trap(portal_.get(), &trap_conditions, &OnIpczPortalActivity, |
| trap_context(), IPCZ_NO_FLAGS, nullptr, &flags, nullptr); |
| if (trap_result == IPCZ_RESULT_OK) { |
| watching_portal_ = true; |
| return; |
| } |
| |
| CHECK_EQ(trap_result, IPCZ_RESULT_FAILED_PRECONDITION); |
| if (flags & IPCZ_TRAP_DEAD) { |
| disconnected_ = true; |
| } |
| } |
| |
| void PortalProxy::FlushAndWatchPipe() { |
| for (;;) { |
| std::vector<uint8_t> data; |
| std::vector<mojo::ScopedHandle> handles; |
| const MojoResult result = mojo::ReadMessageRaw(pipe_.get(), &data, &handles, |
| MOJO_READ_MESSAGE_FLAG_NONE); |
| if (result == MOJO_RESULT_SHOULD_WAIT) { |
| break; |
| } |
| |
| if (result != MOJO_RESULT_OK) { |
| disconnected_ = true; |
| return; |
| } |
| |
| std::vector<IpczHandle> ipcz_handles; |
| ipcz_handles.reserve(handles.size()); |
| for (mojo::ScopedHandle& handle : handles) { |
| ipcz_handles.push_back( |
| TranslateMojoToIpczHandle(std::move(handle)).release()); |
| } |
| |
| const IpczResult put_result = ipcz_->Put( |
| portal_.get(), data.size() ? data.data() : nullptr, data.size(), |
| ipcz_handles.size() ? ipcz_handles.data() : nullptr, |
| ipcz_handles.size(), IPCZ_NO_FLAGS, nullptr); |
| if (put_result != IPCZ_RESULT_OK) { |
| disconnected_ = true; |
| return; |
| } |
| } |
| |
| uint32_t num_events = 1; |
| MojoTrapEvent event{.struct_size = sizeof(event)}; |
| const MojoResult result = |
| MojoArmTrap(pipe_trap_->value(), nullptr, &num_events, &event); |
| if (result == MOJO_RESULT_OK) { |
| watching_pipe_ = true; |
| return; |
| } |
| |
| CHECK_EQ(result, MOJO_RESULT_FAILED_PRECONDITION); |
| CHECK_EQ(num_events, 1u); |
| if (event.result == MOJO_RESULT_FAILED_PRECONDITION) { |
| disconnected_ = true; |
| } |
| } |
| |
| ScopedIpczHandle PortalProxy::TranslateMojoToIpczHandle( |
| mojo::ScopedHandle handle) { |
| // We don't know what kind of handle is in `handle`, but we can find out. |
| // First try to unwrap it as a generic platform handle. |
| MojoPlatformHandle platform_handle; |
| platform_handle.struct_size = sizeof(platform_handle); |
| const MojoResult unwrap_result = |
| MojoUnwrapPlatformHandle(handle->value(), nullptr, &platform_handle); |
| if (unwrap_result == MOJO_RESULT_OK) { |
| std::ignore = handle.release(); |
| // Platform handles in ipcz are transmitted as boxed driver objects. |
| return ScopedIpczHandle( |
| mojo::core::ipcz_driver::WrappedPlatformHandle::MakeBoxed( |
| mojo::PlatformHandle::FromMojoPlatformHandle(&platform_handle))); |
| } |
| |
| // We can non-destructively probe for a shared buffer handle by calling |
| // MojoGetBufferInfo(). |
| MojoSharedBufferInfo info = {.struct_size = sizeof(info)}; |
| const MojoResult info_result = |
| MojoGetBufferInfo(handle->value(), nullptr, &info); |
| if (info_result == MOJO_RESULT_OK) { |
| auto region = |
| mojo::UnwrapPlatformSharedMemoryRegion(mojo::ScopedSharedBufferHandle{ |
| mojo::SharedBufferHandle{handle.release().value()}}); |
| return ScopedIpczHandle( |
| mojo::core::ipcz_driver::SharedBuffer::MakeBoxed(std::move(region))); |
| } |
| |
| // Since data pipe handles are never used on Chrome OS IPC boundaries outside |
| // the browser, we can assume that any other handles are message pipes. |
| IpczHandle portal_to_proxy, portal_to_host; |
| ipcz_->OpenPortals(mojo::core::GetIpczNode(), IPCZ_NO_FLAGS, nullptr, |
| &portal_to_proxy, &portal_to_host); |
| node_proxy_->AddPortalProxy( |
| ScopedIpczHandle{portal_to_proxy}, |
| mojo::ScopedMessagePipeHandle{ |
| mojo::MessagePipeHandle{handle.release().value()}}); |
| return ScopedIpczHandle(portal_to_host); |
| } |
| |
| mojo::ScopedHandle PortalProxy::TranslateIpczToMojoHandle( |
| ScopedIpczHandle handle) { |
| // Attempt a QueryPortalStatus() call. If this succeeds, we have a portal. |
| IpczPortalStatus status = {.size = sizeof(status)}; |
| const IpczResult query_result = |
| ipcz_->QueryPortalStatus(handle.get(), IPCZ_NO_FLAGS, nullptr, &status); |
| if (query_result == IPCZ_RESULT_OK) { |
| // Create a new Mojo message pipe to proxy through. One end is bound to a |
| // new PortalProxy with the input `handle`; the other is returned to be |
| // forwarded to the legacy client. |
| mojo::MessagePipe pipe; |
| node_proxy_->AddPortalProxy(std::move(handle), std::move(pipe.handle0)); |
| return mojo::ScopedHandle{mojo::Handle{pipe.handle1.release().value()}}; |
| } |
| |
| // Otherwise assume it's a boxed driver object. If it's not, something has |
| // gone horribly wrong, so just crash. |
| auto* object = mojo::core::ipcz_driver::ObjectBase::FromBox(handle.get()); |
| CHECK(object); |
| switch (object->type()) { |
| case mojo::core::ipcz_driver::ObjectBase::Type::kWrappedPlatformHandle: { |
| auto wrapped_handle = |
| mojo::core::ipcz_driver::WrappedPlatformHandle::Unbox( |
| handle.release()); |
| return mojo::WrapPlatformHandle(wrapped_handle->TakeHandle()); |
| } |
| |
| case mojo::core::ipcz_driver::ObjectBase::Type::kSharedBuffer: { |
| auto buffer = |
| mojo::core::ipcz_driver::SharedBuffer::Unbox(handle.release()); |
| auto mojo_buffer = |
| mojo::WrapPlatformSharedMemoryRegion(std::move(buffer->region())); |
| return mojo::ScopedHandle{mojo::Handle{mojo_buffer.release().value()}}; |
| } |
| |
| default: |
| // No other types of driver objects are supported by the proxy. |
| NOTREACHED(); |
| } |
| } |
| |
| void PortalProxy::HandlePortalActivity(IpczTrapConditionFlags flags) { |
| if (flags & IPCZ_TRAP_REMOVED) { |
| // Proxy is being shut down. Do nothing. |
| return; |
| } |
| |
| watching_portal_ = false; |
| if (flags & IPCZ_TRAP_DEAD) { |
| disconnected_ = true; |
| if (!in_flush_) { |
| // Deletes `this`. |
| Die(); |
| return; |
| } |
| } else if (!in_flush_) { |
| Flush(); |
| } |
| } |
| |
| void PortalProxy::HandlePipeActivity(MojoResult result) { |
| if (result == MOJO_RESULT_CANCELLED) { |
| // Proxy is being shut down. Do nothing. |
| return; |
| } |
| |
| watching_pipe_ = false; |
| if (result == MOJO_RESULT_FAILED_PRECONDITION) { |
| disconnected_ = true; |
| if (!in_flush_) { |
| // Deletes `this`. |
| Die(); |
| return; |
| } |
| } else if (!in_flush_) { |
| Flush(); |
| } |
| } |
| |
| void PortalProxy::Die() { |
| CHECK(!in_flush_); |
| CHECK(disconnected_); |
| |
| // Deletes `this`. |
| node_proxy_->RemovePortalProxy(this); |
| } |
| |
| } // namespace mojo_proxy |