| // 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 |