Skip to content

Commit c29bf2d

Browse files
SSD DDDSSD DDD
authored andcommitted
feat(t27-flip): hello.t27 + etx.t27 → gen/{rust,zig,c} + guardrail tests
Second + third T27-first flips (after wire.t27 #33). Both specs produce CLEAN gen-rust output (0 broken casts, 0 unsupported) — no dynamic arrays needed, ExprCast lowering (t27@3c912d9) handles all 'as u8'/'as u32'. Generated (6 files, 3 targets × 2 specs): gen/rust/hello.rs (67L) — MAX_HEARD, hello_byte, reports_hearing, etc. gen/rust/etx.rs (68L) — OPTIMISTIC, DEAD_EPS, ONE_FP, calc_etx, etc. gen/zig/hello.zig — @as/@intcast form gen/zig/etx.zig gen/c/hello.c — ((uint8_t)(...)) form gen/c/etx.c Guardrail tests (tests/t27_gen_hello_guardrail.rs, 4 tests): - hello constants (MAX_HEARD=3, HEADER_LEN=13) - hello byte-layout (src field BE, n at idx 8) - etx constants (OPTIMISTIC, DEAD_EPS, ONE_FP in Q8.8) Gate: cargo test --all = 141 passed, 0 failed (was 137 + 4 new). Drift-guard extension (to cover hello + etx) follows in next commit.
1 parent 815e21b commit c29bf2d

7 files changed

Lines changed: 685 additions & 0 deletions

File tree

gen/c/etx.c

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/* ============================================================================
2+
Generated from t27 spec: MeshEtx
3+
DO NOT EDIT - generated by t27c gen-c
4+
phi^2 + 1/phi^2 = 3 | TRINITY
5+
============================================================================ */
6+
7+
#include <stdint.h>
8+
#include <stdbool.h>
9+
#include <stddef.h>
10+
#include <assert.h>
11+
12+
#ifndef MESHETX_H
13+
#define MESHETX_H
14+
15+
/* -------------------------------------------------------
16+
Constants
17+
------------------------------------------------------- */
18+
19+
#define OPTIMISTIC 230
20+
#define DEAD_EPS 38
21+
#define ONE_FP 256
22+
#define ALPHA_HALF 128
23+
24+
/* -------------------------------------------------------
25+
Function prototypes
26+
------------------------------------------------------- */
27+
28+
uint8_t alpha_from_window(uint8_t window);
29+
uint8_t bool_to_sample(bool b);
30+
uint8_t fp_mul(uint8_t a, uint8_t b);
31+
uint8_t ewma_update(uint8_t est, uint8_t sample, uint8_t alpha);
32+
bool is_dead(uint8_t ratio);
33+
uint16_t calc_etx(uint8_t forward, uint8_t reverse);
34+
35+
/* -------------------------------------------------------
36+
Function implementations
37+
------------------------------------------------------- */
38+
39+
uint8_t alpha_from_window(uint8_t window) {
40+
if ((window == 10)) {
41+
return 128;
42+
} else {
43+
return 160;
44+
}
45+
}
46+
47+
uint8_t bool_to_sample(bool b) {
48+
if (b) {
49+
return 255;
50+
} else {
51+
return 0;
52+
}
53+
}
54+
55+
uint8_t fp_mul(uint8_t a, uint8_t b) {
56+
if (((a == 0) || (b == 0))) {
57+
return 0;
58+
}
59+
return ((uint8_t)(((((uint16_t)(a)) * ((uint16_t)(b))) >> 8)));
60+
}
61+
62+
uint8_t ewma_update(uint8_t est, uint8_t sample, uint8_t alpha) {
63+
if (((est == 255) && (sample == 255))) {
64+
return 255;
65+
}
66+
return (fp_mul(alpha, sample) + fp_mul((256 - alpha), est));
67+
}
68+
69+
bool is_dead(uint8_t ratio) {
70+
return (ratio < DEAD_EPS);
71+
}
72+
73+
uint16_t calc_etx(uint8_t forward, uint8_t reverse) {
74+
if ((is_dead(forward) || is_dead(reverse))) {
75+
return 0xFFFF;
76+
}
77+
if (((forward >= 200) && (reverse >= 200))) {
78+
return ONE_FP;
79+
} else if (((forward >= 100) && (reverse >= 200))) {
80+
return 512;
81+
} else if (((forward >= 200) && (reverse >= 100))) {
82+
return 512;
83+
} else if (((forward >= 50) && (reverse >= 50))) {
84+
return 1024;
85+
} else {
86+
return 2048;
87+
}
88+
}
89+
90+
/* -------------------------------------------------------
91+
Tests
92+
------------------------------------------------------- */
93+
94+
void test_test_perfect_link(void) {
95+
fwd = ewma_update(ewma_update(ewma_update(OPTIMISTIC, 255, ALPHA_HALF), 255, ALPHA_HALF), 255, ALPHA_HALF);
96+
rev = ewma_update(ewma_update(ewma_update(OPTIMISTIC, 255, ALPHA_HALF), 255, ALPHA_HALF), 255, ALPHA_HALF);
97+
etx = calc_etx(fwd, rev);
98+
assert(((etx >= 200) && (etx <= 312)), "ETX ~1.0 expected");
99+
}
100+
101+
void test_test_half_forward(void) {
102+
fwd = ewma_update(ewma_update(OPTIMISTIC, 255, ALPHA_HALF), 0, ALPHA_HALF);
103+
rev = ewma_update(ewma_update(OPTIMISTIC, 255, ALPHA_HALF), 255, ALPHA_HALF);
104+
etx = calc_etx(fwd, rev);
105+
assert(((etx >= 384) && (etx <= 512)), "ETX ~2.0 expected");
106+
}
107+
108+
void test_test_dead_direction(void) {
109+
fwd = ewma_update(ewma_update(OPTIMISTIC, 255, ALPHA_HALF), 255, ALPHA_HALF);
110+
rev = ewma_update(ewma_update(OPTIMISTIC, 0, ALPHA_HALF), 0, ALPHA_HALF);
111+
etx = calc_etx(fwd, rev);
112+
assert((etx == 0xFFFF), "dead link should be infinite");
113+
}
114+
115+
void test_test_force_dead(void) {
116+
fwd = ewma_update(OPTIMISTIC, 255, 160);
117+
rev = ewma_update(OPTIMISTIC, 255, 160);
118+
etx_healthy = calc_etx(fwd, rev);
119+
assert((etx_healthy != 0xFFFF), "healthy link should be finite");
120+
etx_dead = calc_etx(0, 0);
121+
assert((etx_dead == 0xFFFF), "zeroed link should be infinite");
122+
fwd2 = ewma_update(0, 255, 160);
123+
rev2 = ewma_update(0, 255, 160);
124+
etx_resurrect = calc_etx(fwd2, rev2);
125+
assert((etx_resurrect != 0xFFFF), "resurrected link should be finite");
126+
}
127+
128+
void test_test_etx_buckets(void) {
129+
etx_perfect = calc_etx(230, 230);
130+
assert(((etx_perfect >= 200) && (etx_perfect <= 312)), "perfect ETX ~1.0");
131+
etx_half = calc_etx(115, 230);
132+
assert(((etx_half >= 384) && (etx_half <= 512)), "half ETX ~2.0");
133+
etx_dead = calc_etx(230, 0);
134+
assert((etx_dead == 0xFFFF), "dead direction infinite");
135+
}
136+
137+
void test_test_ewma_convergence(void) {
138+
est1 = ewma_update(ewma_update(OPTIMISTIC, 255, ALPHA_HALF), 255, ALPHA_HALF);
139+
assert((est1 > OPTIMISTIC), "EWMA should increase");
140+
est2 = ewma_update(ewma_update(OPTIMISTIC, 0, ALPHA_HALF), 0, ALPHA_HALF);
141+
assert((est2 < OPTIMISTIC), "EWMA should decrease");
142+
}
143+
144+
145+
#endif /* MESHETX_H */

gen/c/hello.c

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/* ============================================================================
2+
Generated from t27 spec: MeshHello
3+
DO NOT EDIT - generated by t27c gen-c
4+
phi^2 + 1/phi^2 = 3 | TRINITY
5+
============================================================================ */
6+
7+
#include <stdint.h>
8+
#include <stdbool.h>
9+
#include <stddef.h>
10+
#include <assert.h>
11+
12+
#ifndef MESHHELLO_H
13+
#define MESHHELLO_H
14+
15+
/* -------------------------------------------------------
16+
Constants
17+
------------------------------------------------------- */
18+
19+
#define MAX_HEARD 3
20+
#define HEADER_LEN 13
21+
22+
/* -------------------------------------------------------
23+
Function prototypes
24+
------------------------------------------------------- */
25+
26+
uint8_t u32_byte(uint32_t w, size_t idx);
27+
uint8_t hello_byte(uint32_t src, uint32_t seq, uint32_t heard0, uint32_t heard1, uint32_t heard2, uint8_t n, size_t idx);
28+
uint32_t u32_from_bytes(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3);
29+
uint32_t parse_hello_src(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3);
30+
uint32_t parse_hello_seq(uint8_t b4, uint8_t b5, uint8_t b6, uint8_t b7);
31+
uint32_t parse_hello_heard0(uint8_t b9, uint8_t b10, uint8_t b11, uint8_t b12);
32+
bool reports_hearing(uint32_t heard0, uint32_t heard1, uint32_t heard2, uint8_t n, uint32_t me);
33+
34+
/* -------------------------------------------------------
35+
Function implementations
36+
------------------------------------------------------- */
37+
38+
uint8_t u32_byte(uint32_t w, size_t idx) {
39+
if ((idx == 0)) {
40+
return ((uint8_t)(((w >> 24) & 255)));
41+
} else if ((idx == 1)) {
42+
return ((uint8_t)(((w >> 16) & 255)));
43+
} else if ((idx == 2)) {
44+
return ((uint8_t)(((w >> 8) & 255)));
45+
} else {
46+
return ((uint8_t)((w & 255)));
47+
}
48+
}
49+
50+
uint8_t hello_byte(uint32_t src, uint32_t seq, uint32_t heard0, uint32_t heard1, uint32_t heard2, uint8_t n, size_t idx) {
51+
if ((idx < 4)) {
52+
return u32_byte(src, idx);
53+
} else if ((idx < 8)) {
54+
return u32_byte(seq, (idx - 4));
55+
} else if ((idx == 8)) {
56+
return n;
57+
} else if (((((idx == 9) || (idx == 10)) || (idx == 11)) || (idx == 12))) {
58+
return u32_byte(heard0, (idx - 9));
59+
} else {
60+
return 0;
61+
}
62+
}
63+
64+
uint32_t u32_from_bytes(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) {
65+
return ((((((uint32_t)(b0)) << 24) | (((uint32_t)(b1)) << 16)) | (((uint32_t)(b2)) << 8)) | ((uint32_t)(b3)));
66+
}
67+
68+
uint32_t parse_hello_src(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) {
69+
return u32_from_bytes(b0, b1, b2, b3);
70+
}
71+
72+
uint32_t parse_hello_seq(uint8_t b4, uint8_t b5, uint8_t b6, uint8_t b7) {
73+
return u32_from_bytes(b4, b5, b6, b7);
74+
}
75+
76+
uint32_t parse_hello_heard0(uint8_t b9, uint8_t b10, uint8_t b11, uint8_t b12) {
77+
return u32_from_bytes(b9, b10, b11, b12);
78+
}
79+
80+
bool reports_hearing(uint32_t heard0, uint32_t heard1, uint32_t heard2, uint8_t n, uint32_t me) {
81+
if (((n >= 1) && (heard0 == me))) {
82+
return true;
83+
} else {
84+
return false;
85+
}
86+
}
87+
88+
/* -------------------------------------------------------
89+
Tests
90+
------------------------------------------------------- */
91+
92+
void test_hello_roundtrips(void) {
93+
b0 = hello_byte(7, 42, 1, 0, 0, 3, 0);
94+
b1 = hello_byte(7, 42, 1, 0, 0, 3, 1);
95+
b2 = hello_byte(7, 42, 1, 0, 0, 3, 2);
96+
b3 = hello_byte(7, 42, 1, 0, 0, 3, 3);
97+
b4 = hello_byte(7, 42, 1, 0, 0, 3, 4);
98+
b5 = hello_byte(7, 42, 1, 0, 0, 3, 5);
99+
b6 = hello_byte(7, 42, 1, 0, 0, 3, 6);
100+
b7 = hello_byte(7, 42, 1, 0, 0, 3, 7);
101+
b8 = hello_byte(7, 42, 1, 0, 0, 3, 8);
102+
b9 = hello_byte(7, 42, 1, 0, 0, 3, 9);
103+
b10 = hello_byte(7, 42, 1, 0, 0, 3, 10);
104+
b11 = hello_byte(7, 42, 1, 0, 0, 3, 11);
105+
b12 = hello_byte(7, 42, 1, 0, 0, 3, 12);
106+
src = parse_hello_src(b0, b1, b2, b3);
107+
seq = parse_hello_seq(b4, b5, b6, b7);
108+
heard0 = parse_hello_heard0(b9, b10, b11, b12);
109+
assert((src == 7), "src should be 7");
110+
assert((seq == 42), "seq should be 42");
111+
assert((n == 3), "n should be 3");
112+
assert((heard0 == 1), "heard0 should be 1");
113+
assert(valid, "parse should be valid");
114+
}
115+
116+
void test_empty_heard_list_ok(void) {
117+
b0 = hello_byte(9, 1, 0, 0, 0, 0, 0);
118+
b1 = hello_byte(9, 1, 0, 0, 0, 0, 1);
119+
b2 = hello_byte(9, 1, 0, 0, 0, 0, 2);
120+
b3 = hello_byte(9, 1, 0, 0, 0, 0, 3);
121+
b4 = hello_byte(9, 1, 0, 0, 0, 0, 4);
122+
b5 = hello_byte(9, 1, 0, 0, 0, 0, 5);
123+
b6 = hello_byte(9, 1, 0, 0, 0, 0, 6);
124+
b7 = hello_byte(9, 1, 0, 0, 0, 0, 7);
125+
b8 = hello_byte(9, 1, 0, 0, 0, 0, 8);
126+
assert((n == 0), "n should be 0");
127+
assert(valid, "empty heard list should be valid");
128+
}
129+
130+
void test_max_heard_neighbors(void) {
131+
src = parse_hello_src(0, 0, 0, 1);
132+
seq = parse_hello_seq(0, 0, 0, 100);
133+
heard0 = parse_hello_heard0(0, 0, 0, 5);
134+
assert((n == 3), "n should be 3");
135+
assert((heard0 == 5), "heard0 should be 5");
136+
assert(valid, "max neighbors should be valid");
137+
}
138+
139+
void test_not_in_heard_list(void) {
140+
result = reports_hearing(1, 0, 0, 3, 5);
141+
assert((result == false), "5 not in heard list");
142+
}
143+
144+
void test_in_heard_list_first_position(void) {
145+
result = reports_hearing(7, 0, 0, 3, 7);
146+
assert((result == true), "7 in heard list position 0");
147+
}
148+
149+
void test_n_exceeds_max_rejected(void) {
150+
assert((valid == false), "n > MAX_HEARD should be invalid");
151+
}
152+
153+
154+
#endif /* MESHHELLO_H */

gen/rust/etx.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Generated from .t27 spec
2+
// DO NOT EDIT — generated by t27c
3+
4+
pub const OPTIMISTIC: u8 = 230;
5+
6+
pub const DEAD_EPS: u8 = 38;
7+
8+
pub const ONE_FP: u16 = 256;
9+
10+
pub const ALPHA_HALF: u8 = 128;
11+
12+
pub fn alpha_from_window(window: u8) -> u8 {
13+
if (window == 10) {
14+
return 128;
15+
} else {
16+
return 160;
17+
}
18+
}
19+
20+
pub fn bool_to_sample(b: bool) -> u8 {
21+
if b {
22+
return 255;
23+
} else {
24+
return 0;
25+
}
26+
}
27+
28+
pub fn fp_mul(a: u8, b: u8) -> u8 {
29+
if ((a == 0) || (b == 0)) {
30+
return 0;
31+
}
32+
return ((((a as u16) * (b as u16)) >> 8) as u8);
33+
}
34+
35+
pub fn ewma_update(est: u8, sample: u8, alpha: u8) -> u8 {
36+
if ((est == 255) && (sample == 255)) {
37+
return 255;
38+
}
39+
return (fp_mul(alpha, sample) + fp_mul((256 - alpha), est));
40+
}
41+
42+
pub fn is_dead(ratio: u8) -> bool {
43+
return (ratio < DEAD_EPS);
44+
}
45+
46+
pub fn calc_etx(forward: u8, reverse: u8) -> u16 {
47+
if (is_dead(forward) || is_dead(reverse)) {
48+
return 0xFFFF;
49+
}
50+
if ((forward >= 200) && (reverse >= 200)) {
51+
return ONE_FP;
52+
} else {
53+
if ((forward >= 100) && (reverse >= 200)) {
54+
return 512;
55+
} else {
56+
if ((forward >= 200) && (reverse >= 100)) {
57+
return 512;
58+
} else {
59+
if ((forward >= 50) && (reverse >= 50)) {
60+
return 1024;
61+
} else {
62+
return 2048;
63+
}
64+
}
65+
}
66+
}
67+
}
68+

0 commit comments

Comments
 (0)