1 #include <memory>
2 
3 #include "gmock/gmock.h"
4 #include "nos/device.h"
5 #include "nos/NuggetClient.h"
6 #include "application.h"
7 #include "app_transport_test.h"
8 #include "util.h"
9 
10 using std::cout;
11 using std::string;
12 using std::unique_ptr;
13 
14 /*
15  * These test use the datagram protocol diretly to test that Citadel's transport
16  * implementation works as expected.
17  */
18 namespace {
19 
20 static const uint16_t crc16_table[256] = {
21   0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
22   0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
23   0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
24   0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
25   0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
26   0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
27   0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
28   0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
29   0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
30   0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
31   0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
32   0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
33   0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
34   0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
35   0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
36   0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
37   0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
38   0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
39   0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
40   0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
41   0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
42   0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
43   0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
44   0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
45   0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
46   0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
47   0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
48   0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
49   0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
50   0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
51   0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
52   0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
53 };
54 
crc16_update(const void * buf,uint32_t len,uint16_t crc)55 uint16_t crc16_update(const void *buf, uint32_t len, uint16_t crc) {
56   uint32_t i;
57   const uint8_t *bytes = reinterpret_cast<const uint8_t *>(buf);
58   for (i = 0; i < len; ++i) {
59     crc = crc16_table[((crc >> 8) ^ *bytes++) & 0xFF] ^ (crc << 8);
60   }
61   return crc;
62 }
63 
crc16(const void * buf,uint32_t len)64 uint16_t crc16(const void *buf, uint32_t len) {
65   return crc16_update(buf, len, 0);
66 }
67 
68 #define RETRY_COUNT 30
69 #define RETRY_WAIT_TIME_US 5000
70 
71 /*
72  * Read a datagram from the device, correctly handling retries.
73  */
nos_device_read(const struct nos_device * dev,uint32_t command,void * buf,uint32_t len)74 static int nos_device_read(const struct nos_device *dev, uint32_t command,
75                            void *buf, uint32_t len) {
76   int retries = RETRY_COUNT;
77   while (retries--) {
78     int err = dev->ops.read(dev->ctx, command, reinterpret_cast<uint8_t *>(buf), len);
79 
80     if (err == -EAGAIN) {
81       /* Linux driver returns EAGAIN error if Citadel chip is asleep.
82        * Give to the chip a little bit of time to awake and retry reading
83        * status again. */
84       usleep(RETRY_WAIT_TIME_US);
85       continue;
86     }
87     return -err;
88   }
89 
90   return ETIMEDOUT;
91 }
92 
93 /*
94  * Write a datagram to the device, correctly handling retries.
95  */
nos_device_write(const struct nos_device * dev,uint32_t command,const void * buf,uint32_t len)96 static int nos_device_write(const struct nos_device *dev, uint32_t command,
97                             const void *buf, uint32_t len) {
98   int retries = RETRY_COUNT;
99   while (retries--) {
100     int err = dev->ops.write(dev->ctx, command, reinterpret_cast<const uint8_t *>(buf), len);
101 
102     if (err == -EAGAIN) {
103       /* Linux driver returns EAGAIN error if Citadel chip is asleep.
104        * Give to the chip a little bit of time to awake and retry reading
105        * status again. */
106       usleep(RETRY_WAIT_TIME_US);
107       continue;
108     }
109     return -err;
110   }
111 
112   return ETIMEDOUT;
113 }
114 
115 /*
116  * The transport protocol has 4 stages:
117  *   1. Resetting the slave
118  *   2. Sending a command to the slave
119  *   3. Polling until the slave is done
120  *   4. Fetching the result from the slave
121  *
122  * There are CRCs used on the messages to ensure integrity. If the CRC fails,
123  * the the data transfer is retried. This can happen for the status message,
124  * arguments and command message or the reply data.
125  */
126 class TransportTest: public testing::Test {
127  protected:
128   static nos_device* dev;
129   static unique_ptr<nos::NuggetClient> client;
130   static unique_ptr<test_harness::TestHarness> uart_printer;
131 
132   static void SetUpTestCase();
133   static void TearDownTestCase();
134 
135   virtual void SetUp();
136 };
137 
138 nos_device* TransportTest::dev;
139 unique_ptr<nos::NuggetClient> TransportTest::client;
140 unique_ptr<test_harness::TestHarness> TransportTest::uart_printer;
141 
SetUpTestCase()142 void TransportTest::SetUpTestCase() {
143   uart_printer = test_harness::TestHarness::MakeUnique();
144 
145   client = nugget_tools::MakeDirectNuggetClient();
146   client->Open();
147   EXPECT_TRUE(client->IsOpen()) << "Unable to connect";
148   dev = client->Device();
149 }
150 
TearDownTestCase()151 void TransportTest::TearDownTestCase() {
152   dev = nullptr;
153   client->Close();
154   client.reset();
155 
156   uart_printer = nullptr;
157 }
158 
SetUp()159 void TransportTest::SetUp() {
160   // Reset and give it a bit of time to settle
161   ASSERT_TRUE(nugget_tools::RebootNuggetUnchecked(client.get()));
162   // Give a bit of time for the reboot to take effect
163   usleep(30000);
164 }
165 
StatusMatches(const transport_status & arg,uint32_t status,uint16_t flags,uint8_t * reply,uint16_t reply_len)166 bool StatusMatches(const transport_status& arg, uint32_t status, uint16_t flags,
167                    uint8_t* reply, uint16_t reply_len) {
168   bool ok = true;
169 
170   // v0 fields
171   ok &= arg.status == status;
172   ok &= arg.reply_len == reply_len;
173 
174   // v1 fields
175   ok &= arg.length == sizeof(transport_status);
176   ok &= arg.version == TRANSPORT_V1;
177   ok &= arg.flags == flags;
178 
179   // Check the status is a valid length
180   if (arg.length < STATUS_MIN_LENGTH || arg.length > STATUS_MAX_LENGTH) {
181     return false;
182   }
183 
184   // As of v1, the length shouldn\t be greater than transport_status
185   if (arg.length > sizeof(transport_status)) {
186     return false;
187   }
188 
189   // Check the CRCs are valid
190   transport_status st = arg;
191   st.crc = 0;
192   ok &= arg.crc == crc16(&st, st.length);
193   ok &= arg.reply_crc == crc16(reply, reply_len);
194 
195   return ok;
196 }
197 
198 MATCHER(IsIdle, "") {
199   return StatusMatches(arg,
200                        APP_STATUS_IDLE, 0,
201                        nullptr, 0);
202 }
203 
204 MATCHER(IsWorking, "") {
205   return StatusMatches(arg,
206                        APP_STATUS_IDLE, STATUS_FLAG_WORKING,
207                        nullptr, 0);
208 }
209 
210 MATCHER(IsTooMuch, "") {
211   return StatusMatches(arg,
212                        APP_STATUS_DONE | APP_ERROR_TOO_MUCH, 0,
213                        nullptr, 0);
214 }
215 
216 MATCHER(IsBadCrc, "") {
217   return StatusMatches(arg,
218                        APP_STATUS_DONE | APP_ERROR_CHECKSUM, 0,
219                        nullptr, 0);
220 }
221 
222 MATCHER(IsSuccess, "") {
223   return StatusMatches(arg,
224                        APP_STATUS_DONE | APP_SUCCESS, 0,
225                        nullptr, 0);
226 }
227 
228 MATCHER_P2(IsSuccessWithData, data, len, "") {
229   return StatusMatches(arg,
230                        APP_STATUS_DONE | APP_SUCCESS, 0,
231                        data, len);
232 }
233 
234 // Give the app time to complete rather than polling
WaitForApp()235 void WaitForApp() {
236   usleep(30000);
237 }
238 
239 // Calculate and set the CRC for the command from data or the struct
SetCommandInfoCrc(const void * arg,uint16_t arg_len,uint32_t command,void * command_info,uint16_t command_info_len)240 void SetCommandInfoCrc(const void* arg, uint16_t arg_len, uint32_t command,
241                        void* command_info, uint16_t command_info_len) {
242     uint16_t crc = crc16(&arg_len, sizeof(arg_len));
243     crc = crc16_update(arg, arg_len, crc);
244     crc = crc16_update(&command, sizeof(command), crc);
245     uint16_t* const info_crc
246         = (uint16_t*)&((uint8_t*)command_info)[offsetof(transport_command_info, crc)];
247     *info_crc = 0;
248     *info_crc = crc16_update(command_info, command_info_len, crc);
249 }
SetCommandInfoCrc(const void * arg,uint16_t arg_len,uint32_t command,transport_command_info * info)250 void SetCommandInfoCrc(const void* arg, uint16_t arg_len, uint32_t command,
251                        transport_command_info* info) {
252   SetCommandInfoCrc(arg, arg_len, command, info, sizeof(*info));
253 }
254 
255 /* The initial state is to be idle. */
TEST_F(TransportTest,ResetToIdle)256 TEST_F(TransportTest, ResetToIdle) {
257   transport_status status;
258   const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
259   ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
260   ASSERT_THAT(status, IsIdle());
261 }
262 
263 /* The master will be polling so need to confirm app is still working. */
TEST_F(TransportTest,CommandImmediatelyTriggersWorking)264 TEST_F(TransportTest, CommandImmediatelyTriggersWorking) {
265   { // Send "go" command
266     transport_command_info command_info = {};
267     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
268     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
269   }
270   { // Check status
271     transport_status status;
272     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
273     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
274     ASSERT_THAT(status, IsWorking());
275   }
276 }
277 
TEST_F(TransportTest,CommandBadCrc)278 TEST_F(TransportTest, CommandBadCrc) {
279   { // Send "go" command
280     transport_command_info command_info = {};
281     command_info.length = sizeof(transport_command_info);
282     command_info.version = TRANSPORT_V1;
283     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
284     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
285   }
286   // Let app process command
287   WaitForApp();
288   { // Check status
289     transport_status status;
290     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
291     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
292     ASSERT_THAT(status, IsBadCrc());
293   }
294 }
295 
TEST_F(TransportTest,TooMuchData)296 TEST_F(TransportTest, TooMuchData) {
297   { // Send data
298     uint8_t data[32] = {};
299     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
300     CMD_SET_PARAM(command, sizeof(data));
301     ASSERT_EQ(nos_device_write(dev, command, data, sizeof(data)), 0);
302   }
303   { // Send "go" command
304     transport_command_info command_info = {};
305     command_info.version = TRANSPORT_V1;
306     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
307     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
308   }
309   { // Check status
310     transport_status status;
311     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
312     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
313     ASSERT_THAT(status, IsTooMuch());
314   }
315 }
316 
317 /* Until the app says it is done, it is working. */
TEST_F(TransportTest,HangKeepsWorking)318 TEST_F(TransportTest, HangKeepsWorking) {
319   { // Send "go" command
320     transport_command_info command_info = {};
321     command_info.length = sizeof(transport_command_info);
322     command_info.version = TRANSPORT_V1;
323     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_HANG);
324     SetCommandInfoCrc(nullptr, 0, command, &command_info);
325     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
326   }
327   { // Check status
328     transport_status status;
329     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
330     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
331     ASSERT_THAT(status, IsWorking());
332   }
333   // Let app process command
334   WaitForApp();
335   { // Check status
336     transport_status status;
337     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
338     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
339     ASSERT_THAT(status, IsWorking());
340   }
341   // Let app process command
342   WaitForApp();
343   { // Check status
344     transport_status status;
345     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
346     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
347     ASSERT_THAT(status, IsWorking());
348   }
349 }
350 
351 /* While the app is working, the master can only wait and can't modify memory. */
TEST_F(TransportTest,CannotClearStatusWhileWorking)352 TEST_F(TransportTest, CannotClearStatusWhileWorking) {
353   { // Send "go" command
354     transport_command_info command_info = {};
355     command_info.version = TRANSPORT_V1;
356     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_HANG);
357     SetCommandInfoCrc(nullptr, 0, command, &command_info);
358     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
359   }
360   { // Attempt to clear status
361     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_TRANSPORT;
362     ASSERT_EQ(nos_device_write(dev, command, nullptr, 0), 0);
363   }
364   { // Check status
365     transport_status status;
366     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
367     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
368     ASSERT_THAT(status, IsWorking());
369   }
370 }
371 
372 /* Protect from any race conditions that could arise. */
TEST_F(TransportTest,CannotStartNewCommandWhileWorking)373 TEST_F(TransportTest, CannotStartNewCommandWhileWorking) {
374   { // Send "go" command
375     transport_command_info command_info = {};
376     command_info.length = sizeof(transport_command_info);
377     command_info.version = TRANSPORT_V1;
378     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_HANG);
379     SetCommandInfoCrc(nullptr, 0, command, &command_info);
380     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
381   }
382   { // Send "go" command
383     transport_command_info command_info = {};
384     command_info.length = sizeof(transport_command_info);
385     command_info.version = TRANSPORT_V1;
386     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_1234);
387     SetCommandInfoCrc(nullptr, 0, command, &command_info);
388     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
389   }
390   // Let app process command
391   WaitForApp();
392   WaitForApp();
393   { // Check status
394     transport_status status;
395     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
396     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
397     ASSERT_THAT(status, IsWorking());
398   }
399 }
400 
TEST_F(TransportTest,CommandSuccess)401 TEST_F(TransportTest, CommandSuccess) {
402   { // Send "go" command
403     transport_command_info command_info = {};
404     command_info.length = sizeof(transport_command_info);
405     command_info.version = TRANSPORT_V1;
406     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
407     SetCommandInfoCrc(nullptr, 0, command, &command_info);
408     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
409   }
410   // Let app process command
411   WaitForApp();
412   { // Check status
413     transport_status status;
414     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
415     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
416     ASSERT_THAT(status, IsSuccess());
417   }
418 }
419 
TEST_F(TransportTest,CommandSuccessWithData)420 TEST_F(TransportTest, CommandSuccessWithData) {
421   uint8_t data[4];
422   { // Send "go" command
423     transport_command_info command_info = {};
424     command_info.length = sizeof(transport_command_info);
425     command_info.version = TRANSPORT_V1;
426     command_info.reply_len_hint = sizeof(data);
427     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_1234);
428     SetCommandInfoCrc(nullptr, 0, command, &command_info);
429     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
430   }
431   // Let app process command
432   WaitForApp();
433   { // Check status
434     transport_status status;
435     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
436     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
437     ASSERT_EQ(status.reply_len, sizeof(data));
438 
439     const uint32_t data_command = CMD_ID(APP_ID_TRANSPORT_TEST)
440                                 | CMD_IS_READ | CMD_IS_DATA | CMD_TRANSPORT;
441     ASSERT_EQ(nos_device_read(dev, data_command, data, sizeof(data)), 0);
442     ASSERT_THAT(status, IsSuccessWithData(data, sizeof(data)));
443   }
444 }
445 
446 /* The crc is only calculated over the data the master will read. */
TEST_F(TransportTest,CommandSuccessReplyLenHintRespected)447 TEST_F(TransportTest, CommandSuccessReplyLenHintRespected) {
448   constexpr uint16_t reply_len_hint = 2; // This is less than the actual response
449   { // Send "go" command
450     transport_command_info command_info = {};
451     command_info.length = sizeof(transport_command_info);
452     command_info.version = TRANSPORT_V1;
453     command_info.reply_len_hint = reply_len_hint;
454     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_1234);
455     SetCommandInfoCrc(nullptr, 0, command, &command_info);
456     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
457   }
458   // Let app process command
459   WaitForApp();
460   { // Check status
461     uint8_t data[4];
462     transport_status status;
463     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
464     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
465     ASSERT_EQ(status.reply_len, reply_len_hint);
466 
467     const uint32_t data_command = CMD_ID(APP_ID_TRANSPORT_TEST)
468                                 | CMD_IS_READ | CMD_IS_DATA | CMD_TRANSPORT;
469     ASSERT_EQ(nos_device_read(dev, data_command, data, reply_len_hint), 0);
470     // crc is only calculated over the data the master will read
471     ASSERT_THAT(status, IsSuccessWithData(data, reply_len_hint));
472   }
473 }
474 
475 /* If there was a transmission error, need to be able to re-read data. */
TEST_F(TransportTest,CommandSuccessRetryReadingData)476 TEST_F(TransportTest, CommandSuccessRetryReadingData) {
477   uint8_t data[4];
478   { // Send "go" command
479     transport_command_info command_info = {};
480     command_info.length = sizeof(transport_command_info);
481     command_info.version = TRANSPORT_V1;
482     command_info.reply_len_hint = sizeof(data);
483     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_1234);
484     SetCommandInfoCrc(nullptr, 0, command, &command_info);
485     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
486   }
487   // Let app process command
488   WaitForApp();
489   { // Check status
490     transport_status status;
491     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
492     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
493     ASSERT_EQ(status.reply_len, sizeof(data));
494 
495     // This could happen if there was a crc error
496     for (int i = 0; i < 3; ++i) {
497       const uint32_t data_command = CMD_ID(APP_ID_TRANSPORT_TEST)
498                                   | CMD_IS_READ | CMD_IS_DATA | CMD_TRANSPORT;
499       ASSERT_EQ(nos_device_read(dev, data_command, data, sizeof(data)), 0);
500       ASSERT_THAT(status, IsSuccessWithData(data, sizeof(data)));
501     }
502   }
503 }
504 
TEST_F(TransportTest,ClearStatusAfterSuccess)505 TEST_F(TransportTest, ClearStatusAfterSuccess) {
506   { // Send "go" command
507     transport_command_info command_info = {};
508     command_info.length = sizeof(transport_command_info);
509     command_info.version = TRANSPORT_V1;
510     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
511     SetCommandInfoCrc(nullptr, 0, command, &command_info);
512     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
513   }
514   // Let app process command
515   WaitForApp();
516   { // Clear status
517     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_TRANSPORT;
518     ASSERT_EQ(nos_device_write(dev, command, nullptr, 0), 0);
519   }
520   { // Check status
521     transport_status status;
522     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
523     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
524     ASSERT_THAT(status, IsIdle());
525   }
526 }
527 
TEST_F(TransportTest,ClearStatusAfterError)528 TEST_F(TransportTest, ClearStatusAfterError) {
529   { // Send "go" command
530     transport_command_info command_info = {};
531     command_info.length = sizeof(transport_command_info);
532     command_info.version = TRANSPORT_V1;
533     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
534     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
535   }
536   // Let app process command
537   WaitForApp();
538   { // Clear status
539     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_TRANSPORT;
540     ASSERT_EQ(nos_device_write(dev, command, nullptr, 0), 0);
541   }
542   { // Check status
543     transport_status status;
544     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
545     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
546     ASSERT_THAT(status, IsIdle());
547   }
548 }
549 
TEST_F(TransportTest,SendArgumentData)550 TEST_F(TransportTest, SendArgumentData) {
551   const uint32_t data = 0x09080706;
552   { // Send data
553     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
554     CMD_SET_PARAM(command, sizeof(data));
555     ASSERT_EQ(nos_device_write(dev, command, &data, sizeof(data)), 0);
556   }
557   { // Send "go" command
558     transport_command_info command_info = {};
559     command_info.length = sizeof(transport_command_info);
560     command_info.version = TRANSPORT_V1;
561     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_9876);
562     SetCommandInfoCrc(&data, sizeof(data), command, &command_info);
563     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
564   }
565   // Let app process command
566   WaitForApp();
567   { // Check status
568     transport_status status;
569     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
570     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
571     ASSERT_THAT(status, IsSuccess());
572   }
573 }
574 
575 /* Setting CMD_MORE_TO_COME appends new data. */
TEST_F(TransportTest,SendArgumentDataInMultipleDatagrams)576 TEST_F(TransportTest, SendArgumentDataInMultipleDatagrams) {
577   const uint32_t data = 0x09080706;
578   { // Send data1
579     const uint16_t data1 = 0x0706;
580     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
581     CMD_SET_PARAM(command, sizeof(data1));
582     ASSERT_EQ(nos_device_write(dev, command, &data1, sizeof(data1)), 0);
583   }
584   { // Send data2
585     const uint16_t data2 = 0x0908;
586     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST)
587                      | CMD_IS_DATA | CMD_TRANSPORT | CMD_MORE_TO_COME;
588     CMD_SET_PARAM(command, sizeof(data2));
589     ASSERT_EQ(nos_device_write(dev, command, &data2, sizeof(data2)), 0);
590   }
591   { // Send "go" command
592     transport_command_info command_info = {};
593     command_info.length = sizeof(transport_command_info);
594     command_info.version = TRANSPORT_V1;
595     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_9876);
596     SetCommandInfoCrc(&data, sizeof(data), command, &command_info);
597     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
598   }
599   // Let app process command
600   WaitForApp();
601   { // Check status
602     transport_status status;
603     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
604     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
605     ASSERT_THAT(status, IsSuccess());
606   }
607 }
608 
609 /* Not setting the CMD_MORE_TO_COME flag overwrites rather than appends. */
TEST_F(TransportTest,SendWrongArgumentDataByRestarting)610 TEST_F(TransportTest, SendWrongArgumentDataByRestarting) {
611   const uint32_t data = 0x09080706;
612   { // Send data1
613     const uint16_t data1 = 0x0706;
614     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
615     CMD_SET_PARAM(command, sizeof(data1));
616     ASSERT_EQ(nos_device_write(dev, command, &data1, sizeof(data1)), 0);
617   }
618   { // Send data2, restarting the args
619     const uint16_t data2 = 0x0908;
620     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
621     CMD_SET_PARAM(command, sizeof(data2));
622     ASSERT_EQ(nos_device_write(dev, command, &data2, sizeof(data2)), 0);
623   }
624   { // Send "go" command
625     transport_command_info command_info = {};
626     command_info.length = sizeof(transport_command_info);
627     command_info.version = TRANSPORT_V1;
628     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_9876);
629     SetCommandInfoCrc(&data, sizeof(data), command, &command_info);
630     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
631   }
632   // Let app process command
633   WaitForApp();
634   { // Check status, bad crc as the args data is wrong
635     transport_status status;
636     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
637     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
638     ASSERT_THAT(status, IsBadCrc());
639   }
640 }
641 
642 /* Not setting the CMD_MORE_TO_COME flag clears previous data. */
TEST_F(TransportTest,SendArgumentDataInMultipleDatagramsWithRestart)643 TEST_F(TransportTest, SendArgumentDataInMultipleDatagramsWithRestart) {
644   const uint32_t data = 0x09080706;
645   { // Send data1
646     const uint8_t spam[6] = {12, 46, 123, 63, 23, 75};
647     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
648     CMD_SET_PARAM(command, sizeof(spam));
649     ASSERT_EQ(nos_device_write(dev, command, spam, sizeof(spam)), 0);
650   }
651   { // Send data1, restarting the args
652     const uint16_t data1 = 0x0706;
653     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
654     CMD_SET_PARAM(command, sizeof(data1));
655     ASSERT_EQ(nos_device_write(dev, command, &data1, sizeof(data1)), 0);
656   }
657   { // Send data2
658     const uint16_t data2 = 0x0908;
659     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST)
660                      | CMD_IS_DATA | CMD_TRANSPORT | CMD_MORE_TO_COME;
661     CMD_SET_PARAM(command, sizeof(data2));
662     ASSERT_EQ(nos_device_write(dev, command, &data2, sizeof(data2)), 0);
663   }
664   { // Send "go" command
665     transport_command_info command_info = {};
666     command_info.length = sizeof(transport_command_info);
667     command_info.version = TRANSPORT_V1;
668     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_9876);
669     SetCommandInfoCrc(&data, sizeof(data), command, &command_info);
670     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
671   }
672   // Let app process command
673   WaitForApp();
674   { // Check status
675     transport_status status;
676     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
677     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
678     ASSERT_THAT(status, IsSuccess());
679   }
680 }
681 
682 // Forward compatibility
683 
684 /* New command info fields may be add in future versions. The crc is still
685  * calculated over them to ensure data integrity but, otherwise, the values are
686  * ignored. */
TEST_F(TransportTest,NewCommandInfoIsIgnored)687 TEST_F(TransportTest, NewCommandInfoIsIgnored) {
688   { // Send "go" command
689     union {
690       transport_command_info info;
691       uint8_t buffer[COMMAND_INFO_MAX_LENGTH];
692     } command_info = {};
693     memset(command_info.buffer, 0x48, COMMAND_INFO_MAX_LENGTH);
694     command_info.info.length = COMMAND_INFO_MAX_LENGTH;
695     command_info.info.version = TRANSPORT_V1;
696     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
697     // CRC is still calculated over all the data but new fields aren't used
698     SetCommandInfoCrc(nullptr, 0, command, &command_info, sizeof(command_info));
699     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
700   }
701   // Let app process command
702   WaitForApp();
703   { // Check status
704     transport_status status;
705     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
706     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
707     ASSERT_THAT(status, IsSuccess());
708   }
709 }
710 
711 /* If the protocol adds more data to the command info datagram, it is not
712  * included in the crc. */
TEST_F(TransportTest,CommandCrcOnlyCoversCommandInfoStruct)713 TEST_F(TransportTest, CommandCrcOnlyCoversCommandInfoStruct) {
714   { // Send "go" command
715     union {
716       transport_command_info info;
717       /* The extra byte should not be included in the crc */
718       uint8_t buffer[COMMAND_INFO_MAX_LENGTH + 1];
719     } command_info = {};
720     command_info.info.length = COMMAND_INFO_MAX_LENGTH;
721     command_info.info.version = TRANSPORT_V1;
722     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
723     SetCommandInfoCrc(nullptr, 0, command, &command_info, sizeof(command_info));
724     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
725   }
726   // Let app process command
727   WaitForApp();
728   { // Check status
729     transport_status status;
730     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
731     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
732     ASSERT_THAT(status, IsBadCrc());
733   }
734 }
735 
736 /* Future protocol updates may require more command info data which will be
737  * added after the COMMAND_INFO_MAX_LENGTH bytes currently reserved for the
738  * command info struct. The extra data should be ignored to allow for
739  * compatibility with such an upgrade. */
TEST_F(TransportTest,NewCommandInfoStructIsIgnored)740 TEST_F(TransportTest, NewCommandInfoStructIsIgnored) {
741   { // Send "go" command
742     union {
743       transport_command_info info;
744       struct {
745         uint8_t info[COMMAND_INFO_MAX_LENGTH];
746         uint8_t new_struct[48];
747       } buffer;
748     } command_info = {};
749     memset(command_info.buffer.info, 0xB6, COMMAND_INFO_MAX_LENGTH);
750     memset(command_info.buffer.new_struct, 0x19, sizeof(command_info.buffer.new_struct));
751     command_info.info.length = COMMAND_INFO_MAX_LENGTH;
752     command_info.info.version = TRANSPORT_V1;
753     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
754     // CRC is still calculated over all the data but new fields aren't used
755     SetCommandInfoCrc(nullptr, 0, command, &command_info, sizeof(command_info));
756     ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
757   }
758   // Let app process command
759   WaitForApp();
760   { // Check status
761     transport_status status;
762     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
763     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
764     ASSERT_THAT(status, IsSuccess());
765   }
766 }
767 
768 // Backward compatibility
769 
770 /* V0 does not send any checksums or command info */
TEST_F(TransportTest,CompatibleWithV0)771 TEST_F(TransportTest, CompatibleWithV0) {
772   { // Send data
773     uint8_t data[4] = {23, 54, 133, 249};
774     uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
775     CMD_SET_PARAM(command, sizeof(data));
776     ASSERT_EQ(nos_device_write(dev, command, &data, sizeof(data)), 0);
777   }
778   { // Check status
779     transport_status status;
780     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
781     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
782     ASSERT_THAT(status, IsIdle());
783   }
784   { // Send "go" command (without command info or crc)
785     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
786     ASSERT_EQ(nos_device_write(dev, command, nullptr, 0), 0);
787   }
788   // Let app process command
789   WaitForApp();
790   { // Check status
791     transport_status status;
792     const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
793     ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
794     ASSERT_THAT(status, IsSuccess());
795   }
796 }
797 
798 }  // namespace
799