268 lines
7.6 KiB
C
268 lines
7.6 KiB
C
#include "pcan_boot.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
typedef struct {
|
|
uint8_t flash[2][64];
|
|
uint32_t last_id;
|
|
uint8_t last_data[8];
|
|
uint8_t last_dlc;
|
|
unsigned sends;
|
|
unsigned erases;
|
|
unsigned writes;
|
|
uint8_t pending_slot;
|
|
bool authorized;
|
|
bool verified;
|
|
bool rebooted;
|
|
} fake_t;
|
|
|
|
static bool fake_send(void *user, uint32_t id, const uint8_t *data, uint8_t dlc)
|
|
{
|
|
fake_t *f = (fake_t *)user;
|
|
f->last_id = id;
|
|
f->last_dlc = dlc;
|
|
(void)memset(f->last_data, 0, sizeof(f->last_data));
|
|
if (dlc != 0U) {
|
|
(void)memcpy(f->last_data, data, dlc);
|
|
}
|
|
f->sends++;
|
|
return true;
|
|
}
|
|
|
|
static bool fake_erase(void *user, uint8_t slot)
|
|
{
|
|
fake_t *f = (fake_t *)user;
|
|
(void)memset(f->flash[slot], 0xFF, sizeof(f->flash[slot]));
|
|
f->erases++;
|
|
return true;
|
|
}
|
|
|
|
static bool fake_write(void *user, uint8_t slot, uint32_t offset,
|
|
const uint8_t *data, uint8_t length)
|
|
{
|
|
fake_t *f = (fake_t *)user;
|
|
if ((slot > 1U) || (offset + length > sizeof(f->flash[slot]))) {
|
|
return false;
|
|
}
|
|
(void)memcpy(&f->flash[slot][offset], data, length);
|
|
f->writes++;
|
|
return true;
|
|
}
|
|
|
|
static bool fake_authorize(void *user, const pcan_boot_manifest_t *manifest)
|
|
{
|
|
fake_t *f = (fake_t *)user;
|
|
(void)manifest;
|
|
return f->authorized;
|
|
}
|
|
|
|
static bool fake_verify(void *user, uint8_t slot,
|
|
const pcan_boot_manifest_t *manifest)
|
|
{
|
|
fake_t *f = (fake_t *)user;
|
|
(void)slot;
|
|
(void)manifest;
|
|
return f->verified;
|
|
}
|
|
|
|
static bool fake_pending(void *user, uint8_t slot,
|
|
const pcan_boot_manifest_t *manifest)
|
|
{
|
|
fake_t *f = (fake_t *)user;
|
|
(void)manifest;
|
|
f->pending_slot = slot;
|
|
return true;
|
|
}
|
|
|
|
static bool fake_confirm(void *user)
|
|
{
|
|
(void)user;
|
|
return true;
|
|
}
|
|
|
|
static void fake_reboot(void *user)
|
|
{
|
|
((fake_t *)user)->rebooted = true;
|
|
}
|
|
|
|
static uint32_t crc32(const uint8_t *data, size_t length)
|
|
{
|
|
uint32_t crc = 0xFFFFFFFFUL;
|
|
size_t i;
|
|
for (i = 0U; i < length; ++i) {
|
|
uint8_t bit;
|
|
crc ^= data[i];
|
|
for (bit = 0U; bit < 8U; ++bit) {
|
|
crc = (crc >> 1) ^ ((crc & 1U) != 0U ? 0xEDB88320UL : 0U);
|
|
}
|
|
}
|
|
return ~crc;
|
|
}
|
|
|
|
static void put_u16(uint8_t *p, uint16_t v)
|
|
{
|
|
p[0] = (uint8_t)v;
|
|
p[1] = (uint8_t)(v >> 8);
|
|
}
|
|
|
|
static void put_u32(uint8_t *p, uint32_t v)
|
|
{
|
|
p[0] = (uint8_t)v;
|
|
p[1] = (uint8_t)(v >> 8);
|
|
p[2] = (uint8_t)(v >> 16);
|
|
p[3] = (uint8_t)(v >> 24);
|
|
}
|
|
|
|
static uint32_t request_id(uint8_t type, uint16_t body)
|
|
{
|
|
return pcan_boot_make_id(1U, PCAN_BOOT_ROUTE_FROM_PM, 3U, 5U, type, body);
|
|
}
|
|
|
|
static bool command(pcan_boot_t *boot, uint8_t session, uint8_t cmd,
|
|
const uint8_t *data, uint8_t dlc)
|
|
{
|
|
uint16_t body = (uint16_t)(((uint16_t)session << 8) | cmd);
|
|
return pcan_boot_process(boot, request_id(PCAN_BOOT_MSG_CONTROL, body), data, dlc);
|
|
}
|
|
|
|
static void setup(pcan_boot_t *boot, fake_t *fake)
|
|
{
|
|
pcan_boot_config_t config;
|
|
pcan_boot_port_t port;
|
|
(void)memset(fake, 0, sizeof(*fake));
|
|
(void)memset(&config, 0, sizeof(config));
|
|
(void)memset(&port, 0, sizeof(port));
|
|
fake->authorized = true;
|
|
fake->verified = true;
|
|
fake->pending_slot = PCAN_BOOT_SLOT_NONE;
|
|
config.device_type = 3U;
|
|
config.device_id = 5U;
|
|
config.product_type = 0x1234U;
|
|
config.hardware_revision = 2U;
|
|
config.firmware_version = 0x01020304UL;
|
|
config.active_slot = 0U;
|
|
config.ack_window = 2U;
|
|
port.send = fake_send;
|
|
port.erase_slot = fake_erase;
|
|
port.write_slot = fake_write;
|
|
port.authorize = fake_authorize;
|
|
port.verify_image = fake_verify;
|
|
port.set_pending_slot = fake_pending;
|
|
port.confirm_running_slot = fake_confirm;
|
|
port.reboot = fake_reboot;
|
|
assert(pcan_boot_init(boot, &config, &port, fake));
|
|
}
|
|
|
|
static void begin_update(pcan_boot_t *boot, const uint8_t *image, uint32_t size)
|
|
{
|
|
uint8_t data[8];
|
|
assert(command(boot, 7U, PCAN_BOOT_CMD_ENTER_BOOT, NULL, 0U));
|
|
put_u32(&data[0], size);
|
|
put_u32(&data[4], crc32(image, size));
|
|
assert(command(boot, 7U, PCAN_BOOT_CMD_BEGIN_IMAGE, data, 8U));
|
|
put_u16(&data[0], 0x1234U);
|
|
data[2] = 1U;
|
|
data[3] = 3U;
|
|
put_u32(&data[4], 0x02000000UL);
|
|
assert(command(boot, 7U, PCAN_BOOT_CMD_BEGIN_COMPAT, data, 8U));
|
|
}
|
|
|
|
static void test_id_layout(void)
|
|
{
|
|
uint32_t id = pcan_boot_make_id(1U, 0U, 7U, 15U, 0xAU, 0x1234U);
|
|
assert(id == 0x17FA1234UL);
|
|
assert(pcan_boot_id_device_type(id) == 7U);
|
|
assert(pcan_boot_id_device_id(id) == 15U);
|
|
assert(pcan_boot_id_msg_type(id) == 0xAU);
|
|
assert(pcan_boot_id_msg_body(id) == 0x1234U);
|
|
}
|
|
|
|
static void test_crc32_reference(void)
|
|
{
|
|
static const uint8_t reference[] = "123456789";
|
|
assert(crc32(reference, sizeof(reference) - 1U) == 0xCBF43926UL);
|
|
}
|
|
|
|
static void test_complete_update(void)
|
|
{
|
|
static const uint8_t image[13] = {
|
|
0x10U, 0x11U, 0x12U, 0x13U, 0x14U, 0x15U, 0x16U, 0x17U,
|
|
0x20U, 0x21U, 0x22U, 0x23U, 0x24U
|
|
};
|
|
uint8_t block[8];
|
|
pcan_boot_t boot;
|
|
fake_t fake;
|
|
setup(&boot, &fake);
|
|
begin_update(&boot, image, sizeof(image));
|
|
assert(boot.state == PCAN_BOOT_STATE_READY_TO_ERASE);
|
|
assert(boot.target_slot == 1U);
|
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_ERASE, NULL, 0U));
|
|
assert(fake.erases == 1U);
|
|
|
|
assert(pcan_boot_process(&boot, request_id(PCAN_BOOT_MSG_DATA_B, 0U), image, 8U));
|
|
(void)memset(block, 0xFF, sizeof(block));
|
|
(void)memcpy(block, &image[8], 5U);
|
|
assert(pcan_boot_process(&boot, request_id(PCAN_BOOT_MSG_DATA_B, 1U), block, 8U));
|
|
assert(boot.bytes_received == sizeof(image));
|
|
assert(boot.next_block == 2U);
|
|
assert(memcmp(fake.flash[1], image, sizeof(image)) == 0);
|
|
|
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_VERIFY, NULL, 0U));
|
|
assert(boot.state == PCAN_BOOT_STATE_VERIFIED);
|
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_COMMIT, NULL, 0U));
|
|
assert(fake.pending_slot == 1U);
|
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_REBOOT, NULL, 0U));
|
|
assert(fake.rebooted);
|
|
}
|
|
|
|
static void test_rejects_wrong_address_and_sequence(void)
|
|
{
|
|
uint8_t image[8] = {0U};
|
|
pcan_boot_t boot;
|
|
fake_t fake;
|
|
uint32_t other = pcan_boot_make_id(1U, 0U, 3U, 6U,
|
|
PCAN_BOOT_MSG_CONTROL,
|
|
(uint16_t)((7U << 8) | PCAN_BOOT_CMD_ENTER_BOOT));
|
|
setup(&boot, &fake);
|
|
assert(!pcan_boot_process(&boot, other, NULL, 0U));
|
|
begin_update(&boot, image, sizeof(image));
|
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_ERASE, NULL, 0U));
|
|
assert(pcan_boot_process(&boot, request_id(PCAN_BOOT_MSG_DATA_B, 1U), image, 8U));
|
|
assert(fake.last_data[0] == PCAN_BOOT_STATUS_SEQUENCE_ERROR);
|
|
assert(fake.writes == 0U);
|
|
}
|
|
|
|
static void test_rejects_wrong_session_and_compatibility(void)
|
|
{
|
|
uint8_t data[8] = {0U};
|
|
pcan_boot_t boot;
|
|
fake_t fake;
|
|
setup(&boot, &fake);
|
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_ENTER_BOOT, NULL, 0U));
|
|
assert(command(&boot, 8U, PCAN_BOOT_CMD_ERASE, NULL, 0U));
|
|
assert(fake.last_data[0] == PCAN_BOOT_STATUS_SESSION_ERROR);
|
|
put_u32(&data[0], 8U);
|
|
put_u32(&data[4], 0U);
|
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_BEGIN_IMAGE, data, 8U));
|
|
put_u16(&data[0], 0x9999U);
|
|
data[2] = 1U;
|
|
data[3] = 3U;
|
|
put_u32(&data[4], 1U);
|
|
assert(command(&boot, 7U, PCAN_BOOT_CMD_BEGIN_COMPAT, data, 8U));
|
|
assert(fake.last_data[0] == PCAN_BOOT_STATUS_WRONG_HARDWARE);
|
|
assert(boot.state == PCAN_BOOT_STATE_FAILED);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
test_id_layout();
|
|
test_crc32_reference();
|
|
test_complete_update();
|
|
test_rejects_wrong_address_and_sequence();
|
|
test_rejects_wrong_session_and_compatibility();
|
|
puts("pcan_boot tests: OK");
|
|
return 0;
|
|
}
|