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