OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ipc/ipc_channel_nacl.h" | 5 #include "ipc/ipc_channel_nacl.h" |
6 | 6 |
7 #include <errno.h> | 7 #include <errno.h> |
8 #include <stddef.h> | 8 #include <stddef.h> |
9 #include <sys/types.h> | 9 #include <sys/types.h> |
10 | 10 |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
55 } | 55 } |
56 DCHECK(bytes_read); | 56 DCHECK(bytes_read); |
57 // Resize the buffers down to the number of bytes and fds we actually read. | 57 // Resize the buffers down to the number of bytes and fds we actually read. |
58 contents->data.resize(bytes_read); | 58 contents->data.resize(bytes_read); |
59 contents->fds.resize(msg.desc_length); | 59 contents->fds.resize(msg.desc_length); |
60 return true; | 60 return true; |
61 } | 61 } |
62 | 62 |
63 } // namespace | 63 } // namespace |
64 | 64 |
65 class Channel::ChannelImpl::ReaderThreadRunner | 65 class ChannelNacl::ReaderThreadRunner |
66 : public base::DelegateSimpleThread::Delegate { | 66 : public base::DelegateSimpleThread::Delegate { |
67 public: | 67 public: |
68 // |pipe|: A file descriptor from which we will read using imc_recvmsg. | 68 // |pipe|: A file descriptor from which we will read using imc_recvmsg. |
69 // |data_read_callback|: A callback we invoke (on the main thread) when we | 69 // |data_read_callback|: A callback we invoke (on the main thread) when we |
70 // have read data. | 70 // have read data. |
71 // |failure_callback|: A callback we invoke when we have a failure reading | 71 // |failure_callback|: A callback we invoke when we have a failure reading |
72 // from |pipe|. | 72 // from |pipe|. |
73 // |main_message_loop|: A proxy for the main thread, where we will invoke the | 73 // |main_message_loop|: A proxy for the main thread, where we will invoke the |
74 // above callbacks. | 74 // above callbacks. |
75 ReaderThreadRunner( | 75 ReaderThreadRunner( |
76 int pipe, | 76 int pipe, |
77 base::Callback<void (scoped_ptr<MessageContents>)> data_read_callback, | 77 base::Callback<void (scoped_ptr<MessageContents>)> data_read_callback, |
78 base::Callback<void ()> failure_callback, | 78 base::Callback<void ()> failure_callback, |
79 scoped_refptr<base::MessageLoopProxy> main_message_loop); | 79 scoped_refptr<base::MessageLoopProxy> main_message_loop); |
80 | 80 |
81 // DelegateSimpleThread implementation. Reads data from the pipe in a loop | 81 // DelegateSimpleThread implementation. Reads data from the pipe in a loop |
82 // until either we are told to quit or a read fails. | 82 // until either we are told to quit or a read fails. |
83 virtual void Run() OVERRIDE; | 83 virtual void Run() OVERRIDE; |
84 | 84 |
85 private: | 85 private: |
86 int pipe_; | 86 int pipe_; |
87 base::Callback<void (scoped_ptr<MessageContents>)> data_read_callback_; | 87 base::Callback<void (scoped_ptr<MessageContents>)> data_read_callback_; |
88 base::Callback<void ()> failure_callback_; | 88 base::Callback<void ()> failure_callback_; |
89 scoped_refptr<base::MessageLoopProxy> main_message_loop_; | 89 scoped_refptr<base::MessageLoopProxy> main_message_loop_; |
90 | 90 |
91 DISALLOW_COPY_AND_ASSIGN(ReaderThreadRunner); | 91 DISALLOW_COPY_AND_ASSIGN(ReaderThreadRunner); |
92 }; | 92 }; |
93 | 93 |
94 Channel::ChannelImpl::ReaderThreadRunner::ReaderThreadRunner( | 94 ChannelNacl::ReaderThreadRunner::ReaderThreadRunner( |
95 int pipe, | 95 int pipe, |
96 base::Callback<void (scoped_ptr<MessageContents>)> data_read_callback, | 96 base::Callback<void (scoped_ptr<MessageContents>)> data_read_callback, |
97 base::Callback<void ()> failure_callback, | 97 base::Callback<void ()> failure_callback, |
98 scoped_refptr<base::MessageLoopProxy> main_message_loop) | 98 scoped_refptr<base::MessageLoopProxy> main_message_loop) |
99 : pipe_(pipe), | 99 : pipe_(pipe), |
100 data_read_callback_(data_read_callback), | 100 data_read_callback_(data_read_callback), |
101 failure_callback_(failure_callback), | 101 failure_callback_(failure_callback), |
102 main_message_loop_(main_message_loop) { | 102 main_message_loop_(main_message_loop) { |
103 } | 103 } |
104 | 104 |
105 void Channel::ChannelImpl::ReaderThreadRunner::Run() { | 105 void ChannelNacl::ReaderThreadRunner::Run() { |
106 while (true) { | 106 while (true) { |
107 scoped_ptr<MessageContents> msg_contents(new MessageContents); | 107 scoped_ptr<MessageContents> msg_contents(new MessageContents); |
108 bool success = ReadDataOnReaderThread(pipe_, msg_contents.get()); | 108 bool success = ReadDataOnReaderThread(pipe_, msg_contents.get()); |
109 if (success) { | 109 if (success) { |
110 main_message_loop_->PostTask(FROM_HERE, | 110 main_message_loop_->PostTask(FROM_HERE, |
111 base::Bind(data_read_callback_, base::Passed(&msg_contents))); | 111 base::Bind(data_read_callback_, base::Passed(&msg_contents))); |
112 } else { | 112 } else { |
113 main_message_loop_->PostTask(FROM_HERE, failure_callback_); | 113 main_message_loop_->PostTask(FROM_HERE, failure_callback_); |
114 // Because the read failed, we know we're going to quit. Don't bother | 114 // Because the read failed, we know we're going to quit. Don't bother |
115 // trying to read again. | 115 // trying to read again. |
116 return; | 116 return; |
117 } | 117 } |
118 } | 118 } |
119 } | 119 } |
120 | 120 |
121 Channel::ChannelImpl::ChannelImpl(const IPC::ChannelHandle& channel_handle, | 121 ChannelNacl::ChannelNacl(const IPC::ChannelHandle& channel_handle, |
122 Mode mode, | 122 Mode mode, |
123 Listener* listener) | 123 Listener* listener) |
124 : ChannelReader(listener), | 124 : ChannelReader(listener), |
125 mode_(mode), | 125 mode_(mode), |
126 waiting_connect_(true), | 126 waiting_connect_(true), |
127 pipe_(-1), | 127 pipe_(-1), |
128 pipe_name_(channel_handle.name), | 128 pipe_name_(channel_handle.name), |
129 weak_ptr_factory_(this) { | 129 weak_ptr_factory_(this) { |
130 if (!CreatePipe(channel_handle)) { | 130 if (!CreatePipe(channel_handle)) { |
131 // The pipe may have been closed already. | 131 // The pipe may have been closed already. |
132 const char *modestr = (mode_ & MODE_SERVER_FLAG) ? "server" : "client"; | 132 const char *modestr = (mode_ & MODE_SERVER_FLAG) ? "server" : "client"; |
133 LOG(WARNING) << "Unable to create pipe named \"" << channel_handle.name | 133 LOG(WARNING) << "Unable to create pipe named \"" << channel_handle.name |
134 << "\" in " << modestr << " mode"; | 134 << "\" in " << modestr << " mode"; |
135 } | 135 } |
136 } | 136 } |
137 | 137 |
138 Channel::ChannelImpl::~ChannelImpl() { | 138 ChannelNacl::~ChannelNacl() { |
139 Close(); | 139 Close(); |
140 } | 140 } |
141 | 141 |
142 base::ProcessId Channel::ChannelImpl::peer_pid() const { | 142 base::ProcessId ChannelNacl::GetPeerPID() const { |
143 // This shouldn't actually get used in the untrusted side of the proxy, and we | 143 // This shouldn't actually get used in the untrusted side of the proxy, and we |
144 // don't have the real pid anyway. | 144 // don't have the real pid anyway. |
145 return -1; | 145 return -1; |
146 } | 146 } |
147 | 147 |
148 bool Channel::ChannelImpl::Connect() { | 148 bool ChannelNacl::Connect() { |
149 if (pipe_ == -1) { | 149 if (pipe_ == -1) { |
150 DLOG(WARNING) << "Channel creation failed: " << pipe_name_; | 150 DLOG(WARNING) << "Channel creation failed: " << pipe_name_; |
151 return false; | 151 return false; |
152 } | 152 } |
153 | 153 |
154 // Note that Connect is called on the "Channel" thread (i.e., the same thread | 154 // Note that Connect is called on the "Channel" thread (i.e., the same thread |
155 // where Channel::Send will be called, and the same thread that should receive | 155 // where Channel::Send will be called, and the same thread that should receive |
156 // messages). The constructor might be invoked on another thread (see | 156 // messages). The constructor might be invoked on another thread (see |
157 // ChannelProxy for an example of that). Therefore, we must wait until Connect | 157 // ChannelProxy for an example of that). Therefore, we must wait until Connect |
158 // is called to decide which MessageLoopProxy to pass to ReaderThreadRunner. | 158 // is called to decide which MessageLoopProxy to pass to ReaderThreadRunner. |
159 reader_thread_runner_.reset( | 159 reader_thread_runner_.reset( |
160 new ReaderThreadRunner( | 160 new ReaderThreadRunner( |
161 pipe_, | 161 pipe_, |
162 base::Bind(&Channel::ChannelImpl::DidRecvMsg, | 162 base::Bind(&ChannelNacl::DidRecvMsg, |
163 weak_ptr_factory_.GetWeakPtr()), | 163 weak_ptr_factory_.GetWeakPtr()), |
164 base::Bind(&Channel::ChannelImpl::ReadDidFail, | 164 base::Bind(&ChannelNacl::ReadDidFail, |
165 weak_ptr_factory_.GetWeakPtr()), | 165 weak_ptr_factory_.GetWeakPtr()), |
166 base::MessageLoopProxy::current())); | 166 base::MessageLoopProxy::current())); |
167 reader_thread_.reset( | 167 reader_thread_.reset( |
168 new base::DelegateSimpleThread(reader_thread_runner_.get(), | 168 new base::DelegateSimpleThread(reader_thread_runner_.get(), |
169 "ipc_channel_nacl reader thread")); | 169 "ipc_channel_nacl reader thread")); |
170 reader_thread_->Start(); | 170 reader_thread_->Start(); |
171 waiting_connect_ = false; | 171 waiting_connect_ = false; |
172 // If there were any messages queued before connection, send them. | 172 // If there were any messages queued before connection, send them. |
173 ProcessOutgoingMessages(); | 173 ProcessOutgoingMessages(); |
174 base::MessageLoopProxy::current()->PostTask(FROM_HERE, | 174 base::MessageLoopProxy::current()->PostTask(FROM_HERE, |
175 base::Bind(&Channel::ChannelImpl::CallOnChannelConnected, | 175 base::Bind(&ChannelNacl::CallOnChannelConnected, |
176 weak_ptr_factory_.GetWeakPtr())); | 176 weak_ptr_factory_.GetWeakPtr())); |
177 | 177 |
178 return true; | 178 return true; |
179 } | 179 } |
180 | 180 |
181 void Channel::ChannelImpl::Close() { | 181 void ChannelNacl::Close() { |
182 // For now, we assume that at shutdown, the reader thread will be woken with | 182 // For now, we assume that at shutdown, the reader thread will be woken with |
183 // a failure (see NaClIPCAdapter::BlockingRead and CloseChannel). Or... we | 183 // a failure (see NaClIPCAdapter::BlockingRead and CloseChannel). Or... we |
184 // might simply be killed with no chance to clean up anyway :-). | 184 // might simply be killed with no chance to clean up anyway :-). |
185 // If untrusted code tries to close the channel prior to shutdown, it's likely | 185 // If untrusted code tries to close the channel prior to shutdown, it's likely |
186 // to hang. | 186 // to hang. |
187 // TODO(dmichael): Can we do anything smarter here to make sure the reader | 187 // TODO(dmichael): Can we do anything smarter here to make sure the reader |
188 // thread wakes up and quits? | 188 // thread wakes up and quits? |
189 reader_thread_->Join(); | 189 reader_thread_->Join(); |
190 close(pipe_); | 190 close(pipe_); |
191 pipe_ = -1; | 191 pipe_ = -1; |
192 reader_thread_runner_.reset(); | 192 reader_thread_runner_.reset(); |
193 reader_thread_.reset(); | 193 reader_thread_.reset(); |
194 read_queue_.clear(); | 194 read_queue_.clear(); |
195 output_queue_.clear(); | 195 output_queue_.clear(); |
196 } | 196 } |
197 | 197 |
198 bool Channel::ChannelImpl::Send(Message* message) { | 198 bool ChannelNacl::Send(Message* message) { |
199 DVLOG(2) << "sending message @" << message << " on channel @" << this | 199 DVLOG(2) << "sending message @" << message << " on channel @" << this |
200 << " with type " << message->type(); | 200 << " with type " << message->type(); |
201 scoped_ptr<Message> message_ptr(message); | 201 scoped_ptr<Message> message_ptr(message); |
202 | 202 |
203 #ifdef IPC_MESSAGE_LOG_ENABLED | 203 #ifdef IPC_MESSAGE_LOG_ENABLED |
204 Logging::GetInstance()->OnSendMessage(message_ptr.get(), ""); | 204 Logging::GetInstance()->OnSendMessage(message_ptr.get(), ""); |
205 #endif // IPC_MESSAGE_LOG_ENABLED | 205 #endif // IPC_MESSAGE_LOG_ENABLED |
206 | 206 |
207 message->TraceMessageBegin(); | 207 message->TraceMessageBegin(); |
208 output_queue_.push_back(linked_ptr<Message>(message_ptr.release())); | 208 output_queue_.push_back(linked_ptr<Message>(message_ptr.release())); |
209 if (!waiting_connect_) | 209 if (!waiting_connect_) |
210 return ProcessOutgoingMessages(); | 210 return ProcessOutgoingMessages(); |
211 | 211 |
212 return true; | 212 return true; |
213 } | 213 } |
214 | 214 |
215 void Channel::ChannelImpl::DidRecvMsg(scoped_ptr<MessageContents> contents) { | 215 void ChannelNacl::DidRecvMsg(scoped_ptr<MessageContents> contents) { |
216 // Close sets the pipe to -1. It's possible we'll get a buffer sent to us from | 216 // Close sets the pipe to -1. It's possible we'll get a buffer sent to us from |
217 // the reader thread after Close is called. If so, we ignore it. | 217 // the reader thread after Close is called. If so, we ignore it. |
218 if (pipe_ == -1) | 218 if (pipe_ == -1) |
219 return; | 219 return; |
220 | 220 |
221 linked_ptr<std::vector<char> > data(new std::vector<char>); | 221 linked_ptr<std::vector<char> > data(new std::vector<char>); |
222 data->swap(contents->data); | 222 data->swap(contents->data); |
223 read_queue_.push_back(data); | 223 read_queue_.push_back(data); |
224 | 224 |
225 input_fds_.insert(input_fds_.end(), | 225 input_fds_.insert(input_fds_.end(), |
226 contents->fds.begin(), contents->fds.end()); | 226 contents->fds.begin(), contents->fds.end()); |
227 contents->fds.clear(); | 227 contents->fds.clear(); |
228 | 228 |
229 // In POSIX, we would be told when there are bytes to read by implementing | 229 // In POSIX, we would be told when there are bytes to read by implementing |
230 // OnFileCanReadWithoutBlocking in MessageLoopForIO::Watcher. In NaCl, we | 230 // OnFileCanReadWithoutBlocking in MessageLoopForIO::Watcher. In NaCl, we |
231 // instead know at this point because the reader thread posted some data to | 231 // instead know at this point because the reader thread posted some data to |
232 // us. | 232 // us. |
233 ProcessIncomingMessages(); | 233 ProcessIncomingMessages(); |
234 } | 234 } |
235 | 235 |
236 void Channel::ChannelImpl::ReadDidFail() { | 236 void ChannelNacl::ReadDidFail() { |
237 Close(); | 237 Close(); |
238 } | 238 } |
239 | 239 |
240 bool Channel::ChannelImpl::CreatePipe( | 240 bool ChannelNacl::CreatePipe( |
241 const IPC::ChannelHandle& channel_handle) { | 241 const IPC::ChannelHandle& channel_handle) { |
242 DCHECK(pipe_ == -1); | 242 DCHECK(pipe_ == -1); |
243 | 243 |
244 // There's one possible case in NaCl: | 244 // There's one possible case in NaCl: |
245 // 1) It's a channel wrapping a pipe that is given to us. | 245 // 1) It's a channel wrapping a pipe that is given to us. |
246 // We don't support these: | 246 // We don't support these: |
247 // 2) It's for a named channel. | 247 // 2) It's for a named channel. |
248 // 3) It's for a client that we implement ourself. | 248 // 3) It's for a client that we implement ourself. |
249 // 4) It's the initial IPC channel. | 249 // 4) It's the initial IPC channel. |
250 | 250 |
251 if (channel_handle.socket.fd == -1) { | 251 if (channel_handle.socket.fd == -1) { |
252 NOTIMPLEMENTED(); | 252 NOTIMPLEMENTED(); |
253 return false; | 253 return false; |
254 } | 254 } |
255 pipe_ = channel_handle.socket.fd; | 255 pipe_ = channel_handle.socket.fd; |
256 return true; | 256 return true; |
257 } | 257 } |
258 | 258 |
259 bool Channel::ChannelImpl::ProcessOutgoingMessages() { | 259 bool ChannelNacl::ProcessOutgoingMessages() { |
260 DCHECK(!waiting_connect_); // Why are we trying to send messages if there's | 260 DCHECK(!waiting_connect_); // Why are we trying to send messages if there's |
261 // no connection? | 261 // no connection? |
262 if (output_queue_.empty()) | 262 if (output_queue_.empty()) |
263 return true; | 263 return true; |
264 | 264 |
265 if (pipe_ == -1) | 265 if (pipe_ == -1) |
266 return false; | 266 return false; |
267 | 267 |
268 // Write out all the messages. The trusted implementation is guaranteed to not | 268 // Write out all the messages. The trusted implementation is guaranteed to not |
269 // block. See NaClIPCAdapter::Send for the implementation of imc_sendmsg. | 269 // block. See NaClIPCAdapter::Send for the implementation of imc_sendmsg. |
(...skipping 27 matching lines...) Expand all Loading... |
297 msg->file_descriptor_set()->CommitAll(); | 297 msg->file_descriptor_set()->CommitAll(); |
298 } | 298 } |
299 | 299 |
300 // Message sent OK! | 300 // Message sent OK! |
301 DVLOG(2) << "sent message @" << msg.get() << " with type " << msg->type() | 301 DVLOG(2) << "sent message @" << msg.get() << " with type " << msg->type() |
302 << " on fd " << pipe_; | 302 << " on fd " << pipe_; |
303 } | 303 } |
304 return true; | 304 return true; |
305 } | 305 } |
306 | 306 |
307 void Channel::ChannelImpl::CallOnChannelConnected() { | 307 void ChannelNacl::CallOnChannelConnected() { |
308 listener()->OnChannelConnected(peer_pid()); | 308 listener()->OnChannelConnected(GetPeerPID()); |
309 } | 309 } |
310 | 310 |
311 Channel::ChannelImpl::ReadState Channel::ChannelImpl::ReadData( | 311 ChannelNacl::ReadState ChannelNacl::ReadData( |
312 char* buffer, | 312 char* buffer, |
313 int buffer_len, | 313 int buffer_len, |
314 int* bytes_read) { | 314 int* bytes_read) { |
315 *bytes_read = 0; | 315 *bytes_read = 0; |
316 if (pipe_ == -1) | 316 if (pipe_ == -1) |
317 return READ_FAILED; | 317 return READ_FAILED; |
318 if (read_queue_.empty()) | 318 if (read_queue_.empty()) |
319 return READ_PENDING; | 319 return READ_PENDING; |
320 while (!read_queue_.empty() && *bytes_read < buffer_len) { | 320 while (!read_queue_.empty() && *bytes_read < buffer_len) { |
321 linked_ptr<std::vector<char> > vec(read_queue_.front()); | 321 linked_ptr<std::vector<char> > vec(read_queue_.front()); |
(...skipping 10 matching lines...) Expand all Loading... |
332 // the code simple). | 332 // the code simple). |
333 std::copy(vec->begin(), vec->begin() + bytes_to_read, | 333 std::copy(vec->begin(), vec->begin() + bytes_to_read, |
334 buffer + *bytes_read); | 334 buffer + *bytes_read); |
335 vec->erase(vec->begin(), vec->begin() + bytes_to_read); | 335 vec->erase(vec->begin(), vec->begin() + bytes_to_read); |
336 *bytes_read += bytes_to_read; | 336 *bytes_read += bytes_to_read; |
337 } | 337 } |
338 } | 338 } |
339 return READ_SUCCEEDED; | 339 return READ_SUCCEEDED; |
340 } | 340 } |
341 | 341 |
342 bool Channel::ChannelImpl::WillDispatchInputMessage(Message* msg) { | 342 bool ChannelNacl::WillDispatchInputMessage(Message* msg) { |
343 uint16 header_fds = msg->header()->num_fds; | 343 uint16 header_fds = msg->header()->num_fds; |
344 CHECK(header_fds == input_fds_.size()); | 344 CHECK(header_fds == input_fds_.size()); |
345 if (header_fds == 0) | 345 if (header_fds == 0) |
346 return true; // Nothing to do. | 346 return true; // Nothing to do. |
347 | 347 |
348 // The shenaniganery below with &foo.front() requires input_fds_ to have | 348 // The shenaniganery below with &foo.front() requires input_fds_ to have |
349 // contiguous underlying storage (such as a simple array or a std::vector). | 349 // contiguous underlying storage (such as a simple array or a std::vector). |
350 // This is why the header warns not to make input_fds_ a deque<>. | 350 // This is why the header warns not to make input_fds_ a deque<>. |
351 msg->file_descriptor_set()->SetDescriptors(&input_fds_.front(), | 351 msg->file_descriptor_set()->SetDescriptors(&input_fds_.front(), |
352 header_fds); | 352 header_fds); |
353 input_fds_.clear(); | 353 input_fds_.clear(); |
354 return true; | 354 return true; |
355 } | 355 } |
356 | 356 |
357 bool Channel::ChannelImpl::DidEmptyInputBuffers() { | 357 bool ChannelNacl::DidEmptyInputBuffers() { |
358 // When the input data buffer is empty, the fds should be too. | 358 // When the input data buffer is empty, the fds should be too. |
359 return input_fds_.empty(); | 359 return input_fds_.empty(); |
360 } | 360 } |
361 | 361 |
362 void Channel::ChannelImpl::HandleInternalMessage(const Message& msg) { | 362 void ChannelNacl::HandleInternalMessage(const Message& msg) { |
363 // The trusted side IPC::Channel should handle the "hello" handshake; we | 363 // The trusted side IPC::Channel should handle the "hello" handshake; we |
364 // should not receive the "Hello" message. | 364 // should not receive the "Hello" message. |
365 NOTREACHED(); | 365 NOTREACHED(); |
366 } | 366 } |
367 | 367 |
368 //------------------------------------------------------------------------------ | 368 // Channel's methods |
369 // Channel's methods simply call through to ChannelImpl. | |
370 | 369 |
371 Channel::Channel(const IPC::ChannelHandle& channel_handle, | 370 // static |
372 Mode mode, | 371 scoped_ptr<Channel> Channel::Create( |
373 Listener* listener) | 372 const IPC::ChannelHandle &channel_handle, Mode mode, Listener* listener) { |
374 : channel_impl_(new ChannelImpl(channel_handle, mode, listener)) { | 373 return scoped_ptr<Channel>( |
375 } | 374 new ChannelNacl(channel_handle, mode, listener)); |
376 | |
377 Channel::~Channel() { | |
378 delete channel_impl_; | |
379 } | |
380 | |
381 bool Channel::Connect() { | |
382 return channel_impl_->Connect(); | |
383 } | |
384 | |
385 void Channel::Close() { | |
386 channel_impl_->Close(); | |
387 } | |
388 | |
389 base::ProcessId Channel::peer_pid() const { | |
390 return channel_impl_->peer_pid(); | |
391 } | |
392 | |
393 bool Channel::Send(Message* message) { | |
394 return channel_impl_->Send(message); | |
395 } | 375 } |
396 | 376 |
397 } // namespace IPC | 377 } // namespace IPC |
OLD | NEW |