blob: b57ccae4dc6755bf916b1aaf76ab179b0549c3cc [file] [log] [blame]
#include <memory>
#include "gmock/gmock.h"
#include "nos/device.h"
#include "nos/NuggetClient.h"
#include "application.h"
#include "app_transport_test.h"
#include "util.h"
using std::cout;
using std::string;
using std::unique_ptr;
/*
* These test use the datagram protocol diretly to test that Citadel's transport
* implementation works as expected.
*/
namespace {
static const uint16_t crc16_table[256] = {
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};
uint16_t crc16_update(const void *buf, uint32_t len, uint16_t crc) {
uint32_t i;
const uint8_t *bytes = reinterpret_cast<const uint8_t *>(buf);
for (i = 0; i < len; ++i) {
crc = crc16_table[((crc >> 8) ^ *bytes++) & 0xFF] ^ (crc << 8);
}
return crc;
}
uint16_t crc16(const void *buf, uint32_t len) {
return crc16_update(buf, len, 0);
}
#define RETRY_COUNT 30
#define RETRY_WAIT_TIME_US 5000
/*
* Read a datagram from the device, correctly handling retries.
*/
static int nos_device_read(const struct nos_device *dev, uint32_t command,
void *buf, uint32_t len) {
int retries = RETRY_COUNT;
while (retries--) {
int err = dev->ops.read(dev->ctx, command, reinterpret_cast<uint8_t *>(buf), len);
if (err == -EAGAIN) {
/* Linux driver returns EAGAIN error if Citadel chip is asleep.
* Give to the chip a little bit of time to awake and retry reading
* status again. */
usleep(RETRY_WAIT_TIME_US);
continue;
}
return -err;
}
return ETIMEDOUT;
}
/*
* Write a datagram to the device, correctly handling retries.
*/
static int nos_device_write(const struct nos_device *dev, uint32_t command,
const void *buf, uint32_t len) {
int retries = RETRY_COUNT;
while (retries--) {
int err = dev->ops.write(dev->ctx, command, reinterpret_cast<const uint8_t *>(buf), len);
if (err == -EAGAIN) {
/* Linux driver returns EAGAIN error if Citadel chip is asleep.
* Give to the chip a little bit of time to awake and retry reading
* status again. */
usleep(RETRY_WAIT_TIME_US);
continue;
}
return -err;
}
return ETIMEDOUT;
}
/*
* The transport protocol has 4 stages:
* 1. Resetting the slave
* 2. Sending a command to the slave
* 3. Polling until the slave is done
* 4. Fetching the result from the slave
*
* There are CRCs used on the messages to ensure integrity. If the CRC fails,
* the the data transfer is retried. This can happen for the status message,
* arguments and command message or the reply data.
*/
class TransportTest: public testing::Test {
protected:
static nos_device* dev;
static unique_ptr<nos::NuggetClient> client;
static unique_ptr<test_harness::TestHarness> uart_printer;
static void SetUpTestCase();
static void TearDownTestCase();
virtual void SetUp();
};
nos_device* TransportTest::dev;
unique_ptr<nos::NuggetClient> TransportTest::client;
unique_ptr<test_harness::TestHarness> TransportTest::uart_printer;
void TransportTest::SetUpTestCase() {
uart_printer = test_harness::TestHarness::MakeUnique();
client = nugget_tools::MakeDirectNuggetClient();
client->Open();
EXPECT_TRUE(client->IsOpen()) << "Unable to connect";
dev = client->Device();
}
void TransportTest::TearDownTestCase() {
dev = nullptr;
client->Close();
client.reset();
uart_printer = nullptr;
}
void TransportTest::SetUp() {
// Reset and give it a bit of time to settle
ASSERT_TRUE(nugget_tools::RebootNuggetUnchecked(client.get()));
// Give a bit of time for the reboot to take effect
usleep(30000);
}
bool StatusMatches(const transport_status& arg, uint32_t status, uint16_t flags,
uint8_t* reply, uint16_t reply_len) {
bool ok = true;
// v0 fields
ok &= arg.status == status;
ok &= arg.reply_len == reply_len;
// v1 fields
ok &= arg.length == sizeof(transport_status);
ok &= arg.version == TRANSPORT_V1;
ok &= arg.flags == flags;
// Check the status is a valid length
if (arg.length < STATUS_MIN_LENGTH || arg.length > STATUS_MAX_LENGTH) {
return false;
}
// As of v1, the length shouldn\t be greater than transport_status
if (arg.length > sizeof(transport_status)) {
return false;
}
// Check the CRCs are valid
transport_status st = arg;
st.crc = 0;
ok &= arg.crc == crc16(&st, st.length);
ok &= arg.reply_crc == crc16(reply, reply_len);
return ok;
}
MATCHER(IsIdle, "") {
return StatusMatches(arg,
APP_STATUS_IDLE, 0,
nullptr, 0);
}
MATCHER(IsWorking, "") {
return StatusMatches(arg,
APP_STATUS_IDLE, STATUS_FLAG_WORKING,
nullptr, 0);
}
MATCHER(IsTooMuch, "") {
return StatusMatches(arg,
APP_STATUS_DONE | APP_ERROR_TOO_MUCH, 0,
nullptr, 0);
}
MATCHER(IsBadCrc, "") {
return StatusMatches(arg,
APP_STATUS_DONE | APP_ERROR_CHECKSUM, 0,
nullptr, 0);
}
MATCHER(IsSuccess, "") {
return StatusMatches(arg,
APP_STATUS_DONE | APP_SUCCESS, 0,
nullptr, 0);
}
MATCHER_P2(IsSuccessWithData, data, len, "") {
return StatusMatches(arg,
APP_STATUS_DONE | APP_SUCCESS, 0,
data, len);
}
// Give the app time to complete rather than polling
void WaitForApp() {
usleep(30000);
}
// Calculate and set the CRC for the command from data or the struct
void SetCommandInfoCrc(const void* arg, uint16_t arg_len, uint32_t command,
void* command_info, uint16_t command_info_len) {
uint16_t crc = crc16(&arg_len, sizeof(arg_len));
crc = crc16_update(arg, arg_len, crc);
crc = crc16_update(&command, sizeof(command), crc);
uint16_t* const info_crc
= (uint16_t*)&((uint8_t*)command_info)[offsetof(transport_command_info, crc)];
*info_crc = 0;
*info_crc = crc16_update(command_info, command_info_len, crc);
}
void SetCommandInfoCrc(const void* arg, uint16_t arg_len, uint32_t command,
transport_command_info* info) {
SetCommandInfoCrc(arg, arg_len, command, info, sizeof(*info));
}
/* The initial state is to be idle. */
TEST_F(TransportTest, ResetToIdle) {
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsIdle());
}
/* The master will be polling so need to confirm app is still working. */
TEST_F(TransportTest, CommandImmediatelyTriggersWorking) {
{ // Send "go" command
transport_command_info command_info = {};
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsWorking());
}
}
TEST_F(TransportTest, CommandBadCrc) {
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsBadCrc());
}
}
TEST_F(TransportTest, TooMuchData) {
{ // Send data
uint8_t data[32] = {};
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
CMD_SET_PARAM(command, sizeof(data));
ASSERT_EQ(nos_device_write(dev, command, data, sizeof(data)), 0);
}
{ // Send "go" command
transport_command_info command_info = {};
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsTooMuch());
}
}
/* Until the app says it is done, it is working. */
TEST_F(TransportTest, HangKeepsWorking) {
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_HANG);
SetCommandInfoCrc(nullptr, 0, command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsWorking());
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsWorking());
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsWorking());
}
}
/* While the app is working, the master can only wait and can't modify memory. */
TEST_F(TransportTest, CannotClearStatusWhileWorking) {
{ // Send "go" command
transport_command_info command_info = {};
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_HANG);
SetCommandInfoCrc(nullptr, 0, command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
{ // Attempt to clear status
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_TRANSPORT;
ASSERT_EQ(nos_device_write(dev, command, nullptr, 0), 0);
}
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsWorking());
}
}
/* Protect from any race conditions that could arise. */
TEST_F(TransportTest, CannotStartNewCommandWhileWorking) {
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_HANG);
SetCommandInfoCrc(nullptr, 0, command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_1234);
SetCommandInfoCrc(nullptr, 0, command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsWorking());
}
}
TEST_F(TransportTest, CommandSuccess) {
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
SetCommandInfoCrc(nullptr, 0, command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsSuccess());
}
}
TEST_F(TransportTest, CommandSuccessWithData) {
uint8_t data[4];
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
command_info.reply_len_hint = sizeof(data);
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_1234);
SetCommandInfoCrc(nullptr, 0, command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_EQ(status.reply_len, sizeof(data));
const uint32_t data_command = CMD_ID(APP_ID_TRANSPORT_TEST)
| CMD_IS_READ | CMD_IS_DATA | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, data_command, data, sizeof(data)), 0);
ASSERT_THAT(status, IsSuccessWithData(data, sizeof(data)));
}
}
/* The crc is only calculated over the data the master will read. */
TEST_F(TransportTest, CommandSuccessReplyLenHintRespected) {
constexpr uint16_t reply_len_hint = 2; // This is less than the actual response
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
command_info.reply_len_hint = reply_len_hint;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_1234);
SetCommandInfoCrc(nullptr, 0, command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
uint8_t data[4];
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_EQ(status.reply_len, reply_len_hint);
const uint32_t data_command = CMD_ID(APP_ID_TRANSPORT_TEST)
| CMD_IS_READ | CMD_IS_DATA | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, data_command, data, reply_len_hint), 0);
// crc is only calculated over the data the master will read
ASSERT_THAT(status, IsSuccessWithData(data, reply_len_hint));
}
}
/* If there was a transmission error, need to be able to re-read data. */
TEST_F(TransportTest, CommandSuccessRetryReadingData) {
uint8_t data[4];
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
command_info.reply_len_hint = sizeof(data);
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_1234);
SetCommandInfoCrc(nullptr, 0, command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_EQ(status.reply_len, sizeof(data));
// This could happen if there was a crc error
for (int i = 0; i < 3; ++i) {
const uint32_t data_command = CMD_ID(APP_ID_TRANSPORT_TEST)
| CMD_IS_READ | CMD_IS_DATA | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, data_command, data, sizeof(data)), 0);
ASSERT_THAT(status, IsSuccessWithData(data, sizeof(data)));
}
}
}
TEST_F(TransportTest, ClearStatusAfterSuccess) {
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
SetCommandInfoCrc(nullptr, 0, command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Clear status
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_TRANSPORT;
ASSERT_EQ(nos_device_write(dev, command, nullptr, 0), 0);
}
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsIdle());
}
}
TEST_F(TransportTest, ClearStatusAfterError) {
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Clear status
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_TRANSPORT;
ASSERT_EQ(nos_device_write(dev, command, nullptr, 0), 0);
}
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsIdle());
}
}
TEST_F(TransportTest, SendArgumentData) {
const uint32_t data = 0x09080706;
{ // Send data
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
CMD_SET_PARAM(command, sizeof(data));
ASSERT_EQ(nos_device_write(dev, command, &data, sizeof(data)), 0);
}
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_9876);
SetCommandInfoCrc(&data, sizeof(data), command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsSuccess());
}
}
/* Setting CMD_MORE_TO_COME appends new data. */
TEST_F(TransportTest, SendArgumentDataInMultipleDatagrams) {
const uint32_t data = 0x09080706;
{ // Send data1
const uint16_t data1 = 0x0706;
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
CMD_SET_PARAM(command, sizeof(data1));
ASSERT_EQ(nos_device_write(dev, command, &data1, sizeof(data1)), 0);
}
{ // Send data2
const uint16_t data2 = 0x0908;
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST)
| CMD_IS_DATA | CMD_TRANSPORT | CMD_MORE_TO_COME;
CMD_SET_PARAM(command, sizeof(data2));
ASSERT_EQ(nos_device_write(dev, command, &data2, sizeof(data2)), 0);
}
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_9876);
SetCommandInfoCrc(&data, sizeof(data), command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsSuccess());
}
}
/* Not setting the CMD_MORE_TO_COME flag overwrites rather than appends. */
TEST_F(TransportTest, SendWrongArgumentDataByRestarting) {
const uint32_t data = 0x09080706;
{ // Send data1
const uint16_t data1 = 0x0706;
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
CMD_SET_PARAM(command, sizeof(data1));
ASSERT_EQ(nos_device_write(dev, command, &data1, sizeof(data1)), 0);
}
{ // Send data2, restarting the args
const uint16_t data2 = 0x0908;
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
CMD_SET_PARAM(command, sizeof(data2));
ASSERT_EQ(nos_device_write(dev, command, &data2, sizeof(data2)), 0);
}
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_9876);
SetCommandInfoCrc(&data, sizeof(data), command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status, bad crc as the args data is wrong
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsBadCrc());
}
}
/* Not setting the CMD_MORE_TO_COME flag clears previous data. */
TEST_F(TransportTest, SendArgumentDataInMultipleDatagramsWithRestart) {
const uint32_t data = 0x09080706;
{ // Send data1
const uint8_t spam[6] = {12, 46, 123, 63, 23, 75};
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
CMD_SET_PARAM(command, sizeof(spam));
ASSERT_EQ(nos_device_write(dev, command, spam, sizeof(spam)), 0);
}
{ // Send data1, restarting the args
const uint16_t data1 = 0x0706;
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
CMD_SET_PARAM(command, sizeof(data1));
ASSERT_EQ(nos_device_write(dev, command, &data1, sizeof(data1)), 0);
}
{ // Send data2
const uint16_t data2 = 0x0908;
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST)
| CMD_IS_DATA | CMD_TRANSPORT | CMD_MORE_TO_COME;
CMD_SET_PARAM(command, sizeof(data2));
ASSERT_EQ(nos_device_write(dev, command, &data2, sizeof(data2)), 0);
}
{ // Send "go" command
transport_command_info command_info = {};
command_info.length = sizeof(transport_command_info);
command_info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_9876);
SetCommandInfoCrc(&data, sizeof(data), command, &command_info);
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsSuccess());
}
}
// Forward compatibility
/* New command info fields may be add in future versions. The crc is still
* calculated over them to ensure data integrity but, otherwise, the values are
* ignored. */
TEST_F(TransportTest, NewCommandInfoIsIgnored) {
{ // Send "go" command
union {
transport_command_info info;
uint8_t buffer[COMMAND_INFO_MAX_LENGTH];
} command_info = {};
memset(command_info.buffer, 0x48, COMMAND_INFO_MAX_LENGTH);
command_info.info.length = COMMAND_INFO_MAX_LENGTH;
command_info.info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
// CRC is still calculated over all the data but new fields aren't used
SetCommandInfoCrc(nullptr, 0, command, &command_info, sizeof(command_info));
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsSuccess());
}
}
/* If the protocol adds more data to the command info datagram, it is not
* included in the crc. */
TEST_F(TransportTest, CommandCrcOnlyCoversCommandInfoStruct) {
{ // Send "go" command
union {
transport_command_info info;
/* The extra byte should not be included in the crc */
uint8_t buffer[COMMAND_INFO_MAX_LENGTH + 1];
} command_info = {};
command_info.info.length = COMMAND_INFO_MAX_LENGTH;
command_info.info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(0);
SetCommandInfoCrc(nullptr, 0, command, &command_info, sizeof(command_info));
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsBadCrc());
}
}
/* Future protocol updates may require more command info data which will be
* added after the COMMAND_INFO_MAX_LENGTH bytes currently reserved for the
* command info struct. The extra data should be ignored to allow for
* compatibility with such an upgrade. */
TEST_F(TransportTest, NewCommandInfoStructIsIgnored) {
{ // Send "go" command
union {
transport_command_info info;
struct {
uint8_t info[COMMAND_INFO_MAX_LENGTH];
uint8_t new_struct[48];
} buffer;
} command_info = {};
memset(command_info.buffer.info, 0xB6, COMMAND_INFO_MAX_LENGTH);
memset(command_info.buffer.new_struct, 0x19, sizeof(command_info.buffer.new_struct));
command_info.info.length = COMMAND_INFO_MAX_LENGTH;
command_info.info.version = TRANSPORT_V1;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
// CRC is still calculated over all the data but new fields aren't used
SetCommandInfoCrc(nullptr, 0, command, &command_info, sizeof(command_info));
ASSERT_EQ(nos_device_write(dev, command, &command_info, sizeof(command_info)), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsSuccess());
}
}
// Backward compatibility
/* V0 does not send any checksums or command info */
TEST_F(TransportTest, CompatibleWithV0) {
{ // Send data
uint8_t data[4] = {23, 54, 133, 249};
uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_DATA | CMD_TRANSPORT;
CMD_SET_PARAM(command, sizeof(data));
ASSERT_EQ(nos_device_write(dev, command, &data, sizeof(data)), 0);
}
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsIdle());
}
{ // Send "go" command (without command info or crc)
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_PARAM(TRANSPORT_TEST_NOP);
ASSERT_EQ(nos_device_write(dev, command, nullptr, 0), 0);
}
// Let app process command
WaitForApp();
{ // Check status
transport_status status;
const uint32_t command = CMD_ID(APP_ID_TRANSPORT_TEST) | CMD_IS_READ | CMD_TRANSPORT;
ASSERT_EQ(nos_device_read(dev, command, &status, sizeof(transport_status)), 0);
ASSERT_THAT(status, IsSuccess());
}
}
} // namespace