mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-25 07:22:24 +00:00
Reduce radio preamble validation overhead with equivalent math
This commit is contained in:
1 parent
a619c78553
commit
645c8a1119
3 files changed
+95
-9
No files matched your search
+14
-3
@@ -118,7 +118,7 @@ class RadioProfiles {
|
||||
if (!isfinite(p.freq) || !isfinite(p.bw) || p.freq < 150 || p.freq > 2500
|
||||
|| p.sf < 5 || p.sf > 12 || p.cr < 5 || p.cr > 8
|
||||
|| (p.preamble != 0 && (p.preamble < 8 || p.preamble > MaxPreamble))) return false;
|
||||
const float bandwidths[] = {7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f,
|
||||
static constexpr float bandwidths[] = {7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f,
|
||||
62.5f, 125, 250, 500, 1000, 812.5f, 1625};
|
||||
for (float bw : bandwidths) if (fabsf(p.bw - bw) < 0.01f) return true;
|
||||
return false;
|
||||
@@ -126,12 +126,23 @@ class RadioProfiles {
|
||||
static double symbolUs(const RadioProfileParams& p) {
|
||||
return p.bw > 0 && p.sf <= 12 ? (double)(1U << p.sf) * 1000.0 / p.bw : 0;
|
||||
}
|
||||
#if defined(STM32_PLATFORM) && defined(__GNUC__) && !defined(__clang__)
|
||||
// Share the complete validation on flash-constrained STM32 targets.
|
||||
__attribute__((noinline, noclone))
|
||||
#endif
|
||||
static bool safePreamble(const RadioProfileParams& p, uint16_t symbols) {
|
||||
const double symbol = symbolUs(p);
|
||||
if (symbol <= 0) return false;
|
||||
const int de = symbol >= 16000 ? 1 : 0;
|
||||
const double payload = 8 + ceil((8.0 * 255 - 4 * p.sf
|
||||
+ (p.sf <= 6 ? 20 : 28) + 16) / (4 * (p.sf - 2 * de))) * 8;
|
||||
const int denominator = 4 * (p.sf - 2 * de);
|
||||
// The payload expression is integral apart from this small rational
|
||||
// ceiling. Preserve even invalid-SF behavior: a zero denominator was
|
||||
// infinite (and failed the limit), while signed division rounds a
|
||||
// negative quotient toward zero, exactly its mathematical ceiling.
|
||||
if (!denominator) return false;
|
||||
const int numerator = 8 * 255 - 4 * p.sf + (p.sf <= 6 ? 20 : 28) + 16;
|
||||
const double payload = 8 + (denominator > 0
|
||||
? (numerator + denominator - 1) / denominator : numerator / denominator) * 8;
|
||||
// RadioLib's microsecond airtime calculation multiplies by four before
|
||||
// dividing. Reject settings that overflow that intermediate for a full
|
||||
// packet, including a retry using coding rate 4/8.
|
||||
|
||||
+69
@@ -1,13 +1,82 @@
|
||||
#include <RadioProfiles.h>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
using Profiles=mesh::RadioProfiles;
|
||||
mesh::RadioProfileParams params(unsigned sf,float bw) {
|
||||
mesh::RadioProfileParams p; p.freq=909.5;p.sf=sf;p.bw=bw;p.cr=5;return p;
|
||||
}
|
||||
static bool originalSafePreamble(const mesh::RadioProfileParams& p, uint16_t symbols) {
|
||||
const double symbol = Profiles::symbolUs(p);
|
||||
if (symbol <= 0) return false;
|
||||
const int de = symbol >= 16000 ? 1 : 0;
|
||||
const double payload = 8 + std::ceil((8.0 * 255 - 4 * p.sf
|
||||
+ (p.sf <= 6 ? 20 : 28) + 16) / (4 * (p.sf - 2 * de))) * 8;
|
||||
return (symbols + (p.sf <= 6 ? 6.25 : 4.25) + payload) * symbol * 4 <= UINT32_MAX;
|
||||
}
|
||||
static void checkSafePreamble(unsigned sf, float bw, uint16_t symbols) {
|
||||
const auto p = params(sf, bw);
|
||||
assert(Profiles::safePreamble(p, symbols) == originalSafePreamble(p, symbols));
|
||||
}
|
||||
int main() {
|
||||
// Sharing the read-only validation table must preserve every supported
|
||||
// value and the strict float tolerance on both sides of its boundary.
|
||||
for (float bw : {7.8f, 10.4f, 15.6f, 20.8f, 31.25f, 41.7f,
|
||||
62.5f, 125.0f, 250.0f, 500.0f, 1000.0f, 812.5f, 1625.0f}) {
|
||||
auto p = params(7, bw);
|
||||
assert(Profiles::valid(p));
|
||||
const float lower = bw - 0.01f, upper = bw + 0.01f;
|
||||
for (float inside : {std::nextafter(lower, bw), std::nextafter(upper, bw)}) {
|
||||
p.bw = inside;
|
||||
assert(std::fabs(inside - bw) < 0.01f && Profiles::valid(p));
|
||||
}
|
||||
for (float outside : {std::nextafter(lower, -std::numeric_limits<float>::infinity()),
|
||||
std::nextafter(upper, std::numeric_limits<float>::infinity())}) {
|
||||
p.bw = outside;
|
||||
assert(std::fabs(outside - bw) >= 0.01f && !Profiles::valid(p));
|
||||
}
|
||||
}
|
||||
for (float unsupported : {0.0f, 1.0f, 8.0f, 12.0f, 100.0f, 1500.0f, 2000.0f})
|
||||
assert(!Profiles::valid(params(7, unsupported)));
|
||||
for (float nonfinite : {std::numeric_limits<float>::quiet_NaN(),
|
||||
std::numeric_limits<float>::infinity(),
|
||||
-std::numeric_limits<float>::infinity()}) {
|
||||
assert(!Profiles::valid(params(7, nonfinite)));
|
||||
auto p = params(7, 125);
|
||||
p.freq = nonfinite;
|
||||
assert(!Profiles::valid(p));
|
||||
}
|
||||
// Compare the production integer ceiling with the original double formula,
|
||||
// including invalid SFs where zero/negative denominators were observable.
|
||||
for (unsigned sf = 0; sf < 256; ++sf) {
|
||||
const float transition = float(double(1u << (sf & 31)) * 1000 / 16000);
|
||||
for (float bw : {0.0f, -0.0f, -1.0f, 7.8f, 62.5f, 125.0f, 500.0f,
|
||||
1000.0f, transition, std::nextafter(transition, 0.0f),
|
||||
std::nextafter(transition, std::numeric_limits<float>::infinity()),
|
||||
std::numeric_limits<float>::min(), std::numeric_limits<float>::max(),
|
||||
std::numeric_limits<float>::denorm_min(),
|
||||
std::numeric_limits<float>::quiet_NaN(),
|
||||
std::numeric_limits<float>::infinity(),
|
||||
-std::numeric_limits<float>::infinity()}) {
|
||||
for (uint16_t symbols : {0, 1, 8, 16, 32, 128, 1024, 65528, 65535})
|
||||
checkSafePreamble(sf, bw, symbols);
|
||||
}
|
||||
}
|
||||
for (unsigned sf = 0; sf <= 12; ++sf)
|
||||
for (float bw : {0.001f, 7.8f, 125.0f})
|
||||
for (unsigned symbols = 0; symbols <= UINT16_MAX; ++symbols)
|
||||
checkSafePreamble(sf, bw, uint16_t(symbols));
|
||||
uint32_t random = 0x89abcdef;
|
||||
for (unsigned i = 0; i < 16384; ++i) {
|
||||
random ^= random << 13; random ^= random >> 17; random ^= random << 5;
|
||||
float bw;
|
||||
static_assert(sizeof(bw) == sizeof(random), "Radio bandwidth is a 32-bit float");
|
||||
std::memcpy(&bw, &random, sizeof(bw));
|
||||
checkSafePreamble((i & 1) ? i % 13 : random & 255, bw, uint16_t(random >> 16));
|
||||
}
|
||||
assert(Profiles::MinListenSymbols==4.6);
|
||||
assert(Profiles::SlowListenSymbols==4.6);
|
||||
assert(Profiles::MinFastListenSymbols==4.6);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Compile the portable production chirp math, independent of radio hardware."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -12,11 +13,16 @@ class RadioChirpMathTest(unittest.TestCase):
|
||||
compiler=shutil.which('g++') or shutil.which('clang++')
|
||||
self.assertIsNotNone(compiler)
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
exe=Path(folder)/'chirps.exe'
|
||||
done=subprocess.run([compiler,'-std=c++17','-Wall','-Wextra','-I',str(ROOT/'src'),
|
||||
str(ROOT/'test/fixtures/radio_profiles/chirp_math.cpp'),'-o',str(exe)],capture_output=True,text=True)
|
||||
self.assertEqual(done.returncode,0,done.stderr)
|
||||
done=subprocess.run([str(exe)],capture_output=True,text=True)
|
||||
self.assertEqual(done.returncode,0,done.stderr)
|
||||
for stm in (False, True):
|
||||
with self.subTest(stm32_defined=stm):
|
||||
exe=Path(folder)/('chirps-stm.exe' if stm else 'chirps.exe')
|
||||
checks=[] if os.name=='nt' else ['-fsanitize=address,undefined',
|
||||
'-fno-sanitize-recover=all','-fno-pie','-no-pie']
|
||||
done=subprocess.run([compiler,'-std=c++17','-O1','-g','-Wall','-Wextra',
|
||||
*checks,*(['-DSTM32_PLATFORM'] if stm else []),'-I',str(ROOT/'src'),
|
||||
str(ROOT/'test/fixtures/radio_profiles/chirp_math.cpp'),'-o',str(exe)],capture_output=True,text=True)
|
||||
self.assertEqual(done.returncode,0,done.stderr)
|
||||
done=subprocess.run([str(exe)],capture_output=True,text=True)
|
||||
self.assertEqual(done.returncode,0,done.stderr)
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in new issue
Block a user