diff --git a/packages/cultnet-rs/Cargo.toml b/packages/cultnet-rs/Cargo.toml index b1cd8d11..f87af6cb 100644 --- a/packages/cultnet-rs/Cargo.toml +++ b/packages/cultnet-rs/Cargo.toml @@ -19,7 +19,6 @@ cultcache-rs = { path = "../cultcache-rs" } ed25519-dalek = "2.2" hmac = "0.12" rand = "0.9" -reed-solomon-erasure = "6" rmp-serde = "1.3" rmpv = { version = "1.3", features = ["with-serde"] } serde = { version = "1", features = ["derive"] } diff --git a/packages/cultnet-rs/src/media_fec.rs b/packages/cultnet-rs/src/media_fec.rs index 490eaaed..38525db5 100644 --- a/packages/cultnet-rs/src/media_fec.rs +++ b/packages/cultnet-rs/src/media_fec.rs @@ -14,11 +14,11 @@ //! the `k + m` shards recover the block (the code is MDS), so any `m` erasures //! are repaired and any more are reported as [`MediaFecError::BeyondRepair`], //! never as wrong bytes. The construction is named on every parity record by -//! [`MEDIA_FEC_SCHEME_RS_GF256_V1`] and backed by the `reed-solomon-erasure` -//! crate. The library is not the contract: the construction below is, and -//! `tests/media_fec.rs` pins the parity bytes of known inputs, so a library that -//! changes the matrix fails there instead of on the wire, and a decoder in -//! another runtime has vectors to meet. +//! [`MEDIA_FEC_SCHEME_RS_GF256_V1`] and implemented in this module, from the +//! text below. The construction is the contract: `tests/media_fec.rs` and +//! `tests/fixtures/media_fec_rs_gf256_v1.kat` pin the parity bytes of known +//! inputs, so a change to the math fails there instead of on the wire, and a +//! decoder in another runtime has vectors to meet. //! //! # The `rs-gf256-v1` construction //! @@ -42,8 +42,10 @@ //! * Any `k` surviving shards recover the block: invert the `k x k` submatrix //! of `E` for the surviving rows and multiply. //! -//! The known-answer vectors in `tests/media_fec.rs` were checked against an -//! independent implementation of exactly this text. +//! The known-answer vectors were produced by `reed-solomon-erasure` 6.0.0's +//! `galois_8` field, which builds exactly this matrix, before this module +//! replaced it; the smaller ones in `tests/media_fec.rs` were also checked +//! against a second, independent implementation of this text. //! //! # What a block is //! @@ -88,8 +90,6 @@ use std::collections::BTreeMap; use std::fmt; -use reed_solomon_erasure::galois_8::ReedSolomon; - use crate::media_stream_contracts::{ GameCultMediaAudioPacketRecord, GameCultMediaAudioParityShardRecord, GameCultMediaVideoAccessUnitRecord, GameCultMediaVideoParityShardRecord, @@ -102,8 +102,8 @@ use crate::media_stream_wire::{ use crate::rudp::RUDP_FIXED_HEADER_BYTES; /// Names the construction on every parity record: systematic Reed-Solomon over -/// GF(2^8), as built by `reed-solomon-erasure`'s `galois_8` field. A different -/// matrix is a different scheme id. +/// GF(2^8), as the module docs state it. A different matrix is a different +/// scheme id. pub const MEDIA_FEC_SCHEME_RS_GF256_V1: &str = "rs-gf256-v1"; /// The largest UDP payload an IPv4 datagram carries on a 1500-byte MTU. @@ -237,40 +237,165 @@ impl fmt::Display for MediaFecError { impl std::error::Error for MediaFecError {} // --------------------------------------------------------------------------- -// The code: equal-length shards in, equal-length shards out. +// The code: rs-gf256-v1 exactly as the module docs state it. Equal-length +// shards in, equal-length shards out; every caller has already bounded a block +// to 1..=256 shards of one validated length, so nothing here allocates more +// than the block's shards and three `k x k` matrices. // --------------------------------------------------------------------------- -fn parity_shards(data: &[Vec], parity_count: usize) -> Result>, MediaFecError> { - let code = ReedSolomon::new(data.len(), parity_count) - .map_err(|error| MediaFecError::invalid(format!("{error:?}")))?; - let mut parity = vec![vec![0_u8; data[0].len()]; parity_count]; - code.encode_sep(data, &mut parity) - .map_err(|error| MediaFecError::invalid(format!("{error:?}")))?; - Ok(parity) +/// `x^8 + x^4 + x^3 + x^2 + 1`, the polynomial the field is reduced by. +const FIELD_POLYNOMIAL: u16 = 0x11D; + +/// `EXP[i]` is `2^i`, written out over two periods so the sum of two logarithms +/// indexes it without reduction; `LOG` is its inverse on the non-zero +/// elements. `2` generates the multiplicative group of GF(2^8) under +/// [`FIELD_POLYNOMIAL`], so the tables cover the field. +const FIELD_TABLES: ([u8; 510], [u8; 256]) = { + let mut exp = [0_u8; 510]; + let mut log = [0_u8; 256]; + let mut power: u16 = 1; + let mut i = 0; + while i < 255 { + exp[i] = power as u8; + exp[i + 255] = power as u8; + log[power as usize] = i as u8; + power <<= 1; + if power & 0x100 != 0 { + power ^= FIELD_POLYNOMIAL; + } + i += 1; + } + (exp, log) +}; + +fn gf_mul(a: u8, b: u8) -> u8 { + let (exp, log) = &FIELD_TABLES; + if a == 0 || b == 0 { + return 0; + } + exp[usize::from(log[usize::from(a)]) + usize::from(log[usize::from(b)])] +} + +/// `1 / a` for non-zero `a`. +fn gf_reciprocal(a: u8) -> u8 { + let (exp, log) = &FIELD_TABLES; + exp[255 - usize::from(log[usize::from(a)])] +} + +/// Row `r` of the Vandermonde matrix `V`: `r^0, r^1, .., r^(k-1)`, the row +/// number read as a field element, with `0^0 = 1`. +fn vandermonde_row(r: usize, k: usize) -> Vec { + let point = r as u8; + let mut row = Vec::with_capacity(k); + let mut power = 1_u8; + for _ in 0..k { + row.push(power); + power = gf_mul(power, point); + } + row +} + +/// The inverse of a square matrix, by Gauss-Jordan elimination. +fn invert(mut matrix: Vec>) -> Vec> { + let n = matrix.len(); + let mut inverse: Vec> = + (0..n).map(|r| (0..n).map(|c| u8::from(r == c)).collect()).collect(); + for col in 0..n { + let pivot = (col..n) + .find(|&r| matrix[r][col] != 0) + .expect("rs-gf256-v1 only inverts matrices whose evaluation points are distinct"); + matrix.swap(col, pivot); + inverse.swap(col, pivot); + let scale = gf_reciprocal(matrix[col][col]); + for c in 0..n { + matrix[col][c] = gf_mul(scale, matrix[col][c]); + inverse[col][c] = gf_mul(scale, inverse[col][c]); + } + let (pivot_row, pivot_inverse) = (matrix[col].clone(), inverse[col].clone()); + for r in (0..n).filter(|&r| r != col) { + let factor = matrix[r][col]; + for c in 0..n { + matrix[r][c] ^= gf_mul(factor, pivot_row[c]); + inverse[r][c] ^= gf_mul(factor, pivot_inverse[c]); + } + } + } + inverse +} + +/// The given rows of the encoding matrix `E = V * inverse(top)` for a block of +/// `k` data shards, where `top` is the first `k` rows of `V`. +fn encoding_rows(k: usize, rows: impl Iterator) -> Vec> { + let top_inverse = invert((0..k).map(|r| vandermonde_row(r, k)).collect()); + rows.map(|r| { + let v = vandermonde_row(r, k); + (0..k) + .map(|c| (0..k).fold(0, |sum, i| sum ^ gf_mul(v[i], top_inverse[i][c]))) + .collect() + }) + .collect() +} + +/// Byte `j` of the result is the sum over `i` of `coefficients[i] * shards[i][j]`. +/// +/// The hot loop of both encode and recovery: each coefficient's 256 products +/// are tabulated once, so a shard byte costs one lookup and one XOR. +fn combine(coefficients: &[u8], shards: &[&[u8]], shard_bytes: usize) -> Vec { + let mut out = vec![0_u8; shard_bytes]; + for (&coefficient, shard) in coefficients.iter().zip(shards) { + match coefficient { + 0 => {} + 1 => { + for (byte, &input) in out.iter_mut().zip(shard.iter()) { + *byte ^= input; + } + } + _ => { + let products: [u8; 256] = std::array::from_fn(|x| gf_mul(coefficient, x as u8)); + for (byte, &input) in out.iter_mut().zip(shard.iter()) { + *byte ^= products[usize::from(input)]; + } + } + } + } + out +} + +/// The `parity_count` parity shards of `data`: rows `k..k + m` of `E`. +fn parity_shards(data: &[Vec], parity_count: usize) -> Vec> { + let k = data.len(); + let inputs: Vec<&[u8]> = data.iter().map(Vec::as_slice).collect(); + encoding_rows(k, k..k + parity_count) + .iter() + .map(|row| combine(row, &inputs, data[0].len())) + .collect() } /// `shards` holds the block's `k` data slots then `m` parity slots, `None` for -/// each missing. Returns the `k` data shards. +/// each missing. Returns the `k` data shards, recovered from the first `k` +/// present shards through the inverse of their rows of `E`. fn recover_data_shards( data_shards: usize, - mut shards: Vec>>, + shards: Vec>>, ) -> Result>, MediaFecError> { - let parity_shards = shards.len() - data_shards; - let present = shards.iter().flatten().count(); - if present < data_shards { + let present: Vec = (0..shards.len()) + .filter(|&slot| shards[slot].is_some()) + .take(data_shards) + .collect(); + if present.len() < data_shards { return Err(MediaFecError::BeyondRepair { - shards_present: present, + shards_present: present.len(), shards_needed: data_shards, }); } - let code = ReedSolomon::new(data_shards, parity_shards) - .map_err(|error| MediaFecError::invalid(format!("{error:?}")))?; - code.reconstruct_data(&mut shards) - .map_err(|error| MediaFecError::invalid(format!("{error:?}")))?; - Ok(shards - .into_iter() - .take(data_shards) - .map(|shard| shard.expect("reconstruct_data fills every data shard")) + let inputs: Vec<&[u8]> = present.iter().map(|&slot| shards[slot].as_deref().unwrap()).collect(); + let shard_bytes = inputs[0].len(); + let decode = invert(encoding_rows(data_shards, present.iter().copied())); + Ok((0..data_shards) + .map(|slot| match &shards[slot] { + Some(shard) => shard.clone(), + None => combine(&decode[slot], &inputs, shard_bytes), + }) .collect()) } @@ -393,7 +518,7 @@ pub fn protect_video_frame( .map(|chunk| padded(&chunk.payload, shard_bytes)) .collect(); let parity_count = policy.video_parity_shards(size); - let parity = parity_shards(&data, usize::from(parity_count))?; + let parity = parity_shards(&data, usize::from(parity_count)); let parity_records: Vec = parity .into_iter() @@ -634,7 +759,7 @@ pub fn protect_audio_block( let data: Vec> = packets.iter().map(|packet| packet.payload.clone()).collect(); let deadline_ticks = packets.iter().map(|packet| packet.deadline_ticks).max().unwrap_or(0); let parity: Vec = - parity_shards(&data, usize::from(policy.audio_parity_shards))? + parity_shards(&data, usize::from(policy.audio_parity_shards)) .into_iter() .enumerate() .map(|(parity_index, payload)| GameCultMediaAudioParityShardRecord { diff --git a/packages/cultnet-rs/tests/fixtures/media_fec_rs_gf256_v1.kat b/packages/cultnet-rs/tests/fixtures/media_fec_rs_gf256_v1.kat new file mode 100644 index 00000000..4df4617f --- /dev/null +++ b/packages/cultnet-rs/tests/fixtures/media_fec_rs_gf256_v1.kat @@ -0,0 +1,1322 @@ +# rs-gf256-v1 known answers for the CultNet media FEC codec. +# +# Produced by reed-solomon-erasure 6.0.0 (galois_8; encode_sep, then +# reconstruct_data, which returned the original data for every pattern below), +# through generate_rs_gf256_v1_known_answers in tests/media_fec.rs at CultLib +# commit 73bf4421, before cultnet-rs replaced the crate with its own +# implementation of the construction documented in src/media_fec.rs. +# If these bytes ever change, rs-gf256-v1 changed meaning: bump the scheme id. +# +# case a block of k data and m parity shards +# fill lcg: data shard i is bytes(k * 65536 + m * 256 + i, shard_bytes), where +# bytes(seed, n) starts state = seed * 2654435761 + 1 and n times sets +# state = state * 1664525 + 1013904223 and emits state >> 24, all +# arithmetic on unsigned 32-bit integers, wrapping. +# fill zero: every data byte is 0x00. fill ff: every data byte is 0xff. +# parity

parity shard p, lowercase hex +# erase a pattern of erased shards (0..k data, k..k+m parity); the +# shards left recover the erased data exactly +case 1 2 37 lcg +parity 0 1d76461ffe37cefddcf40fda2b204102be510c9f0d5207339f2a8a89c6c91c7d1c2e1e0c7f +parity 1 1d76461ffe37cefddcf40fda2b204102be510c9f0d5207339f2a8a89c6c91c7d1c2e1e0c7f +erase 0 +erase 0,1 +case 2 2 37 lcg +parity 0 dd18e10df0fc6c0fa9d8e5e86e4fd21a766207cfd40f5b1f846d30267007b52a7d947ab4f5 +parity 1 db201de79683235efa2a5e1150c2cb417ed32b0a58354c1dd1e80b93460ffa8200ec6c14c2 +erase 0,1 +erase 0,2 +erase 1,2 +case 3 2 37 lcg +parity 0 9f31dd8658475a0b0efce438c7f7591a0596afdc19fe90fce15355887c7de3ca42c39466a9 +parity 1 8db62234c2ba4073f6367b9738b0fb00ad4b5908c5c5482725cbbc1b83452c75f9dc380860 +erase 0,1 +erase 0,3 +erase 1,2 +erase 1,3 +case 4 2 37 lcg +parity 0 a8ee004fd095063619e7584588e5a31cc94e9820e3a5d2d55c36b1e570ce9f68556bf67028 +parity 1 98d97eef9511077e81e7a54752088dd5c122b9c9f2ab3dd78e98f1435ec6709f8f49edd0a3 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 5 2 37 lcg +parity 0 4cbfa04fe94c62ca970a747f837544ca91e95d8364637dcb67f43eb3f829f26edd276081e3 +parity 1 d8be8a2b22f6180407e961be1d6804df6eb56cd85e0e78d4a56b54aa4832a316a9c71fe1d1 +erase 0,1 +erase 0,5 +erase 2,5 +erase 3,4 +case 6 2 37 lcg +parity 0 d61fc5398802d8e9d450d52e1c03f37233b7c586fad4ea84a5a81f6f535ee5fbef8f12a068 +parity 1 ca4789f2758e3a32dd0131195eb49d293b2279d6830a0c9a5c39685cbd592a937042dac379 +erase 0,1 +erase 0,6 +erase 3,6 +erase 4,5 +case 7 2 37 lcg +parity 0 63f9bbbf4551c9c0155b03f6ca672253a3884cad31a3609a5293e5570d96039213b9b7d898 +parity 1 d833010b405b5ed00222ce40c128900dd93f0ecc8d9f6ee6e45000653463d7c3ae0d2deeae +erase 0,1 +erase 0,7 +erase 3,7 +erase 5,6 +case 8 2 37 lcg +parity 0 1f2644cdeef6330582964313ac2a88779168a371ce25e9a331a2feb0e926e80a3fb6dc956e +parity 1 07c99275ea440ffe50a30e803a3cc7b399ab77b35c1e394fd5830164d43b8863cc1128849b +erase 0,1 +erase 0,8 +erase 4,8 +erase 6,7 +case 9 3 37 lcg +parity 0 1783c9967a6b04b7747a8311d8d20945a84c966d9de43a32e8ab7542ae536455acee379d42 +parity 1 7daef83cb52401f06d8e470a46bbcadbafc1424629376b5a9125ef2c0587ffe9d84bad60c4 +parity 2 1faeacfdc3f9e3784c134f037fd1e21646d9fd81d0028752a00a4aae093797896fe4c700e6 +erase 0,1,2 +erase 0,4,9 +erase 4,9,10 +erase 6,7,8 +case 10 3 37 lcg +parity 0 62af6d8e84589baa9d526110950638f093f96b94a45b2d9c30b6a9745234574b9ead9e066f +parity 1 dc28ca8f0e824db76227dbaf40733617dbe74da368e0538121c11a60e82bc4e3b47c8fb74e +parity 2 792cad82f9efcf8246d704887e1929eb262662d402ad1a1105d191b8e8fa6511f901000350 +erase 0,1,2 +erase 0,5,10 +erase 5,10,11 +erase 7,8,9 +case 11 3 37 lcg +parity 0 54ff5670647c20b41f8ceb38e74267f6aaa30804c845d4900e44db8ca11c0f3f0751ff5eed +parity 1 8e3025f78568c3c891d514c143e94db1c9886f45c82af0229a8a1222f14ebf6e761a497780 +parity 2 5a35595594b8eafac3a78f9cd182c985ab079a03fd844c7bc1bc473add2e24b9e6aabb6107 +erase 0,1,2 +erase 0,5,11 +erase 5,11,12 +erase 8,9,10 +case 12 3 37 lcg +parity 0 46e7b0691080090c58b9665a5f1c02c559e279cbb817e8cef8d1a2614e96686fbdfb91a03f +parity 1 70ce7f47fe7e88d78c4e0e8958e93c8b0e5543a62d7c61d55959031e518333fb131342470a +parity 2 925e128717e460de9d9c3e5f1d8a0a98e56c203e74a8dc831262bfd648b79ba2566c80ea79 +erase 0,1,2 +erase 0,6,12 +erase 6,12,13 +erase 9,10,11 +case 13 4 37 lcg +parity 0 5dba6319228d9ffa4d4b314912b2f6463a8c1ff416a1307299b9a0994ee8e85ab12a8cf659 +parity 1 a473d1a714554131a8b900d9359fa176c2c2562f3ce23341c592e3fefa6fd3db019d8e77e8 +parity 2 485ac4a9c57938083530f17f2133f8bacadeb7b8f51540daf9266fc6a8a9b95dc781d58cb0 +parity 3 f2de139b1450bb18d36906a9883d94f5fcd1d50574654f467df2ebd5e7057f7e0edb001fc2 +erase 0,1,2,3 +erase 0,6,13,14 +erase 6,13,14,15 +erase 9,10,11,12 +case 14 4 37 lcg +parity 0 d6fc31195735a1ddf12dc46e1769d12101aa30c0775e05ebe21fcf6a741e3e80f0a2222eb6 +parity 1 f41477b7e559afc4bc64ee9094ea1582996327f11dc7f9c07e95456981810d8966810f8ee7 +parity 2 f48caa91c33fd3601dbf9b8d4c650811811b4ccee2bb874125319d8fee4352229ef5ae988b +parity 3 19a11e9ce28d3af19a6a896c3d59941aed6c97cbbf851bca8e710b4bb9354c667aa7e6a8be +erase 0,1,2,3 +erase 0,7,14,15 +erase 7,14,15,16 +erase 10,11,12,13 +case 15 4 37 lcg +parity 0 436fd02724e4f85321b35365f4e8fbf0b8e59172b59a05c0b133fee9efc201c802249660ef +parity 1 5bcc7fa05ccdcfe4056842ff288d85716bc68b61fdc4d00e60c1c828a1737c1e902fdef318 +parity 2 e426dba0f19e3460737f46901181a5bb36cd6e5428a8cb8e54c8e8869838eaa0f136ea659d +parity 3 7c3f1eb480e565aad5df965694b15d758e010f5f85df523585762922c16bd74569c9921b4c +erase 0,1,2,3 +erase 0,7,15,16 +erase 7,15,16,17 +erase 11,12,13,14 +case 16 4 37 lcg +parity 0 85f104a041c87405e95cc483c09d776675d5820ec5b2f06cb467f6603a6f6c28f82bb7e123 +parity 1 25c276c043503575a53501e07972c6fa4f61faad0602b1c106c825f32094d08a469bf5cc58 +parity 2 c1def77fb8d48ef70aa21ffdc7a53c19535c94f1d9d2a4f9418ba26038abbfb4ea681f4b9b +parity 3 cecdb160ca6d4f767f38aa79601a9b78123c1f6c57a032264146ff7ec479dd12fa9c05cf69 +erase 0,1,2,3 +erase 0,8,16,17 +erase 8,16,17,18 +erase 12,13,14,15 +case 4 2 1 lcg +parity 0 a8 +parity 1 98 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 4 2 37 lcg +parity 0 a8ee004fd095063619e7584588e5a31cc94e9820e3a5d2d55c36b1e570ce9f68556bf67028 +parity 1 98d97eef9511077e81e7a54752088dd5c122b9c9f2ab3dd78e98f1435ec6709f8f49edd0a3 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 4 2 1300 lcg +parity 0 a8ee004fd095063619e7584588e5a31cc94e9820e3a5d2d55c36b1e570ce9f68556bf670284a2589488913ff2c91729b64c6857dacddf1709c4c999d687804effc454640dc56ae2392086c72e5283b9fac105113741de8cca8453864789c22df919591a512eb999af9d6845cf933f15714ec42b35d62d2f57fd9de9edefaa91114ff1ff4abffb647030c6057d9c4c9106a75f6c4ab1063b7ba0d1501c85c1ca6abb77b7cb0cc55cfb5efb037017afeaa3baff4cb0ebfb3469dcd5c757c4ca73201f09d7c7dc660686f0d211188403ae0bdbfdf71da0013d3cd2e4c2f197ff34868eecad4e56c54f14752ab3088aadb26c696b8675ba561e4013abba81fccc430b510c45a13e0488891877cdd5fc9de193a534377b4eccd66b97f3368f8b5e1bf63e2518331940770718c1740e08c2a75f605973194057647d27263467d3f31ab4c7ebf3d7b9f02abff4a2c1d7a83896ff936b2c25cd7fd7b58b3967576e65cae819c71591f64df1d217dbbc866bf7dd7d10efc4f94eecf7b8cd0a8c6f27a9fcf32d97940f5d2accc653c883fa40d4d636e59531cd006766b489b893290a934ab7c1e2c8c61cfd0c218359d97d718b6ff90f5a2d86d6e1de3477f20f92f7893a80c7a5dd8e8f81e43e6f01734dce33676794d043257a63998fd92a4a6fa058f538e890a0e540f81b04bdc9449055427ed6b38da5f98b3a41f4996d796b2b9e0de32cfcfa9eee9ddacbc6bf714f446eb93e324b309c2375cb2e2c3290fd8e6d5ef1ed959fe35febb26c4d44edd13c568b1b827dd9a6e74fbd20061824b2647e612e3eb6eb2e73093b1f12c02fb3a0e03eea185d55b8f0f7657d73d209a99ca7da9fc52014a0278a01bf06fc7dbe3fa501fb1bddfdaeaec1284cb5f1d816160b677db1d69a3cd681680b459051f039417590660eaee8f551865928a50d6e18cc949bff156541a87834c396832a7d9df6dc92425c1b6184e41dffbfbb863826c8dcca2c45019a8f37b5e44e1b3920a177f224e32dc8f4038b37a05de36e8c2d1ceb2ea1efee16954b175b972d27adf19b4cf91a76595ebc7fa36fe4bd913e51c8787bdef2572273d21d4e3d65060a1bcf06a6cbf488c97d1e4e95ad4090be1ee99fdc03cb1055e562456a28025945f7b21ed1c058d2c754af83724da667313366877fe4e75f4be28716e7383db3e02854908fb5117bc94cd2dcaa6162212f5068aa314fe279932d9020be20120052467b75002abfc9552c71042ab5c276dd9fcc0fd5d6e69a3cbd1e23c2c802e7cd8c1ebfc085b4739eb0cb198f734f758d94f19c20c4bc59bf8995caf13a768cac10b9f8de6b0fa005b5c963735edcedc3da07fea83499bf65b6e375682aa7aa9dbf10360d76ff134560ace09f8460060fff0391de5348922653dc19891619720fab3dd46e4ef470b27e3aae767d516d100c5b315a308f3835cee0e6e8d96847ac29db8e3fa06d6bda07d37c6f9759c07cd4a04a72bdbe0ce25ab269778a8c8f2c08aec1401eb23d9df2474dbd53544e10667414f39339c154033a4c0ca44a6ab631c67ffd1ac1e52b42f03d6b2fecb4bd22a4c4e25c0722ed5111039ee32e55d3c5b84478851065b9b7c99034e90119a64b4d3c0992ab83a814bc661f6ea13b4694783857903eb31d3739858a729e652df22000ae17c6723af64607ef7323392d74e4bc647058d38b436aa3573149a296dbc9dab14ae415585822e6eaf63fe7f1e6a22b2280204d9a0bebd0bf68f80a057c42c81b48ca28494569b407d1b01c05b078a23bceba8aed30ccf6e192dfc53be1e6890a104261ceb3d13a5d4b1057dbaa8c2554f294ad1 +parity 1 98d97eef9511077e81e7a54752088dd5c122b9c9f2ab3dd78e98f1435ec6709f8f49edd0a3fa193eb71a66f7374036d453d4f639bcc7fb5f6c8b4b6932ee123eb6fbaec4bcdab7bd7b914beff31eb13dcf1c81d5553567b5fdbc838d38216762d8da05136081c250998a2f7abdf12adcf9b4268d58907113293eb1bf8d8f8f0c05833d7cf39cfd20129b951528c31849539c4607e88fb4c08b9c7b432e0ed6e3aa66742f96b9f9e0646f497380bae9ea946f87285a4c372b470dd3bbb8a52771461d42bbf9c5220e301988ac9f0bf944646977627486dd0fd66dbe1a2b63fe0a02e273c17874870e804d2fb2a2faef323058032d11ab7fc7a30f1f783da2c6f3e115e339c35e0997c268dd30aa8bb15705281537c7e85ce8022af05b31602eeadfa8135246ed91129a8901c21729f643f1f7814de50b7cc494ab1a9cb3bf73023d73bb85673fe5fc9a56a75e6fe9c31d53eaf7e175baa118eddf5446f7c0cf6ffd75f616b3d20f1f12b99ab0b78001b4b09fddc1ebda719858008b2991fd268bb1f18c24ea014025b47c1470a51b111d4e5aed12bc5de6e6e684d19d3d5d62d8f71bf61718654acad324f16b1128627fe6de15107960b9f6a1635da4b647c7e61fbe7adc7a91c1366e5a7cfb492d833cf7d6127d123154445ff19979908c3dbd4b0cabdf6e5406690c9479f4cde0cb0cd2d980442e2be28e176c29d841e261a5b3e45d9eba698b5a7f44b94bb5907b7007e00641f6ddfacabc5028c9a660c015ffd19e81b52c6732fb9cd89e1e0c2c577d890c3e048eb7f9524658298df43aa9e152f11fda1e2eda74ac34f0051ff12b6596e7ba4b5b1cf9057267fff889a918c71bcc50c1a6e09e6aa55a7478bf72c6c6954e0a746b0621fa1fae6f09535fe0a4991f3a2cec769a823130c0e950d4740c2577e6df7bcf330d305edf8e80af521afb5dd5c94cddb715f54fe38fb6ec0a570a9475ae9a6a3aa8aae36fb15711eb09ade6887f8d347532a5b158a8fedac6424c3133d1a9396d6e355964b398b8aea63e68bbed134f335b6280e9f6070be60099af4992b10eca6beaf400e876ec23729b6686b7da637b1fc80ff688f850a8b51d6a59881f7fb114318ab85f4537a5d63c1ac26480cfe1d6b41f10fd401f4a5def8dc4cb4312b740006998f0ed7766580eaec26f7b18cee4f69f7867aea8a9eacfa63200ffb9c7dfa3ddbd5fc612f2844d5592b004897e8c3d7c51670464b75c6feeddb8f8f18f9c1e2c5408807aff0351316f8a143b51b688bfc8f72fd693b192fc1aff4614fb2da557d217e7cda5dbe4725368c886c69a795c6d823ba2b9535c41f6ae4716a30d7948dbc8f65627946d577e3a282786485f5fa73ebfdc63f53321759e4681d53845dac77ebfced9c530dd15c520797fc9d5fc0fe1fb53eb04af1e86eaa0db2b080774e5c902628b3a3d6df445cfa17d06ec536e79fdebe144b61f3bbd269f690f0617f155f3f6792ded800f9ede44d0b77951b28676d9d9898ef250d67e9c0bd71880704b9b1d695b3dafa9d165de70a285e38ec927a93fe2ef4f0ac57c2a622073dca3c25740dd55f7437036505d207bdbbe156382d841741a94942447441be17d95c621bc51260f5c930f4727308cb7e1bf68611a275b5f8da545279323b8096678ba84a0ab805b3162996fb34de22e7fca19820a893effab1095862dedfd711ec413769cf84e93bf71397636efaaf16915f89d48f2ad4a5d7c473901cc350ab47554a303a04efc7ba02a7f593aa45da4bd66b70d45c0863ee9bb5048c894bedadc5528035efca372af3ccbbbcc20c1240a0b025c2e4ee0f5eba8 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 16 4 1300 lcg +parity 0 85f104a041c87405e95cc483c09d776675d5820ec5b2f06cb467f6603a6f6c28f82bb7e12333145d30cf956f6de66d56b8a76754f2b8d60748dae4bee952cfd9a52538e6c13e6fa87b831084edecf3b4e396c1cc7b8eb2abea93e54e63a65cd71d243e7f846cb23dc66df0ba6158bb1b8342a3fb4903083c0f7b29097c88a19a57a98aa959cce846d42ce6214ad3015e7dda73c6e15c1a9b302954ce997b31486be47de00707f9e164ef564d38a12c156205a8b838b11e403271be18a1c6749769e7752e492146dd0373d2d32c4fcc591d128836b6f7e88ac9ad7c5518f9ab56f76eee76fb4ac8e39d7cc7b9711f06e8543e36ecbc8b6b3a8febf0c0424a7a24644a958c989abc25630011444afecbce36b61cfc7098d56807aa31cdecf3ae7d481ea9f8780bb5f4205a936d4bd61409e13cea8db8e7e2c44fc8d9197a9c3131e4edfe9127f1057cce7e42003908410c4f8afbfea530eb4a995299a99b938457933c273b131ef8b7d09b48767e65f4602032e821356a8d779a0f890f7d773326fa3e2f9f79ef71db0690d0107fd6f73c546fc7a3e12f862f2285eb23c598cd5e0bf1086c1df709948f031fbb351ac9e9e77846eaa598ef6272366f0aa667e781accfeb6261225753d5cc8d20415922c8a8dcc5e89120db712a092b6d3722dd9024bb0bda72c83d08527e3db8c1211ebc729279f7026d9119b95f4acb3ca0d2cb4984b0a92265c756feb48c435ad438332b597620b7749b21f3c5aa2ae228fa07fb46f2c8ba9319078dffa9ee1dd4ba0f181d94e367c1596466355f76ac303acc3b58bed365361a8ddb706746fdf595bdfd4e53d0e6d24a37b416b92b09ec173c091f4587889527fb505003c5ad3cd6dba9f56fc163c2bbe859a4e99fa4484341461822fe998b2a8866ed93918a686dca1608b3b3126ed3dff8034d2aa9296949bee7048b76204a65e2d214a096f465b38cd69d59b5591815d6f70c54d904abace5bc1d0e6dc4adb0c5a75b0d3f417519b3921aa63216eb69caf8a41ad29c1f071d00b6ce968155b3053e4dc223670e8139bdad5edb9a2c6d2dd1e4bb8f12b1bb534bc5efb415df36b30a4f3c71152abfcdccd28fefab11bb857bfb777cefe8d7f6a91087f6a0d31b52f33b4e66a8177fa0349d5fdbcdc84cd1b0f0b9ff6b841180bc746ce4738661ffc08eaddf1b9ed2e1d1216a0b9c741c57521d972a429505ce01bd54ea6191a5ebd495202e3bb24609b3785f21bc64cf9ae3bb0df13ded758e501725a73d6df6c89e1eb3c15757a88646176f0ab32392327ac53416b67a17d17d64c9471ddd3f083e877e50f1e335bb1a887e3297d1863414520a3722bbe3b9952b0d1394454e71622705fb49411c457d5003c24d2325b8407cdabab97fcce97c056fa6f516cbbdb984397fb5d80b0d6680f3fb4b755f53a6118e721bb0731a5f793651b16da7aa9a6f97fd7e8dca561052b6f85b14a6736bec6d9549e35cc90dfd1a07c85981a10cd251ac69b96574836a21e3dd536031fd31108d5f572a58e333bed3b9e51a149e074680bd0364fb0e7f71f40ba3f33f4b156bf54ad4f5c0d74c2b41f94f03f11ef93d5e4ed92e8d24f9b13295178f65c0761ea507b35b65d626fc452378c3fea7e7d2c24d8955f1b5fc7c673bb25772c9910b1c36cc0ff770f32002c54bc52e3bded5580fed0a16eaed351992178ced525364c2c38ea405dbb318ddbaee25a157e11f5525c3b073d7bf0069f1bbb8e4df3005c5248b70edd166716eba20390bb295da0135916e406e3363dddad52a4e2ebf47e242fb838c213419461afc7dcffcd1cb2cf546b9a7977aaa +parity 1 25c276c043503575a53501e07972c6fa4f61faad0602b1c106c825f32094d08a469bf5cc5852bc50bc7e30ec60898c1b226edbdacd137973fa898908bcfab88e1ff015216383523f42f7c986b4f89b6ea6f8de9e401a7a865c36924723a85422c6986eeb551aeeae0696c01028f7226bd5e79d42ca2fdb07f1e668d45f345bcc8a30b07a24bf8bc04c507b90462f3e2cec768397cb12afce61252e240c1718e39158b0b2ecbf9391d43ce60b365928bbed1247957c53cbc68fcab81a2895c9021983b43e36607bada430e87eab160a23d326a403f99278894b5cd05d8d70b278150355e42cb6c9e2bd8299c92e9f1a6dc42e2ae016ce606985116fd98a0d95259ce983603edd896cb307e3549c7741b554f03eb90725c82af3ee82d57370f8922377a5a06de63dd828a6546844fdf1bf830eebf62d13f3904360b6993a7ab11bddc7cc497f7e426555ac679b76dde2acbd05c00e3045da6422739778dbb94eb9c79f3c4f1afed83a9f73dac2dfd69137c756368bedc18b51244161af375920608d1ab84189f44a71befab5d16982a3ba2d3377f2de9616ebde5eeb185b835d235a2d014629be593fa61022bc875321474f069eac9441897c8d262bb767b4e45a05c02a889f1b4209e69e53726d429ee59f404dd995698186217aa8d61f53981c599a665bca8785a025af491c47e869172e65f77ceaea3c7ed6399115ebfc71ec2f74c2122570dcc79a77a82b708ca810d1511ac2beb887752ccbb10b7d8e02b6d8f347a165c09cf604843a5ceac51a466c0c07c9ac2100cb658b82acb48dac98ff0cd4729943fcbe1dd1447e1fda5879ccf46c6acb35392a665853219b9f888d4c6592943cc8e5e7a1881a8bd8f319c1c58d54dd4850572a794f66dbfd86ceecc355c6a2e0557374a921691183cb4da4ae13261305d750f44dd5797867d6255ed31f1641f097af5e9e3c94e779629ea3f23ecbc3ba973323670e7fd506f07ded135a60a3d27f5d86cfcc91207c412f53df27745bb603c4e029b3dc6438de42d26f421ec718e34b7ae90a77784f34bb1e15e1def1fbfc661d3942b11fd215a568fc3fa2771b66c93e0fd60da311bf8a4189a2a554c6c68ae37ceab911255c0bb2ba647606e3df65f60a419bca9aa60e600d3f929501cadc29ee17b2ade204a07ce3227b27e2f993eddb8effa59c8d3a235d9eab90135a74adee00d99b8155b1306e7246962948bf2c9804928356456d0dc5ada1eea6ebd906f2a4c9e270dbea9cf6ede8e359aa3f86b90cdd6f2733860171adbc0cc3914b0bea6bd575c3224002ec90842d7c570ef3ce0db05f980e66be16295efeb4f605372a2bcdd20e955bddf8e02dfe8c50691b6fa923d1585c987a0473b47b1054da3e8594d9b8578be77fc9541b0b64de511818b1558dbf079af95d77e3fae697db8b9f186a3c7c8776b42b1c4205551a2002c3e624d4cbf9fc10368a76d2ecb25f4c0126358baa26f261d9d6aace6125ba024bec6d916bcafb92480ada6a15e8812969299fb70ba4df4240a7f2bbbdb7c5548794d25d11d802e0687185d1c0396b422cb7737c35544289aa20a9adb9c1176091109ca1ce8fdc198275e7507b3b2adfa3b23dd394e1c30809a18100e45187c2518e89fbfedf98cef84ae181a7756ac90eeef3e3d7cb6c5937af3d3c21f51b04a3a89610905f3e33c4f2372dacd24a41dad16914f9c5de5c6d3bcb1c6b7ba351d4f5c1874f62c81cd078e13f6460f254d77514ad36340a7d0ccb7b674e578b049d95aa9994da17cee8a60d1c56997ba6df9850c61ec88987c7143f86b0bacd690840c8e5eed3b6a1a982d4be +parity 2 c1def77fb8d48ef70aa21ffdc7a53c19535c94f1d9d2a4f9418ba26038abbfb4ea681f4b9b80f897cc62547e45bd90b3bb4c2d349dca16d288ac5dcaf8e54f50cf133bf425ec9e81159a79ba542d08e789771e444b682f52cf1ebc92883439f410e67e374f925d8e9cc80e752a02cffc546df776090028be00cd43201da2a7ab75ab40d750dc5dc2421ef270b66b8ed817908e4648a6d71a9a108915f53246e775d7fbe3330cd50a10686dcee7bda7d59f697bea302e3c537161e9b45ee096c42ebec3edb2c15b1de7429f090cd7f305b3883c6afc5a434c0abe24174147c5165dd3488e4f506a25775d6af9ffe608c338c3cdba0180b5661018a4e61e21be3644493201881e274a60790c62c907d884c03fbff6a11bcca79aed297c103c28793ee1dbeae7bd27eac73f847822cc6b8b3f0e382646c7086feb1a794c1dff6afb3f42d536a10300a27d659f5268991129da95818c7dfe1bb2fdae912c77417654f61c7804364f8471467f2954a1f2c70cc809a4ddcba86707e6dada3bfdb51eb897a5f5800434f511e47cca68594c2d33b248f9c80eeca626256bf52a79d215f973e0d53ee056326fd6a514ffff012279ed1453bcc47899d9a6de7f2c3f91800be81c18961100db07057eb5796a847ccceecbf6ee09a29a358fad7555232c0efd1b264f494be713767e05c507ddd7344633bf6ef9492292660632157275ec03317a015b7fe096118663bcec9c51710f2d6b7eb52984e15b70c21de6561c69900764f11bb35d50dbccf249f9dd3cba050ae3b04866e5e57135048d7620de0fe02f2e78177ed7016c39fa6ca13a106725fe002ae916357aa05a1a33dc832d728faec96c1a2772aee8255c6cb843f6a74c544eff20fcd76aa9b2812691dea9253c6d9ece8f9f9b30d798078fc77620dfa47a3319cc0c21012addfcea1b6bfbc3d21918118441812b705082dc1784a45ec935e7f780967315df01edfcbd282faffac95ce541b61015c91e2c7ab0d043ccfbd8a0b8df27411de63ecf5e1e5c464c7d22a2d55f86335ce2becac2a2da8f18c1f9ab4f9ffca28a15f8435a749a0cf4e305fc1a63ac4d6df5ea5e70eb7e13b96a427320f8e688fa8134ac82fb38acdaa9166dc7828301dae3ade26e0125ca1c57d813a41f837574f78dc4bef94a2cc249f9d6f7e8f69350219a64163b1c9dffbdd01968d43e8d4b5e398ed0134d44ace13de4e62482de0337501406e468d4db20e3d1c92459002034984f522246f90d8349be1fc56489fd365f18112b51e85718e104402b553c4f341fd72ef70b05e42b0c0e50fb847c6bebc68978c9a26488494b080cbd4268ef7c0c834cd46654389af554c1fcfcaaa9aa11c87856c57c7847f61e836738a67ef186c7fadbebcae06a8b3ed72f2ccd09bdcd92afb44302f7f297ed0c4a5bab9a7fe577d95bbdf41108ca06b4fa73219c0e29fe8b9671afd99b9e380f3601bcd722baf71cfc1e57645fa38ca155ca1de08a6f655901d1617ed7e286c1f13e676b4a6ffbaa3c21de9a6249f32cb3708390c88da2e460648b67a297d48df8963e9ce1aae79ab6088cbbb4ed242687b88143303a04b360e17365d1a7d99230b5dd93ae498c622412c8317db80c90d0d8e619e26255bcbdb7e318457d22825eabb9216a242d87cbc484722a2e34c9bfb6f6bde8533161a28e5afa0a9d61adb70110b21fdd53076cbe33a9787081a05210fbce1d2f69f8d89629577feec55a4ba7fde1013a67065a4e4eca628d37ad8e54b6ebde7e4b32467fdb5c77460cf2ec36fcc861d81e425d53eddec67b0c47dc61dc7d483bc51cf27d4cfa9b76566aac73 +parity 3 cecdb160ca6d4f767f38aa79601a9b78123c1f6c57a032264146ff7ec479dd12fa9c05cf69840fee3a05763be1004d561567481197dbba7be318b51724c17ddc8fc05e36302d2ba0c38baa9df984b5306f35e65701e3deeee7f6e8a8185d31bc7c6a56880c056bfa8c2c180e8ac67216cbe1dd60ec7eec794bb9502158e95068eb34c9517d18f00925e21bceb90be0f7e2a6274bb0ea8ab8ccd8cc8cfb5402ca5e91253acf0fd00272e3adebc452d5b67278c408921235dbe9b867880e13172ab31f4e660217d0ad83c372b21f745353da2e4e2d3c1818fee7416e8877c262ad1a9171fb5b424a2d57ed87bed4c7c00b5f2f0d21beb2df8cc6db6315d0e69daaf9acc2f32f9e97aeb9ca04727abc4035dcde6715a3827a8c85f2398115937eea3a707ae5ecfc81c50ff635a4b7440ce63cbc60c946ca5e1c2404fd2dbb5a3cfed81a46dfb40bd2313c49357d6f16599605796a4246320e17b2d7be8e3d748dc088fb42d3babe46e9b084b9f6857ee950d4595668ef46ec5c0a14f1132e6e5e634c74ab6881957a94b0049c11bf34b9fae8b29d54c65336ad7c5fb83e6ba487e2d605f69baad77ef99aed9a3c92d4396a08979ffc50dc04ddcaf8b0ca0cda334f81471a000f8077e17de673453d7c7a6400edf7c2b6b1bb7a3e21e9e542ef97a350074b5cc1b24c800fa74d53ad5b169c3a0a1bcf1e4a8d7c4e2d57db6099c85834735049dc61b4e6532baff8b0889f9dd60bcda8e71d1115c6bf43b3388b1f7fb698ef2b303c53ecca034c40d27761c671b1e1f18501f193842d91896603bac7d39ed1df95e9cdc20133cb356293dbb202942a3d31215a8136f7745acbad849fbb00f540e44e8eff52a31802723437329a9c8f0915290dbabcbf7234aa2c47afdc4e913186b6d02d3861bf1c03343fe4a89b05657815ebb659430cb074ba68581c1cfbf1c7917bd2ae4bd032f0a6d0b238f1aa77dc03eaf6f84ea28414a2ee3109d172a8d689bb6f3b11997962d839502fe7b615df3074467132ece03a2a889b10fad08420f0824b7f17d4d9bb3db05dea51d8048c52c0415dc6d21a776901121c691280db8892bd645a40d66a55ca686c4e1b258f7805b4f44c93cf929a2061581502ee519ee5645c267ea129f7f047279df3261c63a5c055882b5f1f82d83030916bfba8e543b59fd6d13b9b0e78b8dc2d1d9dcd20f6423cd17128dabd686ea93681a819cdf9ed89c6bca0f82dde6fc79b6978a4a3e52194e23dfeb3ab581fb7a01d7cd72922b274a5fe77f03a21c1c833f73661b0e3a6abeac47d27932037ce8eb4810e542645a534c3e020027c902c1592acbd7ff806cee106077491552315effa60edb2f90069ecf1468d0d1a49f04c91fb3fed5b6c63ebe3cbd181c99a1e9e33efc70b15f71e9075793032bd793b1bff50e759b319c61c29d24209c91726b11a05cb6410e05bb3974cdfc2647e622ddf062e8559e870f5bf09a32a77d46245fd8f02cbb32ea0ae1a111d1060bc764b19b8d558bb319f9cd4f278a17d24e5c1848bcdb2a2f0c65ac3414013e72c60aaed59693328c6cf8f0aead9d87028ccaf1ebaecd0905630e6b7b846790936b9b6983ff04c48a65c37c88b16da6ecedb464a1aea2fc7318791f1cb6f0bf06585e2b27c21524cb4a54bd7ee95c455a2675898d4d88c81d6ac465a6d2f98186fb780b2e1e0836ffeed84d8348f1de726d9bdd71415a8d9fa1336851a79ba842188b4831911d4bfa02a6527b6c5a5413a6858d046d25bbd14cd5cdf871e54bb8f9f467e433f4ef7585b6dda0795be4c6eeb0eca7e2ff61fd259a2e10485f5bcbd31d6adf1 +erase 0,1,2,3 +erase 0,8,16,17 +erase 8,16,17,18 +erase 12,13,14,15 +case 3 2 4000 lcg +parity 0 9f31dd8658475a0b0efce438c7f7591a0596afdc19fe90fce15355887c7de3ca42c39466a9d9eb4cfbdd1e52c3344b99bb47f88f373fd73d7e1425d6d5473112158810cacd6c8db651345d800bdcb9742d15aa909051ec9ef03039d76ffa448e35c78d81584d1cbf16598f09c1bb55c7fa5d73f73d3b22c6eecee2e31b5c7873730da9392b081d3565e9c35f4eef729d38770ca8e9b43bc7d24ee78ad9062c1bebe9fd7921d54c8aca47beff725d0c814843a0f0d606ceb16858c2a2578638d50b27929c2ab4f294ed0a5d5d762b6cf2143cec2067684c415246fb2adf7d32a2f0668f0f222a090cbba0a2ff9c0f0e1a364dbc37f5ab633b0b81a80f9bfc483c0e80d61cdce6310eaafaeaae1fecd59c6de2a730947376f11a349169c7b8e425ef4edfd46f2fdc123ffb7fdf5eed960af439b7bc6145c642c68eaca69b243fb3f152ec5b02234d61ecb8ac91a95bea564a6c30039a26868a3c7437a8299c0eab2d4b0bc9a32167f50f330be99228e4c2f67260897bcf719eac2f323a435f85138870fa16e517e5edbe51b94ecfcaf754fa62a452accaa723fb7fb6f2058ddf0864b76b5cdd0363b3ec95a551b67a63f6f17d4b67e3c796789114dcdcdc25cbcaac28d34dbc9d05acd17e2ddc594d32c63227c876c6360605754db1b76b5f7dd3ff86282207efc18b2eec7e345c49ff4068f24b1a83a7122f48780175d492cd0bc16c8634d286b39765a19ced55e80f2e1e0d7c6023372118bdf120642a97922826b1a2475fdb3b0c2f99df82721db40d7194af59da1459ae5e23f5c629a215a50ca9b225d53060a90b7392dadcff98d479c996ecc27c43b023c04f9fadd7abe573f7dbc6d6a8a8025952d96ae4a42dea867e84a05c393668ddddf46216807b8afa36babcafe4b7140123b9cca9f4e3250e0891124959b948b5550aae343edc7e92b0cf2e40a8def6d86ccac15f0b383230cd74cc207888fe2d502834c96153c2455590a543c7ab43ea278e7a67064c7089e7d8b2b99e1e96cd0a6f68f0f2b0312a00c6fc16a53b3c7951f2f293fc042dffa3ea2d76ae074d2fdea96674c4f588de94079c1a005622ac6c268100c70a47f4c2bc954be1401b15652fb8d6722a91de3ffe0e0aa34d6baa8e9d2b616b2a9cc53ca0f7548dbe3bc11dcc226e13d81275ef0bb84336fedeb2f430a1530424de1c1aca9bf36cb296645571cc5efb77637610f0a12cd1d1d999461925cfa50060096972cab9baad94e140f570cf97867cf7a3e27ed80e6ff453b498fe30aa6098fe1045721b2d24943ce9e3c6255dc59c175d7bc14a3cdfd6e8a264ff97dfc18b955cd806486cd41633f5f19b917d4bfe7b38a8e2913f428ac4868ddfce7304fe23fe741a89c3d2dcf21b707b857bb4d614b4a22be79004df8d03b4a837b1324877fb541bd2d262a703cb2622e82b7275c266900b73362e753f80f0e9d00b31237de97763bcfd5e64721e2d4cfc2387402a0935f795d87ca7f22875aaa70b72765dea646b024f64c82c0187c87e61b4197c2a1d6f135e3e48fbc6fd5189033ea9c0c2f31fa69ad9b403709a97e0b95934c5f2f816d2e91181f7246bf37429df93bfca66232232ac9fc9d8aa1e503e942baa2bde26aed45b67e0910c9720ee7a13590733b92614a418060798b06456fce2334da66ba01f412803a432b51d6e7db63004999ec183d1909063a851bd99ad1c57dd70958c3a01a873f6dfb8706f1ec01940e34dd11664b06fbe27542a1b4ba7543d1d4614e6a113d90ba2d4fd1ed531eb5d3728f2dd9150f448e6466b49e46370204065ae3215cabcd88ece25c9b106f78dbcf3787bcab1444f656e097733a4a90206b41ba44e5760d7b6708d0137702ec5d491b29f9c12b7a92d2aedc09d326a45c3729b977fef91fa723ecad3b68574ba1c8662394ce175a37d853dc47aa5301f1d9698b3f923a4c0f39a49d748f4ffddd1dfb343d1924462d6dfe0393d3e44a3447ab8983126a043006e11d89e123f459441ff63ed40b880d9bc05547a5c2a6f38509ff8576c3be4952133fabb193e0c9f05bfb8b3e7b4e6c1265cb9fcabc6e029d44be54d18c2095920420f1cedf1b72999c1c6abe20833df08334edbf4f3692e83d288c6f30df4bd63bcf138b61bd4e1a33e9b6d688e7ff396ec965d22089c1c5399797648a491d312a6245bf31c1183459a7d1d0cf6615a26d31a2ea8abb0bf5d1d8edb0695ad6be4ff99cd909ae1b4d09b69b40893f9e757fc02fe313ec3cbb3c7229d830e97f3b4d274a560f5f8d2631f269b6843fc11ed870618963b3b62f2fd6ffab5debee09cccb9caf32968db79d47b2fac02dcf899f80422b45c13bc74e34dfe10d2e8f13848d792d997a82f22396bd247423c2f65b07cc52889c97bf0b86ac68a504f0ba7797d30fcbe9c69adf9055079727461e7fa1d1a771387b79e9842b094a64d6d1fee370f423997472cb014563a21602b915aafbd0db7998bc33394e0597fa8b30f8b63399a2d8c2d8d0a725b59a53437317eff5c7ba55db4a32826536a76b1642ddd2c4578d98c625530c14043ac797edbe42d4441197e26699b2f628bc273484ea01b2bd1bc42d3c6ea6e81c5425634d8035c6002e7f4abea71fd1c2c3833137ed2c515db259c2802e20385ee2c02f6d84da3a298c9a970ec8217c6500b166c36597e7b9c7a95caa722d1481cff27ee16a322de219a5343ffd936a58558067a55a35ab0af43b65e44d8ece1c664f3e45a8a262fee1bd37d1c9b2ec44c8f7935bec4f425f2f50207765ef3ea3e67c4bcb0a4c2d2a3ee23120ac5df410e004507433ab6f082589b5a006be8d4cc6c8e2522c5cf62c37b543540263eb133ffd81d4d4be53b32a6fea13024e6bb5809461f9d052ef84428b90f9cd746b3ae3d6cce9deeb1ff736dc3cb024d1389a74224b4aff36a863dc8f54e3e24a725045ebca846d3ed7fe052bc147aeef80ff5aa8272b92a8348d259bf55f2f24df3373448a1f54b9d5bd5c0e63495c972cdccafad00c830dfd9e27e263ae24b4e59f3c9d88270d834d009295107afb97df2f185f0e12c3be20270e7c83c0dbdf287fd236cd91e46654c91e32875864f45503cfe1f387ad17c0194ca5954f43b43dd6105e82dc8799ae68c9d19f73e46d71e51b49e33c05ef8b18be7f350b79a2d7a9631cfeb9c890f4d610628ba937a166e19f83f53700ea256f59c9aca8110bb596a86ecf15a59d69551d51903ecec8fd40a7ebed202efe63f0dca5bd94e474433d9ffe08c71c11b60d52b43f7fb7d46464483584f0d74aa8b808fab854e042bcbca5eb4f6a378ee94b3b60710017d19ad8708309bc2271aa3ad3dfb0b17100e8c8227c0ff615f5d46de82422c4c5395d5ea59e752ef658c995c4a2d6fa14b7ad4b0adecf3d3a1f3da401fcc62c4132eb687f98c2fe65f1dcd55e63c9f08ed64cd13ae367f572492f8aa562daf6ca92c1d9b934538c6f85b86be1f981f71fdae14b80b6e5248512a0a55e02696108541746b206f253284d4e994bfc04929f04568707fb99315bbe5b90e0dfa3ecbfb431b3d190fae608ce6df3e6c76d35e4bdadf90653f03d2c148eb05c6edba0ac28e3f12dc73eb5e023a3a634b0b13cedbe7ee3152c6390db621a832f0b997775c80879298c1ed24e026984e27411385817bce391429659c78b610a1ed29cbe2c4d02f30df0d0898a7ad9166cb5d2a18d48b8590eef75cd564af25aa0d0e3f99144feb2680b2e3fc11162ca1d401e875e26a41f235898df5254b19f52001a979ccbe7fe2d10562469c5120417bcc5488261aa00efd6ad751d2672ee1f073e4ed10cb18b0d2c05f3b4c9c8365dc3fcee37e00f66aa6f1ab6b32f0e9dc51e92e309df594b7d2cdb108c33a09dbd9f91d8556be7e17b7149627687e027d6a98a26d01e1e45a98eade2916fcb970db6a671f0badaac63160594a1446ff6e0fcdb2a95a3a91e3a3ad86c257f114598f49258940f4fada6c9de3c92b25ddb473f84d18357a9e00d94d43ef84a0cc6d7a60ed816468b6ae8fad19cd4f1ee81bde1cdbc8e3c7df83e52bbc94d650c70a1e1d79330cc22bc5ca3f181db525c9915d956f9ededaf3484c5650d99d90b98dcf41767e515c276a9109df549f0e640c46cd5ad59a034f382e6460a5185dc6aba39b9318e19579496a3af1f88d1efc93fd53d1b0914415da71afc06feb727b51e58e4515d7cf5b40fbe1f44caafcd4bdd2065ff99dee099a7bb07842a9a91dee897794837971b78d48f6009221c9fb986a2b609f18ac11665dcb30fa36cebc3bf438007ddcb4ee59e8617773904da76e5be9ed441b99412893f308c03379230bb5ecff01c28ed31f5048546fd026d41c05e3006857e410c4dd66da52eb1628b32b8db84605ce61fb4506ee42988aba1ffe3a0b21b6a0349384da4b54571e9bf01093784976a271e29bcea4959d4cf434b528489858a338277aa73abbfc1f17fa79e024a3b4c0ad13becb63d26e520a08e8e31d795947a67262860a2a14cee09e2f9e04ea251b864d403360aa360edb68caedb5f19437f2dd60d173ebd40fc64767e00ca8e3a92b783d0ccb3f8598b26bfe94878da1207e3bac65046063df1a43fbfaf33648a0f6f4939456e80b766a2f4e277ee243cf63fbc2c147e743c96d4d6abeecc7c8105eae84c7b29f94430abd9749ff752aec3d6504288fe7262645543c5ebad8bb17194689a64530a5952cfda40aa7288c04848d4e91867b8035b36896170d40af461353e6fa634351c784cc05b05d17455d1a41495a700a51b4f7057477eb7fd6590c91def292abfab326733925f9bff8570ddac6d7309ccd99c978cd7ef7d6b6462ba9b462c3c61ab69446d3c1d7722ed7c6270de18ee24c3685d6a5f7e40fb378f02a97ea2dd13b3e6530adc1f29a2c98cc0b4409847cfc8382df7d062a19860b9f966584fa45011a27b49868b4aa3e7c0cc17ab838976efbea25edec1d4bba9245580b7a53711e042c111d3e8ffd3b5cc6246fa6d679b68c0aba113ea1b6a5080f9ec7fb1a1e48efe9ec52b9c5df0a5a752ece9a33fb9047c89e2166629c7268570af5a61fbec1e1d0eaecda14920954bc7616ae1b84d3bd2b5aadaf967fbbdd767c61549f61892a434eaa9cdb9fe5c1c1590c023942fd53cc05cd7ab1e75b577897dbbe5a1b7b2c983d9f9cf3ee699f65011bcd1c02ac4dea4ef91f3b326c6de2c889658ddcde04eb11f8aef94464060ae13a1fbc5010556bc588cfd7f51a1f19f6db6cff455de980bfb18960d3524bf85f7d6d30f4e8d2d329b40eb8367feb5b9d3f7ba70ef84fb9d928822a49aebda04b45f0705702602306ce437eb20da3be0ccd00c2372a9cc6e7e82d0faf29646efec02782481c7f5b3dafe069727a6e59a9809622dc02bca681061e2430b4fdae71b039f7595e38045a8adf6679dd2b632e4a2121298429ce4b6377b79254f6fd3cd25895c1627f716971cf38d6b79af342e8e6c1f0732d59926491ade2e8125db0341e459dea8290dcb91c4ca8279d3fc16b80fcc40495df02e3bb9a48958a4e6254229530bdc40c0cce50bdeb1a7c6ff6f85e289b24028cec87f7f6665720fa88fd628598ee7a6a5b6499115c659cc01e8caa3e2abc3457e2a23fd23e57dcc52957608a1683469fd7 +parity 1 8db62234c2ba4073f6367b9738b0fb00ad4b5908c5c5482725cbbc1b83452c75f9dc380860d5bfb4d609c18a9bc253eadec25917fb990133679b06eef185e7869772f129bf49ce66d26e949db10f7558938e89be24116c9610b6cada1bf07ccf6fc516f1da131d2f3783d5ae5a7c1289bad68b8c41426548e065b303d438db30a26669f25ae43447ed055bc8d760cda1bef3b883e25e487e245e6a8ec994c143fe0dd40163ebd1bd0b9e72818a7417a875b466ebd063b68edd717a65f1b88f0f0801658ef20d619558ff195fb93951c86339d71f4cfada3e7f20dc9bcfaa5e621030f05998d2222bfca592e0b77a15effdd41e546f2fc2709c9e994843006c70b2b2ec29ad51566c82d12103f9f3c314f1cd46ed41ed297cdde81924326b08d318ae4116c6a0b36756a1086cb99ff98b89f89deae44c1c55b6b00f270f60cf041f73b8b6dc978bd5d156b506c3d6f71688e5b5aa74ed5a492be0c20bfe42c8ed3f56bdbd980ce36d85f2b73a90c53247cfed3496dd304f5f58ef3043e5ba375c1c5eade78e456db3e88b2ce64bbb76d30897b8e5f86cb93c02f44e9b0aeebb619efdc0b14de115579b67676159a504792a4447df623724c2133aa654e3f7c1bb7148c473617fc3a80ef1f34e087bd352cac1fba27b232b273b77b3fa950da2a46763f7448f8e706269edff5a231783c5d355a71b9b9cf00490f0515c6706ddf75b82b48e636f75f00a73358bac2bb17f2970d3008212e033b030f5e77ab9845c0c4e7c4e862ca72c19f43e4ce29e02a0b783e61522793cd5e6dfbe8fc79456594ae58303e5df390fb29226d43ba767e1e859ca6a450648c406182f199c74af7332d48bd7811d833460718ea16c39565decea6499f2bcdb7ca4ce6183f167677aac718610d87e8990085e2cce7cb6038da5641be5252ccfb6e09062cb9c49f00fbbbb14ce4a315ca0bb81ff122601d91aae177bed28adaa1bad50e0749b6bb22e21345fd16456ca84e28546f0c2b422f38523e986061613dde2730f13b514635d57e0754f56a6a41db5fc680671769e675345394a0a4d5a96dcd578c20d30d2c51176a7ad2a41e429252d54ef5a8964445a70254a68fe7f87e7c984a82319a0b394697827b4edf578114b786b228fd5f235f3a7979628470caf97690398211a383099224595a1fd6dea45a07374a1cbbda2ab81ee6b09079bebf48def800c8b7ac14612fcfdd4df765127dff0ee2d51d586009e087b388eb7f875a368737e07efa822b708ee083d5813736dc815c300e3a11cc39ed3fd1f6a9f8783a430f5410c501cf852f31ce4c3c3925d8a37198fe79a5e254d185c3ee392d4b806f06c0eb8a1147901a60c43d39382d7c762ff3bfa1c68a428775e3e12d22fa11b4a5cd06e8900ca488904afca4bb07574c8cf2230e5dca426010ca1e5b6d0cd2c206289ce06944625daa27213eef6bb5ef1c4b7441af6719191c1d511e07d3ab5edbd1ef7f6e135cb4291220849ed3b77b3359e1491a7df52883e86713bbe5b14c9f6dde932fd2612ed64fc6eab751bf1243b79749de04a2f6f8c1daae9697cef2144120acb40413ad598854ee06df159c631561ef06c011abc7d651eb8c94db7fb847c88a6582368da91a28599363c638b09201790d4c9ef3a9d8b3fba72490dbebca2f844f26783890960b32b2da80358a11a806cad6813d0ed20edc4d95aa39e31ce6775112f21cbf71bed04f43f61c9a188a8c080c25813b9edb215ba41e35bf223aac9253f3384e7f6c88042a72d43871464089b8a9b6d128a00f582baf30a14cf55793b51f10a2958f9732c4da748d767b8a64eb4a54d1d77d9ea7aeb423fbcac2ddb23f6f68152b6eb371749611fc2e3247511f8e9c8fed5154f43cb8771d6c6ac47b3a6151662a4c328be30d34861d5063e19b3bf2ced7a4d68983fde57f27c2014d7f0f056e880e7db978ff0b2e57909201f0718c62607d141b8cc3f4c68a5defa27dae07507a2832be5e044d24b87ba6921a2df8e8f6c99807ee99c9252ea866f402dfb963cead5bce5dda940c13fa4f2cac7e4cd48b8c967226618bad6cadace7853191e2d09bf68c1c5d19e93139822bbd94e9df52215ed1d06396104173cd5dee2e7f19f8c30a9e599224601d9b2a27b0333b25eaeb35b212f9afc9a0312f581a9aa9b30a71054d8f0a24dc2878b9e0023e15b0dbf884ff2312d64c5d97077659798defea330180e64e23b620c03cc1d85b2b1504e68aca31bd83139d4d3961efa0efc22c8550d73d43d44d917ce9e860309960e866c31a6faee795b002f8c6a0d36c2789555ced1e81f40f6dd1954ac782a4eada84e65c9aaba5f9eeadad0c35684cdbde8f41cad56eb99de2148200ac3e3c2839baca892e8072c93d1d55f43b2ce57d396b964a1ddb1ef305c2f7f0c915eda2605c57ad2d34e99e2ea97c8fc5660d9905a0d35d1c4e17be2645fd355acfac027787ae517718eb5e256f937047bd43acf0215273595aa3da9ceb3e85a76296837c53172c0d609b828868a465cc2dddc7b54011d3467c1f45d534c7f71735dfcec09ac98a2989eb2e8287c37b8a4f53e0fddd642776fda6b601d2ee3e4f88a4ad04cd4a82785d1f21924346138ac773c6688aebdd2aabcb4a3ada64141595ad0c7fc918aa667f1f74b7102b956d5834e689e43ac44f826f1fe6d3734ce61954669b5ca4440c9144b3a0374141fdf1faa85bdd69a15f99228a2eb72609a1b3528e2a432c1fc29d008ad535c6e9c1339414d0105f00095e8b5551326dddca3e5892fc32c8eb22572858f0784194184ccb666f9093a6278ec08cfb20074701f212a25e0717ac19f22deae00126b886daf24a4389b064857f0d6269199d05d366a5b6138bcb0c5998638a75dd17b793171ba196f76de4b1b234375a0a4ca211759a8cecf586364740a963572e76589fadeb31af2c233828a33f5a05887269cbf690b6fd69b875af257c314f7ccb173e64c6032fc7e92fc364017d55f30ce22f379aa8a776474721c1b33ddf3e38e0817528429a3aafa0bc97e221e9f2ec36646ff3b3806bfdc59e3f212fd1fb36f0ac1aadbb10894acc178ddfd8bb64614ba6d9d14d39c27a4c4e6bf14627b0b3559a20e05439119c000eb0add9b2cca503104d477875a6ef1047dacbf47c163234d55c6f064db91c2de73cdd16ac35971d0c5d24575882377d09b0edf776c8acf5d621ba51c4b24e6c25269d4e016a489ebf2ebc8d0b80cc4e39365f46bdd6b2c99a532025a74e8e4a72b1f22dd38f57970893183405114bad8833a93a8a55309f0a24e0edf6a79b1693e9eb4394a289966433c600a9cb0b3ad9ac730a4d8917dbbf595daf415e5da6f869420aab0d1f0ef25bfe0ac102cd04eef5ab4b5b89644ffd56d77b0f6472d8cd4d843601811b0adc71eb3f8f8203c812dbca7a154c061a9a3a9608dd3b8bc3faeea85d9bac2c0912351c34537129ddc667fa3430310a8918c77f72b03a91110f9f8dde0e9ba962f85e78ca9146d94bcf25fc9d26d28787daf4daf3d5f0010b14b7be84a584de43d53b3092a0f2fb8a32894d120e05a9edfe89228e1796e7d405d1730f00597be0c6da789d400754eb0d348316842fcb111deaf516dfc1334cba0a126d26350992d5a515ed95388de27046f66fce32343a012a04b6c5c1ea0086a5b8a5ef2fdbcdc7d3a2206007c37a69e99914902f3555d2e18d1a6cf5c96f8a199796cf8cc92080a0f9b9ae12d0eebc34fd03fb9281855ca2ed662954b4058e550dab8dbe20377f9fb24c1fee31d81c743db2b373da115e4424ceeb19271c3a6a2e7b1dd3f6ff2ac1f400ca6fe5476718a8b2ed0f45b78ca8a5cf859bb0752697e1dddc724a7ef07bbe3611b7bbf11c67c64850899b28d5e62a9584b503ba28cf33cfa8874dd35788d86a7b5659eddf67cd0ccd3bde729a3efc4b573ec17592e96bfd10c6a195b69e91d2bde5c197461c276c3cf21bad5da62a5d8aaa273a195e80d988ee1c508d305e7786994d09a0d6f10a690370a47fecbec2bc2308b937ec666dffbab76f4b4a21638dff8906cd361530eb3e32bf095ed4b499669d9263d4cb552bef4538ae26a790f4f0e58c3b834172df96f4aa25fe00fb0370165c6c8c372c6a722b67a42fe0292ddbe47463144a9e9be3b2670c6f11c13fff6882282a750ee4fb3b6c20c05a9e1695f56753167e3cbca97b479c427ad04a976fac8e21c748d94e3240ffef0eabcdbcba898bbd08884c5eb5d7f9d33e4111fe05f9dbf25c8b6bf8b6dee3f33e660943e6511eb519a34d86910fa76d274c3b6f49f4d7eebbd963ed1639503390fd37a087b394603235aac6f4a8b2e80320522a1e4c5c6815102b8125ed235de64486c25394b7953d009d0992762d9e7db78e8052f1e41c4788c938ea9aec84045ca9279909f02f268bf15250abc89429dc59652ef33ef13b60eb140c10f8e666d9dfdcd43d6ca64c9a48a272d79f6fc4e71be5125eaadded1818b28ea8431cf83a4cf7433533a6de195c59e587ca3f56877b62a0050dcbdea069bc462591a5d9e19c2e5946af0c8e5e87cd44011b999bce703cf11d3fd2e0e41d83d77f23eb17f2e248f144b2aa24089d61dafc8888d48866cb92af89014bc3cd95661516c389161b5f1a7dda17dbb1db0fcd05d6512cf2fd2bb1a2636234ada25bd1374a9fcef7d6c67dda194aac9d4a1b906d4a05b985e289d78481cbb43dfacfe4be66c01058e900f08001b9b5697bd283ed25f090d2fbc162c39e53b935494b3038a370145d6a69afd86231c6371d2eecd7504b5468204df7df593a14d3e3fa4eb8dbd6be9e18ba98163dcb2776e9e0bbb1e55a038c4229f586aed938215ad264c8f6fb35fd18188adfe99ebd2bf433bf019dd85573368a2470aa1fb84816b8113a024d1a4864b059c652a4658d65c529bbd529e6f9bdb44aa4fcefdfa28184ea90874734277d4dada764866ce6225d4fc63f22073cc39319890521cb015846c085f48e9354d9ba6cfb30758c1be393be1110cd67cbc82501861e3410f537f323d54f0a0afd26f96a39b029ba1d2ee5f3f90439fa42b8f025e9cb83c9c0fe27d38f5ad83b716ceea3053be5726aa67cfa06d9475a2cd08fa0cd07808352e999e36cd663d6c66ad406c9040b878e36bf5d1303b3efc42931139d379e7212034ce2cc327d3a6ea923a73eb3e2b11d369e0f9da8f65173cc36b9644add703b8d2642a6d47e962c794d4cc8274a840cc147f2191b41e9c85ca11810c6bb3c4dfb1e03fdeb101996d8cd79e182e47d3ea225199a2eee62eb3b90c75db71faeb283ca3eaedeac7fba5b08b858e05dfbecb20ce5bc02be0709baf18d8815bd10a8909e4c7a41ef3246b81921deb2dbcde2e589d493b89edacfd471c06beb86a8153c5a6606adb48cd145f1e37fffa3217fe729f1ae77445df445367a01979d8197b6e2b6adb6713d6a1cb077e20a825f89da00cf5efa364b7230d8fc7415d5eab427aecca33316d4072dafbe6b62f261dd0a1f6c7161b51e7f8d784092d06dabdd6be343706281e51d59a8951f114391978e2db3ab5797a47f89fa39a0e05f6863b81f82ce80cf8bd6011e321c875a432fa17dcfe884bf39705434add274cd8969a1c8c7ceaa6e0d2460299a26ee01287b55335 +erase 0,1 +erase 0,3 +erase 1,2 +erase 1,3 +case 4 2 16 zero +parity 0 00000000000000000000000000000000 +parity 1 00000000000000000000000000000000 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 16 4 16 ff +parity 0 ffffffffffffffffffffffffffffffff +parity 1 ffffffffffffffffffffffffffffffff +parity 2 ffffffffffffffffffffffffffffffff +parity 3 ffffffffffffffffffffffffffffffff +erase 0,1,2,3 +erase 0,8,16,17 +erase 8,16,17,18 +erase 12,13,14,15 +case 1 1 1 lcg +parity 0 87 +erase 0 +case 1 1 9 lcg +parity 0 8763c23ca4b05c1d0a +erase 0 +case 2 1 5 lcg +parity 0 cd8575a93e +erase 0 +erase 1 +case 255 1 1 lcg +parity 0 51 +erase 0 +erase 127 +erase 254 +case 255 1 3 lcg +parity 0 512073 +erase 0 +erase 127 +erase 254 +case 1 255 1 lcg +parity 0 0d +parity 1 0d +parity 2 0d +parity 3 0d +parity 4 0d +parity 5 0d +parity 6 0d +parity 7 0d +parity 8 0d +parity 9 0d +parity 10 0d +parity 11 0d +parity 12 0d +parity 13 0d +parity 14 0d +parity 15 0d +parity 16 0d +parity 17 0d +parity 18 0d +parity 19 0d +parity 20 0d +parity 21 0d +parity 22 0d +parity 23 0d +parity 24 0d +parity 25 0d +parity 26 0d +parity 27 0d +parity 28 0d +parity 29 0d +parity 30 0d +parity 31 0d +parity 32 0d +parity 33 0d +parity 34 0d +parity 35 0d +parity 36 0d +parity 37 0d +parity 38 0d +parity 39 0d +parity 40 0d +parity 41 0d +parity 42 0d +parity 43 0d +parity 44 0d +parity 45 0d +parity 46 0d +parity 47 0d +parity 48 0d +parity 49 0d +parity 50 0d +parity 51 0d +parity 52 0d +parity 53 0d +parity 54 0d +parity 55 0d +parity 56 0d +parity 57 0d +parity 58 0d +parity 59 0d +parity 60 0d +parity 61 0d +parity 62 0d +parity 63 0d +parity 64 0d +parity 65 0d +parity 66 0d +parity 67 0d +parity 68 0d +parity 69 0d +parity 70 0d +parity 71 0d +parity 72 0d +parity 73 0d +parity 74 0d +parity 75 0d +parity 76 0d +parity 77 0d +parity 78 0d +parity 79 0d +parity 80 0d +parity 81 0d +parity 82 0d +parity 83 0d +parity 84 0d +parity 85 0d +parity 86 0d +parity 87 0d +parity 88 0d +parity 89 0d +parity 90 0d +parity 91 0d +parity 92 0d +parity 93 0d +parity 94 0d +parity 95 0d +parity 96 0d +parity 97 0d +parity 98 0d +parity 99 0d +parity 100 0d +parity 101 0d +parity 102 0d +parity 103 0d +parity 104 0d +parity 105 0d +parity 106 0d +parity 107 0d +parity 108 0d +parity 109 0d +parity 110 0d +parity 111 0d +parity 112 0d +parity 113 0d +parity 114 0d +parity 115 0d +parity 116 0d +parity 117 0d +parity 118 0d +parity 119 0d +parity 120 0d +parity 121 0d +parity 122 0d +parity 123 0d +parity 124 0d +parity 125 0d +parity 126 0d +parity 127 0d +parity 128 0d +parity 129 0d +parity 130 0d +parity 131 0d +parity 132 0d +parity 133 0d +parity 134 0d +parity 135 0d +parity 136 0d +parity 137 0d +parity 138 0d +parity 139 0d +parity 140 0d +parity 141 0d +parity 142 0d +parity 143 0d +parity 144 0d +parity 145 0d +parity 146 0d +parity 147 0d +parity 148 0d +parity 149 0d +parity 150 0d +parity 151 0d +parity 152 0d +parity 153 0d +parity 154 0d +parity 155 0d +parity 156 0d +parity 157 0d +parity 158 0d +parity 159 0d +parity 160 0d +parity 161 0d +parity 162 0d +parity 163 0d +parity 164 0d +parity 165 0d +parity 166 0d +parity 167 0d +parity 168 0d +parity 169 0d +parity 170 0d +parity 171 0d +parity 172 0d +parity 173 0d +parity 174 0d +parity 175 0d +parity 176 0d +parity 177 0d +parity 178 0d +parity 179 0d +parity 180 0d +parity 181 0d +parity 182 0d +parity 183 0d +parity 184 0d +parity 185 0d +parity 186 0d +parity 187 0d +parity 188 0d +parity 189 0d +parity 190 0d +parity 191 0d +parity 192 0d +parity 193 0d +parity 194 0d +parity 195 0d +parity 196 0d +parity 197 0d +parity 198 0d +parity 199 0d +parity 200 0d +parity 201 0d +parity 202 0d +parity 203 0d +parity 204 0d +parity 205 0d +parity 206 0d +parity 207 0d +parity 208 0d +parity 209 0d +parity 210 0d +parity 211 0d +parity 212 0d +parity 213 0d +parity 214 0d +parity 215 0d +parity 216 0d +parity 217 0d +parity 218 0d +parity 219 0d +parity 220 0d +parity 221 0d +parity 222 0d +parity 223 0d +parity 224 0d +parity 225 0d +parity 226 0d +parity 227 0d +parity 228 0d +parity 229 0d +parity 230 0d +parity 231 0d +parity 232 0d +parity 233 0d +parity 234 0d +parity 235 0d +parity 236 0d +parity 237 0d +parity 238 0d +parity 239 0d +parity 240 0d +parity 241 0d +parity 242 0d +parity 243 0d +parity 244 0d +parity 245 0d +parity 246 0d +parity 247 0d +parity 248 0d +parity 249 0d +parity 250 0d +parity 251 0d +parity 252 0d +parity 253 0d +parity 254 0d +erase 0 +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 1 255 4 lcg +parity 0 0d2f7919 +parity 1 0d2f7919 +parity 2 0d2f7919 +parity 3 0d2f7919 +parity 4 0d2f7919 +parity 5 0d2f7919 +parity 6 0d2f7919 +parity 7 0d2f7919 +parity 8 0d2f7919 +parity 9 0d2f7919 +parity 10 0d2f7919 +parity 11 0d2f7919 +parity 12 0d2f7919 +parity 13 0d2f7919 +parity 14 0d2f7919 +parity 15 0d2f7919 +parity 16 0d2f7919 +parity 17 0d2f7919 +parity 18 0d2f7919 +parity 19 0d2f7919 +parity 20 0d2f7919 +parity 21 0d2f7919 +parity 22 0d2f7919 +parity 23 0d2f7919 +parity 24 0d2f7919 +parity 25 0d2f7919 +parity 26 0d2f7919 +parity 27 0d2f7919 +parity 28 0d2f7919 +parity 29 0d2f7919 +parity 30 0d2f7919 +parity 31 0d2f7919 +parity 32 0d2f7919 +parity 33 0d2f7919 +parity 34 0d2f7919 +parity 35 0d2f7919 +parity 36 0d2f7919 +parity 37 0d2f7919 +parity 38 0d2f7919 +parity 39 0d2f7919 +parity 40 0d2f7919 +parity 41 0d2f7919 +parity 42 0d2f7919 +parity 43 0d2f7919 +parity 44 0d2f7919 +parity 45 0d2f7919 +parity 46 0d2f7919 +parity 47 0d2f7919 +parity 48 0d2f7919 +parity 49 0d2f7919 +parity 50 0d2f7919 +parity 51 0d2f7919 +parity 52 0d2f7919 +parity 53 0d2f7919 +parity 54 0d2f7919 +parity 55 0d2f7919 +parity 56 0d2f7919 +parity 57 0d2f7919 +parity 58 0d2f7919 +parity 59 0d2f7919 +parity 60 0d2f7919 +parity 61 0d2f7919 +parity 62 0d2f7919 +parity 63 0d2f7919 +parity 64 0d2f7919 +parity 65 0d2f7919 +parity 66 0d2f7919 +parity 67 0d2f7919 +parity 68 0d2f7919 +parity 69 0d2f7919 +parity 70 0d2f7919 +parity 71 0d2f7919 +parity 72 0d2f7919 +parity 73 0d2f7919 +parity 74 0d2f7919 +parity 75 0d2f7919 +parity 76 0d2f7919 +parity 77 0d2f7919 +parity 78 0d2f7919 +parity 79 0d2f7919 +parity 80 0d2f7919 +parity 81 0d2f7919 +parity 82 0d2f7919 +parity 83 0d2f7919 +parity 84 0d2f7919 +parity 85 0d2f7919 +parity 86 0d2f7919 +parity 87 0d2f7919 +parity 88 0d2f7919 +parity 89 0d2f7919 +parity 90 0d2f7919 +parity 91 0d2f7919 +parity 92 0d2f7919 +parity 93 0d2f7919 +parity 94 0d2f7919 +parity 95 0d2f7919 +parity 96 0d2f7919 +parity 97 0d2f7919 +parity 98 0d2f7919 +parity 99 0d2f7919 +parity 100 0d2f7919 +parity 101 0d2f7919 +parity 102 0d2f7919 +parity 103 0d2f7919 +parity 104 0d2f7919 +parity 105 0d2f7919 +parity 106 0d2f7919 +parity 107 0d2f7919 +parity 108 0d2f7919 +parity 109 0d2f7919 +parity 110 0d2f7919 +parity 111 0d2f7919 +parity 112 0d2f7919 +parity 113 0d2f7919 +parity 114 0d2f7919 +parity 115 0d2f7919 +parity 116 0d2f7919 +parity 117 0d2f7919 +parity 118 0d2f7919 +parity 119 0d2f7919 +parity 120 0d2f7919 +parity 121 0d2f7919 +parity 122 0d2f7919 +parity 123 0d2f7919 +parity 124 0d2f7919 +parity 125 0d2f7919 +parity 126 0d2f7919 +parity 127 0d2f7919 +parity 128 0d2f7919 +parity 129 0d2f7919 +parity 130 0d2f7919 +parity 131 0d2f7919 +parity 132 0d2f7919 +parity 133 0d2f7919 +parity 134 0d2f7919 +parity 135 0d2f7919 +parity 136 0d2f7919 +parity 137 0d2f7919 +parity 138 0d2f7919 +parity 139 0d2f7919 +parity 140 0d2f7919 +parity 141 0d2f7919 +parity 142 0d2f7919 +parity 143 0d2f7919 +parity 144 0d2f7919 +parity 145 0d2f7919 +parity 146 0d2f7919 +parity 147 0d2f7919 +parity 148 0d2f7919 +parity 149 0d2f7919 +parity 150 0d2f7919 +parity 151 0d2f7919 +parity 152 0d2f7919 +parity 153 0d2f7919 +parity 154 0d2f7919 +parity 155 0d2f7919 +parity 156 0d2f7919 +parity 157 0d2f7919 +parity 158 0d2f7919 +parity 159 0d2f7919 +parity 160 0d2f7919 +parity 161 0d2f7919 +parity 162 0d2f7919 +parity 163 0d2f7919 +parity 164 0d2f7919 +parity 165 0d2f7919 +parity 166 0d2f7919 +parity 167 0d2f7919 +parity 168 0d2f7919 +parity 169 0d2f7919 +parity 170 0d2f7919 +parity 171 0d2f7919 +parity 172 0d2f7919 +parity 173 0d2f7919 +parity 174 0d2f7919 +parity 175 0d2f7919 +parity 176 0d2f7919 +parity 177 0d2f7919 +parity 178 0d2f7919 +parity 179 0d2f7919 +parity 180 0d2f7919 +parity 181 0d2f7919 +parity 182 0d2f7919 +parity 183 0d2f7919 +parity 184 0d2f7919 +parity 185 0d2f7919 +parity 186 0d2f7919 +parity 187 0d2f7919 +parity 188 0d2f7919 +parity 189 0d2f7919 +parity 190 0d2f7919 +parity 191 0d2f7919 +parity 192 0d2f7919 +parity 193 0d2f7919 +parity 194 0d2f7919 +parity 195 0d2f7919 +parity 196 0d2f7919 +parity 197 0d2f7919 +parity 198 0d2f7919 +parity 199 0d2f7919 +parity 200 0d2f7919 +parity 201 0d2f7919 +parity 202 0d2f7919 +parity 203 0d2f7919 +parity 204 0d2f7919 +parity 205 0d2f7919 +parity 206 0d2f7919 +parity 207 0d2f7919 +parity 208 0d2f7919 +parity 209 0d2f7919 +parity 210 0d2f7919 +parity 211 0d2f7919 +parity 212 0d2f7919 +parity 213 0d2f7919 +parity 214 0d2f7919 +parity 215 0d2f7919 +parity 216 0d2f7919 +parity 217 0d2f7919 +parity 218 0d2f7919 +parity 219 0d2f7919 +parity 220 0d2f7919 +parity 221 0d2f7919 +parity 222 0d2f7919 +parity 223 0d2f7919 +parity 224 0d2f7919 +parity 225 0d2f7919 +parity 226 0d2f7919 +parity 227 0d2f7919 +parity 228 0d2f7919 +parity 229 0d2f7919 +parity 230 0d2f7919 +parity 231 0d2f7919 +parity 232 0d2f7919 +parity 233 0d2f7919 +parity 234 0d2f7919 +parity 235 0d2f7919 +parity 236 0d2f7919 +parity 237 0d2f7919 +parity 238 0d2f7919 +parity 239 0d2f7919 +parity 240 0d2f7919 +parity 241 0d2f7919 +parity 242 0d2f7919 +parity 243 0d2f7919 +parity 244 0d2f7919 +parity 245 0d2f7919 +parity 246 0d2f7919 +parity 247 0d2f7919 +parity 248 0d2f7919 +parity 249 0d2f7919 +parity 250 0d2f7919 +parity 251 0d2f7919 +parity 252 0d2f7919 +parity 253 0d2f7919 +parity 254 0d2f7919 +erase 0 +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 2 254 2 lcg +parity 0 1de3 +parity 1 061b +parity 2 47c9 +parity 3 5c31 +parity 4 7124 +parity 5 6adc +parity 6 f39d +parity 7 e865 +parity 8 c570 +parity 9 de88 +parity 10 9f5a +parity 11 84a2 +parity 12 a9b7 +parity 13 b24f +parity 14 8635 +parity 15 9dcd +parity 16 b0d8 +parity 17 ab20 +parity 18 eaf2 +parity 19 f10a +parity 20 dc1f +parity 21 c7e7 +parity 22 5ea6 +parity 23 455e +parity 24 684b +parity 25 73b3 +parity 26 3261 +parity 27 2999 +parity 28 048c +parity 29 1f74 +parity 30 6c78 +parity 31 7780 +parity 32 5a95 +parity 33 416d +parity 34 00bf +parity 35 1b47 +parity 36 3652 +parity 37 2daa +parity 38 b4eb +parity 39 af13 +parity 40 8206 +parity 41 99fe +parity 42 d82c +parity 43 c3d4 +parity 44 eec1 +parity 45 f539 +parity 46 c143 +parity 47 dabb +parity 48 f7ae +parity 49 ec56 +parity 50 ad84 +parity 51 b67c +parity 52 9b69 +parity 53 8091 +parity 54 19d0 +parity 55 0228 +parity 56 2f3d +parity 57 34c5 +parity 58 7517 +parity 59 6eef +parity 60 43fa +parity 61 5802 +parity 62 a5e2 +parity 63 be1a +parity 64 930f +parity 65 88f7 +parity 66 c925 +parity 67 d2dd +parity 68 ffc8 +parity 69 e430 +parity 70 7d71 +parity 71 6689 +parity 72 4b9c +parity 73 5064 +parity 74 11b6 +parity 75 0a4e +parity 76 275b +parity 77 3ca3 +parity 78 08d9 +parity 79 1321 +parity 80 3e34 +parity 81 25cc +parity 82 641e +parity 83 7fe6 +parity 84 52f3 +parity 85 490b +parity 86 d04a +parity 87 cbb2 +parity 88 e6a7 +parity 89 fd5f +parity 90 bc8d +parity 91 a775 +parity 92 8a60 +parity 93 9198 +parity 94 e294 +parity 95 f96c +parity 96 d479 +parity 97 cf81 +parity 98 8e53 +parity 99 95ab +parity 100 b8be +parity 101 a346 +parity 102 3a07 +parity 103 21ff +parity 104 0cea +parity 105 1712 +parity 106 56c0 +parity 107 4d38 +parity 108 602d +parity 109 7bd5 +parity 110 4faf +parity 111 5457 +parity 112 7942 +parity 113 62ba +parity 114 2368 +parity 115 3890 +parity 116 1585 +parity 117 0e7d +parity 118 973c +parity 119 8cc4 +parity 120 a1d1 +parity 121 ba29 +parity 122 fbfb +parity 123 e003 +parity 124 cd16 +parity 125 d6ee +parity 126 2acb +parity 127 3133 +parity 128 1c26 +parity 129 07de +parity 130 460c +parity 131 5df4 +parity 132 70e1 +parity 133 6b19 +parity 134 f258 +parity 135 e9a0 +parity 136 c4b5 +parity 137 df4d +parity 138 9e9f +parity 139 8567 +parity 140 a872 +parity 141 b38a +parity 142 87f0 +parity 143 9c08 +parity 144 b11d +parity 145 aae5 +parity 146 eb37 +parity 147 f0cf +parity 148 ddda +parity 149 c622 +parity 150 5f63 +parity 151 449b +parity 152 698e +parity 153 7276 +parity 154 33a4 +parity 155 285c +parity 156 0549 +parity 157 1eb1 +parity 158 6dbd +parity 159 7645 +parity 160 5b50 +parity 161 40a8 +parity 162 017a +parity 163 1a82 +parity 164 3797 +parity 165 2c6f +parity 166 b52e +parity 167 aed6 +parity 168 83c3 +parity 169 983b +parity 170 d9e9 +parity 171 c211 +parity 172 ef04 +parity 173 f4fc +parity 174 c086 +parity 175 db7e +parity 176 f66b +parity 177 ed93 +parity 178 ac41 +parity 179 b7b9 +parity 180 9aac +parity 181 8154 +parity 182 1815 +parity 183 03ed +parity 184 2ef8 +parity 185 3500 +parity 186 74d2 +parity 187 6f2a +parity 188 423f +parity 189 59c7 +parity 190 a427 +parity 191 bfdf +parity 192 92ca +parity 193 8932 +parity 194 c8e0 +parity 195 d318 +parity 196 fe0d +parity 197 e5f5 +parity 198 7cb4 +parity 199 674c +parity 200 4a59 +parity 201 51a1 +parity 202 1073 +parity 203 0b8b +parity 204 269e +parity 205 3d66 +parity 206 091c +parity 207 12e4 +parity 208 3ff1 +parity 209 2409 +parity 210 65db +parity 211 7e23 +parity 212 5336 +parity 213 48ce +parity 214 d18f +parity 215 ca77 +parity 216 e762 +parity 217 fc9a +parity 218 bd48 +parity 219 a6b0 +parity 220 8ba5 +parity 221 905d +parity 222 e351 +parity 223 f8a9 +parity 224 d5bc +parity 225 ce44 +parity 226 8f96 +parity 227 946e +parity 228 b97b +parity 229 a283 +parity 230 3bc2 +parity 231 203a +parity 232 0d2f +parity 233 16d7 +parity 234 5705 +parity 235 4cfd +parity 236 61e8 +parity 237 7a10 +parity 238 4e6a +parity 239 5592 +parity 240 7887 +parity 241 637f +parity 242 22ad +parity 243 3955 +parity 244 1440 +parity 245 0fb8 +parity 246 96f9 +parity 247 8d01 +parity 248 a014 +parity 249 bbec +parity 250 fa3e +parity 251 e1c6 +parity 252 ccd3 +parity 253 d72b +erase 0,1 +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253 +erase 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 128 128 1 lcg +parity 0 3f +parity 1 67 +parity 2 58 +parity 3 ac +parity 4 d6 +parity 5 45 +parity 6 7a +parity 7 81 +parity 8 b0 +parity 9 5d +parity 10 5a +parity 11 e7 +parity 12 35 +parity 13 da +parity 14 5d +parity 15 31 +parity 16 03 +parity 17 35 +parity 18 5c +parity 19 a1 +parity 20 00 +parity 21 4a +parity 22 70 +parity 23 5b +parity 24 13 +parity 25 b4 +parity 26 9c +parity 27 dc +parity 28 f6 +parity 29 9b +parity 30 f1 +parity 31 38 +parity 32 20 +parity 33 59 +parity 34 f9 +parity 35 84 +parity 36 dc +parity 37 4d +parity 38 a6 +parity 39 68 +parity 40 e8 +parity 41 2c +parity 42 8b +parity 43 93 +parity 44 e2 +parity 45 75 +parity 46 55 +parity 47 d1 +parity 48 43 +parity 49 0c +parity 50 9f +parity 51 c7 +parity 52 c0 +parity 53 cc +parity 54 89 +parity 55 77 +parity 56 68 +parity 57 d4 +parity 58 56 +parity 59 a5 +parity 60 b3 +parity 61 c1 +parity 62 12 +parity 63 e4 +parity 64 0d +parity 65 a8 +parity 66 88 +parity 67 9c +parity 68 f9 +parity 69 b9 +parity 70 27 +parity 71 b9 +parity 72 c3 +parity 73 3a +parity 74 84 +parity 75 f9 +parity 76 d9 +parity 77 0a +parity 78 8c +parity 79 79 +parity 80 c7 +parity 81 f4 +parity 82 46 +parity 83 ff +parity 84 ce +parity 85 e7 +parity 86 1c +parity 87 72 +parity 88 07 +parity 89 c2 +parity 90 d1 +parity 91 89 +parity 92 7c +parity 93 ab +parity 94 8d +parity 95 80 +parity 96 3c +parity 97 aa +parity 98 b7 +parity 99 ee +parity 100 5c +parity 101 14 +parity 102 98 +parity 103 05 +parity 104 73 +parity 105 07 +parity 106 9a +parity 107 01 +parity 108 0d +parity 109 f3 +parity 110 9f +parity 111 da +parity 112 3e +parity 113 d9 +parity 114 67 +parity 115 d9 +parity 116 69 +parity 117 7f +parity 118 e2 +parity 119 76 +parity 120 71 +parity 121 a5 +parity 122 7a +parity 123 8d +parity 124 cc +parity 125 5b +parity 126 2f +parity 127 ba +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127 +erase 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191 +erase 64,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 128 128 4 lcg +parity 0 3fd0cd4e +parity 1 67acce3d +parity 2 58c5aec7 +parity 3 ac1f402d +parity 4 d680e2ae +parity 5 454d646d +parity 6 7a15f048 +parity 7 81db42c0 +parity 8 b041c75a +parity 9 5dba99ff +parity 10 5a4d1ce6 +parity 11 e7ca523e +parity 12 35ee7d2a +parity 13 da241b58 +parity 14 5de4a50e +parity 15 31caf2ad +parity 16 0369da6c +parity 17 35997fc0 +parity 18 5c0294e7 +parity 19 a1971585 +parity 20 002a2321 +parity 21 4a974d65 +parity 22 70d4ab7a +parity 23 5b1a8ec6 +parity 24 1395171f +parity 25 b4a002a7 +parity 26 9cf90bbe +parity 27 dcdf3f10 +parity 28 f6cf46fa +parity 29 9b9a0b6a +parity 30 f11027db +parity 31 3833f7af +parity 32 20828a5b +parity 33 596d355f +parity 34 f9f209d1 +parity 35 84b632c0 +parity 36 dce8d468 +parity 37 4d726181 +parity 38 a6835e72 +parity 39 68910de5 +parity 40 e80d1978 +parity 41 2c63092a +parity 42 8bc522ee +parity 43 930f5c98 +parity 44 e221177a +parity 45 75147f7c +parity 46 558b8934 +parity 47 d1a202c7 +parity 48 4311487e +parity 49 0c846b66 +parity 50 9f5d19d2 +parity 51 c761f90c +parity 52 c07a9e70 +parity 53 ccfcf9ab +parity 54 89c8a8fc +parity 55 7779d864 +parity 56 685c954e +parity 57 d448cdf2 +parity 58 56421c35 +parity 59 a59f2518 +parity 60 b3e1bb66 +parity 61 c157d8f2 +parity 62 122452aa +parity 63 e4037089 +parity 64 0dd99b70 +parity 65 a8a9472c +parity 66 8834af03 +parity 67 9c61990b +parity 68 f9545ded +parity 69 b90d3aa6 +parity 70 27bfc30d +parity 71 b92302fc +parity 72 c3cba2dd +parity 73 3acc4530 +parity 74 84d610c8 +parity 75 f9fac232 +parity 76 d97bc887 +parity 77 0a27b288 +parity 78 8c8d80a1 +parity 79 79cd4482 +parity 80 c72e0490 +parity 81 f42d6c27 +parity 82 46173b48 +parity 83 ff95a4c6 +parity 84 ceb1fbfa +parity 85 e7e8039e +parity 86 1cc326f0 +parity 87 725f2675 +parity 88 07c0d0bf +parity 89 c2b987f5 +parity 90 d1c73bfb +parity 91 89e3d746 +parity 92 7c66fdde +parity 93 ab32e989 +parity 94 8dbc7edf +parity 95 8093750b +parity 96 3c645f96 +parity 97 aa542e61 +parity 98 b77c83f1 +parity 99 eeb2e7f6 +parity 100 5c0c86c6 +parity 101 14d3453b +parity 102 98ce7f22 +parity 103 05d8e6a8 +parity 104 73717e05 +parity 105 070cb0d5 +parity 106 9a78d5aa +parity 107 01c97566 +parity 108 0d924c1d +parity 109 f34be814 +parity 110 9f02b477 +parity 111 da85441b +parity 112 3e111d43 +parity 113 d9b57355 +parity 114 673936e5 +parity 115 d90c4645 +parity 116 691c9e27 +parity 117 7fd06f61 +parity 118 e21706a9 +parity 119 76758e4c +parity 120 719d6a94 +parity 121 a5950a27 +parity 122 7a0b1ba9 +parity 123 8d76695b +parity 124 cca2e422 +parity 125 5b805cd2 +parity 126 2fe56bae +parity 127 baf031ad +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127 +erase 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191 +erase 64,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 200 56 2 lcg +parity 0 946f +parity 1 3f89 +parity 2 2f17 +parity 3 2700 +parity 4 5d92 +parity 5 ba5f +parity 6 95aa +parity 7 3395 +parity 8 2109 +parity 9 a097 +parity 10 2313 +parity 11 55e1 +parity 12 9e65 +parity 13 53ce +parity 14 ce44 +parity 15 c58b +parity 16 f2bc +parity 17 64e3 +parity 18 5677 +parity 19 ba5f +parity 20 d1c9 +parity 21 e3e9 +parity 22 7b7b +parity 23 9714 +parity 24 215d +parity 25 f4c8 +parity 26 4eba +parity 27 4e9d +parity 28 2af2 +parity 29 59bb +parity 30 9674 +parity 31 8e33 +parity 32 334b +parity 33 a29b +parity 34 21eb +parity 35 6ba9 +parity 36 7b98 +parity 37 1ada +parity 38 67f0 +parity 39 70b9 +parity 40 19a7 +parity 41 ac6b +parity 42 04b4 +parity 43 1bdb +parity 44 fd80 +parity 45 91c6 +parity 46 b69e +parity 47 da95 +parity 48 1524 +parity 49 4232 +parity 50 8464 +parity 51 f86e +parity 52 2a39 +parity 53 6429 +parity 54 4bcf +parity 55 ea24 +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55 +erase 0,7,14,21,28,35,42,50,57,64,71,78,85,92,100,107,114,121,128,135,142,150,157,164,171,178,185,192,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227 +erase 100,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +erase 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 +case 250 6 5 lcg +parity 0 cd4bb59e20 +parity 1 15f9bc891f +parity 2 8fb793679f +parity 3 fbf644d8e5 +parity 4 009c7d60c8 +parity 5 4cd67a51d4 +erase 0,1,2,3,4,5 +erase 0,83,166,250,251,252 +erase 125,250,251,252,253,254 +erase 244,245,246,247,248,249 diff --git a/packages/cultnet-rs/tests/media_fec.rs b/packages/cultnet-rs/tests/media_fec.rs index 4af9f243..c76709d4 100644 --- a/packages/cultnet-rs/tests/media_fec.rs +++ b/packages/cultnet-rs/tests/media_fec.rs @@ -1141,3 +1141,198 @@ fn a_budget_at_the_media_record_ceiling_admits_large_shards_end_to_end() { .unwrap(); decode_media_wire_record(&wire).unwrap(); } + +// --------------------------------------------------------------------------- +// Known answers from the fixture: every standard geometry and the policy's +// edges, byte for byte. The fixture's header states how its data is made. +// --------------------------------------------------------------------------- + +/// The data of a fixture case, as the fixture's header defines it. +fn kat_data(k: usize, m: usize, shard_bytes: usize, fill: &str) -> Vec> { + (0..k) + .map(|i| match fill { + "lcg" => bytes((k * 65_536 + m * 256 + i) as u32, shard_bytes), + "zero" => vec![0; shard_bytes], + "ff" => vec![0xff; shard_bytes], + other => panic!("unknown fill {other}"), + }) + .collect() +} + +struct KatCase { + k: usize, + m: usize, + shard_bytes: usize, + fill: String, + parity: Vec>, + erasures: Vec>, +} + +impl KatCase { + fn name(&self) -> String { + format!("k={} m={} {} bytes {}", self.k, self.m, self.shard_bytes, self.fill) + } + + /// The case's data as an audio block: the codec path that takes any `(k, m)`. + fn packets(&self) -> Vec { + let mut packets = audio_run(self.k as u64, self.shard_bytes); + for (packet, data) in packets.iter_mut().zip(kat_data(self.k, self.m, self.shard_bytes, &self.fill)) { + packet.payload = data; + } + packets + } + + fn policy(&self) -> MediaFecPolicy { + MediaFecPolicy { + audio_data_shards: self.k as u16, + audio_parity_shards: self.m as u16, + max_wire_bytes: GAMECULT_MEDIA_MAX_WIRE_BYTES, + ..STANDARD + } + } +} + +fn unhex(text: &str) -> Vec { + (0..text.len()) + .step_by(2) + .map(|at| u8::from_str_radix(&text[at..at + 2], 16).expect("fixture hex")) + .collect() +} + +fn kat_cases() -> Vec { + let mut cases: Vec = Vec::new(); + for line in include_str!("fixtures/media_fec_rs_gf256_v1.kat").lines() { + let fields: Vec<&str> = line.split_whitespace().collect(); + match fields.as_slice() { + [] => {} + [comment, ..] if comment.starts_with('#') => {} + ["case", k, m, shard_bytes, fill] => cases.push(KatCase { + k: k.parse().unwrap(), + m: m.parse().unwrap(), + shard_bytes: shard_bytes.parse().unwrap(), + fill: fill.to_string(), + parity: Vec::new(), + erasures: Vec::new(), + }), + ["parity", index, bytes] => { + let case = cases.last_mut().expect("parity follows a case"); + assert_eq!(index.parse::().unwrap(), case.parity.len(), "{}", case.name()); + case.parity.push(unhex(bytes)); + } + ["erase", slots] => cases + .last_mut() + .expect("erase follows a case") + .erasures + .push(slots.split(',').map(|slot| slot.parse().unwrap()).collect()), + other => panic!("unreadable fixture line {other:?}"), + } + } + cases +} + +/// Parity bytes of every fixture case, byte for byte. If these ever change, +/// `rs-gf256-v1` changed meaning: bump the scheme id, do not edit the fixture. +#[test] +fn every_rs_gf256_v1_known_answer_parity_matches_byte_for_byte() { + let cases = kat_cases(); + assert_eq!(cases.len(), 35, "the fixture's case count"); + for case in &cases { + assert_eq!(case.parity.len(), case.m, "{}", case.name()); + let parity = protect_audio_block(&case.packets(), &case.policy(), provenance()) + .unwrap_or_else(|error| panic!("{}: {error}", case.name())); + let produced: Vec = parity.iter().map(|shard| hex(&shard.payload)).collect(); + let expected: Vec = case.parity.iter().map(|shard| hex(shard)).collect(); + assert_eq!(produced, expected, "{}", case.name()); + } +} + +/// Every fixture erasure pattern, data and parity mixed, recovers exactly +/// the erased data from the fixture's own parity bytes. +#[test] +fn every_rs_gf256_v1_known_erasure_pattern_recovers_the_data() { + for case in kat_cases() { + let packets = case.packets(); + let mut parity = protect_audio_block(&packets, &case.policy(), provenance()).unwrap(); + for (shard, bytes) in parity.iter_mut().zip(&case.parity) { + shard.payload = bytes.clone(); // decode from the fixture, not from the encoder + } + assert!(!case.erasures.is_empty(), "{}", case.name()); + for erased in &case.erasures { + let data: Vec<_> = + (0..case.k).filter(|slot| !erased.contains(slot)).map(|slot| packets[slot].clone()).collect(); + let present: Vec<_> = (0..case.m) + .filter(|slot| !erased.contains(&(case.k + slot))) + .map(|slot| parity[slot].clone()) + .collect(); + let recovered = recover_audio_block(&present, &data) + .unwrap_or_else(|error| panic!("{} erased {erased:?}: {error}", case.name())); + let lost: Vec = erased.iter().copied().filter(|&slot| slot < case.k).collect(); + assert_eq!(recovered.len(), lost.len(), "{} erased {erased:?}", case.name()); + for (packet, slot) in recovered.iter().zip(lost) { + assert_eq!(packet.packet_id, packets[slot].packet_id, "{} erased {erased:?}", case.name()); + assert_eq!(packet.payload, packets[slot].payload, "{} erased {erased:?}", case.name()); + } + } + } +} + +/// Seeded random geometries past the fixture's: any `e <= m` erasures, data +/// and parity mixed, recover the data exactly, and `m + 1` are refused. +#[test] +fn random_erasures_up_to_m_recover_exactly_and_m_plus_one_is_refused() { + let mut state = 0x9e37_79b9_u32; + let mut next = |bound: usize| { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + state as usize % bound + }; + for trial in 0..300 { + let k = 1 + next(48); + let m = 1 + next(24); + let shard_bytes = 1 + next(40); + let mut packets = audio_run(k as u64, shard_bytes); + for (index, packet) in packets.iter_mut().enumerate() { + packet.payload = bytes((trial * 1_000 + index) as u32, shard_bytes); + } + let policy = MediaFecPolicy { + audio_data_shards: k as u16, + audio_parity_shards: m as u16, + ..STANDARD + }; + let parity = protect_audio_block(&packets, &policy, provenance()).unwrap(); + for erasures in [1 + next(m), m + 1] { + if erasures + 1 > k + m { + continue; // at least one parity shard must arrive to recover with + } + // A random erasure set that leaves at least one parity shard. + let mut slots: Vec = (0..k + m).collect(); + let erased = loop { + for at in 0..slots.len() { + let swap = at + next(slots.len() - at); + slots.swap(at, swap); + } + let erased = slots[..erasures].to_vec(); + if (k..k + m).any(|slot| !erased.contains(&slot)) { + break erased; + } + }; + let data: Vec<_> = + (0..k).filter(|slot| !erased.contains(slot)).map(|slot| packets[slot].clone()).collect(); + let present: Vec<_> = + (0..m).filter(|slot| !erased.contains(&(k + slot))).map(|slot| parity[slot].clone()).collect(); + let outcome = recover_audio_block(&present, &data); + let context = format!("trial {trial}: k={k} m={m} {shard_bytes} bytes erased {erased:?}"); + if erasures <= m { + let mut all = data; + all.extend(outcome.unwrap_or_else(|error| panic!("{context}: {error}"))); + all.sort_by_key(|packet| packet.packet_id); + let payloads: Vec<_> = all.iter().map(|packet| &packet.payload).collect(); + let expected: Vec<_> = packets.iter().map(|packet| &packet.payload).collect(); + assert_eq!(payloads, expected, "{context}"); + } else { + assert!(matches!(outcome, Err(MediaFecError::BeyondRepair { .. })), "{context}: {outcome:?}"); + } + } + } +} diff --git a/packages/cultnet-rs/tests/media_fec_bench.rs b/packages/cultnet-rs/tests/media_fec_bench.rs new file mode 100644 index 00000000..40c9de78 --- /dev/null +++ b/packages/cultnet-rs/tests/media_fec_bench.rs @@ -0,0 +1,88 @@ +//! Timings for the media FEC codec, without a bench framework. +//! +//! Ignored by default; run in release, where the numbers mean something: +//! +//! ```text +//! cargo test --release --test media_fec_bench -- --ignored --nocapture +//! ``` +//! +//! Each line is nanoseconds per call through the public surface, so record +//! validation and cloning are included. The geometries are the ones the codec +//! meets: standard audio, the widest standard video block at a datagram, and +//! the worst recovery the policy admits (255 + 1). + +use std::hint::black_box; +use std::time::Instant; + +use cultnet_rs::{ + GAMECULT_MEDIA_MAX_WIRE_BYTES, GameCultMediaAudioPacketRecord, MediaFecPolicy, + MediaWireProvenance, protect_audio_block, recover_audio_block, +}; + +fn provenance() -> MediaWireProvenance<'static> { + MediaWireProvenance { + stored_at: "unix:1700000000000", + runtime_id: "bench", + role: "bench.media", + producer: "bench", + } +} + +fn packets(k: usize, len: usize) -> Vec { + let mut state = 0x2545_f491_u32; + (0..k as u64) + .map(|index| GameCultMediaAudioPacketRecord { + stream_id: "bench".to_string(), + session_id: "bench".to_string(), + packet_id: index, + codec: "opus".to_string(), + pts_ticks: 960 * index as i64, + duration_ticks: 960, + timebase_num: 1, + timebase_den: 48_000, + deadline_ticks: 960 * index as i64 + 5_000, + payload: (0..len) + .map(|_| { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + state as u8 + }) + .collect(), + }) + .collect() +} + +/// Mean nanoseconds per call over at least `min_ms`, after three warm-up calls. +fn time(mut f: impl FnMut(), min_ms: u128) -> f64 { + for _ in 0..3 { + f(); + } + let start = Instant::now(); + let mut calls = 0_u64; + while start.elapsed().as_millis() < min_ms { + f(); + calls += 1; + } + start.elapsed().as_nanos() as f64 / calls as f64 +} + +#[test] +#[ignore = "timing, not a test: run in release with --ignored --nocapture"] +fn media_fec_timings() { + for (k, m, len) in [(4, 2, 160), (4, 2, 1300), (16, 4, 1200), (255, 1, 1300)] { + let policy = MediaFecPolicy { + audio_data_shards: k as u16, + audio_parity_shards: m as u16, + max_wire_bytes: GAMECULT_MEDIA_MAX_WIRE_BYTES, + ..MediaFecPolicy::STANDARD + }; + let block = packets(k, len); + let encode = time(|| drop(black_box(protect_audio_block(black_box(&block), &policy, provenance()))), 300); + let parity = protect_audio_block(&block, &policy, provenance()).unwrap(); + let lost = m.min(k); + let survivors = &block[lost..]; + let decode = time(|| drop(black_box(recover_audio_block(black_box(&parity), survivors))), 300); + println!("k={k:3} m={m:3} len={len:5}: protect {encode:12.0} ns | recover {lost} lost {decode:12.0} ns"); + } +}