1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // This file tests the C++ Mojo system core wrappers.
6 // TODO(vtl): Maybe rename "CoreCppTest" -> "CoreTest" if/when this gets
7 // compiled into a different binary from the C API tests.
8
9 #include "mojo/public/cpp/system/core.h"
10
11 #include <stddef.h>
12 #include <stdint.h>
13 #include <map>
14 #include <utility>
15
16 #include "testing/gtest/include/gtest/gtest.h"
17
18 namespace mojo {
19 namespace {
20
21 const MojoHandleSignals kSignalReadableWritable =
22 MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE;
23
24 const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE |
25 MOJO_HANDLE_SIGNAL_WRITABLE |
26 MOJO_HANDLE_SIGNAL_PEER_CLOSED;
27
TEST(CoreCppTest,GetTimeTicksNow)28 TEST(CoreCppTest, GetTimeTicksNow) {
29 const MojoTimeTicks start = GetTimeTicksNow();
30 EXPECT_NE(static_cast<MojoTimeTicks>(0), start)
31 << "GetTimeTicksNow should return nonzero value";
32 }
33
TEST(CoreCppTest,Basic)34 TEST(CoreCppTest, Basic) {
35 // Basic |Handle| implementation:
36 {
37 EXPECT_EQ(MOJO_HANDLE_INVALID, kInvalidHandleValue);
38
39 Handle h0;
40 EXPECT_EQ(kInvalidHandleValue, h0.value());
41 EXPECT_EQ(kInvalidHandleValue, *h0.mutable_value());
42 EXPECT_FALSE(h0.is_valid());
43
44 Handle h1(static_cast<MojoHandle>(123));
45 EXPECT_EQ(static_cast<MojoHandle>(123), h1.value());
46 EXPECT_EQ(static_cast<MojoHandle>(123), *h1.mutable_value());
47 EXPECT_TRUE(h1.is_valid());
48 *h1.mutable_value() = static_cast<MojoHandle>(456);
49 EXPECT_EQ(static_cast<MojoHandle>(456), h1.value());
50 EXPECT_TRUE(h1.is_valid());
51
52 h1.swap(h0);
53 EXPECT_EQ(static_cast<MojoHandle>(456), h0.value());
54 EXPECT_TRUE(h0.is_valid());
55 EXPECT_FALSE(h1.is_valid());
56
57 h1.set_value(static_cast<MojoHandle>(789));
58 h0.swap(h1);
59 EXPECT_EQ(static_cast<MojoHandle>(789), h0.value());
60 EXPECT_TRUE(h0.is_valid());
61 EXPECT_EQ(static_cast<MojoHandle>(456), h1.value());
62 EXPECT_TRUE(h1.is_valid());
63
64 // Make sure copy constructor works.
65 Handle h2(h0);
66 EXPECT_EQ(static_cast<MojoHandle>(789), h2.value());
67 // And assignment.
68 h2 = h1;
69 EXPECT_EQ(static_cast<MojoHandle>(456), h2.value());
70
71 // Make sure that we can put |Handle|s into |std::map|s.
72 h0 = Handle(static_cast<MojoHandle>(987));
73 h1 = Handle(static_cast<MojoHandle>(654));
74 h2 = Handle(static_cast<MojoHandle>(321));
75 Handle h3;
76 std::map<Handle, int> handle_to_int;
77 handle_to_int[h0] = 0;
78 handle_to_int[h1] = 1;
79 handle_to_int[h2] = 2;
80 handle_to_int[h3] = 3;
81
82 EXPECT_EQ(4u, handle_to_int.size());
83 EXPECT_FALSE(handle_to_int.find(h0) == handle_to_int.end());
84 EXPECT_EQ(0, handle_to_int[h0]);
85 EXPECT_FALSE(handle_to_int.find(h1) == handle_to_int.end());
86 EXPECT_EQ(1, handle_to_int[h1]);
87 EXPECT_FALSE(handle_to_int.find(h2) == handle_to_int.end());
88 EXPECT_EQ(2, handle_to_int[h2]);
89 EXPECT_FALSE(handle_to_int.find(h3) == handle_to_int.end());
90 EXPECT_EQ(3, handle_to_int[h3]);
91 EXPECT_TRUE(handle_to_int.find(Handle(static_cast<MojoHandle>(13579))) ==
92 handle_to_int.end());
93
94 // TODO(vtl): With C++11, support |std::unordered_map|s, etc. (Or figure out
95 // how to support the variations of |hash_map|.)
96 }
97
98 // |Handle|/|ScopedHandle| functions:
99 {
100 ScopedHandle h;
101
102 EXPECT_EQ(kInvalidHandleValue, h.get().value());
103
104 // This should be a no-op.
105 Close(std::move(h));
106
107 // It should still be invalid.
108 EXPECT_EQ(kInvalidHandleValue, h.get().value());
109
110 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
111 Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE, 1000000, nullptr));
112
113 std::vector<Handle> wh;
114 wh.push_back(h.get());
115 std::vector<MojoHandleSignals> sigs;
116 sigs.push_back(~MOJO_HANDLE_SIGNAL_NONE);
117 WaitManyResult wait_many_result =
118 WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, nullptr);
119 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result);
120 EXPECT_TRUE(wait_many_result.IsIndexValid());
121 EXPECT_FALSE(wait_many_result.AreSignalsStatesValid());
122
123 // Make sure that our specialized template correctly handles |NULL| as well
124 // as |nullptr|.
125 wait_many_result = WaitMany(wh, sigs, MOJO_DEADLINE_INDEFINITE, NULL);
126 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, wait_many_result.result);
127 EXPECT_EQ(0u, wait_many_result.index);
128 EXPECT_TRUE(wait_many_result.IsIndexValid());
129 EXPECT_FALSE(wait_many_result.AreSignalsStatesValid());
130 }
131
132 // |MakeScopedHandle| (just compilation tests):
133 {
134 EXPECT_FALSE(MakeScopedHandle(Handle()).is_valid());
135 EXPECT_FALSE(MakeScopedHandle(MessagePipeHandle()).is_valid());
136 EXPECT_FALSE(MakeScopedHandle(DataPipeProducerHandle()).is_valid());
137 EXPECT_FALSE(MakeScopedHandle(DataPipeConsumerHandle()).is_valid());
138 EXPECT_FALSE(MakeScopedHandle(SharedBufferHandle()).is_valid());
139 }
140
141 // |MessagePipeHandle|/|ScopedMessagePipeHandle| functions:
142 {
143 MessagePipeHandle h_invalid;
144 EXPECT_FALSE(h_invalid.is_valid());
145 EXPECT_EQ(
146 MOJO_RESULT_INVALID_ARGUMENT,
147 WriteMessageRaw(
148 h_invalid, nullptr, 0, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE));
149 char buffer[10] = {0};
150 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
151 WriteMessageRaw(h_invalid,
152 buffer,
153 sizeof(buffer),
154 nullptr,
155 0,
156 MOJO_WRITE_MESSAGE_FLAG_NONE));
157 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
158 ReadMessageRaw(h_invalid,
159 nullptr,
160 nullptr,
161 nullptr,
162 nullptr,
163 MOJO_READ_MESSAGE_FLAG_NONE));
164 uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
165 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
166 ReadMessageRaw(h_invalid,
167 buffer,
168 &buffer_size,
169 nullptr,
170 nullptr,
171 MOJO_READ_MESSAGE_FLAG_NONE));
172
173 // Basic tests of waiting and closing.
174 MojoHandle hv0 = kInvalidHandleValue;
175 {
176 ScopedMessagePipeHandle h0;
177 ScopedMessagePipeHandle h1;
178 EXPECT_FALSE(h0.get().is_valid());
179 EXPECT_FALSE(h1.get().is_valid());
180
181 CreateMessagePipe(nullptr, &h0, &h1);
182 EXPECT_TRUE(h0.get().is_valid());
183 EXPECT_TRUE(h1.get().is_valid());
184 EXPECT_NE(h0.get().value(), h1.get().value());
185 // Save the handle values, so we can check that things got closed
186 // correctly.
187 hv0 = h0.get().value();
188 MojoHandle hv1 = h1.get().value();
189 MojoHandleSignalsState state;
190
191 EXPECT_EQ(MOJO_RESULT_DEADLINE_EXCEEDED,
192 Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, 0, &state));
193
194 EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals);
195 EXPECT_EQ(kSignalAll, state.satisfiable_signals);
196
197 std::vector<Handle> wh;
198 wh.push_back(h0.get());
199 wh.push_back(h1.get());
200 std::vector<MojoHandleSignals> sigs;
201 sigs.push_back(MOJO_HANDLE_SIGNAL_READABLE);
202 sigs.push_back(MOJO_HANDLE_SIGNAL_WRITABLE);
203 std::vector<MojoHandleSignalsState> states(sigs.size());
204 WaitManyResult wait_many_result = WaitMany(wh, sigs, 1000, &states);
205 EXPECT_EQ(MOJO_RESULT_OK, wait_many_result.result);
206 EXPECT_EQ(1u, wait_many_result.index);
207 EXPECT_TRUE(wait_many_result.IsIndexValid());
208 EXPECT_TRUE(wait_many_result.AreSignalsStatesValid());
209 EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[0].satisfied_signals);
210 EXPECT_EQ(kSignalAll, states[0].satisfiable_signals);
211 EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[1].satisfied_signals);
212 EXPECT_EQ(kSignalAll, states[1].satisfiable_signals);
213
214 // Test closing |h1| explicitly.
215 Close(std::move(h1));
216 EXPECT_FALSE(h1.get().is_valid());
217
218 // Make sure |h1| is closed.
219 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT,
220 Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE,
221 MOJO_DEADLINE_INDEFINITE, nullptr));
222
223 EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION,
224 Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE,
225 MOJO_DEADLINE_INDEFINITE, &state));
226
227 EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals);
228 EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals);
229 }
230 // |hv0| should have been closed when |h0| went out of scope, so this close
231 // should fail.
232 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0));
233
234 // Actually test writing/reading messages.
235 {
236 ScopedMessagePipeHandle h0;
237 ScopedMessagePipeHandle h1;
238 CreateMessagePipe(nullptr, &h0, &h1);
239
240 const char kHello[] = "hello";
241 const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
242 EXPECT_EQ(MOJO_RESULT_OK,
243 WriteMessageRaw(h0.get(),
244 kHello,
245 kHelloSize,
246 nullptr,
247 0,
248 MOJO_WRITE_MESSAGE_FLAG_NONE));
249
250 MojoHandleSignalsState state;
251 EXPECT_EQ(MOJO_RESULT_OK, Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE,
252 MOJO_DEADLINE_INDEFINITE, &state));
253 EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
254 EXPECT_EQ(kSignalAll, state.satisfiable_signals);
255
256 char buffer[10] = {0};
257 uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
258 EXPECT_EQ(MOJO_RESULT_OK,
259 ReadMessageRaw(h1.get(),
260 buffer,
261 &buffer_size,
262 nullptr,
263 nullptr,
264 MOJO_READ_MESSAGE_FLAG_NONE));
265 EXPECT_EQ(kHelloSize, buffer_size);
266 EXPECT_STREQ(kHello, buffer);
267
268 // Send a handle over the previously-establish message pipe. Use the
269 // |MessagePipe| wrapper (to test it), which automatically creates a
270 // message pipe.
271 MessagePipe mp;
272
273 // Write a message to |mp.handle0|, before we send |mp.handle1|.
274 const char kWorld[] = "world!";
275 const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
276 EXPECT_EQ(MOJO_RESULT_OK,
277 WriteMessageRaw(mp.handle0.get(),
278 kWorld,
279 kWorldSize,
280 nullptr,
281 0,
282 MOJO_WRITE_MESSAGE_FLAG_NONE));
283
284 // Send |mp.handle1| over |h1| to |h0|.
285 MojoHandle handles[5];
286 handles[0] = mp.handle1.release().value();
287 EXPECT_NE(kInvalidHandleValue, handles[0]);
288 EXPECT_FALSE(mp.handle1.get().is_valid());
289 uint32_t handles_count = 1;
290 EXPECT_EQ(MOJO_RESULT_OK,
291 WriteMessageRaw(h1.get(),
292 kHello,
293 kHelloSize,
294 handles,
295 handles_count,
296 MOJO_WRITE_MESSAGE_FLAG_NONE));
297 // |handles[0]| should actually be invalid now.
298 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handles[0]));
299
300 // Read "hello" and the sent handle.
301 EXPECT_EQ(MOJO_RESULT_OK, Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE,
302 MOJO_DEADLINE_INDEFINITE, &state));
303 EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
304 EXPECT_EQ(kSignalAll, state.satisfiable_signals);
305
306 memset(buffer, 0, sizeof(buffer));
307 buffer_size = static_cast<uint32_t>(sizeof(buffer));
308 for (size_t i = 0; i < arraysize(handles); i++)
309 handles[i] = kInvalidHandleValue;
310 handles_count = static_cast<uint32_t>(arraysize(handles));
311 EXPECT_EQ(MOJO_RESULT_OK,
312 ReadMessageRaw(h0.get(),
313 buffer,
314 &buffer_size,
315 handles,
316 &handles_count,
317 MOJO_READ_MESSAGE_FLAG_NONE));
318 EXPECT_EQ(kHelloSize, buffer_size);
319 EXPECT_STREQ(kHello, buffer);
320 EXPECT_EQ(1u, handles_count);
321 EXPECT_NE(kInvalidHandleValue, handles[0]);
322
323 // Read from the sent/received handle.
324 mp.handle1.reset(MessagePipeHandle(handles[0]));
325 // Save |handles[0]| to check that it gets properly closed.
326 hv0 = handles[0];
327
328 EXPECT_EQ(MOJO_RESULT_OK,
329 Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE,
330 MOJO_DEADLINE_INDEFINITE, &state));
331 EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals);
332 EXPECT_EQ(kSignalAll, state.satisfiable_signals);
333
334 memset(buffer, 0, sizeof(buffer));
335 buffer_size = static_cast<uint32_t>(sizeof(buffer));
336 for (size_t i = 0; i < arraysize(handles); i++)
337 handles[i] = kInvalidHandleValue;
338 handles_count = static_cast<uint32_t>(arraysize(handles));
339 EXPECT_EQ(MOJO_RESULT_OK,
340 ReadMessageRaw(mp.handle1.get(),
341 buffer,
342 &buffer_size,
343 handles,
344 &handles_count,
345 MOJO_READ_MESSAGE_FLAG_NONE));
346 EXPECT_EQ(kWorldSize, buffer_size);
347 EXPECT_STREQ(kWorld, buffer);
348 EXPECT_EQ(0u, handles_count);
349 }
350 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0));
351 }
352
353 // TODO(vtl): Test |CloseRaw()|.
354 // TODO(vtl): Test |reset()| more thoroughly?
355 }
356
TEST(CoreCppTest,TearDownWithMessagesEnqueued)357 TEST(CoreCppTest, TearDownWithMessagesEnqueued) {
358 // Tear down a message pipe which still has a message enqueued, with the
359 // message also having a valid message pipe handle.
360 {
361 ScopedMessagePipeHandle h0;
362 ScopedMessagePipeHandle h1;
363 CreateMessagePipe(nullptr, &h0, &h1);
364
365 // Send a handle over the previously-establish message pipe.
366 ScopedMessagePipeHandle h2;
367 ScopedMessagePipeHandle h3;
368 if (CreateMessagePipe(nullptr, &h2, &h3) != MOJO_RESULT_OK)
369 CreateMessagePipe(nullptr, &h2, &h3); // Must be old EDK.
370
371 // Write a message to |h2|, before we send |h3|.
372 const char kWorld[] = "world!";
373 const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
374 EXPECT_EQ(MOJO_RESULT_OK,
375 WriteMessageRaw(h2.get(),
376 kWorld,
377 kWorldSize,
378 nullptr,
379 0,
380 MOJO_WRITE_MESSAGE_FLAG_NONE));
381 // And also a message to |h3|.
382 EXPECT_EQ(MOJO_RESULT_OK,
383 WriteMessageRaw(h3.get(),
384 kWorld,
385 kWorldSize,
386 nullptr,
387 0,
388 MOJO_WRITE_MESSAGE_FLAG_NONE));
389
390 // Send |h3| over |h1| to |h0|.
391 const char kHello[] = "hello";
392 const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
393 MojoHandle h3_value;
394 h3_value = h3.release().value();
395 EXPECT_NE(kInvalidHandleValue, h3_value);
396 EXPECT_FALSE(h3.get().is_valid());
397 EXPECT_EQ(MOJO_RESULT_OK,
398 WriteMessageRaw(h1.get(),
399 kHello,
400 kHelloSize,
401 &h3_value,
402 1,
403 MOJO_WRITE_MESSAGE_FLAG_NONE));
404 // |h3_value| should actually be invalid now.
405 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
406
407 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value()));
408 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value()));
409 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value()));
410 }
411
412 // Do this in a different order: make the enqueued message pipe handle only
413 // half-alive.
414 {
415 ScopedMessagePipeHandle h0;
416 ScopedMessagePipeHandle h1;
417 CreateMessagePipe(nullptr, &h0, &h1);
418
419 // Send a handle over the previously-establish message pipe.
420 ScopedMessagePipeHandle h2;
421 ScopedMessagePipeHandle h3;
422 if (CreateMessagePipe(nullptr, &h2, &h3) != MOJO_RESULT_OK)
423 CreateMessagePipe(nullptr, &h2, &h3); // Must be old EDK.
424
425 // Write a message to |h2|, before we send |h3|.
426 const char kWorld[] = "world!";
427 const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld));
428 EXPECT_EQ(MOJO_RESULT_OK,
429 WriteMessageRaw(h2.get(),
430 kWorld,
431 kWorldSize,
432 nullptr,
433 0,
434 MOJO_WRITE_MESSAGE_FLAG_NONE));
435 // And also a message to |h3|.
436 EXPECT_EQ(MOJO_RESULT_OK,
437 WriteMessageRaw(h3.get(),
438 kWorld,
439 kWorldSize,
440 nullptr,
441 0,
442 MOJO_WRITE_MESSAGE_FLAG_NONE));
443
444 // Send |h3| over |h1| to |h0|.
445 const char kHello[] = "hello";
446 const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello));
447 MojoHandle h3_value;
448 h3_value = h3.release().value();
449 EXPECT_NE(kInvalidHandleValue, h3_value);
450 EXPECT_FALSE(h3.get().is_valid());
451 EXPECT_EQ(MOJO_RESULT_OK,
452 WriteMessageRaw(h1.get(),
453 kHello,
454 kHelloSize,
455 &h3_value,
456 1,
457 MOJO_WRITE_MESSAGE_FLAG_NONE));
458 // |h3_value| should actually be invalid now.
459 EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value));
460
461 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value()));
462 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value()));
463 EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value()));
464 }
465 }
466
TEST(CoreCppTest,ScopedHandleMoveCtor)467 TEST(CoreCppTest, ScopedHandleMoveCtor) {
468 ScopedSharedBufferHandle buffer1 = SharedBufferHandle::Create(1024);
469 EXPECT_TRUE(buffer1.is_valid());
470
471 ScopedSharedBufferHandle buffer2 = SharedBufferHandle::Create(1024);
472 EXPECT_TRUE(buffer2.is_valid());
473
474 // If this fails to close buffer1, ScopedHandleBase::CloseIfNecessary() will
475 // assert.
476 buffer1 = std::move(buffer2);
477
478 EXPECT_TRUE(buffer1.is_valid());
479 EXPECT_FALSE(buffer2.is_valid());
480 }
481
TEST(CoreCppTest,BasicSharedBuffer)482 TEST(CoreCppTest, BasicSharedBuffer) {
483 ScopedSharedBufferHandle h0 = SharedBufferHandle::Create(100);
484 ASSERT_TRUE(h0.is_valid());
485
486 // Map everything.
487 ScopedSharedBufferMapping mapping = h0->Map(100);
488 ASSERT_TRUE(mapping);
489 static_cast<char*>(mapping.get())[50] = 'x';
490
491 // Duplicate |h0| to |h1|.
492 ScopedSharedBufferHandle h1 =
493 h0->Clone(SharedBufferHandle::AccessMode::READ_ONLY);
494 ASSERT_TRUE(h1.is_valid());
495
496 // Close |h0|.
497 h0.reset();
498
499 // The mapping should still be good.
500 static_cast<char*>(mapping.get())[51] = 'y';
501
502 // Unmap it.
503 mapping.reset();
504
505 // Map half of |h1|.
506 mapping = h1->MapAtOffset(50, 50);
507 ASSERT_TRUE(mapping);
508
509 // It should have what we wrote.
510 EXPECT_EQ('x', static_cast<char*>(mapping.get())[0]);
511 EXPECT_EQ('y', static_cast<char*>(mapping.get())[1]);
512
513 // Unmap it.
514 mapping.reset();
515 h1.reset();
516
517 // Creating a 1 EB shared buffer should fail without crashing.
518 EXPECT_FALSE(SharedBufferHandle::Create(1ULL << 60).is_valid());
519 }
520
521 // TODO(vtl): Write data pipe tests.
522
523 } // namespace
524 } // namespace mojo
525