#[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, 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) -> usize {
const PRIME_SEARCH_OVERFLOW_MSG: &str =
"next_prime_ge overflow: no prime >= input fits in usize";
if n <= 2 {
return 2;
}
let mut candidate = if n.is_multiple_of(2) {
n.checked_add(1)
.unwrap_or_else(|| panic!("{PRIME_SEARCH_OVERFLOW_MSG}: n={n}"))
} else {
n
};
while !is_prime(candidate) {
candidate = candidate
.checked_add(2)
.unwrap_or_else(|| panic!("{PRIME_SEARCH_OVERFLOW_MSG}: n={n}"));
}
candidate
}
fn require_rfc_u32(name: &str, value: usize) -> u32 {
u32::try_from(value).unwrap_or_else(|error| {
panic!("{name} must fit in u32 for RFC 6330 tuple arithmetic (got {value}): {error}")
})
}
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 {
assert!(lt_width > 1, "W must be > 1");
assert!(pi_count > 0, "P must be > 0");
assert!(pi_modulus > 1, "P1 must be > 1");
assert!(pi_modulus >= pi_count, "P1 must be >= P");
let expected_pi_modulus = next_prime_ge(pi_count);
assert!(
pi_modulus == expected_pi_modulus,
"P1 must equal smallest prime >= P (expected {expected_pi_modulus}, got {pi_modulus})"
);
let systematic_index_u32 = require_rfc_u32("J", systematic_index);
let lt_width_u32 = require_rfc_u32("W", lt_width);
let pi_modulus_u32 = require_rfc_u32("P1", pi_modulus);
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);
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;
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) -> LtTuple {
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 pi_modulus = next_prime_ge(pi_count);
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> {
assert!(w > 1, "W must be > 1");
assert!(p > 0, "P must be > 0");
assert!(p1 >= p, "P1 must be >= P");
let expected_p1 = next_prime_ge(p);
assert!(
p1 == expected_p1,
"P1 must equal smallest prime >= P (expected {expected_p1}, got {p1})"
);
assert!(
(1..=RFC6330_MAX_LT_DEGREE).contains(&tuple.d),
"LT degree must satisfy 1 <= d <= {RFC6330_MAX_LT_DEGREE}"
);
assert!(
matches!(tuple.d1, 2 | 3),
"PI degree must satisfy d1 in {{2, 3}}"
);
assert!(
tuple.a > 0 && tuple.a < w,
"LT step must satisfy 1 <= a < W"
);
assert!(
tuple.a1 > 0 && tuple.a1 < p1,
"PI step must satisfy 1 <= a1 < P1"
);
assert!(tuple.b < w, "LT start must be < W");
assert!(tuple.b1 < p1, "PI start must be < P1");
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 {
use super::*;
#[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 next_prime_ge_basic() {
assert_eq!(next_prime_ge(1), 2);
assert_eq!(next_prime_ge(2), 2);
assert_eq!(next_prime_ge(3), 3);
assert_eq!(next_prime_ge(4), 5);
assert_eq!(next_prime_ge(17), 17);
assert_eq!(next_prime_ge(18), 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_err(),
"next_prime_ge({n}) should fail closed instead of wrapping"
);
}
}
#[test]
fn tuple_deterministic_and_bounded() {
let p1 = next_prime_ge(17);
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);
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_rejects_non_canonical_prime_p1() {
let result = std::panic::catch_unwind(|| tuple(254, 17, 10, 13, 0));
assert!(
result.is_err(),
"tuple should reject larger prime P1 values that drift from RFC 6330"
);
}
#[test]
fn tuple_rejects_composite_p1() {
let result = std::panic::catch_unwind(|| tuple(254, 17, 10, 12, 0));
assert!(
result.is_err(),
"tuple should reject composite P1 values instead of silently drifting"
);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn tuple_rejects_oversized_width_before_u32_truncation() {
let too_wide = (u32::MAX as usize) + 1;
let result = std::panic::catch_unwind(|| tuple(5, too_wide, 17, 17, 0));
assert!(
result.is_err(),
"tuple should fail closed when W exceeds RFC 6330 u32 arithmetic"
);
}
#[cfg(target_pointer_width = "64")]
#[test]
fn tuple_rejects_oversized_systematic_index_before_u32_truncation() {
let too_large_j = (u32::MAX as usize) + 1;
let result = std::panic::catch_unwind(|| tuple(too_large_j, 17, 10, 11, 0));
assert!(
result.is_err(),
"tuple should fail closed when J exceeds RFC 6330 u32 arithmetic"
);
}
#[test]
fn tuple_indices_reject_non_canonical_p1() {
let result = std::panic::catch_unwind(|| {
tuple_indices(
LtTuple {
d: 2,
a: 4,
b: 9,
d1: 2,
a1: 5,
b1: 1,
},
17,
10,
13,
)
});
assert!(
result.is_err(),
"tuple_indices should reject non-canonical P1 values"
);
}
fn assert_tuple_indices_rejects_invalid_tuple(tuple: LtTuple, message: &str) {
let result = std::panic::catch_unwind(|| tuple_indices(tuple, 17, 10, 11));
assert!(result.is_err(), "{message}");
}
#[test]
fn tuple_indices_reject_zero_degrees() {
assert_tuple_indices_rejects_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_rejects_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_rejects_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_rejects_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_rejects_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_rejects_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);
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);
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: 3,
a: 10,
b: 5,
d1: 2,
a1: 5,
b1: 1,
},
),
(
10,
1,
LtTuple {
d: 3,
a: 5,
b: 3,
d1: 2,
a1: 3,
b1: 2,
},
),
(
10,
100,
LtTuple {
d: 3,
a: 11,
b: 16,
d1: 2,
a1: 9,
b1: 8,
},
),
(
20,
0,
LtTuple {
d: 3,
a: 27,
b: 22,
d1: 2,
a1: 5,
b1: 1,
},
),
(
20,
1,
LtTuple {
d: 3,
a: 22,
b: 20,
d1: 2,
a1: 3,
b1: 2,
},
),
(
20,
500,
LtTuple {
d: 6,
a: 4,
b: 23,
d1: 2,
a1: 2,
b1: 5,
},
),
(
50,
0,
LtTuple {
d: 3,
a: 18,
b: 31,
d1: 2,
a1: 5,
b1: 1,
},
),
(
50,
1,
LtTuple {
d: 3,
a: 13,
b: 29,
d1: 2,
a1: 3,
b1: 2,
},
),
(
50,
1000,
LtTuple {
d: 6,
a: 50,
b: 42,
d1: 2,
a1: 4,
b1: 10,
},
),
(
100,
0,
LtTuple {
d: 3,
a: 91,
b: 74,
d1: 2,
a1: 8,
b1: 1,
},
),
(
100,
1,
LtTuple {
d: 3,
a: 86,
b: 72,
d1: 2,
a1: 6,
b1: 2,
},
),
(
100,
5000,
LtTuple {
d: 11,
a: 20,
b: 104,
d1: 3,
a1: 14,
b1: 12,
},
),
(
200,
0,
LtTuple {
d: 3,
a: 154,
b: 127,
d1: 2,
a1: 11,
b1: 1,
},
),
(
200,
1,
LtTuple {
d: 3,
a: 149,
b: 125,
d1: 2,
a1: 9,
b1: 2,
},
),
(
200,
10000,
LtTuple {
d: 18,
a: 63,
b: 165,
d1: 3,
a1: 7,
b1: 19,
},
),
(
256,
0,
LtTuple {
d: 3,
a: 196,
b: 162,
d1: 2,
a1: 14,
b1: 1,
},
),
(
256,
1,
LtTuple {
d: 3,
a: 191,
b: 160,
d1: 2,
a1: 12,
b1: 2,
},
),
(
256,
50000,
LtTuple {
d: 29,
a: 226,
b: 33,
d1: 3,
a1: 18,
b1: 7,
},
),
];
#[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);
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) 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![
3, 0, 0, 0, 10, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0,
],
),
(
10,
1,
vec![
3, 0, 0, 0, 5, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0,
],
),
(
50,
0,
vec![
3, 0, 0, 0, 18, 0, 0, 0, 31, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0,
],
),
(
100,
0,
vec![
3, 0, 0, 0, 91, 0, 0, 0, 74, 0, 0, 0, 2, 0, 0, 0, 8, 0, 0, 0, 1, 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);
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);
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);
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
}
}