Files
MeshCore/examples/companion_radio/CompanionHelpWriter.h
T

160 lines
5.3 KiB
C++

#pragma once
#include <stddef.h>
#include <stdint.h>
class Stream;
namespace mesh { namespace companion {
// The implementation is isolated in one selected C++17 packing source.
size_t writePackedCompanionHelp(Stream& output);
// Native USB accepts bounded producer writes. Honor partial progress, and stop
// if the sink cannot accept another byte rather than spinning on a closed host.
template<typename Output>
size_t writeHelpChunks(Output& output, const char* text, size_t length) {
if (!text) return 0;
size_t written = 0;
while (written < length) {
const size_t remaining = length - written;
const size_t chunk = remaining > 256 ? 256 : remaining;
const size_t accepted = output.write(
reinterpret_cast<const uint8_t*>(text) + written, chunk);
if (accepted == 0 || accepted > chunk) break;
written += accepted;
}
return written;
}
#if __cplusplus >= 201703L
namespace help_packing {
// ASCII literals retain bit 7 clear. Two-byte references encode a 3..18 byte
// match at distance 1..2048. Only the final packed bytes survive evaluation;
// the hash chains and original text occupy no runtime storage.
#if defined(STM32_PLATFORM) && defined(STM32WLxx)
// Small STM32WL Companions retain their 1 KiB minimum stack allowance. A
// smaller history preserves every help byte without adding a 2 KiB frame.
constexpr size_t WindowSize = 256;
#else
constexpr size_t WindowSize = 2048;
#endif
constexpr size_t MaximumMatch = 18;
constexpr uint8_t hash(const char* text) {
return static_cast<uint8_t>((static_cast<uint8_t>(text[0]) * 31u +
static_cast<uint8_t>(text[1])) * 31u + static_cast<uint8_t>(text[2]));
}
template<size_t N, typename Emit>
constexpr size_t encode(const char (&text)[N], Emit emit) {
static_assert(N <= 65535, "Compile-time help offsets use 16 bits");
uint16_t heads[256] = {}, previous[N] = {};
size_t position = 0, packed = 0;
while (position + 1 < N) {
const size_t remaining = N - position - 1;
const size_t maximum = remaining < MaximumMatch ? remaining : MaximumMatch;
size_t best = 0, distance = 0;
if (remaining >= 3) {
uint16_t link = heads[hash(text + position)];
while (link) {
const size_t candidate = link - 1;
const size_t back = position - candidate;
if (back > WindowSize) break;
size_t match = 0;
while (match < maximum && text[candidate + match] == text[position + match]) ++match;
if (match > best) {
best = match;
distance = back;
if (best == maximum) break;
}
link = previous[candidate];
}
}
const size_t step = best >= 3 ? best : 1;
if (best >= 3) {
emit(static_cast<uint8_t>(0x80 | ((best - 3) << 3) | ((distance - 1) >> 8)));
emit(static_cast<uint8_t>(distance - 1));
packed += 2;
} else {
emit(static_cast<uint8_t>(text[position]));
++packed;
}
// Index skipped match bytes too, preserving greedy longest-match choices.
for (size_t end = position + step; position < end; ++position) {
if (position + 3 >= N) continue;
const uint8_t bucket = hash(text + position);
previous[position] = heads[bucket];
heads[bucket] = static_cast<uint16_t>(position + 1);
}
}
return packed;
}
} // namespace help_packing
template<size_t N>
constexpr bool validHelpText(const char (&text)[N]) {
if (text[N - 1] != 0) return false;
for (size_t i = 0; i + 1 < N; ++i) {
if (text[i] == 0 || static_cast<uint8_t>(text[i]) >= 0x80) return false;
}
return true;
}
template<size_t N>
constexpr size_t packedHelpSize(const char (&text)[N]) {
return help_packing::encode(text, [](uint8_t) constexpr {});
}
template<size_t N> struct PackedHelp {
static_assert(N > 0, "A help image must contain text");
uint8_t bytes[N] = {};
};
template<size_t PackedSize, size_t N>
constexpr PackedHelp<PackedSize> packHelp(const char (&text)[N]) {
PackedHelp<PackedSize> result{};
size_t packed = 0;
help_packing::encode(text, [&](uint8_t byte) constexpr { result.bytes[packed++] = byte; });
return result;
}
template<typename Output>
size_t writePackedHelp(Output& output, const uint8_t* packed, size_t length) {
if (!packed) return 0;
uint8_t history[help_packing::WindowSize];
size_t position = 0, produced = 0, written = 0;
size_t distance = 0, remaining = 0;
while (position < length || remaining) {
char chunk[64];
size_t used = 0;
while (used < sizeof(chunk) && (position < length || remaining)) {
uint8_t next;
if (remaining) {
next = history[(produced - distance) & (help_packing::WindowSize - 1)];
--remaining;
} else {
next = packed[position++];
if (next & 0x80) {
if (position == length) return written;
distance = ((static_cast<size_t>(next & 7) << 8) | packed[position++]) + 1;
remaining = ((next >> 3) & 15) + 3;
if (distance > produced || distance > help_packing::WindowSize) return written;
continue;
}
}
history[produced++ & (help_packing::WindowSize - 1)] = next;
chunk[used++] = static_cast<char>(next);
}
const size_t accepted = writeHelpChunks(output, chunk, used);
written += accepted;
if (accepted != used) break;
}
return written;
}
#endif // C++17 constant packing and streaming decoder
} } // namespace mesh::companion