blob: 87e6bbfa4752b8374f5d56da045318c1f74bb10c [file] [log] [blame]
Avi Drissmand387f0922022-09-14 20:51:311// Copyright 2017 The Chromium Authors
Wez9736ba782017-08-10 16:35:362// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Ken Rockotdba46db2018-07-04 18:41:045#include "mojo/core/channel.h"
Wez9736ba782017-08-10 16:35:366
Wezab912ed2019-02-19 22:05:177#include <lib/fdio/fd.h>
Wez5c3c6f152018-06-09 18:24:028#include <lib/fdio/limits.h>
Wez157707d62018-07-10 22:48:479#include <lib/zx/channel.h>
10#include <lib/zx/handle.h>
Scott Grahamfe0e9f462017-09-18 21:25:0411#include <zircon/processargs.h>
12#include <zircon/status.h>
13#include <zircon/syscalls.h>
Avi Drissmanebac4302022-01-10 21:56:5814
Wez9736ba782017-08-10 16:35:3615#include <algorithm>
Peter Boström84d239f2021-05-13 23:02:4416#include <memory>
Avi Drissmanebac4302022-01-10 21:56:5817#include <tuple>
Wez9736ba782017-08-10 16:35:3618
Brett Wilson55ff1475e2017-09-26 00:28:4819#include "base/containers/circular_deque.h"
Wezda440922017-08-22 22:54:3120#include "base/files/scoped_file.h"
Wez157707d62018-07-10 22:48:4721#include "base/fuchsia/fuchsia_logging.h"
Avi Drissmand70f89a2023-01-11 23:52:5522#include "base/functional/bind.h"
Wez9736ba782017-08-10 16:35:3623#include "base/location.h"
Wez9736ba782017-08-10 16:35:3624#include "base/memory/ref_counted.h"
Gabriel Charette19d2ae62018-04-10 14:10:5825#include "base/message_loop/message_pump_for_io.h"
Wez9736ba782017-08-10 16:35:3626#include "base/synchronization/lock.h"
Carlos Caballerob25fe8472020-07-17 10:27:1727#include "base/task/current_thread.h"
Sean Mahere672a662023-01-09 21:42:2828#include "base/task/single_thread_task_runner.h"
Patrick Monette643cdf62021-10-15 19:13:4229#include "base/task/task_runner.h"
Ken Rockotdba46db2018-07-04 18:41:0430#include "mojo/core/platform_handle_in_transit.h"
Wez9736ba782017-08-10 16:35:3631
32namespace mojo {
Ken Rockotdba46db2018-07-04 18:41:0433namespace core {
Wez9736ba782017-08-10 16:35:3634
35namespace {
36
37const size_t kMaxBatchReadCapacity = 256 * 1024;
38
Wezab912ed2019-02-19 22:05:1739bool UnwrapFdioHandle(PlatformHandleInTransit handle,
40 PlatformHandleInTransit* out_handle,
41 Channel::Message::HandleInfoEntry* info) {
Ken Rockot043152da62018-06-29 03:22:1642 DCHECK(handle.handle().is_valid());
Wezda440922017-08-22 22:54:3143
Ken Rockot043152da62018-06-29 03:22:1644 if (!handle.handle().is_valid_fd()) {
Wezab912ed2019-02-19 22:05:1745 info->is_file_descriptor = false;
46 *out_handle = std::move(handle);
Wezda440922017-08-22 22:54:3147 return true;
48 }
Wezcf4155ae2017-08-16 21:01:1449
Wez6961d3b2021-10-08 12:22:4350 // Try to transfer the FD if possible, otherwise take a clone of it.
51 // This allows non-dup()d FDs to be efficiently unwrapped, while dup()d FDs
52 // have a new handle attached to the same underlying resource created.
Wezab912ed2019-02-19 22:05:1753 zx::handle result;
Wez6961d3b2021-10-08 12:22:4354 zx_status_t status = fdio_fd_transfer_or_clone(
55 handle.TakeHandle().ReleaseFD(), result.reset_and_get_address());
Wezab912ed2019-02-19 22:05:1756 if (status != ZX_OK) {
Wez6961d3b2021-10-08 12:22:4357 ZX_DLOG(ERROR, status) << "fdio_fd_transfer_or_clone";
Wez160e4352017-10-19 16:53:1158 return false;
59 }
Weze905d432017-10-19 03:54:4460
Wezab912ed2019-02-19 22:05:1761 info->is_file_descriptor = true;
62 *out_handle = PlatformHandleInTransit(PlatformHandle(std::move(result)));
Wezda440922017-08-22 22:54:3163 return true;
64}
65
Wezab912ed2019-02-19 22:05:1766PlatformHandle WrapFdioHandle(zx::handle handle,
67 Channel::Message::HandleInfoEntry info) {
68 if (!info.is_file_descriptor)
69 return PlatformHandle(std::move(handle));
Wezda440922017-08-22 22:54:3170
Wezab912ed2019-02-19 22:05:1771 base::ScopedFD out_fd;
72 zx_status_t status =
73 fdio_fd_create(handle.release(), base::ScopedFD::Receiver(out_fd).get());
74 if (status != ZX_OK) {
75 ZX_DLOG(ERROR, status) << "fdio_fd_create";
76 return PlatformHandle();
Wezda440922017-08-22 22:54:3177 }
Wezab912ed2019-02-19 22:05:1778 return PlatformHandle(std::move(out_fd));
Wezcf4155ae2017-08-16 21:01:1479}
80
Wez9736ba782017-08-10 16:35:3681// A view over a Channel::Message object. The write queue uses these since
82// large messages may need to be sent in chunks.
83class MessageView {
84 public:
85 // Owns |message|. |offset| indexes the first unsent byte in the message.
86 MessageView(Channel::MessagePtr message, size_t offset)
87 : message_(std::move(message)),
88 offset_(offset),
Robert Sesek5cd63eeb2019-08-02 21:18:0789 handles_(message_->TakeHandles()) {
Wez9736ba782017-08-10 16:35:3690 DCHECK_GT(message_->data_num_bytes(), offset_);
91 }
92
Anand K Mistry3357f672020-06-18 00:10:0093 MessageView(MessageView&& other) = default;
Wez9736ba782017-08-10 16:35:3694
Anand K Mistry3357f672020-06-18 00:10:0095 MessageView& operator=(MessageView&& other) = default;
Wez9736ba782017-08-10 16:35:3696
Peter Boströma8176282021-09-23 22:33:5697 MessageView(const MessageView&) = delete;
98 MessageView& operator=(const MessageView&) = delete;
99
Anand K Mistry48d93df2020-06-18 00:04:22100 ~MessageView() = default;
Wez9736ba782017-08-10 16:35:36101
102 const void* data() const {
103 return static_cast<const char*>(message_->data()) + offset_;
104 }
105
106 size_t data_num_bytes() const { return message_->data_num_bytes() - offset_; }
107
108 size_t data_offset() const { return offset_; }
109 void advance_data_offset(size_t num_bytes) {
110 DCHECK_GT(message_->data_num_bytes(), offset_ + num_bytes);
111 offset_ += num_bytes;
112 }
113
Ken Rockot36d44a572022-08-15 23:19:57114 std::vector<PlatformHandleInTransit> TakeHandles(bool unwrap_fds) {
115 if (handles_.empty() || !unwrap_fds)
116 return std::move(handles_);
Wezda440922017-08-22 22:54:31117
Wez6d83a0b2017-11-28 01:17:15118 // We can only pass Fuchsia handles via IPC, so unwrap any FDIO file-
Wezab912ed2019-02-19 22:05:17119 // descriptors in |handles_| into the underlying handles, with metadata in
120 // the extra header to note which belong to FDIO.
Wez6d83a0b2017-11-28 01:17:15121 auto* handles_info = reinterpret_cast<Channel::Message::HandleInfoEntry*>(
122 message_->mutable_extra_header());
123 memset(handles_info, 0, message_->extra_header_size());
Wezda440922017-08-22 22:54:31124
Wezab912ed2019-02-19 22:05:17125 // Since file descriptors unwrap to a single handle, we can unwrap in-place
126 // in the |handles_| vector.
127 for (size_t i = 0; i < handles_.size(); i++) {
128 if (!UnwrapFdioHandle(std::move(handles_[i]), &handles_[i],
129 &handles_info[i])) {
Ken Rockot043152da62018-06-29 03:22:16130 return std::vector<PlatformHandleInTransit>();
Wezab912ed2019-02-19 22:05:17131 }
Wezcf4155ae2017-08-16 21:01:14132 }
133 return std::move(handles_);
Wez9736ba782017-08-10 16:35:36134 }
135
136 private:
137 Channel::MessagePtr message_;
138 size_t offset_;
Ken Rockot043152da62018-06-29 03:22:16139 std::vector<PlatformHandleInTransit> handles_;
Wez9736ba782017-08-10 16:35:36140};
141
142class ChannelFuchsia : public Channel,
Carlos Caballerob25fe8472020-07-17 10:27:17143 public base::CurrentThread::DestructionObserver,
Gabriel Charette19d2ae62018-04-10 14:10:58144 public base::MessagePumpForIO::ZxHandleWatcher {
Wez9736ba782017-08-10 16:35:36145 public:
146 ChannelFuchsia(Delegate* delegate,
147 ConnectionParams connection_params,
Ken Rockotfada58122018-12-11 16:49:54148 HandlePolicy handle_policy,
Gabriel Charettee926fc12019-12-16 19:00:02149 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
Ken Rockotfada58122018-12-11 16:49:54150 : Channel(delegate, handle_policy),
Wez9736ba782017-08-10 16:35:36151 self_(this),
Wez157707d62018-07-10 22:48:47152 handle_(
153 connection_params.TakeEndpoint().TakePlatformHandle().TakeHandle()),
Wez9736ba782017-08-10 16:35:36154 io_task_runner_(io_task_runner) {
155 CHECK(handle_.is_valid());
156 }
157
Peter Boströmfeef05a2021-10-05 21:35:08158 ChannelFuchsia(const ChannelFuchsia&) = delete;
159 ChannelFuchsia& operator=(const ChannelFuchsia&) = delete;
160
Wez9736ba782017-08-10 16:35:36161 void Start() override {
162 if (io_task_runner_->RunsTasksInCurrentSequence()) {
163 StartOnIOThread();
164 } else {
165 io_task_runner_->PostTask(
Wez3e64a8a2018-03-13 05:36:06166 FROM_HERE, base::BindOnce(&ChannelFuchsia::StartOnIOThread, this));
Wez9736ba782017-08-10 16:35:36167 }
168 }
169
170 void ShutDownImpl() override {
171 // Always shut down asynchronously when called through the public interface.
172 io_task_runner_->PostTask(
Wez3e64a8a2018-03-13 05:36:06173 FROM_HERE, base::BindOnce(&ChannelFuchsia::ShutDownOnIOThread, this));
Wez9736ba782017-08-10 16:35:36174 }
175
176 void Write(MessagePtr message) override {
177 bool write_error = false;
178 {
179 base::AutoLock lock(write_lock_);
180 if (reject_writes_)
181 return;
182 if (!WriteNoLock(MessageView(std::move(message), 0)))
183 reject_writes_ = write_error = true;
184 }
185 if (write_error) {
Wez3e64a8a2018-03-13 05:36:06186 // Do not synchronously invoke OnWriteError(). Write() may have been
187 // called by the delegate and we don't want to re-enter it.
Wez9736ba782017-08-10 16:35:36188 io_task_runner_->PostTask(
Wez3e64a8a2018-03-13 05:36:06189 FROM_HERE, base::BindOnce(&ChannelFuchsia::OnWriteError, this,
190 Error::kDisconnected));
Wez9736ba782017-08-10 16:35:36191 }
192 }
193
194 void LeakHandle() override {
195 DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
196 leak_handle_ = true;
197 }
198
Ken Rockot043152da62018-06-29 03:22:16199 bool GetReadPlatformHandles(const void* payload,
200 size_t payload_size,
201 size_t num_handles,
202 const void* extra_header,
203 size_t extra_header_size,
204 std::vector<PlatformHandle>* handles,
205 bool* deferred) override {
Wez9736ba782017-08-10 16:35:36206 DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
207 if (num_handles > std::numeric_limits<uint16_t>::max())
208 return false;
Wezcf4155ae2017-08-16 21:01:14209
210 // Locate the handle info and verify there is enough of it.
211 if (!extra_header)
212 return false;
213 const auto* handles_info =
214 reinterpret_cast<const Channel::Message::HandleInfoEntry*>(
215 extra_header);
216 size_t handles_info_size = sizeof(handles_info[0]) * num_handles;
217 if (handles_info_size > extra_header_size)
218 return false;
219
Wezda440922017-08-22 22:54:31220 // If there are too few handles then we're not ready yet, so return true
221 // indicating things are OK, but leave |handles| empty.
Wezab912ed2019-02-19 22:05:17222 if (incoming_handles_.size() < num_handles)
Wez9736ba782017-08-10 16:35:36223 return true;
Wez9736ba782017-08-10 16:35:36224
Wez6d83a0b2017-11-28 01:17:15225 handles->reserve(num_handles);
Wez9736ba782017-08-10 16:35:36226 for (size_t i = 0; i < num_handles; ++i) {
Wezab912ed2019-02-19 22:05:17227 handles->emplace_back(WrapFdioHandle(std::move(incoming_handles_.front()),
228 handles_info[i]));
229 DCHECK(handles->back().is_valid());
230 incoming_handles_.pop_front();
Wez9736ba782017-08-10 16:35:36231 }
Wez9736ba782017-08-10 16:35:36232 return true;
233 }
234
Ken Rockot36d44a572022-08-15 23:19:57235 bool GetReadPlatformHandlesForIpcz(
236 size_t num_handles,
237 std::vector<PlatformHandle>& handles) override {
238 if (incoming_handles_.size() < num_handles) {
239 return true;
240 }
241
242 DCHECK(handles.empty());
243 handles.reserve(num_handles);
244 for (size_t i = 0; i < num_handles; ++i) {
245 handles.emplace_back(std::move(incoming_handles_.front()));
246 incoming_handles_.pop_front();
247 }
248 return true;
249 }
250
Wez9736ba782017-08-10 16:35:36251 private:
Ken Rockotdba46db2018-07-04 18:41:04252 ~ChannelFuchsia() override { DCHECK(!read_watch_); }
Wez9736ba782017-08-10 16:35:36253
254 void StartOnIOThread() {
255 DCHECK(!read_watch_);
256
Carlos Caballerob25fe8472020-07-17 10:27:17257 base::CurrentThread::Get()->AddDestructionObserver(this);
Wez9736ba782017-08-10 16:35:36258
Peter Boström84d239f2021-05-13 23:02:44259 read_watch_ =
260 std::make_unique<base::MessagePumpForIO::ZxHandleWatchController>(
261 FROM_HERE);
Carlos Caballerob25fe8472020-07-17 10:27:17262 base::CurrentIOThread::Get()->WatchZxHandle(
Wez157707d62018-07-10 22:48:47263 handle_.get(), true /* persistent */,
Scott Grahamfe0e9f462017-09-18 21:25:04264 ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, read_watch_.get(), this);
Wez9736ba782017-08-10 16:35:36265 }
266
267 void ShutDownOnIOThread() {
Carlos Caballerob25fe8472020-07-17 10:27:17268 base::CurrentThread::Get()->RemoveDestructionObserver(this);
Wez9736ba782017-08-10 16:35:36269
270 read_watch_.reset();
271 if (leak_handle_)
Avi Drissmanebac4302022-01-10 21:56:58272 std::ignore = handle_.release();
Wez9736ba782017-08-10 16:35:36273 handle_.reset();
274
275 // May destroy the |this| if it was the last reference.
276 self_ = nullptr;
277 }
278
Carlos Caballerob25fe8472020-07-17 10:27:17279 // base::CurrentThread::DestructionObserver:
Wez9736ba782017-08-10 16:35:36280 void WillDestroyCurrentMessageLoop() override {
281 DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
282 if (self_)
283 ShutDownOnIOThread();
284 }
285
Gabriel Charette19d2ae62018-04-10 14:10:58286 // base::MessagePumpForIO::ZxHandleWatcher:
Scott Grahamfe0e9f462017-09-18 21:25:04287 void OnZxHandleSignalled(zx_handle_t handle, zx_signals_t signals) override {
Wez9736ba782017-08-10 16:35:36288 DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
Wez157707d62018-07-10 22:48:47289 CHECK_EQ(handle, handle_.get());
Scott Grahamfe0e9f462017-09-18 21:25:04290 DCHECK((ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED) & signals);
Wez9736ba782017-08-10 16:35:36291
Scott Grahamfe0e9f462017-09-18 21:25:04292 // We always try to read message(s), even if ZX_CHANNEL_PEER_CLOSED, since
Wez9736ba782017-08-10 16:35:36293 // the peer may have closed while messages were still unread, in the pipe.
294
295 bool validation_error = false;
296 bool read_error = false;
297 size_t next_read_size = 0;
298 size_t buffer_capacity = 0;
299 size_t total_bytes_read = 0;
300 do {
301 buffer_capacity = next_read_size;
302 char* buffer = GetReadBuffer(&buffer_capacity);
303 DCHECK_GT(buffer_capacity, 0u);
304
305 uint32_t bytes_read = 0;
306 uint32_t handles_read = 0;
Scott Grahamfe0e9f462017-09-18 21:25:04307 zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES] = {};
Wez9736ba782017-08-10 16:35:36308
Wez157707d62018-07-10 22:48:47309 zx_status_t read_result =
Daniel Cheng37aad832022-02-27 01:05:30310 handle_.read(0, buffer, handles, buffer_capacity, std::size(handles),
Wez64f1c0c2019-04-17 00:38:25311 &bytes_read, &handles_read);
Scott Grahamfe0e9f462017-09-18 21:25:04312 if (read_result == ZX_OK) {
Wez9736ba782017-08-10 16:35:36313 for (size_t i = 0; i < handles_read; ++i) {
Wez157707d62018-07-10 22:48:47314 incoming_handles_.emplace_back(handles[i]);
Wez9736ba782017-08-10 16:35:36315 }
316 total_bytes_read += bytes_read;
317 if (!OnReadComplete(bytes_read, &next_read_size)) {
318 read_error = true;
319 validation_error = true;
320 break;
321 }
Scott Grahamfe0e9f462017-09-18 21:25:04322 } else if (read_result == ZX_ERR_BUFFER_TOO_SMALL) {
Daniel Cheng37aad832022-02-27 01:05:30323 DCHECK_LE(handles_read, std::size(handles));
Wez9736ba782017-08-10 16:35:36324 next_read_size = bytes_read;
Scott Grahamfe0e9f462017-09-18 21:25:04325 } else if (read_result == ZX_ERR_SHOULD_WAIT) {
Wez9736ba782017-08-10 16:35:36326 break;
327 } else {
Wez157707d62018-07-10 22:48:47328 ZX_DLOG_IF(ERROR, read_result != ZX_ERR_PEER_CLOSED, read_result)
329 << "zx_channel_read";
Wez9736ba782017-08-10 16:35:36330 read_error = true;
331 break;
332 }
333 } while (total_bytes_read < kMaxBatchReadCapacity && next_read_size > 0);
334 if (read_error) {
335 // Stop receiving read notifications.
336 read_watch_.reset();
337 if (validation_error)
338 OnError(Error::kReceivedMalformedData);
339 else
340 OnError(Error::kDisconnected);
341 }
342 }
343
344 // Attempts to write a message directly to the channel. If the full message
345 // cannot be written, it's queued and a wait is initiated to write the message
346 // ASAP on the I/O thread.
347 bool WriteNoLock(MessageView message_view) {
348 uint32_t write_bytes = 0;
349 do {
350 message_view.advance_data_offset(write_bytes);
351
Ken Rockot043152da62018-06-29 03:22:16352 std::vector<PlatformHandleInTransit> outgoing_handles =
Ken Rockot36d44a572022-08-15 23:19:57353 message_view.TakeHandles(/*unwrap_fds=*/!is_for_ipcz());
Scott Grahamfe0e9f462017-09-18 21:25:04354 zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES] = {};
Wez6d83a0b2017-11-28 01:17:15355 size_t handles_count = outgoing_handles.size();
Wez9736ba782017-08-10 16:35:36356
Daniel Cheng37aad832022-02-27 01:05:30357 DCHECK_LE(handles_count, std::size(handles));
Wez6d83a0b2017-11-28 01:17:15358 for (size_t i = 0; i < handles_count; ++i) {
Ken Rockot043152da62018-06-29 03:22:16359 DCHECK(outgoing_handles[i].handle().is_valid());
360 handles[i] = outgoing_handles[i].handle().GetHandle().get();
Wez9736ba782017-08-10 16:35:36361 }
362
363 write_bytes = std::min(message_view.data_num_bytes(),
Scott Grahamfe0e9f462017-09-18 21:25:04364 static_cast<size_t>(ZX_CHANNEL_MAX_MSG_BYTES));
Wez157707d62018-07-10 22:48:47365 zx_status_t result = handle_.write(0, message_view.data(), write_bytes,
366 handles, handles_count);
Wez676b5fae2018-06-20 22:34:08367 // zx_channel_write() consumes |handles| whether or not it succeeds, so
368 // release() our copies now, to avoid them being double-closed.
369 for (auto& outgoing_handle : outgoing_handles)
Ken Rockot043152da62018-06-29 03:22:16370 outgoing_handle.CompleteTransit();
Wez676b5fae2018-06-20 22:34:08371
Scott Grahamfe0e9f462017-09-18 21:25:04372 if (result != ZX_OK) {
Alison Gale923a33e2024-04-22 23:34:28373 // TODO(crbug.com/42050611): Handle ZX_ERR_SHOULD_WAIT flow-control
Fabrice de Gans-Riberi69f65b42020-09-30 23:37:14374 // errors, once the platform starts generating them.
Wez157707d62018-07-10 22:48:47375 ZX_DLOG_IF(ERROR, result != ZX_ERR_PEER_CLOSED, result)
376 << "WriteNoLock(zx_channel_write)";
Wez9736ba782017-08-10 16:35:36377 return false;
378 }
379
Wez9736ba782017-08-10 16:35:36380 } while (write_bytes < message_view.data_num_bytes());
381
382 return true;
383 }
384
Wez3e64a8a2018-03-13 05:36:06385 void OnWriteError(Error error) {
386 DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
387 DCHECK(reject_writes_);
388
389 if (error == Error::kDisconnected) {
390 // If we can't write because the pipe is disconnected then continue
391 // reading to fetch any in-flight messages, relying on end-of-stream to
392 // signal the actual disconnection.
393 if (read_watch_) {
Alison Gale923a33e2024-04-22 23:34:28394 // TODO(crbug.com/42050611): When we add flow-control for writes, we
395 // also need to reset the write-watcher here.
Wez3e64a8a2018-03-13 05:36:06396 return;
397 }
398 }
399
400 OnError(error);
401 }
402
Wez9736ba782017-08-10 16:35:36403 // Keeps the Channel alive at least until explicit shutdown on the IO thread.
404 scoped_refptr<Channel> self_;
405
Wez157707d62018-07-10 22:48:47406 zx::channel handle_;
Gabriel Charettee926fc12019-12-16 19:00:02407 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
Wez9736ba782017-08-10 16:35:36408
409 // These members are only used on the IO thread.
Gabriel Charette19d2ae62018-04-10 14:10:58410 std::unique_ptr<base::MessagePumpForIO::ZxHandleWatchController> read_watch_;
Wez157707d62018-07-10 22:48:47411 base::circular_deque<zx::handle> incoming_handles_;
Wez9736ba782017-08-10 16:35:36412 bool leak_handle_ = false;
413
414 base::Lock write_lock_;
415 bool reject_writes_ = false;
Wez9736ba782017-08-10 16:35:36416};
417
418} // namespace
419
420// static
421scoped_refptr<Channel> Channel::Create(
422 Delegate* delegate,
423 ConnectionParams connection_params,
Ken Rockotfada58122018-12-11 16:49:54424 HandlePolicy handle_policy,
Gabriel Charettee926fc12019-12-16 19:00:02425 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) {
Wez9736ba782017-08-10 16:35:36426 return new ChannelFuchsia(delegate, std::move(connection_params),
Ken Rockotfada58122018-12-11 16:49:54427 handle_policy, std::move(io_task_runner));
Wez9736ba782017-08-10 16:35:36428}
429
Ken Rockotdba46db2018-07-04 18:41:04430} // namespace core
Wez9736ba782017-08-10 16:35:36431} // namespace mojo