feat(firmware): add OTA updater library skeleton with version comparison
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
53
firmware/lib/ota_updater/ota_updater.cpp
Normal file
53
firmware/lib/ota_updater/ota_updater.cpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// firmware/lib/ota_updater/ota_updater.cpp
|
||||||
|
#include "ota_updater.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// ── version comparison ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
bool ota_version_newer(const char* current, const char* remote) {
|
||||||
|
int ca = 0, cb = 0, cc = 0;
|
||||||
|
int ra = 0, rb = 0, rc = 0;
|
||||||
|
if (sscanf(current, "%d.%d.%d", &ca, &cb, &cc) != 3) return false;
|
||||||
|
if (sscanf(remote, "%d.%d.%d", &ra, &rb, &rc) != 3) return false;
|
||||||
|
if (ra != ca) return ra > ca;
|
||||||
|
if (rb != cb) return rb > cb;
|
||||||
|
return rc > cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── signature verification ─────────────────────────────────────────────────
|
||||||
|
// (real implementation added in Task 7)
|
||||||
|
bool ota_verify_signature_with_key(const uint8_t hash32[32], const uint8_t sig64[64],
|
||||||
|
const uint8_t pubkey65[65]) {
|
||||||
|
(void)hash32; (void)sig64; (void)pubkey65;
|
||||||
|
return false; // placeholder — filled in Task 7
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── device-only code ───────────────────────────────────────────────────────
|
||||||
|
#ifndef NATIVE_TEST
|
||||||
|
|
||||||
|
#include "ota_pubkey.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
bool ota_verify_signature(const uint8_t hash32[32], const uint8_t sig64[64]) {
|
||||||
|
return ota_verify_signature_with_key(hash32, sig64, kOtaPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* s_server_base = nullptr;
|
||||||
|
static const char* s_device_id = nullptr;
|
||||||
|
static const char* s_hmac_secret = nullptr;
|
||||||
|
static uint32_t s_interval_ms = 21600000UL; // 6 h default
|
||||||
|
static uint32_t s_last_check_ms = 0;
|
||||||
|
|
||||||
|
void ota_updater_init(const char* server_base, const char* device_id,
|
||||||
|
const char* hmac_secret, uint32_t check_interval_ms) {
|
||||||
|
s_server_base = server_base;
|
||||||
|
s_device_id = device_id;
|
||||||
|
s_hmac_secret = hmac_secret;
|
||||||
|
s_interval_ms = check_interval_ms;
|
||||||
|
s_last_check_ms = 0; // force first check on next call
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ota_updater_check_and_apply() { return false; } // filled in Task 8
|
||||||
|
|
||||||
|
#endif // NATIVE_TEST
|
||||||
27
firmware/lib/ota_updater/ota_updater.h
Normal file
27
firmware/lib/ota_updater/ota_updater.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// firmware/lib/ota_updater/ota_updater.h
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// One-time init. Call from setup() after WiFi is ready.
|
||||||
|
// server_base: e.g. "http://logs.research.bike:8000"
|
||||||
|
// check_interval_ms: milliseconds between polls (e.g. 6*3600*1000 = 21600000)
|
||||||
|
void ota_updater_init(const char* server_base,
|
||||||
|
const char* device_id,
|
||||||
|
const char* hmac_secret,
|
||||||
|
uint32_t check_interval_ms);
|
||||||
|
|
||||||
|
// Polls server; downloads, verifies, and flashes if newer version available.
|
||||||
|
// Returns true if update was applied (device reboots before returning false path).
|
||||||
|
// Safe to call from any task; blocks during download.
|
||||||
|
bool ota_updater_check_and_apply();
|
||||||
|
|
||||||
|
// Exposed for unit testing — pass an arbitrary 65-byte uncompressed P-256 pubkey.
|
||||||
|
bool ota_version_newer(const char* current, const char* remote);
|
||||||
|
bool ota_verify_signature_with_key(const uint8_t hash32[32], const uint8_t sig64[64],
|
||||||
|
const uint8_t pubkey65[65]);
|
||||||
|
|
||||||
|
// Production wrapper — uses the compiled-in kOtaPublicKey from ota_pubkey.h.
|
||||||
|
// Not callable from native tests (requires ota_pubkey.h / device build).
|
||||||
|
bool ota_verify_signature(const uint8_t hash32[32], const uint8_t sig64[64]);
|
||||||
44
firmware/test/test_ota/test_version.cpp
Normal file
44
firmware/test/test_ota/test_version.cpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// firmware/test/test_ota/test_version.cpp
|
||||||
|
#include <unity.h>
|
||||||
|
|
||||||
|
// Pull in the function under test — include .cpp directly for native builds
|
||||||
|
// so we don't need a separate compilation unit per test.
|
||||||
|
#define NATIVE_TEST
|
||||||
|
#include "../../lib/ota_updater/ota_updater.cpp"
|
||||||
|
|
||||||
|
void setUp() {}
|
||||||
|
void tearDown() {}
|
||||||
|
|
||||||
|
void test_remote_newer_patch() {
|
||||||
|
TEST_ASSERT_TRUE(ota_version_newer("1.0.0", "1.0.1"));
|
||||||
|
}
|
||||||
|
void test_remote_newer_minor() {
|
||||||
|
TEST_ASSERT_TRUE(ota_version_newer("1.0.9", "1.1.0"));
|
||||||
|
}
|
||||||
|
void test_remote_newer_major() {
|
||||||
|
TEST_ASSERT_TRUE(ota_version_newer("0.9.9", "1.0.0"));
|
||||||
|
}
|
||||||
|
void test_same_version() {
|
||||||
|
TEST_ASSERT_FALSE(ota_version_newer("1.2.3", "1.2.3"));
|
||||||
|
}
|
||||||
|
void test_remote_older() {
|
||||||
|
TEST_ASSERT_FALSE(ota_version_newer("1.2.3", "1.2.2"));
|
||||||
|
}
|
||||||
|
void test_malformed_current() {
|
||||||
|
TEST_ASSERT_FALSE(ota_version_newer("bad", "1.0.0"));
|
||||||
|
}
|
||||||
|
void test_malformed_remote() {
|
||||||
|
TEST_ASSERT_FALSE(ota_version_newer("1.0.0", "bad"));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_remote_newer_patch);
|
||||||
|
RUN_TEST(test_remote_newer_minor);
|
||||||
|
RUN_TEST(test_remote_newer_major);
|
||||||
|
RUN_TEST(test_same_version);
|
||||||
|
RUN_TEST(test_remote_older);
|
||||||
|
RUN_TEST(test_malformed_current);
|
||||||
|
RUN_TEST(test_malformed_remote);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user