#[allow(clippy::unreadable_literal)]
#[rustfmt::skip]
pub const V0: [u32; 256] = [
251291136, 3952231631, 3370958628, 4070167936, 123631495, 3351110283, 3218676425, 2011642291,
774603218, 2402805061, 1004366930, 1843948209, 428891132, 3746331984, 1591258008, 3067016507,
1433388735, 504005498, 2032657933, 3419319784, 2805686246, 3102436986, 3808671154, 2501582075,
3978944421, 246043949, 4016898363, 649743608, 1974987508, 2651273766, 2357956801, 689605112,
715807172, 2722736134, 191939188, 3535520147, 3277019569, 1470435941, 3763101702, 3232409631,
122701163, 3920852693, 782246947, 372121310, 2995604341, 2045698575, 2332962102, 4005368743,
218596347, 3415381967, 4207612806, 861117671, 3676575285, 2581671944, 3312220480, 681232419,
307306866, 4112503940, 1158111502, 709227802, 2724140433, 4201101115, 4215970289, 4048876515,
3031661061, 1909085522, 510985033, 1361682810, 129243379, 3142379587, 2569842483, 3033268270,
1658118006, 932109358, 1982290045, 2983082771, 3007670818, 3448104768, 683749698, 778296777,
1399125101, 1939403708, 1692176003, 3868299200, 1422476658, 593093658, 1878973865, 2526292949,
1591602827, 3986158854, 3964389521, 2695031039, 1942050155, 424618399, 1347204291, 2669179716,
2434425874, 2540801947, 1384069776, 4123580443, 1523670218, 2708475297, 1046771089, 2229796016,
1255426612, 4213663089, 1521339547, 3041843489, 420130494, 10677091, 515623176, 3457502702,
2115821274, 2720124766, 3242576090, 854310108, 425973987, 325832382, 1796851292, 2462744411,
1976681690, 1408671665, 1228817808, 3917210003, 263976645, 2593736473, 2471651269, 4291353919,
650792940, 1191583883, 3046561335, 2466530435, 2545983082, 969168436, 2019348792, 2268075521,
1169345068, 3250240009, 3963499681, 2560755113, 911182396, 760842409, 3569308693, 2687243553,
381854665, 2613828404, 2761078866, 1456668111, 883760091, 3294951678, 1604598575, 1985308198,
1014570543, 2724959607, 3062518035, 3115293053, 138853680, 4160398285, 3322241130, 2068983570,
2247491078, 3669524410, 1575146607, 828029864, 3732001371, 3422026452, 3370954177, 4006626915,
543812220, 1243116171, 3928372514, 2791443445, 4081325272, 2280435605, 885616073, 616452097,
3188863436, 2780382310, 2340014831, 1208439576, 258356309, 3837963200, 2075009450, 3214181212,
3303882142, 880813252, 1355575717, 207231484, 2420803184, 358923368, 1617557768, 3272161958,
1771154147, 2842106362, 1751209208, 1421030790, 658316681, 194065839, 3241510581, 38625260,
301875395, 4176141739, 297312930, 2137802113, 1502984205, 3669376622, 3728477036, 234652930,
2213589897, 2734638932, 1129721478, 3187422815, 2859178611, 3284308411, 3819792700, 3557526733,
451874476, 1740576081, 3592838701, 1709429513, 3702918379, 3533351328, 1641660745, 179350258,
2380520112, 3936163904, 3685256204, 3156252216, 1854258901, 2861641019, 3176611298, 834787554,
331353807, 517858103, 3010168884, 4012642001, 2217188075, 3756943137, 3077882590, 2054995199,
3081443129, 3895398812, 1141097543, 2376261053, 2626898255, 2554703076, 401233789, 1460049922,
678083952, 1064990737, 940909784, 1673396780, 528881783, 1712547446, 3629685652, 1358307511,
];
#[allow(clippy::unreadable_literal)]
#[rustfmt::skip]
pub const V1: [u32; 256] = [
807385413, 2043073223, 3336749796, 1302105833, 2278607931, 541015020, 1684564270, 372709334,
3508252125, 1768346005, 1270451292, 2603029534, 2049387273, 3891424859, 2152948345, 4114760273,
915180310, 3754787998, 700503826, 2131559305, 1308908630, 224437350, 4065424007, 3638665944,
1679385496, 3431345226, 1779595665, 3068494238, 1424062773, 1033448464, 4050396853, 3302235057,
420600373, 2868446243, 311689386, 259047959, 4057180909, 1575367248, 4151214153, 110249784,
3006865921, 4293710613, 3501256572, 998007483, 499288295, 1205710710, 2997199489, 640417429,
3044194711, 486690751, 2686640734, 2394526209, 2521660077, 49993987, 3843885867, 4201106668,
415906198, 19296841, 2402488407, 2137119134, 1744097284, 579965637, 2037662632, 852173610,
2681403713, 1047144830, 2982173936, 910285038, 4187576520, 2589870048, 989448887, 3292758024,
506322719, 176010738, 1865471968, 2619324712, 564829442, 1996870325, 339697593, 4071072948,
3618966336, 2111320126, 1093955153, 957978696, 892010560, 1854601078, 1873407527, 2498544695,
2694156259, 1927339682, 1650555729, 183933047, 3061444337, 2067387204, 228962564, 3904109414,
1595995433, 1780701372, 2463145963, 307281463, 3237929991, 3852995239, 2398693510, 3754138664,
522074127, 146352474, 4104915256, 3029415884, 3545667983, 332038910, 976628269, 3123492423,
3041418372, 2258059298, 2139377204, 3243642973, 3226247917, 3674004636, 2698992189, 3453843574,
1963216666, 3509855005, 2358481858, 747331248, 1957348676, 1097574450, 2435697214, 3870972145,
1888833893, 2914085525, 4161315584, 1273113343, 3269644828, 3681293816, 412536684, 1156034077,
3823026442, 1066971017, 3598330293, 1979273937, 2079029895, 1195045909, 1071986421, 2712821515,
3377754595, 2184151095, 750918864, 2585729879, 4249895712, 1832579367, 1192240192, 946734366,
31230688, 3174399083, 3549375728, 1642430184, 1904857554, 861877404, 3277825584, 4267074718,
3122860549, 666423581, 644189126, 226475395, 307789415, 1196105631, 3191691839, 782852669,
1608507813, 1847685900, 4069766876, 3931548641, 2526471011, 766865139, 2115084288, 4259411376,
3323683436, 568512177, 3736601419, 1800276898, 4012458395, 1823982, 27980198, 2023839966,
869505096, 431161506, 1024804023, 1853869307, 3393537983, 1500703614, 3019471560, 1351086955,
3096933631, 3034634988, 2544598006, 1230942551, 3362230798, 159984793, 491590373, 3993872886,
3681855622, 903593547, 3535062472, 1799803217, 772984149, 895863112, 1899036275, 4187322100,
101856048, 234650315, 3183125617, 3190039692, 525584357, 1286834489, 455810374, 1869181575,
922673938, 3877430102, 3422391938, 1414347295, 1971054608, 3061798054, 830555096, 2822905141,
167033190, 1079139428, 4210126723, 3593797804, 429192890, 372093950, 1779187770, 3312189287,
204349348, 452421568, 2800540462, 3733109044, 1235082423, 1765319556, 3174729780, 3762994475,
3171962488, 442160826, 198349622, 45942637, 1324086311, 2901868599, 678860040, 3812229107,
19936821, 1119590141, 3640121682, 3545931032, 2102949142, 2828208598, 3603378023, 4135048896,
];
#[allow(clippy::unreadable_literal)]
#[rustfmt::skip]
pub const V2: [u32; 256] = [
1629829892, 282540176, 2794583710, 496504798, 2990494426, 3070701851, 2575963183, 4094823972,
2775723650, 4079480416, 176028725, 2246241423, 3732217647, 2196843075, 1306949278, 4170992780,
4039345809, 3209664269, 3387499533, 293063229, 3660290503, 2648440860, 2531406539, 3537879412,
773374739, 4184691853, 1804207821, 3347126643, 3479377103, 3970515774, 1891731298, 2368003842,
3537588307, 2969158410, 4230745262, 831906319, 2935838131, 264029468, 120852739, 3200326460,
355445271, 2296305141, 1566296040, 1760127056, 20073893, 3427103620, 2866979760, 2359075957,
2025314291, 1725696734, 3346087406, 2690756527, 99815156, 4248519977, 2253762642, 3274144518,
598024568, 3299672435, 556579346, 4121041856, 2896948975, 3620123492, 918453629, 3249461198,
2231414958, 3803272287, 3657597946, 2588911389, 242262274, 1725007475, 2026427718, 46776484,
2873281403, 2919275846, 3177933051, 1918859160, 2517854537, 1857818511, 3234262050, 479353687,
200201308, 2801945841, 1621715769, 483977159, 423502325, 3689396064, 1850168397, 3359959416,
3459831930, 841488699, 3570506095, 930267420, 1564520841, 2505122797, 593824107, 1116572080,
819179184, 3139123629, 1414339336, 1076360795, 512403845, 177759256, 1701060666, 2239736419,
515179302, 2935012727, 3821357612, 1376520851, 2700745271, 966853647, 1041862223, 715860553,
171592961, 1607044257, 1227236688, 3647136358, 1417559141, 4087067551, 2241705880, 4194136288,
1439041934, 20464430, 119668151, 2021257232, 2551262694, 1381539058, 4082839035, 498179069,
311508499, 3580908637, 2889149671, 142719814, 1232184754, 3356662582, 2973775623, 1469897084,
1728205304, 1415793613, 50111003, 3133413359, 4074115275, 2710540611, 2700083070, 2457757663,
2612845330, 3775943755, 2469309260, 2560142753, 3020996369, 1691667711, 4219602776, 1687672168,
1017921622, 2307642321, 368711460, 3282925988, 213208029, 4150757489, 3443211944, 2846101972,
4106826684, 4272438675, 2199416468, 3710621281, 497564971, 285138276, 765042313, 916220877,
3402623607, 2768784621, 1722849097, 3386397442, 487920061, 3569027007, 3424544196, 217781973,
2356938519, 3252429414, 145109750, 2692588106, 2454747135, 1299493354, 4120241887, 2088917094,
932304329, 1442609203, 952586974, 3509186750, 753369054, 854421006, 1954046388, 2708927882,
4047539230, 3048925996, 1667505809, 805166441, 1182069088, 4265546268, 4215029527, 3374748959,
373532666, 2454243090, 2371530493, 3651087521, 2619878153, 1651809518, 1553646893, 1227452842,
703887512, 3696674163, 2552507603, 2635912901, 895130484, 3287782244, 3098973502, 990078774,
3780326506, 2290845203, 41729428, 1949580860, 2283959805, 1036946170, 1694887523, 4880696,
466000198, 2765355283, 3318686998, 1266458025, 3919578154, 3545413527, 2627009988, 3744680394,
1696890173, 3250684705, 4142417708, 915739411, 3308488877, 1289361460, 2942552331, 1169105979,
3342228712, 698560958, 1356041230, 2401944293, 107705232, 3701895363, 903928723, 3646581385,
844950914, 1944371367, 3863894844, 2946773319, 1972431613, 1706989237, 29917467, 3497665928,
];
#[allow(clippy::unreadable_literal)]
#[rustfmt::skip]
pub const V3: [u32; 256] = [
1191369816, 744902811, 2539772235, 3213192037, 3286061266, 1200571165, 2463281260, 754888894,
714651270, 1968220972, 3628497775, 1277626456, 1493398934, 364289757, 2055487592, 3913468088,
2930259465, 902504567, 3967050355, 2056499403, 692132390, 186386657, 832834706, 859795816,
1283120926, 2253183716, 3003475205, 1755803552, 2239315142, 4271056352, 2184848469, 769228092,
1249230754, 1193269205, 2660094102, 642979613, 1687087994, 2726106182, 446402913, 4122186606,
3771347282, 37667136, 192775425, 3578702187, 1952659096, 3989584400, 3069013882, 2900516158,
4045316336, 3057163251, 1702104819, 4116613420, 3575472384, 2674023117, 1409126723, 3215095429,
1430726429, 2544497368, 1029565676, 1855801827, 4262184627, 1854326881, 2906728593, 3277836557,
2787697002, 2787333385, 3105430738, 2477073192, 748038573, 1088396515, 1611204853, 201964005,
3745818380, 3654683549, 3816120877, 3915783622, 2563198722, 1181149055, 33158084, 3723047845,
3790270906, 3832415204, 2959617497, 372900708, 1286738499, 1932439099, 3677748309, 2454711182,
2757856469, 2134027055, 2780052465, 3190347618, 3758510138, 3626329451, 1120743107, 1623585693,
1389834102, 2719230375, 3038609003, 462617590, 260254189, 3706349764, 2556762744, 2874272296,
2502399286, 4216263978, 2683431180, 2168560535, 3561507175, 668095726, 680412330, 3726693946,
4180630637, 3335170953, 942140968, 2711851085, 2059233412, 4265696278, 3204373534, 232855056,
881788313, 2258252172, 2043595984, 3758795150, 3615341325, 2138837681, 1351208537, 2923692473,
3402482785, 2105383425, 2346772751, 499245323, 3417846006, 2366116814, 2543090583, 1828551634,
3148696244, 3853884867, 1364737681, 2200687771, 2689775688, 232720625, 4071657318, 2671968983,
3531415031, 1212852141, 867923311, 3740109711, 1923146533, 3237071777, 3100729255, 3247856816,
906742566, 4047640575, 4007211572, 3495700105, 1171285262, 2835682655, 1634301229, 3115169925,
2289874706, 2252450179, 944880097, 371933491, 1649074501, 2208617414, 2524305981, 2496569844,
2667037160, 1257550794, 3399219045, 3194894295, 1643249887, 342911473, 891025733, 3146861835,
3789181526, 938847812, 1854580183, 2112653794, 2960702988, 1238603378, 2205280635, 1666784014,
2520274614, 3355493726, 2310872278, 3153920489, 2745882591, 1200203158, 3033612415, 2311650167,
1048129133, 4206710184, 4209176741, 2640950279, 2096382177, 4116899089, 3631017851, 4104488173,
1857650503, 3801102932, 445806934, 3055654640, 897898279, 3234007399, 1325494930, 2982247189,
1619020475, 2720040856, 885096170, 3485255499, 2983202469, 3891011124, 546522756, 1524439205,
2644317889, 2170076800, 2969618716, 961183518, 1081831074, 1037015347, 3289016286, 2331748669,
620887395, 303042654, 3990027945, 1562756376, 3413341792, 2059647769, 2823844432, 674595301,
2457639984, 4076754716, 2447737904, 1583323324, 625627134, 3076006391, 345777990, 1684954145,
879227329, 3436182180, 1522273219, 3802543817, 1456017040, 1897819847, 2970081129, 1382576028,
3820044861, 1044428167, 612252599, 3340478395, 2150613904, 3397625662, 3573635640, 3432275192,
];
#[must_use]
#[inline]
pub fn rand(y: u32, i: u8, m: u32) -> u32 {
debug_assert!(m > 0, "modulus must be positive");
let x0 = ((y.wrapping_add(u32::from(i))) & 0xFF) as usize;
let x1 = (((y >> 8).wrapping_add(u32::from(i))) & 0xFF) as usize;
let x2 = (((y >> 16).wrapping_add(u32::from(i))) & 0xFF) as usize;
let x3 = (((y >> 24).wrapping_add(u32::from(i))) & 0xFF) as usize;
(V0[x0] ^ V1[x1] ^ V2[x2] ^ V3[x3]) % m
}
#[must_use]
#[inline]
pub fn deg(v: u32) -> usize {
#[allow(clippy::unreadable_literal)]
const DEGREE_TABLE: [(u32, usize); 30] = [
(5243, 1),
(529531, 2),
(704294, 3),
(791675, 4),
(844104, 5),
(879057, 6),
(904023, 7),
(922747, 8),
(937311, 9),
(948962, 10),
(958494, 11),
(966438, 12),
(973160, 13),
(978921, 14),
(983914, 15),
(988283, 16),
(992138, 17),
(995565, 18),
(998631, 19),
(1001391, 20),
(1003887, 21),
(1006157, 22),
(1008229, 23),
(1010129, 24),
(1011876, 25),
(1013490, 26),
(1014983, 27),
(1016370, 28),
(1017662, 29),
(1048576, 30),
];
for &(threshold, d) in &DEGREE_TABLE[..30] {
if v < threshold {
return d;
}
}
30
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct LtTuple {
pub d: usize,
pub a: usize,
pub b: usize,
pub d1: usize,
pub a1: usize,
pub b1: usize,
}
const RFC6330_MAX_LT_DEGREE: usize = 30;
#[must_use]
pub fn next_prime_ge(n: usize) -> Option<usize> {
if n <= 2 {
return Some(2);
}
let mut candidate = if n.is_multiple_of(2) {
n.checked_add(1)?
} else {
n
};
while !is_prime(candidate) {
candidate = candidate.checked_add(2)?;
}
Some(candidate)
}
fn is_prime(n: usize) -> bool {
if n < 2 {
return false;
}
if n.is_multiple_of(2) {
return n == 2;
}
let mut d = 3usize;
while d <= n / d {
if n.is_multiple_of(d) {
return false;
}
d += 2;
}
true
}
#[must_use]
pub fn tuple(
systematic_index: usize,
lt_width: usize,
pi_count: usize,
pi_modulus: usize,
encoding_symbol_id: u32,
) -> LtTuple {
try_tuple(
systematic_index,
lt_width,
pi_count,
pi_modulus,
encoding_symbol_id,
)
.unwrap_or_default()
}
#[must_use]
pub fn try_tuple(
systematic_index: usize,
lt_width: usize,
pi_count: usize,
pi_modulus: usize,
encoding_symbol_id: u32,
) -> Option<LtTuple> {
let expected_pi_modulus = next_prime_ge(pi_count)?;
let valid = lt_width > 1
&& pi_count > 0
&& pi_modulus > 1
&& pi_modulus >= pi_count
&& pi_modulus == expected_pi_modulus
&& u32::try_from(systematic_index).is_ok()
&& u32::try_from(lt_width).is_ok()
&& u32::try_from(pi_modulus).is_ok();
if !valid {
return None;
}
let systematic_index_u32 = u32::try_from(systematic_index).ok()?;
let lt_width_u32 = u32::try_from(lt_width).ok()?;
let pi_modulus_u32 = u32::try_from(pi_modulus).ok()?;
let mut linear_factor = 53_591u32.wrapping_add(997u32.wrapping_mul(systematic_index_u32));
if linear_factor.is_multiple_of(2) {
linear_factor = linear_factor.wrapping_add(1);
}
let constant_offset = 10_267u32.wrapping_mul(systematic_index_u32.wrapping_add(1));
let random_input = constant_offset.wrapping_add(encoding_symbol_id.wrapping_mul(linear_factor));
let degree_input = rand(random_input, 0, 1 << 20);
let lt_degree = deg(degree_input).min(lt_width - 2);
let lt_step = 1 + rand(random_input, 1, lt_width_u32 - 1) as usize;
let lt_start = rand(random_input, 2, lt_width_u32) as usize;
let pi_degree = if lt_degree < 4 {
2 + rand(encoding_symbol_id, 3, 2) as usize
} else {
2
};
let pi_step = 1 + rand(encoding_symbol_id, 4, pi_modulus_u32 - 1) as usize;
let pi_start = rand(encoding_symbol_id, 5, pi_modulus_u32) as usize;
Some(LtTuple {
d: lt_degree,
a: lt_step,
b: lt_start,
d1: pi_degree,
a1: pi_step,
b1: pi_start,
})
}
#[must_use]
pub fn tuple_with_prime_p1(j: usize, w: usize, p: usize, x: u32) -> Option<LtTuple> {
Some(tuple(j, w, p, next_prime_ge(p)?, x))
}
#[must_use]
pub fn repair_indices_for_esi(
systematic_index: usize,
lt_width: usize,
pi_count: usize,
encoding_symbol_id: u32,
) -> Vec<usize> {
let Some(pi_modulus) = next_prime_ge(pi_count) else {
return Vec::new();
};
let lt_tuple = tuple(
systematic_index,
lt_width,
pi_count,
pi_modulus,
encoding_symbol_id,
);
tuple_indices(lt_tuple, lt_width, pi_count, pi_modulus)
}
#[must_use]
pub fn tuple_indices(tuple: LtTuple, w: usize, p: usize, p1: usize) -> Vec<usize> {
let Some(expected_p1) = next_prime_ge(p) else {
return Vec::new();
};
let valid = w > 1
&& p > 0
&& p1 >= p
&& p1 == expected_p1
&& (1..=RFC6330_MAX_LT_DEGREE).contains(&tuple.d)
&& matches!(tuple.d1, 2 | 3)
&& tuple.a > 0
&& tuple.a < w
&& tuple.a1 > 0
&& tuple.a1 < p1
&& tuple.b < w
&& tuple.b1 < p1;
if !valid {
return Vec::new();
}
let mut out = Vec::with_capacity(tuple.d + tuple.d1);
let mut x = tuple.b % w;
out.push(x);
for _ in 1..tuple.d {
x = (x + tuple.a) % w;
out.push(x);
}
let mut x1 = tuple.b1 % p1;
while x1 >= p {
x1 = (x1 + tuple.a1) % p1;
}
out.push(w + x1);
for _ in 1..tuple.d1 {
x1 = (x1 + tuple.a1) % p1;
while x1 >= p {
x1 = (x1 + tuple.a1) % p1;
}
out.push(w + x1);
}
out
}
#[cfg(test)]
mod tests {
#![allow(
clippy::pedantic,
clippy::nursery,
clippy::expect_fun_call,
clippy::map_unwrap_or,
clippy::cast_possible_wrap,
clippy::future_not_send
)]
use super::*;
#[test]
fn v0_v3_lookup_tables_byte_exact() {
use crate::util::det_hash::DetHasher;
use std::hash::Hasher;
fn hash_table(name: &'static str, table: &[u32; 256]) -> u64 {
let mut h = DetHasher::default();
h.write(name.as_bytes());
h.write_usize(table.len());
for &v in table {
h.write_u32(v);
}
h.finish()
}
assert_eq!(
V0.len(),
256,
"V0 must have 256 entries per RFC 6330 §5.5.2"
);
assert_eq!(
V1.len(),
256,
"V1 must have 256 entries per RFC 6330 §5.5.2"
);
assert_eq!(
V2.len(),
256,
"V2 must have 256 entries per RFC 6330 §5.5.2"
);
assert_eq!(
V3.len(),
256,
"V3 must have 256 entries per RFC 6330 §5.5.2"
);
const EXPECTED_V0_HASH: u64 = 0xeab2_902a_a719_eff6;
const EXPECTED_V1_HASH: u64 = 0x5c31_223d_15cd_98f4;
const EXPECTED_V2_HASH: u64 = 0x46c9_f312_3cd9_532e;
const EXPECTED_V3_HASH: u64 = 0xc2d7_9e10_36cd_4bfc;
assert_eq!(
hash_table("V0", &V0),
EXPECTED_V0_HASH,
"V0 lookup table byte-exact regression: deterministic \
hash diverged from the RFC 6330 §5.5.2 pin. Either a \
value moved (table is now wrong) or someone updated \
the table without updating the pin."
);
assert_eq!(
hash_table("V1", &V1),
EXPECTED_V1_HASH,
"V1 lookup table byte-exact regression"
);
assert_eq!(
hash_table("V2", &V2),
EXPECTED_V2_HASH,
"V2 lookup table byte-exact regression"
);
assert_eq!(
hash_table("V3", &V3),
EXPECTED_V3_HASH,
"V3 lookup table byte-exact regression"
);
}
#[derive(Clone, Copy)]
struct TupleScenario {
scenario_id: &'static str,
seed: u64,
k: usize,
symbol_size: usize,
loss_pattern: &'static str,
j: usize,
w: usize,
p: usize,
x: u32,
expected_tuple: LtTuple,
expected_indices: &'static [usize],
}
fn tuple_scenarios() -> [TupleScenario; 3] {
[
TupleScenario {
scenario_id: "RQ-B2-TUPLE-GOLDEN-001",
seed: 1234,
k: 8,
symbol_size: 32,
loss_pattern: "none",
j: 5,
w: 101,
p: 17,
x: 1234,
expected_tuple: LtTuple {
d: 3,
a: 8,
b: 51,
d1: 2,
a1: 10,
b1: 5,
},
expected_indices: &[51, 59, 67, 106, 116],
},
TupleScenario {
scenario_id: "RQ-B2-TUPLE-GOLDEN-002",
seed: 77,
k: 16,
symbol_size: 64,
loss_pattern: "drop_10pct",
j: 3,
w: 257,
p: 29,
x: 77,
expected_tuple: LtTuple {
d: 3,
a: 129,
b: 223,
d1: 3,
a1: 28,
b1: 20,
},
expected_indices: &[223, 95, 224, 277, 276, 275],
},
TupleScenario {
scenario_id: "RQ-B2-TUPLE-GOLDEN-003",
seed: 999,
k: 32,
symbol_size: 128,
loss_pattern: "drop_25pct_burst",
j: 7,
w: 503,
p: 31,
x: 999,
expected_tuple: LtTuple {
d: 6,
a: 12,
b: 398,
d1: 2,
a1: 8,
b1: 4,
},
expected_indices: &[398, 410, 422, 434, 446, 458, 507, 515],
},
]
}
fn tuple_context(scenario: &TupleScenario, outcome: &'static str) -> String {
format!(
"scenario_id={} seed={} k={} symbol_size={} loss_pattern={} outcome={} \
artifact_path=artifacts/raptorq_b2_tuple_scenarios_v1.json \
fixture_ref=RQ-B2-TUPLE-V1 \
repro_cmd='rch exec -- cargo test -p asupersync --lib \
rfc6330::tests::tuple_scenario_matrix_deterministic_replay -- --nocapture'",
scenario.scenario_id,
scenario.seed,
scenario.k,
scenario.symbol_size,
scenario.loss_pattern,
outcome
)
}
#[test]
fn rand_deterministic() {
assert_eq!(rand(0, 0, 1000), rand(0, 0, 1000));
assert_eq!(rand(12345, 6, 100), rand(12345, 6, 100));
}
#[test]
fn rand_in_range() {
for y in [0, 1, 100, 1000, 65535, 0xFFFF_FFFF] {
for i in 0..10u8 {
for m in [1, 10, 100, 1000, 65536] {
let result = rand(y, i, m);
assert!(result < m, "rand({y}, {i}, {m}) = {result} >= {m}");
}
}
}
}
#[test]
fn rand_known_values() {
let r1 = rand(0, 0, 256);
let r2 = rand(1, 0, 256);
assert_ne!(r1, r2);
}
#[test]
fn deg_basic() {
let d = deg(0);
assert!((1..=30).contains(&d));
}
#[test]
fn deg_deterministic() {
assert_eq!(deg(12_345), deg(12_345));
}
#[test]
fn deg_threshold_edges() {
assert_eq!(deg(0), 1);
assert_eq!(deg(5_242), 1);
assert_eq!(deg(5_243), 2);
assert_eq!(deg(1_048_575), 30);
}
#[test]
fn rand_worked_examples_match_rfc_5_3_5_1_canonical_formula() {
let test_cases: &[(u32, u8, u32)] = &[
(0, 0, 256),
(0, 1, 1024),
(1, 0, 65536),
(12_345, 7, 100),
(0xDEAD_BEEF, 3, 4096),
(1_048_576, 30, 256),
(u32::MAX, u8::MAX, 1024),
(0, 0, 1),
(0xDEAD_BEEF, 17, 1),
(u32::MAX, u8::MAX, 1),
(0, 0, 2),
(0xCAFE_BABE, 11, 2),
(0, 0, u32::MAX),
(0xDEAD_BEEF, 0, u32::MAX),
(u32::MAX, u8::MAX, u32::MAX),
(255, 0, 256),
(255, 1, 256),
(256, 0, 256),
(256, 1, 256),
(0xFF00, 0, 4096),
(0x1_0000, 0, 4096),
(u32::MAX, 0, 4096),
(u32::MAX, 1, 4096),
(0, u8::MAX, 4096),
(1, u8::MAX, 4096),
];
for &(y, i, m) in test_cases {
let i_u32 = u32::from(i);
let v0_idx = (y.wrapping_add(i_u32) & 0xFF) as usize;
let v1_idx = ((y >> 8).wrapping_add(i_u32) & 0xFF) as usize;
let v2_idx = ((y >> 16).wrapping_add(i_u32) & 0xFF) as usize;
let v3_idx = ((y >> 24).wrapping_add(i_u32) & 0xFF) as usize;
let xor = V0[v0_idx] ^ V1[v1_idx] ^ V2[v2_idx] ^ V3[v3_idx];
let expected = xor % m;
let observed = rand(y, i, m);
assert_eq!(
observed, expected,
"rand({y}, {i}, {m}) returned {observed}, expected {expected} \
from canonical RFC 6330 §5.3.5.1 formula"
);
}
}
#[test]
fn deg_distribution_matches_rfc_thresholds_within_5_sigma() {
const TOTAL: u32 = 1 << 20;
const THRESHOLDS: [u32; 30] = [
5_243, 529_531, 704_294, 791_675, 844_104, 879_057, 904_023, 922_747, 937_311, 948_962,
958_494, 966_438, 973_160, 978_921, 983_914, 988_283, 992_138, 995_565, 998_631,
1_001_391, 1_003_887, 1_006_157, 1_008_229, 1_010_129, 1_011_876, 1_013_490, 1_014_983,
1_016_370, 1_017_662, 1_048_576,
];
let n: u64 = 200_000;
let mut histogram = [0u64; 31];
let mut state: u64 = 0xCAFE_F00D_DEAD_BEEFu64;
for _ in 0..n {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
let v = (state >> 11) as u32 & (TOTAL - 1);
let d = deg(v);
histogram[d] += 1;
}
let mut prev_threshold: u32 = 0;
for (i, &threshold) in THRESHOLDS.iter().enumerate() {
let degree = i + 1;
let mass = u64::from(threshold - prev_threshold);
let expected = (n * mass) / u64::from(TOTAL);
#[allow(clippy::cast_precision_loss)]
let sigma = (expected as f64).sqrt().max(1.0);
let observed = histogram[degree];
#[allow(clippy::cast_possible_wrap, clippy::cast_precision_loss)]
let deviation = (observed as f64 - expected as f64).abs();
assert!(
deviation < 5.0 * sigma,
"degree {degree} histogram out of tolerance: expected {expected}, \
observed {observed}, deviation {deviation:.1}, 5σ={:.1} (mass={mass})",
5.0 * sigma
);
prev_threshold = threshold;
}
let total: u64 = histogram.iter().sum();
assert_eq!(total, n, "histogram total {total} != samples drawn {n}");
assert_eq!(histogram[0], 0, "deg() must never return 0");
}
#[test]
fn next_prime_ge_basic() {
assert_eq!(next_prime_ge(1), Some(2));
assert_eq!(next_prime_ge(2), Some(2));
assert_eq!(next_prime_ge(3), Some(3));
assert_eq!(next_prime_ge(4), Some(5));
assert_eq!(next_prime_ge(17), Some(17));
assert_eq!(next_prime_ge(18), Some(19));
}
#[test]
fn next_prime_ge_overflow_adjacent_inputs_fail_closed() {
for n in [usize::MAX - 1, usize::MAX] {
let result = std::panic::catch_unwind(|| next_prime_ge(n));
assert!(
result.is_ok() && result.unwrap().is_none(),
"next_prime_ge({n}) should fail closed instead of wrapping"
);
}
}
#[test]
fn tuple_deterministic_and_bounded() {
let p1 = next_prime_ge(17).expect("test P1 must fit");
let t1 = tuple(5, 101, 17, p1, 1234);
let t2 = tuple(5, 101, 17, p1, 1234);
assert_eq!(t1, t2);
assert!((1..=30).contains(&t1.d));
assert!(t1.a >= 1 && t1.a < 101);
assert!(t1.b < 101);
assert!(t1.d1 == 2 || t1.d1 == 3);
assert!(t1.a1 >= 1 && t1.a1 < p1);
assert!(t1.b1 < p1);
}
#[test]
fn tuple_indices_are_in_range() {
let w = 257;
let p = 29;
let p1 = next_prime_ge(p).expect("test P1 must fit");
let t = tuple(3, w, p, p1, 77);
let idx = tuple_indices(t, w, p, p1);
assert_eq!(idx.len(), t.d + t.d1);
assert!(idx.iter().all(|i| *i < w + p));
assert!(idx.iter().any(|i| *i >= w));
}
#[test]
fn tuple_returns_zero_sentinel_for_non_canonical_prime_p1() {
let tuple = tuple(254, 17, 10, 13, 0);
assert_eq!(
tuple,
LtTuple::default(),
"tuple should fail closed with the zero sentinel for larger prime P1 values"
);
}
#[test]
fn tuple_returns_zero_sentinel_for_composite_p1() {
let tuple = tuple(254, 17, 10, 12, 0);
assert_eq!(
tuple,
LtTuple::default(),
"tuple should fail closed with the zero sentinel for composite P1 values"
);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn tuple_returns_zero_sentinel_for_oversized_width_before_u32_truncation() {
let too_wide = (u32::MAX as usize) + 1;
let tuple = tuple(5, too_wide, 17, 17, 0);
assert_eq!(
tuple,
LtTuple::default(),
"tuple should fail closed with the zero sentinel when W exceeds RFC 6330 u32 arithmetic"
);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn tuple_returns_zero_sentinel_for_oversized_systematic_index_before_u32_truncation() {
let too_large_j = (u32::MAX as usize) + 1;
let tuple = tuple(too_large_j, 17, 10, 11, 0);
assert_eq!(
tuple,
LtTuple::default(),
"tuple should fail closed with the zero sentinel when J exceeds RFC 6330 u32 arithmetic"
);
}
#[test]
fn tuple_indices_return_empty_vec_for_non_canonical_p1() {
let result = tuple_indices(
LtTuple {
d: 2,
a: 4,
b: 9,
d1: 2,
a1: 5,
b1: 1,
},
17,
10,
13,
);
assert!(
result.is_empty(),
"tuple_indices should fail closed with an empty schedule for non-canonical P1 values"
);
}
fn assert_tuple_indices_returns_empty_on_invalid_tuple(tuple: LtTuple, message: &str) {
let result = tuple_indices(tuple, 17, 10, 11);
assert!(result.is_empty(), "{message}");
}
#[test]
fn tuple_indices_reject_zero_degrees() {
assert_tuple_indices_returns_empty_on_invalid_tuple(
LtTuple {
d: 0,
a: 4,
b: 9,
d1: 2,
a1: 5,
b1: 1,
},
"tuple_indices should reject zero LT degree instead of silently emitting an extra LT index",
);
assert_tuple_indices_returns_empty_on_invalid_tuple(
LtTuple {
d: 2,
a: 4,
b: 9,
d1: 0,
a1: 5,
b1: 1,
},
"tuple_indices should reject zero PI degree instead of silently emitting an extra PI index",
);
}
#[test]
fn tuple_indices_reject_zero_steps_before_iteration() {
assert_tuple_indices_returns_empty_on_invalid_tuple(
LtTuple {
d: 2,
a: 0,
b: 9,
d1: 2,
a1: 5,
b1: 1,
},
"tuple_indices should reject zero LT step instead of producing degenerate duplicate walks",
);
assert_tuple_indices_returns_empty_on_invalid_tuple(
LtTuple {
d: 2,
a: 4,
b: 9,
d1: 2,
a1: 0,
b1: 10,
},
"tuple_indices should reject zero PI step instead of risking a non-terminating PI-side walk",
);
}
#[test]
fn tuple_indices_reject_oversized_degrees() {
assert_tuple_indices_returns_empty_on_invalid_tuple(
LtTuple {
d: RFC6330_MAX_LT_DEGREE + 1,
a: 4,
b: 9,
d1: 2,
a1: 5,
b1: 1,
},
"tuple_indices should reject oversized LT degree instead of expanding an out-of-contract walk",
);
assert_tuple_indices_returns_empty_on_invalid_tuple(
LtTuple {
d: 2,
a: 4,
b: 9,
d1: 4,
a1: 5,
b1: 1,
},
"tuple_indices should reject oversized PI degree instead of allocating extra PI-side work",
);
}
#[test]
fn tuple_scenario_matrix_golden_vectors() {
for scenario in tuple_scenarios() {
let p1 = next_prime_ge(scenario.p).expect("scenario P1 must fit");
let actual_tuple = tuple(scenario.j, scenario.w, scenario.p, p1, scenario.x);
let actual_indices = tuple_indices(actual_tuple, scenario.w, scenario.p, p1);
let context = tuple_context(&scenario, "golden_vector_compare");
assert_eq!(
actual_tuple, scenario.expected_tuple,
"{context} tuple mismatch"
);
assert_eq!(
actual_indices, scenario.expected_indices,
"{context} tuple index mismatch"
);
}
}
#[test]
fn tuple_scenario_matrix_deterministic_replay() {
for scenario in tuple_scenarios() {
let p1 = next_prime_ge(scenario.p).expect("scenario P1 must fit");
let tuple_first = tuple(scenario.j, scenario.w, scenario.p, p1, scenario.x);
let tuple_second = tuple(scenario.j, scenario.w, scenario.p, p1, scenario.x);
let indices_first = tuple_indices(tuple_first, scenario.w, scenario.p, p1);
let indices_second = tuple_indices(tuple_second, scenario.w, scenario.p, p1);
let context = tuple_context(&scenario, "deterministic_replay");
assert_eq!(
tuple_first, tuple_second,
"{context} tuple is not deterministic"
);
assert_eq!(
indices_first, indices_second,
"{context} tuple index replay mismatch"
);
}
}
#[test]
fn lt_tuple_debug_clone_copy_eq() {
let a = LtTuple {
d: 3,
a: 5,
b: 2,
d1: 2,
a1: 7,
b1: 1,
};
let b = a; let c = a;
assert_eq!(a, b);
assert_eq!(a, c);
assert_ne!(
a,
LtTuple {
d: 0,
a: 0,
b: 0,
d1: 0,
a1: 0,
b1: 0
}
);
let dbg = format!("{a:?}");
assert!(dbg.contains("LtTuple"));
}
const SYSTEMATIC_INDEX_TABLE_K256: &[(u32, u16, u16, u8, u32)] = &[
(10, 254, 7, 10, 17), (12, 630, 7, 10, 19), (18, 682, 11, 10, 29), (20, 293, 11, 10, 31), (26, 80, 11, 10, 37), (30, 566, 11, 10, 41), (32, 860, 11, 10, 43), (36, 267, 11, 10, 47), (42, 822, 11, 10, 53), (46, 506, 13, 10, 59), (48, 589, 13, 10, 61), (49, 87, 13, 10, 61), (55, 520, 13, 10, 67), (60, 159, 13, 10, 71), (62, 235, 13, 10, 73), (69, 157, 13, 10, 79), (75, 502, 17, 10, 89), (84, 334, 17, 10, 97), (88, 583, 17, 10, 101), (91, 66, 17, 10, 103), (95, 352, 17, 10, 107), (97, 365, 17, 10, 109), (101, 562, 17, 10, 113), (114, 5, 19, 10, 127), (119, 603, 19, 10, 131), (125, 721, 19, 10, 137), (127, 28, 19, 10, 139), (138, 660, 19, 10, 149), (140, 829, 19, 10, 151), (149, 900, 23, 10, 163), (153, 930, 23, 10, 167), (160, 814, 23, 10, 173), (166, 661, 23, 10, 179), (168, 693, 23, 10, 181), (179, 780, 23, 10, 191), (181, 605, 23, 10, 193), (185, 551, 23, 10, 197), (187, 777, 23, 10, 199), (200, 491, 23, 10, 211), (213, 396, 23, 10, 223), (217, 764, 29, 10, 233), (225, 843, 29, 10, 241), (236, 646, 29, 10, 251), (242, 557, 29, 10, 257), (248, 608, 29, 10, 263), (257, 265, 29, 10, 271), ];
const GOLDEN_TUPLE_VECTORS: &[(usize, u32, LtTuple)] = &[
(
10,
0,
LtTuple {
d: 2,
a: 4,
b: 9,
d1: 2,
a1: 5,
b1: 1,
},
),
(
10,
1,
LtTuple {
d: 7,
a: 6,
b: 12,
d1: 2,
a1: 1,
b1: 3,
},
),
(
10,
100,
LtTuple {
d: 2,
a: 13,
b: 10,
d1: 2,
a1: 8,
b1: 5,
},
),
(
20,
0,
LtTuple {
d: 11,
a: 15,
b: 10,
d1: 2,
a1: 5,
b1: 1,
},
),
(
20,
1,
LtTuple {
d: 2,
a: 16,
b: 27,
d1: 3,
a1: 1,
b1: 3,
},
),
(
20,
500,
LtTuple {
d: 9,
a: 16,
b: 5,
d1: 2,
a1: 5,
b1: 10,
},
),
(
50,
0,
LtTuple {
d: 8,
a: 62,
b: 20,
d1: 2,
a1: 5,
b1: 1,
},
),
(
50,
1,
LtTuple {
d: 2,
a: 9,
b: 22,
d1: 3,
a1: 1,
b1: 3,
},
),
(
50,
1000,
LtTuple {
d: 2,
a: 38,
b: 24,
d1: 2,
a1: 6,
b1: 7,
},
),
(
100,
0,
LtTuple {
d: 2,
a: 30,
b: 4,
d1: 2,
a1: 5,
b1: 12,
},
),
(
100,
1,
LtTuple {
d: 2,
a: 16,
b: 96,
d1: 3,
a1: 9,
b1: 16,
},
),
(
100,
5000,
LtTuple {
d: 13,
a: 92,
b: 111,
d1: 2,
a1: 13,
b1: 15,
},
),
(
200,
0,
LtTuple {
d: 2,
a: 209,
b: 205,
d1: 2,
a1: 9,
b1: 10,
},
),
(
200,
1,
LtTuple {
d: 2,
a: 26,
b: 58,
d1: 3,
a1: 1,
b1: 16,
},
),
(
200,
10000,
LtTuple {
d: 4,
a: 38,
b: 109,
d1: 2,
a1: 8,
b1: 19,
},
),
(
256,
0,
LtTuple {
d: 2,
a: 204,
b: 197,
d1: 2,
a1: 13,
b1: 10,
},
),
(
256,
1,
LtTuple {
d: 2,
a: 66,
b: 60,
d1: 3,
a1: 17,
b1: 8,
},
),
(
256,
50000,
LtTuple {
d: 2,
a: 190,
b: 235,
d1: 2,
a1: 21,
b1: 11,
},
),
];
#[test]
fn tuple_generator_conformance_golden_vectors() {
for &(k, esi, expected) in GOLDEN_TUPLE_VECTORS {
let params = crate::raptorq::systematic::SystematicParams::for_source_block(k, 1024);
let p1 = next_prime_ge(params.l - params.w).expect("params P1 must fit");
let actual = tuple(params.j, params.w, params.l - params.w, p1, esi);
assert_eq!(
actual,
expected,
"Tuple mismatch for K={k}, ESI={esi}: \
expected {expected:?}, got {actual:?} \
(J={}, W={}, P={}, P1={p1})",
params.j,
params.w,
params.l - params.w
);
}
}
#[test]
fn tuple_generator_determinism_across_all_k256_values() {
for &(k_prime, j, s, h, w) in SYSTEMATIC_INDEX_TABLE_K256 {
let l = k_prime + u32::from(s) + u32::from(h);
let p = l - w;
let p1 = next_prime_ge(p as usize).expect("table P1 must fit") as u32;
for esi in [0u32, 1, 100, 1000, 65535] {
let tuple1 = tuple(j as usize, w as usize, p as usize, p1 as usize, esi);
let tuple2 = tuple(j as usize, w as usize, p as usize, p1 as usize, esi);
assert_eq!(
tuple1, tuple2,
"Non-deterministic tuple generation for K'={k_prime}, J={j}, W={w}, P={p}, P1={p1}, ESI={esi}"
);
assert!(
(1..=30).contains(&tuple1.d),
"LT degree d={} out of range [1,30] for K'={k_prime}",
tuple1.d
);
assert!(
tuple1.a >= 1 && tuple1.a < w as usize,
"LT step a={} out of range [1,{}) for K'={k_prime}",
tuple1.a,
w
);
assert!(
tuple1.b < w as usize,
"LT start b={} >= W={} for K'={k_prime}",
tuple1.b,
w
);
assert!(
[2, 3].contains(&tuple1.d1),
"PI degree d1={} not in {{2,3}} for K'={k_prime}",
tuple1.d1
);
assert!(
tuple1.a1 >= 1 && tuple1.a1 < p1 as usize,
"PI step a1={} out of range [1,{}) for K'={k_prime}",
tuple1.a1,
p1
);
assert!(
tuple1.b1 < p1 as usize,
"PI start b1={} >= P1={} for K'={k_prime}",
tuple1.b1,
p1
);
}
}
}
#[test]
fn tuple_generator_byte_identical_reference_vectors() {
let reference_cases = [
(
10,
0,
vec![
2, 0, 0, 0, 4, 0, 0, 0, 9, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0,
],
),
(
10,
1,
vec![
7, 0, 0, 0, 6, 0, 0, 0, 12, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0,
],
),
(
50,
0,
vec![
8, 0, 0, 0, 62, 0, 0, 0, 20, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0,
],
),
(
100,
0,
vec![
2, 0, 0, 0, 30, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 12, 0, 0, 0,
],
),
];
for &(k, esi, ref expected_bytes) in &reference_cases {
let params = crate::raptorq::systematic::SystematicParams::for_source_block(k, 1024);
let p1 = next_prime_ge(params.l - params.w).expect("params P1 must fit");
let actual_tuple = tuple(params.j, params.w, params.l - params.w, p1, esi);
let actual_bytes = tuple_to_bytes(&actual_tuple);
assert_eq!(
actual_bytes, *expected_bytes,
"Byte-level tuple mismatch for K={k}, ESI={esi}: \
expected {:?}, got {:?}",
expected_bytes, actual_bytes
);
}
}
#[test]
fn tuple_generator_cross_esi_uniqueness() {
for &(k_prime, j, s, h, w) in SYSTEMATIC_INDEX_TABLE_K256.iter().take(10) {
let l = k_prime + u32::from(s) + u32::from(h);
let p = l - w;
let p1 = next_prime_ge(p as usize).expect("table P1 must fit");
let mut seen_tuples = std::collections::HashSet::new();
for esi in 0..100 {
let tuple_result = tuple(j as usize, w as usize, p as usize, p1, esi);
let serialized = (
tuple_result.d,
tuple_result.a,
tuple_result.b,
tuple_result.d1,
tuple_result.a1,
tuple_result.b1,
);
assert!(
seen_tuples.insert(serialized),
"Duplicate tuple generated for K'={k_prime}, ESI={esi}: {tuple_result:?}"
);
}
}
}
#[test]
fn tuple_generator_systematic_index_coverage() {
let mut tested_k_primes = std::collections::HashSet::new();
for &(k_prime, j, s, h, w) in SYSTEMATIC_INDEX_TABLE_K256 {
tested_k_primes.insert(k_prime);
let l = k_prime + u32::from(s) + u32::from(h);
let p = l - w;
let p1 = next_prime_ge(p as usize).expect("table P1 must fit");
let result = tuple(j as usize, w as usize, p as usize, p1, 42);
assert!(result.d > 0, "Invalid LT degree for K'={k_prime}");
assert!(result.d1 > 0, "Invalid PI degree for K'={k_prime}");
assert!(
result.a > 0 && result.a < w as usize,
"Invalid LT step for K'={k_prime}"
);
assert!(
result.a1 > 0 && result.a1 < p1,
"Invalid PI step for K'={k_prime}"
);
assert!(result.b < w as usize, "Invalid LT start for K'={k_prime}");
assert!(result.b1 < p1, "Invalid PI start for K'={k_prime}");
}
assert!(
tested_k_primes.len() >= 40,
"Should test at least 40 different K' values for K ≤ 256"
);
assert!(
tested_k_primes.contains(&10),
"Should include smallest K' value"
);
assert!(
tested_k_primes.contains(&257),
"Should include K'=257 for K=256"
);
}
fn tuple_to_bytes(tuple: &LtTuple) -> Vec<u8> {
let mut bytes = Vec::with_capacity(24); bytes.extend_from_slice(&(tuple.d as u32).to_le_bytes());
bytes.extend_from_slice(&(tuple.a as u32).to_le_bytes());
bytes.extend_from_slice(&(tuple.b as u32).to_le_bytes());
bytes.extend_from_slice(&(tuple.d1 as u32).to_le_bytes());
bytes.extend_from_slice(&(tuple.a1 as u32).to_le_bytes());
bytes.extend_from_slice(&(tuple.b1 as u32).to_le_bytes());
bytes
}
#[test]
fn try_tuple_rejects_malformed_inputs() {
assert!(try_tuple(0, 0, 1, 2, 0).is_none());
assert!(try_tuple(0, 1, 1, 2, 0).is_none());
assert!(try_tuple(0, 4, 0, 2, 0).is_none());
assert!(try_tuple(0, 4, 1, 0, 0).is_none());
assert!(try_tuple(0, 4, 1, 1, 0).is_none());
assert!(try_tuple(0, 4, 4, 7, 0).is_none()); }
#[test]
fn tuple_returns_zero_sentinel_on_malformed_inputs() {
let sentinel = tuple(0, 0, 1, 2, 0);
assert_eq!(sentinel, LtTuple::default());
assert_eq!(sentinel.d, 0);
assert_eq!(sentinel.d1, 0);
let indices = tuple_indices(sentinel, 4, 1, 2);
assert!(
indices.is_empty(),
"tuple_indices must reject zero sentinel"
);
}
#[test]
fn try_tuple_matches_tuple_on_valid_input() {
let from_panic = tuple(5, 32, 7, 7, 42);
let from_try = try_tuple(5, 32, 7, 7, 42).expect("valid input must succeed");
assert_eq!(from_panic, from_try);
}
#[test]
fn rfc6330_section_4_4_systematic_encoder_reference_vector() {
use crate::config::EncodingConfig;
use crate::encoding::{EncodedSymbol, EncodingPipeline};
use crate::types::{
ObjectId,
resource::{PoolConfig, SymbolPool},
};
struct Rfc6330ReferenceVector {
test_id: &'static str,
requirement_level: &'static str,
source_data: &'static [u8],
symbol_size: u16,
expected_k: usize,
systematic_property: bool,
}
let test_vector = Rfc6330ReferenceVector {
test_id: "RFC6330-4.4-1",
requirement_level: "MUST",
source_data: &[
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, ],
symbol_size: 4, expected_k: 4, systematic_property: true,
};
let config = EncodingConfig {
repair_overhead: 1.5,
max_block_size: 64,
symbol_size: test_vector.symbol_size,
encoding_parallelism: 1,
decoding_parallelism: 1,
};
let mut pipeline = EncodingPipeline::new(config, SymbolPool::new(PoolConfig::default()));
let object_id = ObjectId::new_for_test(0x4444);
let symbols: Vec<EncodedSymbol> = pipeline
.encode(object_id, test_vector.source_data)
.collect::<Result<Vec<_>, _>>()
.expect("encoding should succeed for valid input");
assert!(
symbols.len() >= test_vector.expected_k,
"{} {} produce at least K={} source symbols, got {}",
test_vector.test_id,
test_vector.requirement_level,
test_vector.expected_k,
symbols.len()
);
assert!(
test_vector.systematic_property,
"{} {} requires the encoder to preserve the systematic property",
test_vector.test_id, test_vector.requirement_level
);
for (i, symbol) in symbols.iter().take(test_vector.expected_k).enumerate() {
let esi = symbol.id().esi();
assert!(
esi < test_vector.expected_k as u32,
"RFC6330-4.4-1: First {} symbols must be source symbols (ESI < K), \
but symbol {} has ESI {} >= K={}",
test_vector.expected_k,
i,
esi,
test_vector.expected_k
);
let expected_start = i * test_vector.symbol_size as usize;
let expected_end = expected_start + test_vector.symbol_size as usize;
let expected_symbol_data = &test_vector.source_data[expected_start..expected_end];
assert_eq!(
symbol.symbol().data(),
expected_symbol_data,
"RFC6330-4.4-1: Source symbol {} data must match original input exactly.\n\
Expected: {:02x?}\n\
Actual: {:02x?}\n\
Test vector: {} ({})",
i,
expected_symbol_data,
symbol.symbol().data(),
test_vector.test_id,
test_vector.requirement_level
);
}
for (i, symbol) in symbols.iter().take(test_vector.expected_k).enumerate() {
let expected_esi = i as u32;
let actual_esi = symbol.id().esi();
assert_eq!(
actual_esi, expected_esi,
"RFC6330-4.4-1: Source symbols must appear in order. \
Symbol at position {} must have ESI {}, but has ESI {}",
i, expected_esi, actual_esi
);
}
let expected_sbn = symbols[0].id().sbn();
for (i, symbol) in symbols.iter().enumerate() {
assert_eq!(
symbol.id().sbn(),
expected_sbn,
"RFC6330-4.4-1: All symbols must have consistent Source Block Number. \
Symbol {} has SBN {}, expected SBN {}",
i,
symbol.id().sbn(),
expected_sbn
);
}
println!(
"RFC 6330 §4.4 CONFORMANCE: ✅ PASS - {} ({}) systematic_property={} and \
the encoder correctly produces {} source symbols as first K encoding symbols",
test_vector.test_id,
test_vector.requirement_level,
test_vector.systematic_property,
test_vector.expected_k
);
}
#[test]
fn rfc6330_section_5_systematic_encoder_parameter_conformance() {
use crate::config::EncodingConfig;
use crate::encoding::{EncodedSymbol, EncodingPipeline};
use crate::raptorq::systematic::SystematicParams;
use crate::types::{
ObjectId,
resource::{PoolConfig, SymbolPool},
};
struct Rfc6330Section5Vector {
test_id: &'static str,
requirement_level: &'static str,
k: usize,
symbol_size: u16,
source_data: Vec<u8>,
expected_k_prime: usize,
expected_j: usize,
expected_s: usize,
expected_h: usize,
}
let test_vector = Rfc6330Section5Vector {
test_id: "RFC6330-5.1-1",
requirement_level: "MUST",
k: 10, symbol_size: 8,
source_data: (0..80).collect(), expected_k_prime: 10,
expected_j: 254,
expected_s: 7,
expected_h: 10,
};
let params =
SystematicParams::try_for_source_block(test_vector.k, test_vector.symbol_size as usize)
.expect("RFC6330-5.1-1: Parameter derivation must succeed for supported K");
assert_eq!(
params.k, test_vector.k,
"{} {}: Source block size K must match input",
test_vector.test_id, test_vector.requirement_level
);
assert_eq!(
params.k_prime, test_vector.expected_k_prime,
"RFC6330-5.1-1: K' derivation must match RFC 6330 Table 2. \
Expected K'={}, actual K'={}",
test_vector.expected_k_prime, params.k_prime
);
assert_eq!(
params.j, test_vector.expected_j,
"RFC6330-5.1-1: J(K') derivation must match RFC 6330 Table 2. \
Expected J={}, actual J={}",
test_vector.expected_j, params.j
);
assert_eq!(
params.s, test_vector.expected_s,
"RFC6330-5.1-1: S parameter must match RFC 6330 Table 2. \
Expected S={}, actual S={}",
test_vector.expected_s, params.s
);
assert_eq!(
params.h, test_vector.expected_h,
"RFC6330-5.1-1: H parameter must match RFC 6330 Table 2. \
Expected H={}, actual H={}",
test_vector.expected_h, params.h
);
let expected_l = params.k_prime + params.s + params.h;
assert_eq!(
params.l, expected_l,
"RFC6330-5.1-1: L = K' + S + H relationship must hold. \
L={}, K'={}, S={}, H={}",
params.l, params.k_prime, params.s, params.h
);
assert!(
params.w <= params.l,
"RFC6330-5.1-1: W ≤ L constraint must hold. W={}, L={}",
params.w,
params.l
);
let expected_p = params.l - params.w;
assert_eq!(
params.p, expected_p,
"RFC6330-5.1-1: P = L - W relationship must hold. \
P={}, L={}, W={}",
params.p, params.l, params.w
);
let config = EncodingConfig {
repair_overhead: 1.5,
max_block_size: 64,
symbol_size: test_vector.symbol_size,
encoding_parallelism: 1,
decoding_parallelism: 1,
};
let mut pipeline = EncodingPipeline::new(config, SymbolPool::new(PoolConfig::default()));
let object_id = ObjectId::new_for_test(0x5555);
let symbols: Vec<EncodedSymbol> = pipeline
.encode(object_id, &test_vector.source_data)
.collect::<Result<Vec<_>, _>>()
.expect("RFC6330-5.1-1: Encoding with valid §5 parameters must succeed");
assert!(
symbols.len() >= test_vector.k,
"RFC6330-5.1-1: Must produce at least K={} symbols with §5 parameters",
test_vector.k
);
for (i, symbol) in symbols.iter().take(test_vector.k).enumerate() {
let esi = symbol.id().esi();
assert!(
esi < test_vector.k as u32,
"RFC6330-5.1-1: Source symbol ESI must be < K under §5 constraints. \
Symbol {} has ESI {} ≥ K={}",
i,
esi,
test_vector.k
);
let expected_start = i * test_vector.symbol_size as usize;
let expected_end = expected_start + test_vector.symbol_size as usize;
let expected_data = &test_vector.source_data[expected_start..expected_end];
assert_eq!(
symbol.symbol().data(),
expected_data,
"RFC6330-5.1-1: Systematic symbol {} data must match source under §5 encoding",
i
);
}
println!(
"RFC 6330 §5 CONFORMANCE: ✅ PASS - {} ({}) parameters conform to \
§5.3 table (K={}, K'={}, J={}, S={}, H={}, L={}) and preserve systematic property",
test_vector.test_id,
test_vector.requirement_level,
params.k,
params.k_prime,
params.j,
params.s,
params.h,
params.l
);
}
}