feat: OTA-Trailer bauen und prüfen
This commit is contained in:
@@ -52,6 +52,7 @@ set(NL_FW_SOURCES
|
||||
src/app_main.c
|
||||
src/util/nl_sha256.c
|
||||
src/util/nl_crc.c
|
||||
src/util/nl_fwup_trailer.c
|
||||
src/board/nl_board.c
|
||||
src/light/nl_color.c
|
||||
src/light/nl_engine.c
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "util/nl_fwup_trailer.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* NB: This layout is mirrored in Python at tools/mkimage/nl_mkimage.py (Task 3),
|
||||
since there are two languages involved. Keep both in sync. */
|
||||
|
||||
#define OFF_MAGIC 0u
|
||||
#define OFF_VERSION 4u
|
||||
#define OFF_DEVICE 5u
|
||||
#define OFF_FW 21u
|
||||
#define OFF_SHA 37u
|
||||
|
||||
static bool copy_field(uint8_t *dst, const char *src, uint32_t len)
|
||||
{
|
||||
uint32_t n = (uint32_t)strlen(src);
|
||||
if (n >= len)
|
||||
return false;
|
||||
memset(dst, 0, len);
|
||||
memcpy(dst, src, n);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool nl_trailer_build(uint8_t out[NL_TRAILER_SIZE], const char *device, const char *version, const uint8_t sha256[32])
|
||||
{
|
||||
memset(out, 0xFF, NL_TRAILER_SIZE);
|
||||
memcpy(&out[OFF_MAGIC], "NINL", 4);
|
||||
out[OFF_VERSION] = NL_TRAILER_VERSION;
|
||||
if (!copy_field(&out[OFF_DEVICE], device, NL_TRAILER_DEVICE_LEN) ||
|
||||
!copy_field(&out[OFF_FW], version, NL_TRAILER_VERSION_LEN))
|
||||
return false;
|
||||
memcpy(&out[OFF_SHA], sha256, 32);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool terminated(const uint8_t *field, uint32_t len)
|
||||
{
|
||||
for (uint32_t i = 0; i < len; i++)
|
||||
if (field[i] == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
nl_trailer_err_t nl_trailer_verify(const uint8_t *tail, uint32_t tail_len, const char *device, const uint8_t sha256[32])
|
||||
{
|
||||
if (tail == NULL || tail_len < NL_TRAILER_SIZE)
|
||||
return NL_TRAILER_ERR_SIZE;
|
||||
if (memcmp(&tail[OFF_MAGIC], "NINL", 4) != 0)
|
||||
return NL_TRAILER_ERR_MAGIC;
|
||||
if (tail[OFF_VERSION] != NL_TRAILER_VERSION)
|
||||
return NL_TRAILER_ERR_VERSION;
|
||||
if (!terminated(&tail[OFF_DEVICE], NL_TRAILER_DEVICE_LEN) ||
|
||||
strcmp((const char *)&tail[OFF_DEVICE], device) != 0)
|
||||
return NL_TRAILER_ERR_DEVICE;
|
||||
if (memcmp(&tail[OFF_SHA], sha256, 32) != 0)
|
||||
return NL_TRAILER_ERR_HASH;
|
||||
return NL_TRAILER_OK;
|
||||
}
|
||||
|
||||
void nl_trailer_version(const uint8_t *tail, char *dst)
|
||||
{
|
||||
memcpy(dst, &tail[OFF_FW], NL_TRAILER_VERSION_LEN);
|
||||
dst[NL_TRAILER_VERSION_LEN - 1] = '\0';
|
||||
}
|
||||
|
||||
const char *nl_trailer_err_name(nl_trailer_err_t e)
|
||||
{
|
||||
switch (e) {
|
||||
case NL_TRAILER_ERR_DEVICE: return "device_mismatch";
|
||||
case NL_TRAILER_ERR_HASH: return "hash";
|
||||
case NL_TRAILER_OK: return NULL;
|
||||
default: return "trailer";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef NL_FWUP_TRAILER_H
|
||||
#define NL_FWUP_TRAILER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define NL_TRAILER_SIZE 512u
|
||||
#define NL_TRAILER_VERSION 1u
|
||||
#define NL_TRAILER_DEVICE_LEN 16u
|
||||
#define NL_TRAILER_VERSION_LEN 16u
|
||||
|
||||
typedef enum {
|
||||
NL_TRAILER_OK = 0,
|
||||
NL_TRAILER_ERR_SIZE,
|
||||
NL_TRAILER_ERR_MAGIC,
|
||||
NL_TRAILER_ERR_VERSION,
|
||||
NL_TRAILER_ERR_DEVICE,
|
||||
NL_TRAILER_ERR_HASH
|
||||
} nl_trailer_err_t;
|
||||
|
||||
/* "NINL" | version | device[16] | fw_version[16] | sha256[32] | 0xFF padding */
|
||||
bool nl_trailer_build(uint8_t out[NL_TRAILER_SIZE], const char *device, const char *version, const uint8_t sha256[32]);
|
||||
nl_trailer_err_t nl_trailer_verify(const uint8_t *tail, uint32_t tail_len, const char *device, const uint8_t sha256[32]);
|
||||
void nl_trailer_version(const uint8_t *tail, char *dst); /* dst: NL_TRAILER_VERSION_LEN bytes */
|
||||
/* Maps to the last_ota_error vocabulary; NULL for NL_TRAILER_OK. */
|
||||
const char *nl_trailer_err_name(nl_trailer_err_t e);
|
||||
|
||||
#endif
|
||||
@@ -48,3 +48,4 @@ nl_test(test_api test_api.c fake_api_ctx.c ${NL_SRC}/net/nl_api.c ${NL_SRC}/boar
|
||||
${NL_SRC}/light/nl_boot_state.c ${NL_SRC}/config/nl_settings.c ${NL_SRC}/util/nl_crc.c fake_flash.c)
|
||||
target_link_libraries(test_api PRIVATE nl_cjson)
|
||||
nl_test(test_wifi_sm test_wifi_sm.c ${NL_SRC}/net/nl_wifi_sm.c)
|
||||
nl_test(test_fwup_trailer test_fwup_trailer.c ${NL_SRC}/util/nl_fwup_trailer.c)
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#include <string.h>
|
||||
#include "unity.h"
|
||||
#include "util/nl_fwup_trailer.h"
|
||||
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
static uint8_t HASH[32];
|
||||
static uint8_t tail[NL_TRAILER_SIZE];
|
||||
|
||||
static void fill_hash(void)
|
||||
{
|
||||
for (int i = 0; i < 32; i++)
|
||||
HASH[i] = (uint8_t)(i * 3 + 1);
|
||||
}
|
||||
|
||||
static void test_build_layout(void)
|
||||
{
|
||||
fill_hash();
|
||||
TEST_ASSERT_TRUE(nl_trailer_build(tail, "e27_rgb", "1.2.3", HASH));
|
||||
TEST_ASSERT_EQUAL_MEMORY("NINL", tail, 4);
|
||||
TEST_ASSERT_EQUAL_UINT8(NL_TRAILER_VERSION, tail[4]);
|
||||
TEST_ASSERT_EQUAL_STRING("e27_rgb", (const char *)&tail[5]);
|
||||
TEST_ASSERT_EQUAL_STRING("1.2.3", (const char *)&tail[21]);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(HASH, &tail[37], 32);
|
||||
for (uint32_t i = 69; i < NL_TRAILER_SIZE; i++)
|
||||
TEST_ASSERT_EQUAL_HEX8(0xFF, tail[i]);
|
||||
}
|
||||
|
||||
static void test_build_rejects_too_long_strings(void)
|
||||
{
|
||||
fill_hash();
|
||||
TEST_ASSERT_FALSE(nl_trailer_build(tail, "0123456789abcdef", "1.0", HASH)); /* 16 chars, no room for NUL */
|
||||
TEST_ASSERT_FALSE(nl_trailer_build(tail, "evk", "0123456789abcdef", HASH));
|
||||
TEST_ASSERT_TRUE(nl_trailer_build(tail, "012345678901234", "012345678901234", HASH)); /* 15 chars fit */
|
||||
}
|
||||
|
||||
static void test_verify_accepts_matching(void)
|
||||
{
|
||||
fill_hash();
|
||||
nl_trailer_build(tail, "evk", "1.0.0", HASH);
|
||||
TEST_ASSERT_EQUAL(NL_TRAILER_OK, nl_trailer_verify(tail, NL_TRAILER_SIZE, "evk", HASH));
|
||||
}
|
||||
|
||||
static void test_verify_rejects(void)
|
||||
{
|
||||
char version[NL_TRAILER_VERSION_LEN];
|
||||
uint8_t other[32];
|
||||
|
||||
fill_hash();
|
||||
memcpy(other, HASH, 32);
|
||||
other[5] ^= 0x01;
|
||||
nl_trailer_build(tail, "evk", "1.0.0", HASH);
|
||||
|
||||
TEST_ASSERT_EQUAL(NL_TRAILER_ERR_SIZE, nl_trailer_verify(tail, 256, "evk", HASH));
|
||||
TEST_ASSERT_EQUAL(NL_TRAILER_ERR_DEVICE, nl_trailer_verify(tail, NL_TRAILER_SIZE, "gu10_cw", HASH));
|
||||
TEST_ASSERT_EQUAL(NL_TRAILER_ERR_HASH, nl_trailer_verify(tail, NL_TRAILER_SIZE, "evk", other));
|
||||
nl_trailer_version(tail, version);
|
||||
TEST_ASSERT_EQUAL_STRING("1.0.0", version);
|
||||
|
||||
tail[4] = 2;
|
||||
TEST_ASSERT_EQUAL(NL_TRAILER_ERR_VERSION, nl_trailer_verify(tail, NL_TRAILER_SIZE, "evk", HASH));
|
||||
tail[0] = 'X';
|
||||
TEST_ASSERT_EQUAL(NL_TRAILER_ERR_MAGIC, nl_trailer_verify(tail, NL_TRAILER_SIZE, "evk", HASH));
|
||||
}
|
||||
|
||||
static void test_verify_needs_terminated_device_string(void)
|
||||
{
|
||||
fill_hash();
|
||||
nl_trailer_build(tail, "evk", "1.0.0", HASH);
|
||||
memset(&tail[5], 'a', NL_TRAILER_DEVICE_LEN); /* no NUL inside the field */
|
||||
TEST_ASSERT_EQUAL(NL_TRAILER_ERR_DEVICE, nl_trailer_verify(tail, NL_TRAILER_SIZE, "aaaaaaaaaaaaaaaa", HASH));
|
||||
}
|
||||
|
||||
static void test_error_names(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("trailer", nl_trailer_err_name(NL_TRAILER_ERR_MAGIC));
|
||||
TEST_ASSERT_EQUAL_STRING("trailer", nl_trailer_err_name(NL_TRAILER_ERR_SIZE));
|
||||
TEST_ASSERT_EQUAL_STRING("trailer", nl_trailer_err_name(NL_TRAILER_ERR_VERSION));
|
||||
TEST_ASSERT_EQUAL_STRING("device_mismatch", nl_trailer_err_name(NL_TRAILER_ERR_DEVICE));
|
||||
TEST_ASSERT_EQUAL_STRING("hash", nl_trailer_err_name(NL_TRAILER_ERR_HASH));
|
||||
TEST_ASSERT_NULL(nl_trailer_err_name(NL_TRAILER_OK));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_build_layout);
|
||||
RUN_TEST(test_build_rejects_too_long_strings);
|
||||
RUN_TEST(test_verify_accepts_matching);
|
||||
RUN_TEST(test_verify_rejects);
|
||||
RUN_TEST(test_verify_needs_terminated_device_string);
|
||||
RUN_TEST(test_error_names);
|
||||
return UNITY_END();
|
||||
}
|
||||
Reference in New Issue
Block a user