ipc_handler_linux.cc revision e415c050edbb2710e8807dd2602c851412953268
1fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//
2fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  Copyright (C) 2015 Google, Inc.
3fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//
4fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  Licensed under the Apache License, Version 2.0 (the "License");
5fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  you may not use this file except in compliance with the License.
6fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  You may obtain a copy of the License at:
7fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//
8fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  http://www.apache.org/licenses/LICENSE-2.0
9fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//
10fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  Unless required by applicable law or agreed to in writing, software
11fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  distributed under the License is distributed on an "AS IS" BASIS,
12fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  See the License for the specific language governing permissions and
14fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//  limitations under the License.
15fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray//
16fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
17e415c050edbb2710e8807dd2602c851412953268Scott James Remnant#include "service/ipc/ipc_handler_linux.h"
18fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
19fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray#include <sys/socket.h>
20fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray#include <sys/un.h>
21fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
22fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray#include <base/bind.h>
23fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
24c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge#include "osi/include/socket_utils/sockets.h"
25fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray#include "service/daemon.h"
26e415c050edbb2710e8807dd2602c851412953268Scott James Remnant#include "service/ipc/linux_ipc_host.h"
27fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray#include "service/settings.h"
28fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
29fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguraynamespace ipc {
30fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
31e415c050edbb2710e8807dd2602c851412953268Scott James RemnantIPCHandlerLinux::IPCHandlerLinux(bluetooth::Adapter* adapter,
32e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray                               IPCManager::Delegate* delegate)
33d6a4b0c950f44d3eab34825880d26c19e362d22bArman Uguray    : IPCHandler(adapter, delegate),
34fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray      running_(false),
35e415c050edbb2710e8807dd2602c851412953268Scott James Remnant      thread_("IPCHandlerLinux"),
36e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray      keep_running_(true) {
37fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray}
38fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
39e415c050edbb2710e8807dd2602c851412953268Scott James RemnantIPCHandlerLinux::~IPCHandlerLinux() {
40e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // This will only be set if the Settings::create_ipc_socket_path() was
41e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // originally provided.
42e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  if (!socket_path_.empty())
43e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray    unlink(socket_path_.value().c_str());
44fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray}
45fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
46e415c050edbb2710e8807dd2602c851412953268Scott James Remnantbool IPCHandlerLinux::Run() {
47fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  CHECK(!running_);
48fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
49c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge  const std::string& android_suffix =
50f8881fee3d08cb50896b22adc0841223694d51d2Arman Uguray      bluetooth::Daemon::Get()->GetSettings()->android_ipc_socket_suffix();
51fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  const base::FilePath& path =
52f8881fee3d08cb50896b22adc0841223694d51d2Arman Uguray      bluetooth::Daemon::Get()->GetSettings()->create_ipc_socket_path();
53c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge
54c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge  // Both flags cannot be set at the same time.
55c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge  CHECK(android_suffix.empty() || path.empty());
56c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge  if (android_suffix.empty() && path.empty()) {
57fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    LOG(ERROR) << "No domain socket path provided";
58fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    return false;
59fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  }
60fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
61fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  CHECK(base::MessageLoop::current());  // An origin event loop is required.
62fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  origin_task_runner_ = base::MessageLoop::current()->task_runner();
63fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
64c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge  if (!android_suffix.empty()) {
65c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    int server_fd = osi_android_get_control_socket(android_suffix.c_str());
66c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    if (server_fd == -1) {
67c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge      LOG(ERROR) << "Unable to get Android socket from: " << android_suffix;
68c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge      return false;
69c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    }
70c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    LOG(INFO) << "Binding to Android server socket:" << android_suffix;
71c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    socket_.reset(server_fd);
72c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge  } else {
73c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    LOG(INFO) << "Creating a Unix domain socket:" << path.value();
74c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge
75c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    // TODO(armansito): This is opens the door to potentially unlinking files in
76c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    // the current directory that we're not supposed to. For now we will have an
77c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    // assumption that the daemon runs in a sandbox but we should generally do
78c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    // this properly.
79c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    unlink(path.value().c_str());
80c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge
81c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    base::ScopedFD server_socket(socket(PF_UNIX, SOCK_SEQPACKET, 0));
82c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    if (!server_socket.is_valid()) {
83c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge      LOG(ERROR) << "Failed to open domain socket for IPC";
84c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge      return false;
85c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    }
86c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge
87c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    struct sockaddr_un address;
88c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    memset(&address, 0, sizeof(address));
89c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    address.sun_family = AF_UNIX;
90c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    strncpy(address.sun_path, path.value().c_str(),
91c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge            sizeof(address.sun_path) - 1);
92c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    if (bind(server_socket.get(), (struct sockaddr*)&address, sizeof(address)) <
93c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge        0) {
94c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge      LOG(ERROR) << "Failed to bind IPC socket to address: " << strerror(errno);
95c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge      return false;
96c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    }
97c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge
98c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge    socket_.swap(server_socket);
99e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray    socket_path_ = path;
100fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  }
101fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
102c6760d82357f46943406c579f5b1c291a20afdebIan Coolidge  CHECK(socket_.is_valid());
103fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
104fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  running_ = true;  // Set this here before launching the thread.
105fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
106fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  // Start an IO thread and post the listening task.
107fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  base::Thread::Options options(base::MessageLoop::TYPE_IO, 0);
108fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  if (!thread_.StartWithOptions(options)) {
109e415c050edbb2710e8807dd2602c851412953268Scott James Remnant    LOG(ERROR) << "Failed to start IPCHandlerLinux thread";
110fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    running_ = false;
111fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    return false;
112fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  }
113fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
114fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  thread_.task_runner()->PostTask(
115fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray      FROM_HERE,
116e415c050edbb2710e8807dd2602c851412953268Scott James Remnant      base::Bind(&IPCHandlerLinux::StartListeningOnThread, this));
117fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
118fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  return true;
119fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray}
120fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
121e415c050edbb2710e8807dd2602c851412953268Scott James Remnantvoid IPCHandlerLinux::Stop() {
122e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  keep_running_ = false;
123e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
124e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // At this moment the listening thread might be blocking on the accept
125e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // syscall. Shutdown and close the server socket before joining the thread to
126e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // interrupt accept so that the main thread doesn't keep blocking.
127e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  shutdown(socket_.get(), SHUT_RDWR);
128e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  socket_.reset();
129e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
130e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // Join and clean up the thread.
131e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  thread_.Stop();
132e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
133e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // Thread exited. Notify the delegate. Post this on the event loop so that the
134e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // callback isn't reentrant.
135e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  NotifyStoppedOnOriginThread();
136e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray}
137e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
138e415c050edbb2710e8807dd2602c851412953268Scott James Remnantvoid IPCHandlerLinux::StartListeningOnThread() {
139fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  CHECK(socket_.is_valid());
140d6a4b0c950f44d3eab34825880d26c19e362d22bArman Uguray  CHECK(adapter());
141fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  CHECK(running_);
142fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
143fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  LOG(INFO) << "Listening to incoming connections";
144fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
145fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  int status = listen(socket_.get(), SOMAXCONN);
146fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  if (status < 0) {
147fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    LOG(ERROR) << "Failed to listen on domain socket: " << strerror(errno);
148fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    origin_task_runner_->PostTask(
149fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray        FROM_HERE,
150e415c050edbb2710e8807dd2602c851412953268Scott James Remnant        base::Bind(&IPCHandlerLinux::ShutDownOnOriginThread, this));
151fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    return;
152fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  }
153fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
154e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  NotifyStartedOnOriginThread();
155e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
156e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // TODO(armansito): The code below can cause the daemon to run indefinitely if
157e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // the thread is joined while it's in the middle of the EventLoop() call. The
158e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // EventLoop() won't exit until a client terminates the connection, however
159e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // this can be fixed by using the |thread_|'s MessageLoopForIO instead (since
160e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  // it gets stopped along with the thread).
161fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  // TODO(icoolidge): accept simultaneous clients
162e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  while (keep_running_.load()) {
163fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    int client_socket = accept4(socket_.get(), nullptr, nullptr, SOCK_NONBLOCK);
164e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray    if (client_socket < 0) {
165fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray      LOG(ERROR) << "Failed to accept client connection: " << strerror(errno);
166fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray      continue;
167fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    }
168fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
169fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    LOG(INFO) << "Established client connection: fd=" << client_socket;
170d6a4b0c950f44d3eab34825880d26c19e362d22bArman Uguray
171e415c050edbb2710e8807dd2602c851412953268Scott James Remnant    LinuxIPCHost ipc_host(client_socket, adapter());
172d6a4b0c950f44d3eab34825880d26c19e362d22bArman Uguray
173fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    // TODO(armansito): Use |thread_|'s MessageLoopForIO instead of using a
174fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    // custom event loop to poll from the socket.
175fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray    ipc_host.EventLoop();
176fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  }
177fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray}
178fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
179e415c050edbb2710e8807dd2602c851412953268Scott James Remnantvoid IPCHandlerLinux::ShutDownOnOriginThread() {
180e415c050edbb2710e8807dd2602c851412953268Scott James Remnant  LOG(INFO) << "Shutting down IPCHandlerLinux thread";
181fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  thread_.Stop();
182fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray  running_ = false;
183fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
184e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  NotifyStoppedOnCurrentThread();
185e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray}
186e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
187e415c050edbb2710e8807dd2602c851412953268Scott James Remnantvoid IPCHandlerLinux::NotifyStartedOnOriginThread() {
188e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  if (!delegate())
189e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray    return;
190e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
191e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  origin_task_runner_->PostTask(
192e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray      FROM_HERE,
193e415c050edbb2710e8807dd2602c851412953268Scott James Remnant      base::Bind(&IPCHandlerLinux::NotifyStartedOnCurrentThread, this));
194e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray}
195e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
196e415c050edbb2710e8807dd2602c851412953268Scott James Remnantvoid IPCHandlerLinux::NotifyStartedOnCurrentThread() {
197e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  if (delegate())
198e415c050edbb2710e8807dd2602c851412953268Scott James Remnant    delegate()->OnIPCHandlerStarted(IPCManager::TYPE_LINUX);
199e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray}
200e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
201e415c050edbb2710e8807dd2602c851412953268Scott James Remnantvoid IPCHandlerLinux::NotifyStoppedOnOriginThread() {
202e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  if (!delegate())
203e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray    return;
204e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
205e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  origin_task_runner_->PostTask(
206e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray      FROM_HERE,
207e415c050edbb2710e8807dd2602c851412953268Scott James Remnant      base::Bind(&IPCHandlerLinux::NotifyStoppedOnCurrentThread, this));
208e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray}
209e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray
210e415c050edbb2710e8807dd2602c851412953268Scott James Remnantvoid IPCHandlerLinux::NotifyStoppedOnCurrentThread() {
211e0d08c9fecdf92c386b52375501a306c8f67d63eArman Uguray  if (delegate())
212e415c050edbb2710e8807dd2602c851412953268Scott James Remnant    delegate()->OnIPCHandlerStopped(IPCManager::TYPE_LINUX);
213fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray}
214fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray
215fe65fb7978bc9257a36d1e5eae59c5f412dbdb49Arman Uguray}  // namespace ipc
216