Reduce nRF52 infrastructure flash usage without trimming sensors

Keep Companion and Terminal Chat profiles unchanged; retain full-sensor and reduced-sensor OTA builds. Add compact crypto and OTA names, qualified display asset compression, font license packaging, and scope regression tests.
This commit is contained in:
mikecarper committed 2026-10-02 15:52:23 -07:00
1 parent 010374d7db
commit 8a69e73226
44 files changed
+11150 -14

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
#include "helpers/ui/CompactArialFonts.h"
extern "C" const uint8_t* fontFromOtherTranslationUnit(unsigned id) {
return mesh::ui::arial::font(id);
}
extern "C" int identifyInOtherTranslationUnit(const uint8_t* descriptor) {
return mesh::ui::arial::identify(descriptor);
}
+81
View File
@@ -0,0 +1,81 @@
#include "helpers/ui/CompactArialFonts.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
using namespace mesh::ui::arial;
extern "C" const uint8_t* fontFromOtherTranslationUnit(unsigned id);
extern "C" int identifyInOtherTranslationUnit(const uint8_t* descriptor);
static void rleFailures() {
uint8_t guarded[5] = { 0x5a, 0, 0, 0, 0xa5 };
const uint8_t literal[] = { 2, 1, 2, 3 };
assert(decodeRle(literal, sizeof(literal), guarded + 1, 3));
assert(guarded[1] == 1 && guarded[2] == 2 && guarded[3] == 3);
assert(!decodeRle(literal, sizeof(literal), guarded + 1, 2));
assert(!decodeRle(literal, sizeof(literal) - 1, guarded + 1, 3));
const uint8_t repeat[] = { 0x80, 7 };
assert(decodeRle(repeat, sizeof(repeat), guarded + 1, 3));
assert(guarded[1] == 7 && guarded[2] == 7 && guarded[3] == 7);
assert(!decodeRle(repeat, 1, guarded + 1, 3));
assert(!decodeRle(repeat, sizeof(repeat), guarded + 1, 2));
assert(!decodeRle(repeat, sizeof(repeat), guarded + 1, 4));
assert(!decodeRle(nullptr, 1, guarded + 1, 3));
assert(!decodeRle(repeat, sizeof(repeat), nullptr, 3));
assert(decodeRle(nullptr, 0, nullptr, 0));
assert(guarded[0] == 0x5a && guarded[4] == 0xa5);
}
int main(int argc, char**) {
uint8_t scratch[kScratchSize + 2];
if (argc > 1) {
// Python alters the descriptor or first stream in a private header copy.
const Glyph decoded = glyph(font(1), 33, scratch + 1, kScratchSize);
assert(!decoded.valid);
return 0;
}
rleFailures();
assert(font(0) == nullptr && font(3) == nullptr && font(100) == nullptr);
assert(identify(nullptr) == -1);
assert(!glyph(nullptr, 65, scratch + 1, kScratchSize).valid);
for (unsigned id = 1; id <= 2; ++id) {
const uint8_t* descriptor = font(id);
assert(identify(descriptor) == int(id));
assert(fontFromOtherTranslationUnit(id) == descriptor);
assert(identifyInOtherTranslationUnit(descriptor) == int(id));
uint8_t copy[900];
memcpy(copy, descriptor, sizeof(copy));
assert(identify(copy) == -1);
assert(!glyph(copy, 65, scratch + 1, kScratchSize).valid);
assert(!glyph(descriptor, 31, scratch + 1, kScratchSize).valid);
printf("F %u %u %u %u %u\n", id, descriptor[0], descriptor[1], descriptor[2], descriptor[3]);
for (unsigned code = descriptor[2]; code < unsigned(descriptor[2]) + descriptor[3]; ++code) {
memset(scratch, 0xcc, sizeof(scratch));
scratch[0] = 0x5a;
scratch[sizeof(scratch) - 1] = 0xa5;
const Glyph decoded = glyph(descriptor, uint8_t(code), scratch + 1, kScratchSize);
assert(decoded.valid);
assert(decoded.drawable == (decoded.length > 0));
assert(scratch[0] == 0x5a && scratch[sizeof(scratch) - 1] == 0xa5);
const uint8_t* record = descriptor + 4 + 4 * (code - descriptor[2]);
assert(record[2] == decoded.length && record[3] == decoded.width);
if (decoded.drawable) {
#if MESH_ARIAL_EXPERIMENT == 2
if (!(record[0] & 0x80)) {
assert(!glyph(descriptor, uint8_t(code), nullptr, kScratchSize).valid);
assert(!glyph(descriptor, uint8_t(code), scratch + 1, decoded.length - 1).valid);
}
#else
assert(!glyph(descriptor, uint8_t(code), nullptr, kScratchSize).valid);
assert(!glyph(descriptor, uint8_t(code), scratch + 1, 0).valid);
#endif
} else {
assert(decoded.data == nullptr);
assert(glyph(descriptor, uint8_t(code), nullptr, 0).valid);
}
printf("G %u %u %u %u ", id, code, decoded.width, decoded.length);
for (unsigned index = 0; index < decoded.length; ++index) printf("%02x", decoded.data[index]);
puts("");
}
}
}
+9
View File
@@ -0,0 +1,9 @@
#include "helpers/ui/CompactNotoFonts.h"
extern "C" const uint8_t* fontFromOtherTranslationUnit(unsigned id) {
return mesh::ui::noto::font(id);
}
extern "C" int identifyInOtherTranslationUnit(const uint8_t* descriptor) {
return mesh::ui::noto::identify(descriptor);
}
+61
View File
@@ -0,0 +1,61 @@
#include "helpers/ui/CompactNotoFonts.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
using namespace mesh::ui::noto;
extern "C" const uint8_t* fontFromOtherTranslationUnit(unsigned id);
extern "C" int identifyInOtherTranslationUnit(const uint8_t* descriptor);
int main(int argc, char**) {
uint8_t scratch[kScratchSize + 2];
if (argc > 1) {
const Glyph decoded = glyph(font(1), 33, scratch + 1, kScratchSize);
assert(!decoded.valid);
return 0;
}
assert(kScratchSize == 512);
assert(font(0) == nullptr && font(3) == nullptr && font(100) == nullptr);
assert(identify(nullptr) == -1);
assert(!glyph(nullptr, 65, scratch + 1, kScratchSize).valid);
for (unsigned id = 1; id <= 2; ++id) {
const uint8_t* descriptor = font(id);
assert(identify(descriptor) == int(id));
assert(fontFromOtherTranslationUnit(id) == descriptor);
assert(identifyInOtherTranslationUnit(descriptor) == int(id));
uint8_t copy[900];
memcpy(copy, descriptor, sizeof(copy));
assert(identify(copy) == -1);
assert(!glyph(copy, 65, scratch + 1, kScratchSize).valid);
assert(!glyph(descriptor, 31, scratch + 1, kScratchSize).valid);
printf("F %u %u %u %u %u\n", id, descriptor[0], descriptor[1], descriptor[2], descriptor[3]);
for (unsigned code = descriptor[2]; code < unsigned(descriptor[2]) + descriptor[3]; ++code) {
memset(scratch, 0xcc, sizeof(scratch));
scratch[0] = 0x5a;
scratch[sizeof(scratch) - 1] = 0xa5;
const Glyph decoded = glyph(descriptor, uint8_t(code), scratch + 1, kScratchSize);
assert(decoded.valid);
assert(decoded.drawable == (decoded.length > 0));
assert(scratch[0] == 0x5a && scratch[sizeof(scratch) - 1] == 0xa5);
const uint8_t* record = descriptor + 4 + 4 * (code - descriptor[2]);
assert(record[2] == decoded.length && record[3] == decoded.width);
if (decoded.drawable) {
assert(!glyph(descriptor, uint8_t(code), nullptr, kScratchSize).valid);
assert(!glyph(descriptor, uint8_t(code), scratch + 1, 0).valid);
} else {
assert(decoded.data == nullptr);
assert(glyph(descriptor, uint8_t(code), nullptr, 0).valid);
}
printf("G %u %u %u %u ", id, code, decoded.width, decoded.length);
for (unsigned index = 0; index < decoded.length; ++index) printf("%02x", decoded.data[index]);
puts("");
}
}
uint8_t first[kScratchSize], second[kScratchSize];
const Glyph a = glyph(font(1), 'A', first, sizeof(first));
uint8_t snapshot[kScratchSize];
memcpy(snapshot, first, sizeof(first));
const Glyph b = glyph(font(2), 'W', second, sizeof(second));
assert(a.valid && b.valid);
assert(memcmp(snapshot, first, sizeof(first)) == 0);
}
+7
View File
@@ -0,0 +1,7 @@
/* Standalone ABI stub for a feature-preservation unit test, not firmware. */
typedef struct { unsigned marker; } CRYS_ECPKI_Domain_t;
typedef enum {
CRYS_ECPKI_DomainID_secp192r1 = 0,
CRYS_ECPKI_DomainID_secp256r1 = 8,
CRYS_ECPKI_DomainID_Invalid = 255
} CRYS_ECPKI_DomainID_t;
+22
View File
@@ -0,0 +1,22 @@
#include <assert.h>
#include <stddef.h>
#include "crys_ecpki_domain.h"
static const CRYS_ECPKI_Domain_t p256 = {256};
static unsigned calls;
const CRYS_ECPKI_Domain_t *SaSi_ECPKI_GetSecp256r1DomainP(void) {
++calls;
return &p256;
}
extern const CRYS_ECPKI_Domain_t *__wrap_CRYS_ECPKI_GetEcDomain(CRYS_ECPKI_DomainID_t);
int main(void) {
assert(__wrap_CRYS_ECPKI_GetEcDomain(CRYS_ECPKI_DomainID_secp256r1) == &p256);
assert(calls == 1);
assert(__wrap_CRYS_ECPKI_GetEcDomain(CRYS_ECPKI_DomainID_secp192r1) == NULL);
assert(__wrap_CRYS_ECPKI_GetEcDomain(CRYS_ECPKI_DomainID_Invalid) == NULL);
assert(calls == 1);
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
// Controlled library setup used to check that the trim preserves hardware.
#ifndef USER_SETUP_LOADED
#define ILI9341_DRIVER
#define TFT_CS 42
#define SPI_FREQUENCY 40000000
#define LOAD_GLCD
#define LOAD_GFXFF
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_FONT8N
#define SMOOTH_FONT
#endif
+28
View File
@@ -0,0 +1,28 @@
#include "helpers/ui/Nrf52TftFontTrim.h"
#include <User_Setup_Select.h>
#if !defined(ILI9341_DRIVER) || TFT_CS != 42 || SPI_FREQUENCY != 40000000
#error "The trim changed the selected hardware setup"
#endif
#if !defined(LOAD_GLCD) || !defined(LOAD_GFXFF)
#error "The current bitmap font or setFreeFont API was removed"
#endif
#if EXPECT_TRIM
#if defined(LOAD_FONT2) || defined(LOAD_FONT4) || defined(LOAD_FONT6) || \
defined(LOAD_FONT7) || defined(LOAD_FONT8) || defined(LOAD_FONT8N) || \
defined(SMOOTH_FONT)
#error "Unused fonts are still enabled"
#endif
#ifndef USER_SETUP_LOADED
#error "The selected setup could reload the unused fonts"
#endif
#else
#if !defined(LOAD_FONT2) || !defined(LOAD_FONT4) || !defined(LOAD_FONT6) || \
!defined(LOAD_FONT7) || !defined(LOAD_FONT8) || !defined(LOAD_FONT8N) || \
!defined(SMOOTH_FONT)
#error "An excluded build lost its original fonts"
#endif
#endif
int main() { return 0; }
+41
View File
@@ -0,0 +1,41 @@
/* Minimal host-only Arduino surface for exercising the real OLED renderer. */
#pragma once
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#define PROGMEM
#define pgm_read_byte(address) (*reinterpret_cast<const uint8_t *>(address))
template <typename A, typename B>
inline auto min(A a, B b) -> decltype(a + b) { return a < b ? a : b; }
template <typename A, typename B>
inline auto max(A a, B b) -> decltype(a + b) { return a > b ? a : b; }
inline void yield() {}
class String {
std::string value;
public:
String(const char *text) : value(text ? text : "") {}
const char *c_str() const { return value.c_str(); }
unsigned int length() const { return static_cast<unsigned int>(value.size()); }
void toCharArray(char *target, unsigned int capacity, unsigned int index = 0) const {
if (!capacity) return;
const size_t available = index < value.size() ? value.size() - index : 0;
const size_t copied = std::min<size_t>(capacity - 1, available);
std::memcpy(target, value.data() + std::min<size_t>(index, value.size()), copied);
target[copied] = 0;
}
};
class Print {
public:
virtual ~Print() {}
virtual size_t write(uint8_t byte) = 0;
};
+229
View File
@@ -0,0 +1,229 @@
#include "helpers/ui/OLEDDisplay.h"
#include <cassert>
#include <iostream>
#include <string>
#ifndef EXPECT_MODE
#define EXPECT_MODE 0
#endif
#ifndef MESHCORE_ARIAL_FONT_MODE
#define MESHCORE_ARIAL_FONT_MODE 0
#endif
static_assert(MESHCORE_ARIAL_FONT_MODE == EXPECT_MODE, "font experiment escaped its scope");
class TestDisplay : public OLEDDisplay {
public:
TestDisplay() { setGeometry(GEOMETRY_RAWMODE, 96, 64); assert(init()); }
void display() override {}
int getBufferOffset() override { return 0; }
bool connect() override { return true; }
uint8_t initialFontHeight() const { return pgm_read_byte(fontData + HEIGHT_POS); }
size_t framebufferSize() const { return displayBufferSize; }
uint16_t drawRaw(int x, int y, const char *text, uint16_t count) {
return drawStringInternal(x, y, text, count, getStringWidth(text, count, false), false);
}
uint16_t drawControl(const char *text) {
return drawStringInternal(0, 0, text, 1, 0, false);
}
};
struct Digest {
uint64_t hash = UINT64_C(14695981039346656037);
size_t cases = 0;
void byte(uint8_t value) { hash = (hash ^ value) * UINT64_C(1099511628211); }
void number(uint16_t value) { byte(value & 255); byte(value >> 8); }
void framebuffer(const TestDisplay &display) {
for (size_t i = 0; i < display.framebufferSize(); ++i) byte(display.buffer[i]);
++cases;
}
void print(const char *name) const {
std::cout << name << ' ' << cases << ' ' << std::hex << hash << std::dec << '\n';
}
};
static char identityLookup(uint8_t code) { return static_cast<char>(code); }
static void fillPattern(TestDisplay &display, unsigned seed) {
for (size_t i = 0; i < display.framebufferSize(); ++i)
display.buffer[i] = static_cast<uint8_t>(seed + 31 * i);
}
// Independent pixel-at-a-time reference for the uncompressed family trials.
// This does not reuse drawInternal's byte/shift implementation.
static void assertRawFamilyGlyph(TestDisplay &display, const uint8_t *font,
unsigned code, unsigned font_index) {
const unsigned line_height = font[HEIGHT_POS];
const unsigned count = font[CHAR_NUM_POS];
assert(font[FIRST_CHAR_POS] == 32 && count == 224);
assert(line_height == (font_index == 0 ? 19 : 28));
const unsigned table_index = 4 + 4 * (code - font[FIRST_CHAR_POS]);
const unsigned offset = (unsigned(font[table_index]) << 8) | font[table_index + 1];
const unsigned length = font[table_index + 2], advance = font[table_index + 3];
char text[] = {static_cast<char>(code), 0};
assert(display.getStringWidth(text, 1, false) == advance);
uint8_t expected[96 * 64 / 8] = {};
int top = 255, bottom = -1;
const unsigned stride = (line_height + 7) / 8;
if (offset == 0xffff) {
assert(length == 0);
} else {
assert(length && advance);
const auto *bitmap = font + 4 + 4 * count + offset;
assert(length <= advance * stride);
for (unsigned byte = 0; byte < length; ++byte)
for (unsigned bit = 0; bit < 8; ++bit)
if (bitmap[byte] & (1 << bit)) {
const unsigned x = byte / stride, y = 8 * (byte % stride) + bit;
assert(x < advance && y < line_height); // No clipped/padding-row ink.
top = min(top, int(y)); bottom = max(bottom, int(y));
if (x < 96) expected[x + 96 * (y / 8)] |= 1 << (y & 7);
}
assert(bottom >= top); // Every drawable Latin-1 glyph still has pixels.
}
if (code == 'H') {
assert(top == (font_index == 0 ? 3 : 5));
assert(bottom == (font_index == 0 ? 14 : 21));
assert(bottom - top + 1 == (font_index == 0 ? 12 : 17));
}
assert(std::memcmp(expected, display.buffer, sizeof(expected)) == 0);
}
int main() {
TestDisplay display;
const uint8_t initial_height = display.initialFontHeight();
assert(initial_height == (EXPECT_MODE == 0 ? 13 : 19));
std::cout << "initial " << static_cast<unsigned>(initial_height) << '\n';
const uint8_t *fonts[] = {ArialMT_Plain_16, ArialMT_Plain_24};
Digest glyphs, clipping, utf8, wrapping, controls, bitmaps, logs, custom;
unsigned font_index = 0;
for (const auto *font : fonts) {
display.setFont(font);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFontTableLookupFunction(identityLookup);
for (unsigned code = 32; code <= 255; ++code) {
char text[] = {static_cast<char>(code), 0};
display.clear();
display.setColor(WHITE);
glyphs.number(display.getStringWidth(text, 1, false));
glyphs.number(display.drawRaw(0, 0, text, 1));
if (EXPECT_MODE >= 4 && EXPECT_MODE <= 8)
assertRawFamilyGlyph(display, font, code, font_index);
glyphs.framebuffer(display);
}
if (EXPECT_MODE >= 4) {
unsigned drawable = 0;
for (unsigned slot = 0; slot < 224; ++slot)
if (font[4 + 4 * slot] != 255 || font[5 + 4 * slot] != 255) ++drawable;
assert(drawable == 189);
}
const char samples[] = {'A', 'W', 'j', static_cast<char>(0xc0), static_cast<char>(0xff)};
for (char sample : samples)
for (int color = BLACK; color <= INVERSE; ++color)
for (int offset = 0; offset < 8; ++offset)
for (int edge = 0; edge < 4; ++edge) {
char text[] = {sample, 0};
const int x[] = {-9, 4, 93, 4};
const int y[] = {offset, -19 + offset, offset, 60 + offset};
fillPattern(display, 0x53);
display.setColor(static_cast<OLEDDISPLAY_COLOR>(color));
clipping.number(display.drawRaw(x[edge], y[edge], text, 1));
clipping.framebuffer(display);
}
// Control codes must never index before the font's first character.
for (unsigned code = 1; code < 32; ++code) {
if (code == '\n') continue;
char text[] = {static_cast<char>(code), 0};
display.clear();
// Original mode 0 has no guard for control-code width lookup. Compare
// against its safe drawing behavior, not its out-of-bounds width read.
const auto width = EXPECT_MODE ? display.getStringWidth(text, 1, false) : 0;
assert(width == 0);
controls.number(width);
controls.number(display.drawControl(text));
for (size_t i = 0; i < display.framebufferSize(); ++i) assert(display.buffer[i] == 0);
controls.framebuffer(display);
}
display.setFontTableLookupFunction(DefaultFontTableLookup);
const char *strings[] = {
"MeshCore repeater 909.5 MHz",
"\xc3\x80\xc3\x89\xc3\xa9 \xc2\xb0\xc2\xb5\xc3\xbf",
"\xc2\xa0\xc2\xa3\xc2\xa9 \xe2\x82\xac",
"Line one / Line two",
"unsupported \xf0\x9f\x93\xbb emoji",
};
for (const char *text : strings)
for (int alignment = TEXT_ALIGN_LEFT; alignment <= TEXT_ALIGN_CENTER_BOTH; ++alignment) {
DefaultFontTableLookup(' ');
fillPattern(display, 0x62);
display.setColor(INVERSE);
display.setTextAlignment(static_cast<OLEDDISPLAY_TEXT_ALIGNMENT>(alignment));
utf8.number(display.getStringWidth(text, std::strlen(text), true));
utf8.number(display.drawString(48, 28, String(text)));
utf8.framebuffer(display);
}
display.setTextAlignment(TEXT_ALIGN_LEFT);
for (const char *text : strings)
for (unsigned width : {18, 47, 96}) {
DefaultFontTableLookup(' ');
display.clear();
display.setColor(WHITE);
wrapping.number(display.drawStringMaxWidth(-2, 3, width, String(text)));
wrapping.framebuffer(display);
}
if (EXPECT_MODE) {
// Newline width lookup is independently checked for the experiment;
// baseline font code indexes before its header for '\n'.
const char a[] = {'A', 0}, w[] = {'W', 0};
const auto expected = max(display.getStringWidth(a, 1), display.getStringWidth(w, 1));
assert(display.getStringWidth("A\nW", 3) == expected);
display.clear();
assert(display.drawStringMaxWidth(0, 0, 96, String("A\nW")) == 0);
}
assert(display.setLogBuffer(4, 64));
display.write("One\nTwo\nThree");
display.clear();
display.drawLogBuffer(0, 0);
logs.framebuffer(display);
++font_index;
}
// The font decoder must not accidentally intercept regular bitmap drawing.
const uint8_t bitmap[] = {0x5a, 0xff, 0x01, 0x80, 0x55, 0xaa, 0x00, 0x20};
display.clear();
display.drawFastImage(4, 4, 8, 8, bitmap);
bitmaps.framebuffer(display);
// Public setFont() remains compatible with user-supplied raw ThingPulse
// fonts even when the built-in Arial glyph storage is compressed.
static const uint8_t custom_font[] = {
4, 8, 'A', 2,
0, 0, 4, 4, 0, 4, 3, 3,
0x3c, 0x42, 0x42, 0x3c, 0x7e, 0x4a, 0x34,
};
display.setFont(custom_font);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFontTableLookupFunction(identityLookup);
display.setColor(WHITE);
display.clear();
assert(display.getStringWidth("AB", 2, false) == 7);
assert(display.drawRaw(0, 0, "AB", 2) == 2);
assert(std::memcmp(display.buffer, custom_font + 12, 7) == 0);
for (size_t i = 7; i < display.framebufferSize(); ++i) assert(display.buffer[i] == 0);
custom.framebuffer(display);
if (EXPECT_MODE) {
// Unsupported codes in a shorter external font cannot index its bitmaps
// as if they were extra metadata rows.
display.clear();
assert(display.getStringWidth("C", 1, false) == 0);
display.drawRaw(0, 0, "C", 1);
for (size_t i = 0; i < display.framebufferSize(); ++i) assert(display.buffer[i] == 0);
}
glyphs.print("glyphs"); clipping.print("clipping"); utf8.print("utf8");
wrapping.print("wrapping"); controls.print("controls"); bitmaps.print("bitmaps");
logs.print("logs"); custom.print("custom");
}
+39
View File
@@ -0,0 +1,39 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "helpers/ota/OtaTargets.h"
using mesh::ota::ota_target_env_name;
int main() {
unsigned id;
while (scanf("%x", &id) == 1) {
#if OTA_TARGET_NAME_TABLE && OTA_TARGET_NAME_FRONT_CODED
char buffer[mesh::ota::OTA_TARGET_ENV_NAME_CAPACITY];
const char* name = ota_target_env_name(id, buffer, sizeof buffer);
if (name) {
const size_t length = strlen(name);
char exact[mesh::ota::OTA_TARGET_ENV_NAME_CAPACITY];
assert(ota_target_env_name(id, exact, length + 1) == exact);
assert(strcmp(name, exact) == 0);
// A failed or unrelated lookup must not clobber another caller's result.
char short_buffer[mesh::ota::OTA_TARGET_ENV_NAME_CAPACITY];
memset(short_buffer, 'X', sizeof short_buffer);
assert(ota_target_env_name(id, short_buffer, length) == nullptr);
assert(short_buffer[0] == 0);
assert(short_buffer[length] == 'X');
assert(ota_target_env_name(id, nullptr, sizeof buffer) == nullptr);
assert(ota_target_env_name(id, short_buffer, 0) == nullptr);
assert(ota_target_env_name(0, short_buffer, sizeof short_buffer) == nullptr);
assert(short_buffer[0] == 0);
assert(strcmp(name, exact) == 0);
}
#else
// Keep the original one-argument API, including table-disabled/local fallback.
const char* name = ota_target_env_name(id);
assert(ota_target_env_name(id, nullptr, 0) == name);
#endif
puts(name ? name : "?");
}
return 0;
}
+151
View File
@@ -0,0 +1,151 @@
"""Lossless experimental Arial compression with the existing C tinf decoder."""
from pathlib import Path
import random
import re
import subprocess
import sys
import tempfile
import unittest
import zlib
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import generate_compact_arial as generate
HEADER = ROOT / "src/helpers/ui/CompactArialFonts.h"
HARNESS = ROOT / "test/fixtures/compact_arial/test.cpp"
def unpack_rle(data):
output = bytearray()
index = 0
while index < len(data):
token = data[index]
index += 1
if token < 128:
count = token + 1
output.extend(data[index:index + count])
index += count
else:
output.extend(bytes((data[index],)) * ((token & 127) + 3))
index += 1
return bytes(output)
class CompactArialTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.fonts = generate.read_fonts()
cls.header = HEADER.read_text(encoding="utf-8")
def compile_and_run(self, mode, header=None, invalid=False):
with tempfile.TemporaryDirectory() as directory:
folder = Path(directory)
if header is not None:
include = folder / "helpers/ui"
include.mkdir(parents=True)
(include / "CompactArialFonts.h").write_text(header, encoding="utf-8")
(folder / "helpers/ota/tinf").mkdir(parents=True)
(folder / "helpers/ota/tinf/tinf.h").write_text(
(ROOT / "src/helpers/ota/tinf/tinf.h").read_text(encoding="utf-8"), encoding="utf-8")
decoder = folder / "tinf.o"
subprocess.run([
"cc", "-Os", "-DMESHCORE_TINF_IMPLEMENTATION=1", "-c",
str(ROOT / "src/helpers/ota/tinf/tinflate.c"), "-o", str(decoder),
], check=True)
executable = folder / "fonts"
subprocess.run([
"c++", "-std=c++11", "-O2", "-Wall", "-Wextra", "-Werror",
f"-DMESH_ARIAL_EXPERIMENT={mode}", f"-I{folder}", f"-I{ROOT / 'src'}",
str(HARNESS), str(HARNESS.with_name("other.cpp")), str(decoder), "-o", str(executable),
], check=True)
return subprocess.run(
[str(executable)] + (["invalid"] if invalid else []),
text=True, capture_output=True, check=True).stdout.splitlines()
def test_generated_header_is_reproducible_and_omits_unused_10(self):
self.assertEqual(self.header, generate.generate_header(self.fonts))
self.assertNotIn("FONT_0", self.header)
self.assertEqual([len(font.original) for font in self.fonts], [2731, 5049, 9643])
self.assertEqual(max(len(glyph.bitmap) for font in self.fonts for glyph in font.glyphs), 91)
def test_rle_and_raw_bypass_roundtrip_every_glyph_and_metric(self):
for font in self.fonts:
packed, rows = generate.rle_font(font)
descriptor = generate.descriptor(font, rows)
self.assertEqual(descriptor[:4], font.header)
for index, (glyph, record) in enumerate(zip(font.glyphs, rows)):
location, length, width = record
self.assertEqual((length, width), (len(glyph.bitmap), glyph.width))
self.assertEqual(descriptor[6 + 4 * index:8 + 4 * index], font.original[6 + 4 * index:8 + 4 * index])
if not length:
self.assertEqual(location, 0xffff)
continue
first = location & 0x7fff
end = next((next_row[0] & 0x7fff for next_row in rows[index + 1:] if next_row[1]), len(packed))
encoded = packed[first:end]
self.assertEqual(encoded if location & 0x8000 else unpack_rle(encoded), glyph.bitmap)
def test_rle_literal_and_run_boundaries(self):
cases = [bytes(range(256)), b"x" * 130, b"x" * 131, b"x" * 260,
b"abc" + b"x" * 130 + b"def", b"", b"x", b"xx"]
randomizer = random.Random(1234)
cases += [bytes(randomizer.randrange(8) for _ in range(length)) for length in range(300)]
for data in cases:
self.assertEqual(unpack_rle(generate.rle_encode(data)), data)
def test_zopfli_chunks_roundtrip_every_glyph_with_512_byte_cap(self):
original_total = 0
compressed_total = 0
for font in self.fonts[1:]:
packed, rows, offsets, lengths = generate.chunk_font(font)
raw = [zlib.decompress(packed[offsets[index]:offsets[index + 1]], -15) for index in range(len(lengths))]
self.assertEqual(list(map(len, raw)), list(lengths))
self.assertLessEqual(max(lengths), 512)
for glyph, (location, length, width) in zip(font.glyphs, rows):
self.assertEqual((length, width), (len(glyph.bitmap), glyph.width))
if length:
self.assertEqual(raw[location >> 9][(location & 511):(location & 511) + length], glyph.bitmap)
else:
self.assertEqual(location, 0xffff)
original_total += len(font.original)
compressed_total += len(packed) + len(generate.descriptor(font, rows)) + 2 * (len(offsets) + len(lengths))
self.assertEqual(original_total - compressed_total, 7555)
def test_google_zopfli_uses_1000_iterations(self):
from unittest.mock import patch
generate.chunk_font.cache_clear()
original = generate.raw_deflate_compress
with patch.object(generate, "raw_deflate_compress", wraps=original) as compressor:
generate.chunk_font(self.fonts[0])
self.assertEqual(compressor.call_count, 4)
self.assertTrue(all(call.kwargs == {"numiterations": 1000} for call in compressor.call_args_list))
def test_cpp_both_variants_preserve_all_448_glyphs_and_metadata(self):
expected = []
for id, font in enumerate(self.fonts[1:], 1):
expected.append("F " + " ".join(map(str, (id, *font.header))))
for slot, glyph in enumerate(font.glyphs):
expected.append(f"G {id} {slot + font.header[2]} {glyph.width} {len(glyph.bitmap)} {glyph.bitmap.hex()}")
for mode in (2, 3):
with self.subTest(mode=mode):
self.assertEqual(self.compile_and_run(mode), expected)
def test_cpp_corrupt_descriptor_or_deflate_stream_fails_closed(self):
# Glyph 33 is the first bitmap; glyph32 is a blank space.
for mode in (2, 3):
bad = re.sub(
r"(static const uint8_t FONT_1_DESCRIPTOR\[\] = \{\s*"
r"(?:0x[0-9a-f]{2},\s*){8})0x[0-9a-f]{2},\s*0x[0-9a-f]{2}",
lambda match: match[1] + "0xff, 0xfe", self.header)
self.assertNotEqual(bad, self.header)
self.compile_and_run(mode, bad, invalid=True)
bad = re.sub(r"(static const uint8_t FONT_1_DATA\[\] = \{\s*)0x[0-9a-f]{2}",
lambda match: match[1] + "0x07", self.header)
self.compile_and_run(3, bad, invalid=True)
if __name__ == "__main__":
unittest.main()
+129
View File
@@ -0,0 +1,129 @@
"""Production Noto compression preserves all selected font pixels and metrics."""
from pathlib import Path
import re
import subprocess
import sys
import tempfile
import unittest
import zlib
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import generate_compact_noto as generate
import generate_compact_arial as codec
HEADER = ROOT / "src/helpers/ui/CompactNotoFonts.h"
HARNESS = ROOT / "test/fixtures/compact_noto/test.cpp"
class CompactNotoTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.fonts, cls.license = generate.read_fonts()
cls.header = HEADER.read_text(encoding="utf-8")
def compile_and_run(self, header=None, invalid=False):
with tempfile.TemporaryDirectory() as directory:
folder = Path(directory)
if header is not None:
include = folder / "helpers/ui"
include.mkdir(parents=True)
(include / "CompactNotoFonts.h").write_text(header, encoding="utf-8")
(folder / "helpers/ota/tinf").mkdir(parents=True)
(folder / "helpers/ota/tinf/tinf.h").write_text(
(ROOT / "src/helpers/ota/tinf/tinf.h").read_text(encoding="utf-8"), encoding="utf-8")
decoder = folder / "tinf.o"
subprocess.run([
"cc", "-Os", "-DMESHCORE_TINF_IMPLEMENTATION=1", "-c",
str(ROOT / "src/helpers/ota/tinf/tinflate.c"), "-o", str(decoder),
], check=True)
executable = folder / "fonts"
subprocess.run([
"c++", "-std=c++11", "-O2", "-Wall", "-Wextra", "-Werror",
f"-I{folder}", f"-I{ROOT / 'src'}", str(HARNESS),
str(HARNESS.with_name("other.cpp")), str(decoder), "-o", str(executable),
], check=True)
return subprocess.run(
[str(executable)] + (["invalid"] if invalid else []),
text=True, capture_output=True, check=True).stdout.splitlines()
def test_generated_header_matches_authoritative_mode7_and_has_required_notice(self):
self.assertEqual(self.header, generate.generate_header(self.fonts, self.license))
self.assertEqual([len(font.original) for font in self.fonts], [4419, 8176])
self.assertNotIn("FONT_0", self.header)
self.assertNotIn("MESH_ARIAL_EXPERIMENT", self.header)
self.assertNotIn("decodeRle", self.header)
self.assertIn("SIL OPEN FONT LICENSE Version 1.1", self.header)
self.assertIn("Copyright 2015 Google Inc. All Rights Reserved.", self.header)
self.assertIn("4efc2789b9fcaa87b6b55f19c25ac9a2919e4f9ca31d6b5b97bea7977d729d4f", self.header)
self.assertIn("Pillow 12.3.0 / FreeType 2.14.3", self.header)
def test_all_glyph_chunks_roundtrip_in_at_most_512_decoded_bytes(self):
total = 0
for font in self.fonts:
packed, rows, offsets, lengths = codec.chunk_font(font)
raw = [zlib.decompress(packed[offsets[group]:offsets[group + 1]], -15)
for group in range(len(lengths))]
self.assertEqual(list(map(len, raw)), list(lengths))
self.assertLessEqual(max(lengths), 512)
self.assertEqual(len(rows), 224)
self.assertEqual(sum(bool(glyph.bitmap) for glyph in font.glyphs), 189)
for glyph, (location, length, width) in zip(font.glyphs, rows):
self.assertEqual((length, width), (len(glyph.bitmap), glyph.width))
if length:
self.assertEqual(raw[location >> 9][(location & 511):(location & 511) + length], glyph.bitmap)
else:
self.assertEqual(location, 0xffff)
total += len(packed) + len(codec.descriptor(font, rows)) + 2 * (len(offsets) + len(lengths))
self.assertEqual(total, 6777)
def test_google_zopfli_requires_1000_iterations(self):
from unittest.mock import patch
codec.chunk_font.cache_clear()
original = codec.raw_deflate_compress
with patch.object(codec, "raw_deflate_compress", wraps=original) as compressor:
generate.generate_header(self.fonts, self.license)
self.assertEqual(compressor.call_count, 22)
self.assertTrue(all(call.kwargs == {"numiterations": 1000} for call in compressor.call_args_list))
def test_cpp_all448_glyphs_match_raw_mode7_without_experimental_macro(self):
expected = []
for id, font in enumerate(self.fonts, 1):
expected.append("F " + " ".join(map(str, (id, *font.header))))
for slot, glyph in enumerate(font.glyphs):
expected.append(f"G {id} {slot + font.header[2]} {glyph.width} {len(glyph.bitmap)} {glyph.bitmap.hex()}")
self.assertEqual(self.compile_and_run(), expected)
def test_cpp_rejects_corrupt_descriptor_deflate_truncation_and_trailing_bytes(self):
invalid_headers = [re.sub(
r"(static const uint8_t FONT_1_DESCRIPTOR\[\] = \{\s*"
r"(?:0x[0-9a-f]{2},\s*){8})0x[0-9a-f]{2},\s*0x[0-9a-f]{2}",
lambda match: match[1] + "0xff, 0xfe", self.header)]
invalid_headers.append(re.sub(
r"(static const uint8_t FONT_1_DATA\[\] = \{\s*)0x[0-9a-f]{2}",
lambda match: match[1] + "0x07", self.header))
offsets = re.search(r"static const uint16_t FONT_1_OFFSETS\[\] = \{\s*0, (\d+)", self.header)
for end in (1, int(offsets[1]) + 1):
invalid_headers.append(re.sub(
r"(static const uint16_t FONT_1_OFFSETS\[\] = \{\s*0, )\d+",
lambda match: match[1] + str(end), self.header))
for header in invalid_headers:
self.assertNotEqual(header, self.header)
self.compile_and_run(header, invalid=True)
def test_reader_rejects_missing_source_coverage_or_license(self):
source = generate.SOURCE.read_text(encoding="utf-8")
invalid = [source.replace("#if MESH_ARIAL_EXPERIMENT == 7", "#if MESH_ARIAL_EXPERIMENT == 17"),
source.replace("SIL OPEN FONT LICENSE Version 1.1", "removed license")]
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "source.h"
for content in invalid:
path.write_text(content, encoding="utf-8")
with self.assertRaises(ValueError):
generate.read_fonts(path)
if __name__ == "__main__":
unittest.main()
+106
View File
@@ -0,0 +1,106 @@
"""Same-size libre font trials preserve the complete existing character coverage."""
from pathlib import Path
import re
import sys
import unittest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import generate_font_family_trials as generate
from generate_compact_arial import Font, Glyph, read_fonts
HEADER = ROOT / "src/helpers/ui/FontFamilyTrials.h"
def parse_font(name, data):
count = data[3]
base = 4 + 4 * count
glyphs = []
consumed = 0
for index in range(count):
offset = int.from_bytes(data[4 + 4 * index:6 + 4 * index], "big")
length, width = data[6 + 4 * index:8 + 4 * index]
if offset == 0xffff:
if length:
raise ValueError("Blank glyph has bitmap bytes")
bitmap = b""
else:
if not length or offset != consumed or base + offset + length > len(data):
raise ValueError("Glyph data is not contiguous/in bounds")
bitmap = data[base + offset:base + offset + length]
consumed += length
glyphs.append(Glyph(width, bitmap))
if base + consumed != len(data):
raise ValueError("Trailing font bytes")
return Font(name, data, tuple(glyphs))
class FontFamilyTrialsTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.text = HEADER.read_text(encoding="utf-8")
cls.baseline = read_fonts()
cls.trials = {}
for mode, body in re.findall(r"#if MESH_ARIAL_EXPERIMENT == ([45678])\s*(.*?)\n#endif", cls.text, re.S):
fonts = []
for id, encoded in re.findall(r"static const uint8_t TRIAL_([12])\[\] = \{(.*?)\};", body, re.S):
data = bytes(int(value, 16) for value in re.findall(r"0x([0-9a-f]{2})", encoded))
fonts.append(parse_font(f"MeshFontTrial{mode}_{id}", data))
cls.trials[int(mode)] = tuple(fonts)
def test_all_five_trials_cover_exact_original_drawable_slots(self):
self.assertEqual(set(self.trials), {4, 5, 6, 7, 8})
for mode, fonts in self.trials.items():
self.assertEqual(len(fonts), 2)
for id, font in enumerate(fonts, 1):
with self.subTest(mode=mode, id=id):
self.assertEqual(font.header, self.baseline[id].header)
self.assertEqual(len(font.glyphs), 224)
self.assertEqual([bool(glyph.bitmap) for glyph in font.glyphs],
[bool(glyph.bitmap) for glyph in self.baseline[id].glyphs])
self.assertEqual(sum(bool(glyph.bitmap) for glyph in font.glyphs), 189)
def test_H_visible_size_top_and_line_height_are_unchanged(self):
for mode, fonts in self.trials.items():
for id, font in enumerate(fonts, 1):
with self.subTest(mode=mode, id=id):
self.assertEqual(generate.ink_y_bounds(font, ord("H")), (3, 14) if id == 1 else (5, 21))
self.assertEqual(font.header[1], 19 if id == 1 else 28)
def test_every_glyph_pixel_fits_its_advance_and_line_box(self):
for mode, fonts in self.trials.items():
for id, font in enumerate(fonts, 1):
stride = (font.header[1] + 7) // 8
for code, glyph in enumerate(font.glyphs, 32):
with self.subTest(mode=mode, id=id, code=code):
self.assertLessEqual(len(glyph.bitmap), glyph.width * stride)
for index, value in enumerate(glyph.bitmap):
for bit in range(8):
if value & (1 << bit):
self.assertLess(index // stride, glyph.width)
self.assertLess((index % stride) * 8 + bit, font.header[1])
def test_raw_byte_measurements_and_source_notices_are_exact(self):
self.assertEqual({mode: sum(len(font.original) for font in fonts)
for mode, fonts in self.trials.items()},
{4: 14979, 5: 14871, 6: 15701, 7: 12595, 8: 14230})
for family in generate.FAMILIES:
self.assertIn(family.sha256, self.text)
self.assertIn(family.upstream, self.text)
self.assertIn(f"SPDX-License-Identifier: {family.spdx}", self.text)
for notice in ("SIL OPEN FONT LICENSE Version 1.1", "Copyright (c) 2012 Red Hat, Inc.",
"Copyright 2015 Google Inc. All Rights Reserved.", "Bitstream Vera Fonts Copyright",
"Arev Fonts Copyright", "Copyright (c) 2006 by Tavmjong Bah."):
self.assertIn(notice, self.text)
self.assertNotIn("FONT_0", self.text)
self.assertNotIn("LiberationSansNarrow", self.text)
def test_serializer_preserves_glyph_bytes_and_missing_slot_width(self):
base = self.baseline[1]
self.assertEqual(generate.make_font(base.name, base.header, list(base.glyphs)), base)
if __name__ == "__main__":
unittest.main()
+165
View File
@@ -0,0 +1,165 @@
import importlib.util
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
SPEC = importlib.util.spec_from_file_location("nrf52_flash_trim", ROOT / "scripts/nrf52_flash_trim.py")
TRIM = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(TRIM)
class Nrf52FlashTrimTest(unittest.TestCase):
def policy(self, name="RAK_4631_repeater", role="simple_repeater", **kwargs):
options = dict(platform="nordicnrf52", mcu="nrf52840", env_name=name,
src_filter="+<*.cpp> +<../examples/%s>" % role)
options.update(kwargs)
return TRIM.trim_policy(**options)
def test_every_infrastructure_role_keeps_compact_crypto_and_names(self):
for role in ("simple_repeater", "simple_room_server", "simple_sensor", "kiss_modem"):
with self.subTest(role=role):
defines = self.policy(role=role)
for name in ("MESH_NRF52_FLASH_TRIM", "ED25519_COMPACT_BASE",
"ED25519_COMPACT_SHA512", "OTA_TARGET_NAME_FRONT_CODED",
"SSD1306_NO_SPLASH"):
self.assertEqual(defines[name], 1)
self.assertEqual(defines["CFG_TUD_HID"], 0)
self.assertNotIn("CFG_TUD_CDC", defines)
self.assertFalse(any(name.startswith("CFG_TUH") for name in defines))
def test_every_companion_alias_is_excluded(self):
for suffix in ("companion_radio_full", "companion_radio_usb", "companion_radio_ble",
"companion_radio_wifi", "companion_radio_serial", "companion_radio_ethernet",
"companion_usb", "companion_ble", "comp_radio_usb", "terminal_chat", "term_chat"):
with self.subTest(suffix=suffix):
self.assertEqual(self.policy(name="RAK_4631_" + suffix), {})
def test_source_filter_and_synthetic_alias_also_exclude_companion(self):
for role in ("companion_radio/*.cpp", "simple_secure_chat/main.cpp"):
self.assertEqual(self.policy(name="custom", role=role), {})
for flags in ('-DCOMPANION_RADIO_FULL=1', '-D COMPANION_FEATURE_USB_MOTA_SOURCE=1',
'-DOTA_VARIANT=\'"RAK_4631_terminal_chat"\'',
'-DOTA_VARIANT=\'"RAK_4631_companion_radio_full"\''):
with self.subTest(flags=flags):
self.assertEqual(self.policy(flags=flags), {})
self.assertEqual(self.policy(cppdefines=[("COMPANION_RADIO_FULL", 1)]), {})
def test_unknown_roles_other_platforms_and_explicit_optout_are_unchanged(self):
for options in (dict(role="custom"), dict(platform="espressif32", mcu="esp32"),
dict(platform="ststm32", mcu="stm32f411"),
dict(flags="-DMESH_NRF52_FLASH_TRIM=0")):
self.assertEqual(self.policy(**options), {})
def test_effective_filter_is_ordered_and_does_not_include_outside_source_by_wildcard(self):
self.assertEqual(self.policy(src_filter="+<*> +<../examples/simple_repeater> -<../examples/simple_repeater>"), {})
self.assertEqual(self.policy(src_filter="+<*>"), {})
self.assertTrue(self.policy(src_filter="+<../examples/companion_radio> -<../examples/companion_radio> +<../examples/simple_repeater>"))
def test_board_and_user_defines_are_not_overridden(self):
defines = self.policy(flags="-D CFG_TUD_HID=1 -UED25519_COMPACT_BASE -USSD1306_NO_SPLASH")
self.assertNotIn("CFG_TUD_HID", defines)
self.assertNotIn("ED25519_COMPACT_BASE", defines)
self.assertNotIn("SSD1306_NO_SPLASH", defines)
self.assertEqual(TRIM.flag_defines("-DMESH_NRF52_FLASH_TRIM=1 -UMESH_NRF52_FLASH_TRIM")["MESH_NRF52_FLASH_TRIM"], None)
def test_cc310_archive_is_exact_and_requires_supported_mcu_and_enabled_crypto(self):
with tempfile.TemporaryDirectory() as temp_dir:
archive = Path(temp_dir) / "libraries/Adafruit_nRFCrypto" / TRIM.CC310_ARCHIVE
archive.parent.mkdir(parents=True)
archive.write_bytes(b"future-sdk")
enabled = {"USE_CC310_HW_CRYPTO": "1"}
self.assertFalse(TRIM.supported_cc310(temp_dir, "nrf52840", enabled))
with patch.object(TRIM, "CC310_ARCHIVE_SHA256", TRIM.hashlib.sha256(b"future-sdk").hexdigest()):
self.assertTrue(TRIM.supported_cc310(temp_dir, "nrf52840", enabled))
self.assertFalse(TRIM.supported_cc310(temp_dir, "nrf52832", enabled))
self.assertFalse(TRIM.supported_cc310(temp_dir, "nrf52840", {"USE_CC310_HW_CRYPTO": "0"}))
self.assertFalse(TRIM.supported_cc310(temp_dir, "nrf52840", {}))
def test_profile_wiring_is_nrf52_only_and_companion_common_flags_unchanged(self):
config = (ROOT / "platformio.ini").read_text()
nrf = config.split("[nrf52_base]", 1)[1].split("\n[", 1)[0]
self.assertIn("pre:scripts/nrf52_flash_trim.py", nrf)
self.assertEqual(config.count("pre:scripts/nrf52_flash_trim.py"), 1)
self.assertNotIn("ED25519_COMPACT", nrf)
self.assertNotIn("CFG_TUD_HID", nrf)
self.assertNotIn("SSD1306_NO_SPLASH", nrf)
self.assertIn("-D MESH_NRF52_LOOP_STACK_WORDS=2048", nrf)
def test_tft_force_include_is_per_library_and_preserves_existing_include_pair(self):
self.assertTrue(TRIM.source_selected("+<helpers/ui/ST7735Display.cpp>",
"helpers/ui/ST7735Display.cpp"))
self.assertFalse(TRIM.source_selected("+<helpers/ui/*.cpp> -<helpers/ui/ST7735Display.cpp>",
"helpers/ui/ST7735Display.cpp"))
class LibraryEnvironment(dict):
def Append(self, **kwargs):
for key, value in kwargs.items():
self.setdefault(key, []).extend(value)
def subst(self, text):
return text.replace("$PROJECT_DIR", str(ROOT))
library = LibraryEnvironment(CCFLAGS=["-include", "SoftDeviceSvcCompat.h"])
source = object()
self.assertIs(TRIM.trim_tft_library(library, source), source)
self.assertEqual(library["CCFLAGS"].count("-include"), 2)
before = list(library["CCFLAGS"])
TRIM.trim_tft_library(library, source)
self.assertEqual(library["CCFLAGS"], before)
def test_install_does_not_mutate_excluded_roles_and_scopes_wrapper_build(self):
class Platform:
name = "nordicnrf52"
def get_package_dir(self, _name):
return "sdk"
class BuildEnvironment(dict):
def PioPlatform(self):
return Platform()
def BoardConfig(self):
return {"build.mcu": "nrf52840"}
def AppendUnique(self, **kwargs):
for name, values in kwargs.items():
for value in values:
if value not in self.setdefault(name, []):
self[name].append(value)
def BuildSources(self, *args):
self.setdefault("BUILT_SOURCES", []).append(args)
base = dict(BUILD_FLAGS=["-DUSE_CC310_HW_CRYPTO=1"],
SRC_FILTER=["+<../examples/simple_repeater>"], CPPDEFINES=[])
excluded = BuildEnvironment(**base, PIOENV="RAK_4631_companion_radio_full")
before = dict(excluded)
TRIM.install(excluded)
self.assertEqual(excluded, before)
active = BuildEnvironment(BUILD_FLAGS=list(base["BUILD_FLAGS"]),
SRC_FILTER=list(base["SRC_FILTER"]), CPPDEFINES=[],
PIOENV="RAK_4631_repeater")
with patch.object(TRIM, "supported_cc310", return_value=True):
TRIM.install(active)
self.assertTrue(active["MESH_NRF52_FLASH_TRIM_ACTIVE"])
self.assertIn("-Wl,--wrap=CRYS_ECPKI_GetEcDomain", active["LINKFLAGS"])
self.assertEqual(active["BUILT_SOURCES"][0][-1], "+<Cc310DomainTrim.c>")
@unittest.skipUnless(shutil.which("cc"), "a native C compiler is required")
def test_wrapped_domain_preserves_p256_and_rejects_unused_curves(self):
fixture = ROOT / "test/fixtures/nrf52_flash_trim"
with tempfile.TemporaryDirectory() as temp_dir:
executable = Path(temp_dir) / "cc310-trim-test"
subprocess.run(["cc", "-std=c99", "-Wall", "-Wextra", "-Werror",
"-DMESH_NRF52_FLASH_TRIM=1", "-I" + str(fixture),
str(ROOT / "src/helpers/nrf52/Cc310DomainTrim.c"),
str(fixture / "test_domain_trim.c"), "-o", str(executable)], check=True)
subprocess.run([str(executable)], check=True)
if __name__ == "__main__":
unittest.main()
+318
View File
@@ -0,0 +1,318 @@
"""Font notices travel with licensed firmware without changing DFU payloads."""
import hashlib
import json
from pathlib import Path
import shutil
import subprocess
import struct
import sys
import tempfile
import unittest
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import package_nrf52_font_license as font_license
import firmware_memory_manifest as memory
import package_cascade_release as release
import nrf52_flash_trim as trim
class BuildEnvironment(dict):
def __init__(self, **changes):
super().__init__(MESH_NRF52_FLASH_TRIM_ACTIVE=True,
PIOENV="Heltec_t114_repeater", BUILD_FLAGS=[],
CPPDEFINES=["NRF52_PLATFORM", "ST7789", ("MESH_NRF52_FLASH_TRIM", 1)])
self.update(changes)
self.actions = []
self.dependencies = []
def Depends(self, target, sources):
self.dependencies.append((target, sources))
def AddPostAction(self, target, action):
self.actions.append((target, action))
def subst(self, value):
return (value.replace("$PROJECT_DIR", str(ROOT))
.replace("$BUILD_DIR", str(self.get("BUILD_DIR", "build")))
.replace("${PROGNAME}", "firmware"))
def PioPlatform(self):
class Platform:
name = "nordicnrf52"
def get_package_dir(self, _name):
return None
return Platform()
def BoardConfig(self):
return {"build.mcu": "nrf52840"}
def AppendUnique(self, **changes):
for name, values in changes.items():
for value in values:
if value not in self.setdefault(name, []):
self[name].append(value)
class Nrf52FontLicenseTest(unittest.TestCase):
def dfu(self, path):
manifest = {"manifest": {"application": {"bin_file": "firmware.bin",
"dat_file": "firmware.dat"}}}
with ZipFile(path, "w") as archive:
for name, data in (("manifest.json", json.dumps(manifest).encode()),
("firmware.bin", bytes(range(256)) * 8),
("firmware.dat", b"original-init-packet")):
info = ZipInfo(name, (2026, 1, 2, 3, 4, 6))
info.compress_type = ZIP_DEFLATED
archive.writestr(info, data)
return manifest
def test_notice_matches_generated_source_copyright_license_and_provenance(self):
notice = font_license.notice_bytes().decode()
generated = (ROOT / "src/helpers/ui/CompactNotoFonts.h").read_text(encoding="utf-8")
copyright = "Copyright 2015 Google Inc. All Rights Reserved."
end = "OTHER DEALINGS IN THE FONT SOFTWARE."
source_license = generated.split(copyright, 1)[1].split(end, 1)[0]
packaged_license = notice.split(copyright, 1)[1].split(end, 1)[0]
self.assertEqual(source_license, packaged_license)
for item in (copyright, "MeshFontProduction9", "OFL-1.1",
"4efc2789b9fcaa87b6b55f19c25ac9a2919e4f9ca31d6b5b97bea7977d729d4f",
"2.000;GOOG;noto-source:20170915:90ef993387c0"):
self.assertIn(item, notice)
self.assertIn(item, generated)
self.assertNotIn(b"\r", font_license.notice_bytes())
def test_only_effective_production_mode_installs_for_qualified_infrastructure(self):
for role in ("repeater", "room_server", "sensor", "kiss"):
self.assertTrue(font_license.qualifies(BuildEnvironment(PIOENV="custom_" + role)))
self.assertTrue(font_license.qualifies(BuildEnvironment(BUILD_FLAGS="-DMESH_NRF52_FONT_MODE=9")))
self.assertTrue(font_license.qualifies(BuildEnvironment(
BUILD_FLAGS="-DMESH_NRF52_FONT_MODE=0 -DMESH_ARIAL_EXPERIMENT=9")))
exclusions = (
dict(MESH_NRF52_FLASH_TRIM_ACTIVE=False),
dict(PIOENV="Heltec_t114_companion_radio_ble"),
dict(BUILD_FLAGS='-DOTA_VARIANT=\'"custom_companion_radio_full"\''),
dict(BUILD_FLAGS="-DCOMPANION_FEATURE_USB_MOTA_SOURCE=1"),
dict(BUILD_FLAGS="-DCOMPANION_RADIO_FULL=0"),
dict(BUILD_FLAGS="-UMESH_NRF52_FLASH_TRIM"),
dict(BUILD_FLAGS="-DMESH_NRF52_FLASH_TRIM=0"),
dict(BUILD_FLAGS="-UST7789"), dict(BUILD_FLAGS="-UNRF52_PLATFORM"),
dict(BUILD_FLAGS="-DMESH_NRF52_FONT_MODE=0"),
dict(BUILD_FLAGS="-DMESH_NRF52_FONT_MODE=9 -DMESH_ARIAL_EXPERIMENT=7"),
dict(BUILD_FLAGS="-DMESH_ARIAL_EXPERIMENT=unknown"),
dict(CPPDEFINES=["NRF52_PLATFORM", ("MESH_NRF52_FLASH_TRIM", 1)]),
)
for changes in exclusions:
with self.subTest(changes=changes):
env = BuildEnvironment(**changes)
before = dict(env)
self.assertFalse(font_license.install(env))
self.assertEqual(env, before)
self.assertEqual(env.actions, [])
self.assertEqual(env.dependencies, [])
def test_install_dependencies_and_cached_build_callback(self):
with tempfile.TemporaryDirectory() as temp:
directory = Path(temp)
self.dfu(directory / "firmware.zip")
env = BuildEnvironment(BUILD_DIR=directory)
self.assertTrue(font_license.install(env))
self.assertFalse(font_license.install(env))
self.assertEqual([target for target, _action in env.actions],
["$BUILD_DIR/${PROGNAME}.zip", "buildprog"])
self.assertIn(str(font_license.NOTICE_PATH), env.dependencies[0][1])
self.assertIn(str(ROOT / "scripts/package_nrf52_font_license.py"), env.dependencies[0][1])
env.actions[1][1]([], [], env)
self.assertEqual((directory / "firmware.font-license.txt").read_bytes(), font_license.notice_bytes())
before = (directory / "firmware.zip").read_bytes()
(directory / "firmware.font-license.txt").unlink()
env.actions[1][1]([], [], env)
self.assertEqual((directory / "firmware.zip").read_bytes(), before)
self.assertTrue((directory / "firmware.font-license.txt").is_file())
def test_trim_install_integrates_notice_for_display_infrastructure_only(self):
for role in ("simple_repeater", "simple_room_server", "simple_sensor", "kiss_modem"):
env = BuildEnvironment(MESH_NRF52_FLASH_TRIM_ACTIVE=False,
CPPDEFINES=["NRF52_PLATFORM", "ST7789"],
SRC_FILTER=["+<../examples/" + role + ">"])
trim.install(env)
self.assertTrue(env["MESH_NRF52_FONT_LICENSE_INSTALLED"])
self.assertEqual(len(env.actions), 2)
for changes in (
dict(CPPDEFINES=["NRF52_PLATFORM"]),
dict(PIOENV="Heltec_t114_companion_radio_usb"),
dict(SRC_FILTER=["+<../examples/companion_radio>"]),
dict(BUILD_FLAGS="-DMESH_NRF52_FONT_MODE=0")):
env = BuildEnvironment(MESH_NRF52_FLASH_TRIM_ACTIVE=False,
SRC_FILTER=["+<../examples/simple_repeater>"])
env.update(changes)
trim.install(env)
self.assertNotIn("MESH_NRF52_FONT_LICENSE_INSTALLED", env)
self.assertEqual(env.actions, [])
def test_zip_original_entries_manifest_and_physical_payload_bytes_are_unchanged(self):
with tempfile.TemporaryDirectory() as temp:
directory = Path(temp)
package = directory / "firmware.zip"
self.dfu(package)
with ZipFile(package) as archive:
entries = {info.filename: (archive.read(info), vars(info).copy()
if hasattr(info, "__dict__") else
(info.CRC, info.compress_size, info.file_size, info.date_time, info.header_offset,
info.compress_type, info.external_attr)) for info in archive.infolist()}
original_payload_prefix = package.read_bytes()[:archive.start_dir]
sidecar = font_license.package_font_license(package)
self.assertEqual(package.read_bytes()[:len(original_payload_prefix)], original_payload_prefix)
with ZipFile(package) as archive:
self.assertEqual(set(archive.namelist()), set(entries) | {"FONT-LICENSE.txt"})
for name, (data, attributes) in entries.items():
info = archive.getinfo(name)
actual = vars(info).copy() if hasattr(info, "__dict__") else (
info.CRC, info.compress_size, info.file_size, info.date_time, info.header_offset,
info.compress_type, info.external_attr)
self.assertEqual(archive.read(name), data)
self.assertEqual(actual, attributes)
self.assertIsNone(archive.testzip())
self.assertEqual(archive.read("FONT-LICENSE.txt"), sidecar.read_bytes())
self.assertEqual(archive.getinfo("FONT-LICENSE.txt").date_time, (1980, 1, 1, 0, 0, 0))
def test_deterministic_and_idempotent_without_rewriting_cached_files(self):
with tempfile.TemporaryDirectory() as temp:
directory = Path(temp)
first, second = directory / "one.zip", directory / "two.zip"
self.dfu(first)
shutil.copyfile(first, second)
sidecar = font_license.package_font_license(first)
font_license.package_font_license(second)
self.assertEqual(first.read_bytes(), second.read_bytes())
before = (first.read_bytes(), first.stat().st_mtime_ns, sidecar.stat().st_mtime_ns)
font_license.package_font_license(first)
self.assertEqual((first.read_bytes(), first.stat().st_mtime_ns, sidecar.stat().st_mtime_ns), before)
def test_refuses_foreign_entries_and_sidecars_before_mutating_zip(self):
with tempfile.TemporaryDirectory() as temp:
directory = Path(temp)
package = directory / "firmware.zip"
self.dfu(package)
sidecar = directory / "firmware.font-license.txt"
sidecar.write_bytes(b"user's unrelated text")
before = package.read_bytes()
with self.assertRaisesRegex(ValueError, "refusing to overwrite"):
font_license.package_font_license(package)
self.assertEqual(package.read_bytes(), before)
self.assertEqual(sidecar.read_bytes(), b"user's unrelated text")
sidecar.unlink()
with ZipFile(package, "a") as archive:
archive.writestr("FONT-LICENSE.txt", b"another license")
before = package.read_bytes()
with self.assertRaisesRegex(ValueError, "unexpected or duplicate"):
font_license.package_font_license(package)
self.assertEqual(package.read_bytes(), before)
self.assertFalse(sidecar.exists())
def test_collection_ignores_stale_source_notice_and_rejects_stale_output(self):
with tempfile.TemporaryDirectory() as temp:
directory = Path(temp)
build = directory / "build"
output = directory / "out"
build.mkdir()
output.mkdir()
self.dfu(build / "firmware.zip")
source_sidecar = build / "firmware.font-license.txt"
source_sidecar.write_bytes(font_license.notice_bytes())
stem = output / "unaffected"
self.assertIsNone(font_license.collect_build_notice(build, stem))
self.assertTrue(source_sidecar.exists())
stale = output / "unaffected.font-license.txt"
stale.write_bytes(font_license.notice_bytes())
with self.assertRaisesRegex(ValueError, "stale output"):
font_license.collect_build_notice(build, stem)
self.assertEqual(stale.read_bytes(), font_license.notice_bytes())
def test_notice_is_staged_hashed_and_tampering_or_removal_is_rejected(self):
with tempfile.TemporaryDirectory() as temp:
directory = Path(temp)
build, output = directory / "build", directory / "out"
build.mkdir()
output.mkdir()
self.dfu(build / "firmware.zip")
font_license.package_font_license(build / "firmware.zip")
label = "v1.17.1.5-halo-keymind-cascade-dev"
version = label + "-deadbeef"
stem = output / ("Heltec_t114_repeater-" + version)
shutil.copyfile(build / "firmware.zip", output / (stem.name + ".zip"))
uf2_data = (struct.pack("<8I", 0x0A324655, 0x9E5D5157, 0x2000, 0x26000,
256, 0, 1, 0xADA52840)
+ b"u" * 256 + b"\0" * 220 + struct.pack("<I", 0x0AB16F30))
(output / (stem.name + ".uf2")).write_bytes(uf2_data)
notice = font_license.collect_build_notice(build, stem)
manifest = dict(schema_version=2, target="Heltec_t114_repeater",
artifact_target="Heltec_t114_repeater", platform="NRF52_PLATFORM",
verified=True, ota_update_verified=True, verification=[], build_profile="standard")
(output / (stem.name + ".capabilities.json")).write_text(json.dumps(manifest))
elf = build / "firmware.elf"
elf.write_bytes(b"qualified-ELF")
report = dict(schema_version=1, passed=True, available_internal_bytes=80000,
required_heap_bytes=50000, largest_internal_region_bytes=80000,
required_contiguous_bytes=5120,
elf_sha256=hashlib.sha256(elf.read_bytes()).hexdigest(), target="build_env")
(build / "firmware.memory.json").write_text(json.dumps(report))
memory.package_report(build, stem)
proof = memory.validate_package(stem)
self.assertEqual(proof["files"][notice.name], hashlib.sha256(notice.read_bytes()).hexdigest())
records = release.collect_artifacts(output, version)
self.assertIn(notice, records[0]["files"])
self.assertEqual(sum(path == notice for path in records[0]["files"]), 1)
self.assertEqual((output / (stem.name + ".uf2")).read_bytes(), uf2_data)
commit = "deadbeef" + "0" * 32
status = directory / "status"
status.write_text("state=completed\nexit_code=0\n"
f"working_directory={directory}\noutput_directory=out\n"
f"source_commit={commit}\nfirmware_version={label}\nfirmware_profile=cascade\n"
"radio_frequency=909.5\nradio_bandwidth=500\nradio_sf=5\nradio_cr=5\n")
result = subprocess.run([sys.executable, str(ROOT / "scripts/package_cascade_release.py"),
"--input", str(output), "--output", str(directory / "stage"),
"--build-status", str(status), "--commit", commit],
capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
staged = directory / "stage/repeater-room"
self.assertEqual((staged / notice.name).read_bytes(), notice.read_bytes())
self.assertIn(notice.name, (staged / "TARGET-MANIFEST.json").read_text())
checksums = dict(line.split(" ", 1)[::-1] for line in
(staged / "SHA256SUMS.txt").read_text().splitlines())
self.assertEqual(checksums[notice.name], hashlib.sha256(notice.read_bytes()).hexdigest())
notice.write_bytes(b"tampered")
with self.assertRaisesRegex(ValueError, "mismatched font-license"):
memory.validate_package(stem)
notice.unlink()
with self.assertRaisesRegex(ValueError, "missing or mismatched"):
memory.validate_package(stem)
def test_adafruit_loader_accepts_extra_notice_when_installed(self):
site_packages = (Path.home() / ".platformio/packages/tool-adafruit-nrfutil/site-packages")
if not (site_packages / "nordicsemi/dfu/package.py").is_file():
self.skipTest("local Adafruit nRF DFU utility is not installed")
with tempfile.TemporaryDirectory() as temp:
directory = Path(temp)
package = directory / "firmware.zip"
self.dfu(package)
font_license.package_font_license(package)
code = (
"import sys; from pathlib import Path; "
"sys.path.insert(0,sys.argv[1]); "
"from nordicsemi.dfu.package import Package; "
"m=Package.unpack_package(sys.argv[2],sys.argv[3]); "
"assert m.application.bin_file=='firmware.bin'; "
"assert m.application.dat_file=='firmware.dat'; "
"assert (Path(sys.argv[3])/'FONT-LICENSE.txt').is_file()"
)
result = subprocess.run([sys.executable, "-c", code, str(site_packages), str(package),
str(directory / "unpacked")], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
if __name__ == "__main__":
unittest.main()
+34
View File
@@ -0,0 +1,34 @@
"""Unused TFT fonts are omitted without altering the selected hardware setup."""
import subprocess
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FIXTURES = ROOT / "test" / "fixtures" / "nrf52_tft_trim"
class Nrf52TftFontTrimTest(unittest.TestCase):
def test_selected_setup_and_current_font_survive(self):
cases = (
([], False),
(["-DNRF52_PLATFORM=1"], False),
(["-DMESH_NRF52_FLASH_TRIM=1"], False),
(["-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=0"], False),
(["-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=1"], True),
)
with tempfile.TemporaryDirectory() as temp_dir:
for flags, trimmed in cases:
with self.subTest(flags=flags):
executable = Path(temp_dir) / "font_scope"
subprocess.run([
"c++", "-std=c++11", "-Wall", "-Wextra", "-Werror",
*flags, f"-DEXPECT_TRIM={int(trimmed)}",
f"-I{FIXTURES}", f"-I{ROOT / 'src'}",
str(FIXTURES / "test.cpp"), "-o", str(executable),
], check=True)
subprocess.run([str(executable)], check=True)
if __name__ == "__main__":
unittest.main()
+247
View File
@@ -0,0 +1,247 @@
"""nRF52 font storage choices preserve renderer behavior and stay in scope."""
import hashlib
import re
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
FIXTURES = ROOT / "test" / "fixtures" / "oled_font_experiments"
class OledFontExperimentsTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temp = tempfile.TemporaryDirectory()
cls.directory = Path(cls.temp.name)
cls.decoder = cls.directory / "tinf.o"
subprocess.run([
"cc", "-Os", "-DMESHCORE_TINF_IMPLEMENTATION=1", "-c",
str(ROOT / "src/helpers/ota/tinf/tinflate.c"), "-o", str(cls.decoder),
], check=True)
cls.results = {}
@classmethod
def tearDownClass(cls):
cls.temp.cleanup()
def run_renderer(self, mode, flags=(), signed_char=False, sanitized=False, explicit=True):
key = (mode, tuple(flags), signed_char, sanitized, explicit)
if key in self.results:
return self.results[key]
executable = self.directory / f"font_{len(self.results)}"
subprocess.run([
"c++", "-std=c++11", "-O2", "-Wall", "-Wextra",
"-Wno-implicit-fallthrough", "-DARDUINO=1",
"-fsigned-char" if signed_char else "-funsigned-char",
*( ["-fsanitize=address,undefined", "-fno-omit-frame-pointer"] if sanitized else [] ),
"-DOLEDDISPLAY_REDUCE_MEMORY=1",
*([f"-DMESH_ARIAL_EXPERIMENT={mode}"] if explicit else []),
f"-DEXPECT_MODE={mode if not flags else 0}",
*(flags or ("-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=1", "-DST7789=1")),
f"-I{FIXTURES}", f"-I{ROOT / 'src'}",
str(FIXTURES / "test.cpp"),
str(ROOT / "src/helpers/ui/OLEDDisplay.cpp"),
str(ROOT / "src/helpers/ui/OLEDDisplayFonts.cpp"),
str(self.decoder), "-o", str(executable),
], check=True)
symbols = subprocess.run(["nm", "-C", "--defined-only", str(executable)],
text=True, capture_output=True, check=True).stdout
if not flags and mode != 0:
self.assertNotIn("ArialMT_Plain_10", symbols, "Unused initial Arial10 is still linked")
result = subprocess.run([str(executable)], text=True, capture_output=True, check=True)
self.results[key] = result.stdout.splitlines()
return self.results[key]
def test_all_glyphs_metrics_and_framebuffers_match(self):
baseline = self.run_renderer(0)
self.assertEqual(baseline[0], "initial 13")
self.assertEqual([line.split()[0] for line in baseline[1:]], [
"glyphs", "clipping", "utf8", "wrapping", "controls", "bitmaps", "logs", "custom"])
self.assertTrue(baseline[1].startswith("glyphs 448 "))
self.assertTrue(baseline[2].startswith("clipping 960 "))
for mode in (1, 2, 3):
with self.subTest(mode=mode):
actual = self.run_renderer(mode)
self.assertEqual(actual[0], "initial 19")
self.assertEqual(actual[1:], baseline[1:])
def test_experiment_guards_signed_char_latin1_indices(self):
baseline = self.run_renderer(0)
for mode in (1, 2, 3):
with self.subTest(mode=mode):
self.assertEqual(self.run_renderer(mode, signed_char=True)[1:], baseline[1:])
def test_experimental_renderer_passes_memory_and_undefined_behavior_checks(self):
baseline = self.run_renderer(0)
for mode in (1, 2, 3):
with self.subTest(mode=mode):
actual = self.run_renderer(mode, signed_char=True, sanitized=True)
self.assertEqual(actual[1:], baseline[1:])
def test_tinf_is_enabled_only_for_effective_zopfli_font_or_existing_features(self):
qualified = ("-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=1", "-DST7789=1")
cases = [(*qualified, f"-DMESH_ARIAL_EXPERIMENT={mode}") for mode in range(10)]
expected = [mode in (3, 9) for mode in range(10)]
cases.append(qualified) # No override: production compressed Noto font.
expected.append(True)
cases.extend([
(*qualified, "-DMESH_NRF52_FONT_MODE=0"),
(*qualified, "-DMESH_NRF52_FONT_MODE=9"),
(*qualified, "-DMESH_NRF52_FONT_MODE=9", "-DMESH_ARIAL_EXPERIMENT=0"),
])
expected.extend((False, True, False))
excluded = (
("-DNRF52_PLATFORM=1", "-DST7789=1"),
("-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=0", "-DST7789=1"),
("-DMESH_NRF52_FLASH_TRIM=1", "-DST7789=1"),
("-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=1"),
(*qualified, "-DCOMPANION_RADIO_FULL=1"),
)
cases.extend((*flags, "-DMESH_ARIAL_EXPERIMENT=3") for flags in excluded)
expected.extend(False for _ in excluded)
cases.extend((*flags, "-DMESH_ARIAL_EXPERIMENT=9") for flags in excluded)
expected.extend(False for _ in excluded)
cases.extend(excluded) # Production default remains off outside scope.
expected.extend(False for _ in excluded)
cases.extend((*flags, "-DMESH_NRF52_FONT_MODE=9") for flags in excluded)
expected.extend(False for _ in excluded)
cases.extend([
("-DENABLE_OTA=1",), ("-DOTA_TRANSPORT_DEFLATE_TEST=1",),
("-DNRF52_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"),
])
expected.extend((True, True, True))
for flags, decoder_enabled in zip(cases, expected):
with self.subTest(flags=flags):
output = self.directory / "guard.o"
subprocess.run([
"cc", "-Os", *flags, "-c", str(ROOT / "src/helpers/ota/OtaTinf.c"),
"-o", str(output),
], check=True)
symbols = subprocess.run(
["nm", "-g", "--defined-only", str(output)],
text=True, capture_output=True, check=True).stdout
self.assertEqual("tinf_uncompress_exact" in symbols, decoder_enabled)
def test_similar_size_family_trials_match_raw_pixels_and_keep_full_coverage(self):
families = []
for mode in (4, 5, 6, 7, 8):
with self.subTest(mode=mode):
unsigned = self.run_renderer(mode)
self.assertEqual(unsigned[0], "initial 19")
self.assertTrue(unsigned[1].startswith("glyphs 448 "))
self.assertTrue(unsigned[2].startswith("clipping 960 "))
# The fixture compares every glyph against a separate
# pixel-at-a-time reference and checks 189 drawable slots,
# exact 19/28 line boxes and 12/17-pixel H cap heights.
self.assertEqual(self.run_renderer(mode, signed_char=True), unsigned)
self.assertEqual(self.run_renderer(mode, signed_char=True, sanitized=True), unsigned)
families.append(unsigned[1])
self.assertEqual(len(set(families)), 5, "family trials unexpectedly contain the same pixels")
def test_production_compressed_noto_default_matches_raw_family_exactly(self):
raw_noto = self.run_renderer(7)
compressed = self.run_renderer(9)
self.assertEqual(compressed, raw_noto)
self.assertEqual(self.run_renderer(9, explicit=False), raw_noto)
self.assertEqual(self.run_renderer(9, signed_char=True), raw_noto)
self.assertEqual(self.run_renderer(9, signed_char=True, sanitized=True), raw_noto)
self.assertEqual(self.run_renderer(9, signed_char=True, sanitized=True, explicit=False), raw_noto)
def test_production_font_optout_restores_original_renderer(self):
qualified = ("-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=1", "-DST7789=1")
baseline = self.run_renderer(0)
self.assertEqual(self.run_renderer(
9, (*qualified, "-DMESH_NRF52_FONT_MODE=0"), explicit=False), baseline)
self.assertEqual(self.run_renderer(
0, (*qualified, "-DMESH_NRF52_FONT_MODE=9")), baseline)
def test_experiment_is_disabled_outside_nrf52_infrastructure_st7789(self):
baseline = self.run_renderer(0)
excluded = (
("-DNRF52_PLATFORM=1", "-DST7789=1"), # Companion: no trim macro
("-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=0", "-DST7789=1"),
("-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=1", "-DST7789=1",
"-DCOMPANION_RADIO_FULL=1"),
("-DMESH_NRF52_FLASH_TRIM=1", "-DST7789=1"), # not nRF52
("-DNRF52_PLATFORM=1", "-DMESH_NRF52_FLASH_TRIM=1"), # another display
)
for mode in (3, 8, 9):
for flags in excluded:
with self.subTest(mode=mode, flags=flags):
self.assertEqual(self.run_renderer(mode, flags), baseline)
for flags in excluded:
with self.subTest(default=True, flags=flags):
self.assertEqual(self.run_renderer(9, flags, explicit=False), baseline)
with self.subTest(override=True, flags=flags):
self.assertEqual(self.run_renderer(
9, (*flags, "-DMESH_NRF52_FONT_MODE=9"), explicit=False), baseline)
class FontTrialSourceAuditTest(unittest.TestCase):
def test_pinned_source_glyphs_keep_all_ink_pixels_when_sources_are_available(self):
# Normal firmware builds/CI need no TrueType files or rasterizer. The
# optional regeneration audit runs on the host that owns pinned fonts.
try:
from PIL import Image, ImageDraw, ImageFont, features
except ImportError:
self.skipTest("Pinned rasterizer is not installed")
sys.path.insert(0, str(ROOT / "scripts"))
import generate_font_family_trials as generator
if Image.__version__ != generator.PILLOW_VERSION or features.version("freetype2") != generator.FREETYPE_VERSION:
self.skipTest("Pinned Pillow/FreeType versions are not installed")
font_directory = Path("C:/Windows/Fonts")
if not font_directory.exists():
font_directory = Path("/mnt/c/Windows/Fonts")
if not all((font_directory / family.filename).exists() for family in generator.FAMILIES):
self.skipTest("Pinned source font files are not available")
header = (ROOT / "src/helpers/ui/FontFamilyTrials.h").read_text(encoding="utf-8")
for family in generator.FAMILIES:
source = font_directory / family.filename
self.assertEqual(hashlib.sha256(source.read_bytes()).hexdigest(), family.sha256)
section_match = re.search(
rf"#if MESH_ARIAL_EXPERIMENT == {family.mode}\b(.*?)#endif", header, re.S)
self.assertIsNotNone(section_match, "Generated header lacks a configured font family")
section = section_match[1]
for font_id, size in enumerate(family.sizes, 1):
body = re.search(rf"static const uint8_t TRIAL_{font_id}\[\] = \{{(.*?)\}};", section, re.S)[1]
data = bytes(int(value, 16) for value in re.findall(r"0x([0-9a-fA-F]+)", body))
first, count, height = data[2], data[3], data[1]
self.assertEqual((first, count, height), (32, 224, 19 if font_id == 1 else 28))
source_font = ImageFont.truetype(str(source), size, layout_engine=ImageFont.Layout.BASIC)
drawable = 0
for slot in range(count):
entry = data[4 + 4 * slot:8 + 4 * slot]
offset = int.from_bytes(entry[:2], "big")
length = entry[2]
if offset == 0xffff:
self.assertEqual(length, 0)
continue
drawable += 1
bitmap = data[4 + 4 * count + offset:4 + 4 * count + offset + length]
stride = (height + 7) // 8
encoded = {(index // stride, 8 * (index % stride) + bit)
for index, value in enumerate(bitmap)
for bit in range(8) if value & (1 << bit)}
image = Image.new("L", (256, 256), 0)
ImageDraw.Draw(image).text((96, 96), chr(first + slot),
font=source_font, fill=255, anchor="ls")
image = image.point(lambda value: 255 if value >= 128 else 0)
box = image.getbbox()
self.assertIsNotNone(box)
original = {(x, y) for y in range(box[1], box[3])
for x in range(box[0], box[2]) if image.getpixel((x, y))}
def normalize(pixels):
left = min(x for x, _ in pixels)
top = min(y for _, y in pixels)
return {(x - left, y - top) for x, y in pixels}
with self.subTest(mode=family.mode, size=size, code=first + slot):
self.assertEqual(normalize(encoded), normalize(original),
"Source ink was cropped or altered during bitmap placement")
self.assertEqual(drawable, 189)
if __name__ == "__main__":
unittest.main()
+139
View File
@@ -0,0 +1,139 @@
"""All OTA names survive front coding; original Companion API stays unchanged."""
import re
import subprocess
import sys
import tempfile
import unittest
import zlib
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path[:0] = [str(ROOT / "tools" / "mota"), str(ROOT / "scripts")]
import gen_targets
HEADER = ROOT / "src" / "helpers" / "ota" / "OtaTargets.h"
HARNESS = ROOT / "test" / "fixtures" / "ota_target_names" / "test.cpp"
class OtaTargetNameTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.text = HEADER.read_text(encoding="utf-8")
cls.rows = [(int(target, 16), name) for target, name in re.findall(
r'\{ 0x([0-9a-fA-F]{8}), "([^"]+)" \},', cls.text)]
def compile_and_lookup(self, flags, ids, header=None):
with tempfile.TemporaryDirectory() as temp_dir:
executable = Path(temp_dir) / "target_names"
if header is not None:
alternate = Path(temp_dir) / "helpers/ota"
alternate.mkdir(parents=True)
(alternate / "OtaTargets.h").write_text(header, encoding="utf-8")
(alternate / "tinf").mkdir()
(alternate / "tinf/tinf.h").write_text(
(ROOT / "src/helpers/ota/tinf/tinf.h").read_text(), encoding="utf-8")
decoder = Path(temp_dir) / "tinf.o"
subprocess.run([
"cc", "-Os", "-DMESHCORE_TINF_IMPLEMENTATION=1", "-c",
str(ROOT / "src/helpers/ota/tinf/tinflate.c"), "-o", str(decoder),
], check=True)
subprocess.run([
"c++", "-std=c++11", "-O2", "-Wall", "-Wextra", "-Werror",
*flags, f"-I{temp_dir}", f"-I{ROOT / 'src'}", str(HARNESS), str(decoder), "-o", str(executable),
], check=True)
result = subprocess.run(
[str(executable)], input="\n".join(f"{target:08x}" for target in ids),
text=True, capture_output=True, check=True)
return result.stdout.splitlines()
def test_generator_reproduces_checked_in_header_and_all_ids(self):
self.assertGreater(len(self.rows), 600)
self.assertEqual(self.rows, gen_targets.target_rows(name for _, name in self.rows))
self.assertEqual(self.text, gen_targets.generate_header(self.rows))
def test_zopfli_chunks_retain_every_name_and_have_bounded_output(self):
data, offsets, raw_lengths, firsts = gen_targets.compressed_name_chunks(self.rows)
actual = []
for group, raw_length in enumerate(raw_lengths):
raw = zlib.decompress(data[offsets[group]:offsets[group + 1]], -15)
self.assertEqual(len(raw), raw_length)
self.assertLessEqual(raw_length, 512)
position = 0
previous = b""
for row in range(firsts[group], firsts[group + 1]):
prefix = raw[position]
self.assertLessEqual(prefix, len(previous))
end = raw.index(0, position + 1)
previous = previous[:prefix] + raw[position + 1:end]
position = end + 1
actual.append(previous.decode("utf-8"))
self.assertEqual(position, len(raw))
self.assertEqual(actual, [name for _, name in self.rows])
original_size = sum(8 + len(name.encode("utf-8")) + 1 for _, name in self.rows)
compressed_size = 4 * len(self.rows) + 2 * (len(offsets) + len(raw_lengths) + len(firsts)) + len(data)
self.assertGreater(original_size - compressed_size, 19000)
def test_generator_requires_google_zopfli_1000(self):
from unittest.mock import patch
sys.path.insert(0, str(ROOT / "scripts"))
import zopfli_compress
gen_targets._compressed_name_chunks.cache_clear()
original = zopfli_compress.raw_deflate_compress
with patch.object(zopfli_compress, "raw_deflate_compress", wraps=original) as compress:
gen_targets.compressed_name_chunks([(1, "unique_test_target")])
self.assertEqual(compress.call_count, 1)
self.assertEqual(compress.call_args.kwargs, {"numiterations": 1000})
def test_cpp_standard_and_compact_match_all_names(self):
ids = [target for target, _ in self.rows] + [0, 0xffffffff]
expected = [name for _, name in self.rows] + ["?", "?"]
for flags in ([], ["-DOTA_TARGET_NAME_FRONT_CODED=1"]):
with self.subTest(flags=flags):
self.assertEqual(self.compile_and_lookup(flags, ids), expected)
def test_table_disabled_preserves_optional_local_name(self):
local_id, local_name = self.rows[0]
ids = [local_id, self.rows[1][0], 0]
for compressed in (0, 1):
flags = ["-DOTA_TARGET_NAME_TABLE=0", f"-DOTA_TARGET_NAME_FRONT_CODED={compressed}"]
with self.subTest(compressed=compressed):
self.assertEqual(self.compile_and_lookup(flags, ids), ["?", "?", "?"])
self.assertEqual(self.compile_and_lookup(flags + [
f"-DOTA_LOCAL_TARGET_ID=0x{local_id:08x}",
f'-DOTA_LOCAL_TARGET_NAME="{local_name}"',
], ids), [local_name, "?", "?"])
def test_cpp_decoder_fails_closed_on_invalid_records_and_trailing_bytes(self):
from zopfli_compress import raw_deflate_compress
rows = [(1, "hello")]
header = gen_targets.generate_header(rows)
original, _, _, _ = gen_targets.compressed_name_chunks(rows)
invalid = [
raw_deflate_compress(b"\xffhello\0", numiterations=1000), # invalid restart prefix
raw_deflate_compress(b"\0hello!", numiterations=1000), # missing terminator
original + b"\0", # trailing whole byte
original[:-1], # truncated input
]
for stream in invalid:
altered = re.sub(
r"(static const uint8_t NAMES\[\] = \{).*?(\n \};)",
lambda match: match[1] + "\n " + ", ".join(f"0x{byte:02x}" for byte in stream) + "," + match[2],
header, flags=re.S)
altered = re.sub(
r"(static const uint16_t OFFSETS\[\] = \{).*?(\n \};)",
lambda match: match[1] + f"\n 0, {len(stream)}," + match[2],
altered, flags=re.S)
with self.subTest(stream=stream.hex()):
self.assertEqual(self.compile_and_lookup(
["-DOTA_TARGET_NAME_FRONT_CODED=1"], [1], altered), ["?"])
def test_generator_rejects_unencodable_names(self):
for name in ("x\0y", "x" * 256):
with self.subTest(name=name):
with self.assertRaises(ValueError):
gen_targets.compressed_name_chunks([(1, name)])
if __name__ == "__main__":
unittest.main()