1 // Copyright 2018 The Fuchsia 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 #include <fidl/test/spaceship/c/fidl.h>
6 #include <lib/async-loop/loop.h>
7 #include <lib/fidl-utils/bind.h>
8 #include <lib/zx/channel.h>
9 #include <string.h>
10 #include <zircon/fidl.h>
11 #include <zircon/syscalls.h>
12 
13 #include <unittest/unittest.h>
14 
15 #include <utility>
16 
17 namespace {
18 
19 class SpaceShip {
20 public:
21     using SpaceShipBinder = fidl::Binder<SpaceShip>;
22 
~SpaceShip()23     virtual ~SpaceShip() {};
24 
AdjustHeading(const uint32_t * stars_data,size_t stars_count,fidl_txn_t * txn)25     virtual zx_status_t AdjustHeading(const uint32_t* stars_data,
26                                       size_t stars_count, fidl_txn_t* txn) {
27         EXPECT_EQ(3u, stars_count, "");
28         EXPECT_EQ(11u, stars_data[0], "");
29         EXPECT_EQ(0u, stars_data[1], "");
30         EXPECT_EQ(UINT32_MAX, stars_data[2], "");
31         return fidl_test_spaceship_SpaceShipAdjustHeading_reply(txn, -12);
32     }
33 
ScanForLifeforms(fidl_txn_t * txn)34     virtual zx_status_t ScanForLifeforms(fidl_txn_t* txn) {
35         const uint32_t lifesigns[5] = {42u, 43u, UINT32_MAX, 0u, 9u};
36         return fidl_test_spaceship_SpaceShipScanForLifeforms_reply(txn, lifesigns, 5);
37     }
38 
ScanForTensorLifeforms(fidl_txn_t * txn)39     virtual zx_status_t ScanForTensorLifeforms(fidl_txn_t* txn) {
40         uint32_t lifesigns[8][5][3] = {};
41         // fill the array with increasing counter
42         uint32_t counter = 0;
43         for (size_t i = 0; i < 8; i++) {
44             for (size_t j = 0; j < 5; j++) {
45                 for (size_t k = 0; k < 3; k++) {
46                     lifesigns[i][j][k] = counter;
47                     counter += 1;
48                 }
49             }
50         }
51         return fidl_test_spaceship_SpaceShipScanForTensorLifeforms_reply(txn, lifesigns);
52     }
53 
SetAstrometricsListener(zx_handle_t listener)54     virtual zx_status_t SetAstrometricsListener(zx_handle_t listener) {
55         EXPECT_EQ(ZX_OK, fidl_test_spaceship_AstrometricsListenerOnNova(listener), "");
56         EXPECT_EQ(ZX_OK, zx_handle_close(listener), "");
57         return ZX_OK;
58     }
59 
SetDefenseCondition(fidl_test_spaceship_Alert alert)60     virtual zx_status_t SetDefenseCondition(fidl_test_spaceship_Alert alert) {
61         EXPECT_EQ(fidl_test_spaceship_Alert_RED, alert, "");
62         return ZX_OK;
63     }
64 
GetFuelRemaining(zx_handle_t cancel,fidl_txn_t * txn)65     virtual zx_status_t GetFuelRemaining(zx_handle_t cancel, fidl_txn_t* txn) {
66         EXPECT_EQ(ZX_HANDLE_INVALID, cancel, "");
67         const fidl_test_spaceship_FuelLevel level = {
68             .reaction_mass = 1641u,
69         };
70         return fidl_test_spaceship_SpaceShipGetFuelRemaining_reply(txn, ZX_OK, &level);
71     }
72 
AddFuelTank(const fidl_test_spaceship_FuelLevel * level,fidl_txn_t * txn)73     virtual zx_status_t AddFuelTank(const fidl_test_spaceship_FuelLevel* level, fidl_txn_t* txn) {
74         return fidl_test_spaceship_SpaceShipAddFuelTank_reply(txn, level->reaction_mass / 2);
75     }
76 
ReportAstrologicalData(const fidl_test_spaceship_AstrologicalData * data,fidl_txn_t * txn)77     virtual zx_status_t ReportAstrologicalData(const fidl_test_spaceship_AstrologicalData* data,
78                                                fidl_txn_t* txn) {
79         EXPECT_EQ(data->tag, fidl_test_spaceship_AstrologicalDataTag_star, "");
80         for (size_t idx = 0; idx < sizeof(data->star.data); ++idx) {
81             EXPECT_EQ(42, data->star.data[idx], "");
82         }
83 
84         const zx_status_t status = ZX_OK;
85         return fidl_test_spaceship_SpaceShipReportAstrologicalData_reply(txn, status);
86     }
87 
Bind(async_dispatcher_t * dispatcher,zx::channel channel)88     virtual zx_status_t Bind(async_dispatcher_t* dispatcher, zx::channel channel) {
89         static constexpr fidl_test_spaceship_SpaceShip_ops_t kOps = {
90             .AdjustHeading = SpaceShipBinder::BindMember<&SpaceShip::AdjustHeading>,
91             .ScanForLifeforms = SpaceShipBinder::BindMember<&SpaceShip::ScanForLifeforms>,
92             .SetAstrometricsListener =
93                     SpaceShipBinder::BindMember<&SpaceShip::SetAstrometricsListener>,
94             .SetDefenseCondition = SpaceShipBinder::BindMember<&SpaceShip::SetDefenseCondition>,
95             .GetFuelRemaining = SpaceShipBinder::BindMember<&SpaceShip::GetFuelRemaining>,
96             .AddFuelTank = SpaceShipBinder::BindMember<&SpaceShip::AddFuelTank>,
97             .ScanForTensorLifeforms =
98                     SpaceShipBinder::BindMember<&SpaceShip::ScanForTensorLifeforms>,
99             .ReportAstrologicalData =
100                 SpaceShipBinder::BindMember<&SpaceShip::ReportAstrologicalData>,
101         };
102 
103         return SpaceShipBinder::BindOps<fidl_test_spaceship_SpaceShip_dispatch>(
104             dispatcher, std::move(channel), this, &kOps);
105     }
106 };
107 
spaceship_test(void)108 static bool spaceship_test(void) {
109     BEGIN_TEST;
110 
111     zx::channel client, server;
112     zx_status_t status = zx::channel::create(0, &client, &server);
113     ASSERT_EQ(ZX_OK, status, "");
114 
115     async_loop_t* loop = NULL;
116     ASSERT_EQ(ZX_OK, async_loop_create(&kAsyncLoopConfigNoAttachToThread, &loop), "");
117     ASSERT_EQ(ZX_OK, async_loop_start_thread(loop, "spaceship-dispatcher", NULL), "");
118 
119     async_dispatcher_t* dispatcher = async_loop_get_dispatcher(loop);
120     SpaceShip ship;
121     ASSERT_EQ(ZX_OK, ship.Bind(dispatcher, std::move(server)));
122 
123     {
124         const uint32_t stars[3] = {11u, 0u, UINT32_MAX};
125         int8_t result = 0;
126         ASSERT_EQ(ZX_OK,
127                   fidl_test_spaceship_SpaceShipAdjustHeading(client.get(), stars, 3, &result));
128         ASSERT_EQ(-12, result, "");
129     }
130 
131     {
132         constexpr uint32_t kNumStarsOverflow = fidl_test_spaceship_MaxStarsAdjustHeading * 2;
133         const uint32_t stars[kNumStarsOverflow] = {};
134         int8_t result = 0;
135         ASSERT_EQ(ZX_ERR_INVALID_ARGS,
136                   fidl_test_spaceship_SpaceShipAdjustHeading(client.get(),
137                                                              stars,
138                                                              kNumStarsOverflow,
139                                                              &result));
140     }
141 
142     {
143         int8_t result = 0;
144         ASSERT_EQ(
145             ZX_ERR_INVALID_ARGS,
146             fidl_test_spaceship_SpaceShipAdjustHeading(client.get(), nullptr, 1 << 31, &result));
147     }
148 
149     {
150         uint32_t lifesigns[64];
151         size_t actual = 0;
152         ASSERT_EQ(ZX_OK, fidl_test_spaceship_SpaceShipScanForLifeforms(client.get(), lifesigns,
153                                                                        64, &actual));
154         ASSERT_EQ(5u, actual, "");
155         ASSERT_EQ(42u, lifesigns[0], "");
156         ASSERT_EQ(43u, lifesigns[1], "");
157         ASSERT_EQ(UINT32_MAX, lifesigns[2], "");
158         ASSERT_EQ(0u, lifesigns[3], "");
159         ASSERT_EQ(9u, lifesigns[4], "");
160     }
161 
162     {
163         uint32_t lifesigns[8][5][3];
164         ASSERT_EQ(ZX_OK, fidl_test_spaceship_SpaceShipScanForTensorLifeforms(client.get(),
165                                                                              lifesigns), "");
166         uint32_t counter = 0;
167         for (size_t i = 0; i < 8; i++) {
168             for (size_t j = 0; j < 5; j++) {
169                 for (size_t k = 0; k < 3; k++) {
170                     ASSERT_EQ(counter, lifesigns[i][j][k], "");
171                     counter += 1;
172                 }
173             }
174         }
175     }
176 
177     {
178         zx::channel listener_client, listener_server;
179         status = zx::channel::create(0, &listener_client, &listener_server);
180         ASSERT_EQ(ZX_OK, status, "");
181         ASSERT_EQ(ZX_OK,
182                   fidl_test_spaceship_SpaceShipSetAstrometricsListener(client.get(),
183                                                                        listener_client.release()));
184         ASSERT_EQ(ZX_OK, listener_server.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), NULL));
185         ASSERT_EQ(ZX_OK, zx_handle_close(listener_server.release()));
186     }
187 
188     {
189         ASSERT_EQ(ZX_OK,
190                   fidl_test_spaceship_SpaceShipSetDefenseCondition(client.get(),
191                                                                    fidl_test_spaceship_Alert_RED));
192     }
193 
194     {
195         fidl_test_spaceship_FuelLevel level;
196         ASSERT_EQ(ZX_OK, fidl_test_spaceship_SpaceShipGetFuelRemaining(client.get(),
197                                                                        ZX_HANDLE_INVALID, &status,
198                                                                        &level));
199         ASSERT_EQ(ZX_OK, status, "");
200         ASSERT_EQ(1641u, level.reaction_mass, "");
201     }
202 
203     {
204         fidl_test_spaceship_FuelLevel level = {
205             .reaction_mass = 9482,
206         };
207         uint32_t out_consumed = 0u;
208         ASSERT_EQ(ZX_OK, fidl_test_spaceship_SpaceShipAddFuelTank(client.get(), &level,
209                                                                   &out_consumed));
210         ASSERT_EQ(4741u, out_consumed, "");
211     }
212 
213     {
214         fidl_test_spaceship_AstrologicalData data = {};
215         data.tag = fidl_test_spaceship_AstrologicalDataTag_star;
216         memset(&data.star, 42, sizeof(data.star));
217         ASSERT_EQ(ZX_OK, fidl_test_spaceship_SpaceShipReportAstrologicalData(client.get(), &data, &status));
218         ASSERT_EQ(ZX_OK, status);
219     }
220 
221     ASSERT_EQ(ZX_OK, zx_handle_close(client.release()));
222 
223     async_loop_destroy(loop);
224 
225     END_TEST;
226 }
227 
228 // A variant of spaceship which responds to requests asynchronously.
229 class AsyncSpaceShip : public SpaceShip {
230 public:
231     using SpaceShipBinder = fidl::Binder<AsyncSpaceShip>;
232 
~AsyncSpaceShip()233     virtual ~AsyncSpaceShip() {};
234 
235     // Creates a |fidl_async_txn| using the C++ wrapper, and pushes the
236     // computation to a background thread.
237     //
238     // This background thread responds to the original |txn|, and rebinds the connection
239     // to the dispatcher.
AdjustHeading(const uint32_t * stars_data,size_t stars_count,fidl_txn_t * txn)240     zx_status_t AdjustHeading(const uint32_t* stars_data, size_t stars_count,
241                               fidl_txn_t* txn) override {
242         EXPECT_EQ(3u, stars_count, "");
243         EXPECT_EQ(11u, stars_data[0], "");
244         EXPECT_EQ(0u, stars_data[1], "");
245         EXPECT_EQ(UINT32_MAX, stars_data[2], "");
246         static constexpr auto handler = [](void* arg) {
247             auto spaceship = reinterpret_cast<AsyncSpaceShip*>(arg);
248             EXPECT_EQ(ZX_OK,
249                       fidl_test_spaceship_SpaceShipAdjustHeading_reply(
250                               spaceship->async_txn_.Transaction(), -12));
251             EXPECT_EQ(ZX_OK, spaceship->async_txn_.Rebind());
252             return 0;
253         };
254 
255         async_txn_.Reset(txn);
256         EXPECT_EQ(thrd_success, thrd_create(&thrd_, handler, this));
257         return ZX_ERR_ASYNC;
258     }
259 
260     // Creates a |fidl_async_txn| using the C++ wrapper, and pushes the
261     // computation to a background thread.
262     //
263     // This background thread responds to the original |txn|, but does not rebind
264     // the connection to the dispatcher. This completes the asynchronous transaction and destroys
265     // the original binding.
ScanForLifeforms(fidl_txn_t * txn)266     zx_status_t ScanForLifeforms(fidl_txn_t* txn) override {
267         static constexpr auto handler = [](void* arg) {
268             auto spaceship = reinterpret_cast<AsyncSpaceShip*>(arg);
269             const uint32_t lifesigns[2] = {42u, 43u};
270             EXPECT_EQ(ZX_OK,
271                       fidl_test_spaceship_SpaceShipScanForLifeforms_reply(
272                               spaceship->async_txn_.Transaction(), lifesigns, 2));
273             spaceship->async_txn_.Reset();
274             return 0;
275         };
276 
277         async_txn_.Reset(txn);
278         EXPECT_EQ(thrd_success, thrd_create(&thrd_, handler, this));
279         return ZX_ERR_ASYNC;
280     }
281 
Join()282     void Join() {
283         int res;
284         EXPECT_EQ(thrd_success, thrd_join(thrd_, &res));
285         EXPECT_EQ(0, res);
286     }
287 
Bind(async_dispatcher_t * dispatcher,zx::channel channel)288     zx_status_t Bind(async_dispatcher_t* dispatcher, zx::channel channel) override {
289         static constexpr fidl_test_spaceship_SpaceShip_ops_t kOps = {
290             .AdjustHeading = SpaceShipBinder::BindMember<&AsyncSpaceShip::AdjustHeading>,
291             .ScanForLifeforms = SpaceShipBinder::BindMember<&AsyncSpaceShip::ScanForLifeforms>,
292             .SetAstrometricsListener =
293                     SpaceShipBinder::BindMember<&SpaceShip::SetAstrometricsListener>,
294             .SetDefenseCondition = SpaceShipBinder::BindMember<&SpaceShip::SetDefenseCondition>,
295             .GetFuelRemaining = SpaceShipBinder::BindMember<&SpaceShip::GetFuelRemaining>,
296             .AddFuelTank = SpaceShipBinder::BindMember<&SpaceShip::AddFuelTank>,
297             .ScanForTensorLifeforms =
298                     SpaceShipBinder::BindMember<&SpaceShip::ScanForTensorLifeforms>,
299             .ReportAstrologicalData =
300                     SpaceShipBinder::BindMember<&SpaceShip::ReportAstrologicalData>,
301         };
302 
303         return SpaceShipBinder::BindOps<fidl_test_spaceship_SpaceShip_dispatch>(
304             dispatcher, std::move(channel), this, &kOps);
305     }
306 
307 private:
308     thrd_t thrd_;
309     fidl::AsyncTransaction async_txn_;
310 };
311 
spaceship_async_test(void)312 static bool spaceship_async_test(void) {
313     BEGIN_TEST;
314 
315     zx::channel client, server;
316     zx_status_t status = zx::channel::create(0, &client, &server);
317     ASSERT_EQ(ZX_OK, status, "");
318 
319     async_loop_t* loop = NULL;
320     ASSERT_EQ(ZX_OK, async_loop_create(&kAsyncLoopConfigNoAttachToThread, &loop), "");
321     ASSERT_EQ(ZX_OK, async_loop_start_thread(loop, "spaceship-dispatcher", NULL), "");
322 
323     async_dispatcher_t* dispatcher = async_loop_get_dispatcher(loop);
324     AsyncSpaceShip ship;
325     ASSERT_EQ(ZX_OK, ship.Bind(dispatcher, std::move(server)));
326 
327     // Try invoking a member function which responds asynchronously and rebinds the connection.
328     {
329         const uint32_t stars[3] = {11u, 0u, UINT32_MAX};
330         int8_t result = 0;
331         ASSERT_EQ(ZX_OK,
332                   fidl_test_spaceship_SpaceShipAdjustHeading(client.get(), stars, 3, &result));
333         ASSERT_EQ(-12, result, "");
334         ship.Join();
335     }
336 
337     // Try invoking a member function which responds asynchronously, but does not rebind the
338     // connection. We should be able to observe that the server terminates the connection.
339     {
340         uint32_t lifesigns[64];
341         size_t actual = 0;
342         ASSERT_EQ(ZX_OK, fidl_test_spaceship_SpaceShipScanForLifeforms(client.get(), lifesigns,
343                                                                        64, &actual));
344         ASSERT_EQ(2u, actual, "");
345         ASSERT_EQ(42u, lifesigns[0], "");
346         ASSERT_EQ(43u, lifesigns[1], "");
347 
348         zx_signals_t pending;
349         zx::time deadline = zx::deadline_after(zx::sec(5));
350         ASSERT_EQ(ZX_OK, client.wait_one(ZX_CHANNEL_PEER_CLOSED, deadline, &pending));
351         ASSERT_EQ(pending & ZX_CHANNEL_PEER_CLOSED, ZX_CHANNEL_PEER_CLOSED);
352         ship.Join();
353     }
354 
355     ASSERT_EQ(ZX_OK, zx_handle_close(client.release()));
356 
357     async_loop_destroy(loop);
358 
359     END_TEST;
360 }
361 
362 // These classes represents a compile-time check:
363 //
364 // We should be able to bind a derived class to its own methods,
365 // but also to methods of the base class.
366 //
367 // However, we should not be able to bind to an unrelated class.
368 class NotDerived {
369 public:
AdjustHeading(const uint32_t * stars_data,size_t stars_count,fidl_txn_t * txn)370     zx_status_t AdjustHeading(const uint32_t* stars_data, size_t stars_count, fidl_txn_t* txn) {
371         return ZX_ERR_NOT_SUPPORTED;
372     }
373 };
374 
375 class Derived : public SpaceShip {
376 public:
377     using DerivedBinder = fidl::Binder<Derived>;
378 
ScanForLifeforms(fidl_txn_t * txn)379     zx_status_t ScanForLifeforms(fidl_txn_t* txn) override {
380         return ZX_OK;
381     }
382 
Bind(async_dispatcher_t * dispatcher,zx::channel channel)383     zx_status_t Bind(async_dispatcher_t* dispatcher, zx::channel channel) override {
384         static constexpr fidl_test_spaceship_SpaceShip_ops_t kOps = {
385             // (Under the failure case) Tries to bind to a member, such that the
386             // context object passed to BindOps does not match the BindMember
387             // callback. This should fail at compile time.
388 #if TEST_WILL_NOT_COMPILE || 0
389             .AdjustHeading = DerivedBinder::BindMember<&NotDerived::AdjustHeading>,
390 #else
391             .AdjustHeading = DerivedBinder::BindMember<&SpaceShip::AdjustHeading>,
392 #endif
393             // Binds a member of the derived class to the derived method:
394             // This is the typical use case of the Binder object.
395             .ScanForLifeforms = DerivedBinder::BindMember<&Derived::ScanForLifeforms>,
396 
397             // Binds a member of the derived class to the base method:
398             // The compile time check should allow this, because the "Derived"
399             // class should be castable to a "SpaceShip" class.
400             .SetAstrometricsListener =
401                     DerivedBinder::BindMember<&SpaceShip::SetAstrometricsListener>,
402 
403             // The remaining functions cover already tested behavior, but just
404             // fill the ops table.
405             .SetDefenseCondition = DerivedBinder::BindMember<&SpaceShip::SetDefenseCondition>,
406             .GetFuelRemaining = DerivedBinder::BindMember<&SpaceShip::GetFuelRemaining>,
407             .AddFuelTank = DerivedBinder::BindMember<&SpaceShip::AddFuelTank>,
408             .ScanForTensorLifeforms = DerivedBinder::BindMember<&Derived::ScanForTensorLifeforms>,
409             .ReportAstrologicalData = DerivedBinder::BindMember<&Derived::ReportAstrologicalData>,
410         };
411 
412         return SpaceShipBinder::BindOps<fidl_test_spaceship_SpaceShip_dispatch>(
413             dispatcher, std::move(channel), this, &kOps);
414     }
415 };
416 
417 } // namespace
418 
419 BEGIN_TEST_CASE(spaceship_tests_cpp)
420 RUN_NAMED_TEST("fidl.test.spaceship.SpaceShip test", spaceship_test)
421 RUN_NAMED_TEST("fidl.test.spaceship.SpaceShip async test", spaceship_async_test)
422 END_TEST_CASE(spaceship_tests_cpp);
423