Добавить идентификацию прошивки и чтение EEPROM Бальзам-167
This commit is contained in:
19
c/firmware-info/tests/fixtures/firmware_identity.h
vendored
Normal file
19
c/firmware-info/tests/fixtures/firmware_identity.h
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/* Deliberately fixed vector, not production identity. Used only by host tests. */
|
||||
#define FIRMWARE_VERSION_MAJOR 1U
|
||||
#define FIRMWARE_VERSION_MINOR 2U
|
||||
#define FIRMWARE_VERSION_PATCH 3U
|
||||
#define FI_YEAR 2026U
|
||||
#define FI_MONTH 10U
|
||||
#define FI_DAY 5U
|
||||
#define FI_HOUR 12U
|
||||
#define FI_MINUTE 34U
|
||||
#define FI_SECOND 56U
|
||||
#define FIRMWARE_BUILD_ID "abcdef0+"
|
||||
#define FI_ID_0 'a'
|
||||
#define FI_ID_1 'b'
|
||||
#define FI_ID_2 'c'
|
||||
#define FI_ID_3 'd'
|
||||
#define FI_ID_4 'e'
|
||||
#define FI_ID_5 'f'
|
||||
#define FI_ID_6 '0'
|
||||
#define FI_ID_7 '+'
|
||||
74
c/firmware-info/tests/test_identity.py
Normal file
74
c/firmware-info/tests/test_identity.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Host lifecycle tests: dirty dependencies, reproducible stamps, file integrity.
|
||||
|
||||
Run: python -m unittest discover -s c/firmware-info/tests -p test_identity.py
|
||||
"""
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
TOOL = Path(__file__).resolve().parents[1] / "tools/identity.py"
|
||||
spec = importlib.util.spec_from_file_location("identity", TOOL)
|
||||
identity = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(identity)
|
||||
|
||||
|
||||
class IdentityTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
self.config = self.root / "config.json"
|
||||
self.config.write_text(json.dumps({"product":"test", "target":"host", "version":[1,2,3]}))
|
||||
|
||||
def prepare(self, states):
|
||||
with patch.object(identity, "git_state", side_effect=states), patch.dict(os.environ, {"SOURCE_DATE_EPOCH":"1791194400"}):
|
||||
return identity.prepare(self.root, self.root, self.config, self.root)
|
||||
|
||||
def test_clean_and_dirty_dependency(self):
|
||||
clean = {"commit":"abcdef0123456789", "dirty":False}
|
||||
meta = self.prepare([clean,clean])
|
||||
self.assertEqual(meta["build_id"], "abcdef01")
|
||||
header = (self.root / "firmware_identity.h").read_text()
|
||||
self.assertIn("#define FIRMWARE_VERSION_PATCH 3U",header)
|
||||
dirty = dict(clean, dirty=True)
|
||||
self.assertEqual(self.prepare([clean,dirty])["build_id"], "abcdef0+")
|
||||
self.assertEqual(self.prepare([dirty,clean])["build_id"], "abcdef0+")
|
||||
|
||||
def test_missing_git_is_explicit(self):
|
||||
unknown = {"commit":None,"dirty":None}
|
||||
self.assertEqual(self.prepare([unknown,unknown])["build_id"], "NOGIT000")
|
||||
|
||||
def test_invalid_version(self):
|
||||
self.config.write_text(json.dumps({"product":"x","target":"x","version":[1,2,1000]}))
|
||||
with self.assertRaises(ValueError):
|
||||
identity.prepare(self.root,self.root,self.config,self.root)
|
||||
|
||||
def test_untracked_source_is_dirty(self):
|
||||
def git(*args):
|
||||
subprocess.run(["git","-C",str(self.root),*args],check=True,capture_output=True)
|
||||
git("init")
|
||||
git("-c","user.name=Test","-c","user.email=test@example.invalid","commit","--allow-empty","-m","test")
|
||||
# config.json is untracked and must prevent a clean-looking build ID.
|
||||
self.assertTrue(identity.git_state(self.root)["dirty"])
|
||||
|
||||
def test_cli_seal_verify_and_corruption(self):
|
||||
clean = {"commit":"abcdef0123456789", "dirty":False}
|
||||
self.prepare([clean,clean])
|
||||
image = self.root / "firmware.bin"
|
||||
image.write_bytes(b"abc")
|
||||
import sys
|
||||
subprocess.run([sys.executable,str(TOOL),"seal","--image",str(image),"--metadata",str(self.root/"firmware_identity.json")],check=True,capture_output=True)
|
||||
manifest = image.with_suffix(".bin.manifest.json")
|
||||
meta = identity.verify(image,manifest)
|
||||
self.assertEqual(meta["sha256"],"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
||||
image.write_bytes(b"abd")
|
||||
with self.assertRaises(ValueError): identity.verify(image,manifest)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
53
c/firmware-info/tests/test_static.c
Normal file
53
c/firmware-info/tests/test_static.c
Normal file
@@ -0,0 +1,53 @@
|
||||
/* One set of vectors checks static metadata against Climate's runtime encoder.
|
||||
* This catches word order and ASCII order changes on the host, before MCU use. */
|
||||
#include "firmware_identity.h"
|
||||
#include "firmware_info.h"
|
||||
#include "firmware_info_static.h"
|
||||
#include "ump_protocol.h"
|
||||
#include <assert.h>
|
||||
|
||||
static unsigned writes;
|
||||
static uint16_t read_identity(void *context, uint16_t address)
|
||||
{
|
||||
(void)context;
|
||||
return firmware_info_static_read(address - UMP_FIRMWARE_INFO);
|
||||
}
|
||||
static int write_identity(void *context, uint16_t address, uint16_t value)
|
||||
{
|
||||
(void)context; (void)address; (void)value;
|
||||
writes++;
|
||||
return 1;
|
||||
}
|
||||
int main(void)
|
||||
{
|
||||
firmware_info_t info;
|
||||
uint16_t expected[12], reply[UMP_MAX_WORDS];
|
||||
ump_octet frame[UMP_RTU_CAPACITY];
|
||||
ump_backend backend = {0, read_identity, write_identity};
|
||||
unsigned i, error;
|
||||
info.major = FIRMWARE_VERSION_MAJOR; info.minor = FIRMWARE_VERSION_MINOR;
|
||||
info.patch = FIRMWARE_VERSION_PATCH; info.year = FI_YEAR;
|
||||
info.month = FI_MONTH; info.day = FI_DAY; info.hour = FI_HOUR;
|
||||
info.minute = FI_MINUTE; info.second = FI_SECOND;
|
||||
assert(firmware_info_set_build_id(&info, FIRMWARE_BUILD_ID) == FIRMWARE_INFO_OK);
|
||||
assert(firmware_info_to_registers(&info, expected, 12) == FIRMWARE_INFO_OK);
|
||||
for (i=0; i<12; ++i) assert(firmware_info_static_read(i) == expected[i]);
|
||||
assert(firmware_info_static_read(12) == 0);
|
||||
assert(ump_prepare(&backend, 3, UMP_FIRMWARE_INFO, 12, 1, reply, &error) == 12 && !error);
|
||||
for (i=0; i<12; ++i) assert(reply[i] == expected[i]);
|
||||
for (i=0; i<6; ++i) {
|
||||
assert(ump_can_fragment(reply,12,i,42,0,frame) == 8);
|
||||
assert(frame[0] == expected[2*i] >> 8 && frame[1] == (expected[2*i] & 255));
|
||||
assert(frame[6] == 42 && frame[7] == 0);
|
||||
}
|
||||
assert(ump_rtu_response(&backend, 1, 3, UMP_FIRMWARE_INFO, 12, frame, sizeof(frame)/sizeof(frame[0])) == 29);
|
||||
assert(frame[2] == 24 && ump_crc(frame,29) == 0);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO+11, 1, 0) == 0);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO+11, 2, 0) == 2);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO, 0, 0) == 2);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO, 0xffffU, 0) == 2);
|
||||
assert(ump_validate(3, UMP_FIRMWARE_INFO+1, 11, 1) == 2);
|
||||
ump_prepare(&backend,6,UMP_FIRMWARE_INFO,1,0,reply,&error);
|
||||
assert(error == 2 && writes == 0);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user