#![allow(non_snake_case)]
#![allow(mixed_script_confusables)]
use std::convert::TryFrom;
use std::fmt;
use rand;
use rand::Rng;
use ::gf256::*;
pub const DATA_SIZE: usize = 223;
pub const ECC_SIZE: usize = 32;
pub const BLOCK_SIZE: usize = DATA_SIZE + ECC_SIZE;
pub const GENERATOR_POLY: [gf256; ECC_SIZE+1] = {
let mut g = [gf256(0); ECC_SIZE+1];
g[ECC_SIZE] = gf256(1);
let mut i = 0usize;
while i < ECC_SIZE {
let root = [
gf256(1),
gf256::GENERATOR.naive_pow(i as u8),
];
let mut product = [gf256(0); ECC_SIZE+1];
let mut j = 0usize;
while j < i+1 {
let mut k = 0usize;
while k < root.len() {
product[product.len()-1-(j+k)] = product[product.len()-1-(j+k)].naive_add(
g[g.len()-1-j].naive_mul(root[root.len()-1-k])
);
k += 1;
}
j += 1;
}
g = product;
i += 1;
}
g
};
#[derive(Debug, Clone)]
pub enum RsError {
TooManyErrors,
}
impl fmt::Display for RsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RsError::TooManyErrors => write!(f, "Too many errors to correct"),
}
}
}
fn rs_poly_eval(f: &[gf256], x: gf256) -> gf256 {
let mut y = gf256(0);
for c in f {
y = y*x + c;
}
y
}
fn rs_poly_scale(f: &mut [gf256], c: gf256) {
for i in 0..f.len() {
f[i] *= c;
}
}
fn rs_poly_add(f: &mut [gf256], g: &[gf256]) {
debug_assert!(f.len() >= g.len());
for i in 0..f.len() {
f[f.len()-1-i] += g[g.len()-1-i];
}
}
fn rs_poly_mul(f: &mut [gf256], g: &[gf256]) {
debug_assert!(f[..g.len()-1].iter().all(|x| *x == gf256(0)));
for i in (0..f.len()-g.len()+1).rev() {
let fi = f[f.len()-1-i];
f[f.len()-1-i] = gf256(0);
for j in 0..g.len() {
f[f.len()-1-(i+j)] += fi * g[g.len()-1-j];
}
}
}
fn rs_poly_divrem(f: &mut [gf256], g: &[gf256]) {
debug_assert!(f.len() >= g.len());
let leading_coeff = g[0];
for i in 0 .. (f.len() - g.len() + 1) {
if f[i] != gf256(0) {
f[i] /= leading_coeff;
for j in 1..g.len() {
f[i+j] -= f[i] * g[j];
}
}
}
}
pub fn rs_encode(message: &mut [u8]) {
assert!(message.len() <= BLOCK_SIZE);
assert!(message.len() >= ECC_SIZE);
let data_len = message.len() - ECC_SIZE;
let mut divrem = message.to_vec();
divrem[data_len..].fill(0);
rs_poly_divrem(
gf256::slice_from_slice_mut(&mut divrem),
&GENERATOR_POLY
);
message[data_len..].copy_from_slice(&divrem[data_len..]);
}
fn rs_find_syndromes(f: &[gf256]) -> Vec<gf256> {
let mut S = vec![];
for i in 0..ECC_SIZE {
S.push(
rs_poly_eval(f, gf256::GENERATOR.pow(u8::try_from(i).unwrap()))
);
}
S
}
fn rs_find_forney_syndromes(
codeword: &[gf256],
S: &[gf256],
erasures: &[usize]
) -> Vec<gf256> {
let mut S = S.to_vec();
for j in erasures {
let Xj = gf256::GENERATOR.pow(u8::try_from(codeword.len()-1-j).unwrap());
for i in 0 .. S.len()-1 {
S[i] = S[i+1] - S[i]*Xj;
}
}
S.drain(S.len()-erasures.len()..);
S
}
fn rs_find_erasure_locator(codeword: &[gf256], erasures: &[usize]) -> Vec<gf256> {
let mut Λ = vec![gf256(0); erasures.len()+1];
let Λ_len = Λ.len();
Λ[Λ_len-1] = gf256(1);
for j in erasures {
rs_poly_mul(&mut Λ, &[
-gf256::GENERATOR.pow(u8::try_from(codeword.len()-1-j).unwrap()),
gf256(1)
]);
}
Λ
}
fn rs_find_error_locator(S: &[gf256]) -> Vec<gf256> {
let mut Λ = vec![gf256(0); S.len()+1];
let Λ_len = Λ.len();
Λ[Λ_len-1] = gf256(1);
let mut prev_Λ = Λ.clone();
let mut delta_Λ = Λ.clone();
let mut v = 0;
for i in 0..S.len() {
let mut delta = S[i];
for j in 1..v+1 {
delta += Λ[Λ.len()-1-j] * S[i-j];
}
prev_Λ.rotate_left(1);
if delta != gf256(0) {
if 2*v <= i {
core::mem::swap(&mut Λ, &mut prev_Λ);
rs_poly_scale(&mut Λ, delta);
rs_poly_scale(&mut prev_Λ, delta.recip());
v = i+1-v;
}
delta_Λ.copy_from_slice(&prev_Λ);
rs_poly_scale(&mut delta_Λ, delta);
rs_poly_add(&mut Λ, &delta_Λ);
}
}
let zeros = Λ.iter().take_while(|x| **x == gf256(0)).count();
Λ.drain(0..zeros);
Λ
}
fn rs_find_error_locations(codeword: &[gf256], Λ: &[gf256]) -> Vec<usize> {
let mut error_locations = vec![];
for j in 0..codeword.len() {
let Xj = gf256::GENERATOR.pow(u8::try_from(codeword.len()-1-j).unwrap());
let zero = rs_poly_eval(&Λ, Xj.recip());
if zero == gf256(0) {
error_locations.push(j);
}
}
error_locations
}
fn rs_find_error_magnitudes(
codeword: &[gf256],
S: &[gf256],
Λ: &[gf256],
error_locations: &[usize]
) -> Vec<gf256> {
let mut Ω = vec![gf256(0); S.len()+Λ.len()-1];
let Ω_len = Ω.len();
Ω[Ω_len-S.len()..].copy_from_slice(&S);
Ω[Ω_len-S.len()..].reverse();
rs_poly_mul(&mut Ω, &Λ);
Ω.drain(..Ω.len()-S.len());
let mut Λ_prime = vec![gf256(0); Λ.len()-1];
for i in 1..Λ.len() {
let mut sum = gf256(0);
for _ in 0..i {
sum += Λ[Λ.len()-1-i];
}
let Λ_prime_len = Λ_prime.len();
Λ_prime[Λ_prime_len-1-(i-1)] = sum;
}
let mut error_magnitudes = vec![];
for j in error_locations {
let Xj = gf256::GENERATOR.pow(u8::try_from(codeword.len()-1-j).unwrap());
let Yj = (-Xj*rs_poly_eval(&Ω, Xj.recip()))
.checked_div(rs_poly_eval(&Λ_prime, Xj.recip()))
.unwrap_or(gf256(0));
error_magnitudes.push(Yj);
}
error_magnitudes
}
pub fn rs_is_correct(codeword: &[u8]) -> bool {
let codeword = gf256::slice_from_slice(codeword);
let syndromes = rs_find_syndromes(codeword);
syndromes.iter().all(|s| *s == gf256(0))
}
pub fn rs_correct_erasures(
codeword: &mut [u8],
erasures: &[usize]
) -> Result<usize, RsError> {
let codeword = gf256::slice_from_slice_mut(codeword);
if erasures.len() > ECC_SIZE {
return Err(RsError::TooManyErrors);
}
let S = rs_find_syndromes(codeword);
if S.iter().all(|s| *s == gf256(0)) {
return Ok(0);
}
let Λ = rs_find_erasure_locator(codeword, &erasures);
let erasure_magnitudes = rs_find_error_magnitudes(
codeword,
&S,
&Λ,
&erasures,
);
for (&Xj, Yj) in erasures.iter().zip(erasure_magnitudes) {
codeword[Xj] += Yj;
}
let S = rs_find_syndromes(codeword);
if !S.iter().all(|s| *s == gf256(0)) {
return Err(RsError::TooManyErrors);
}
Ok(erasures.len())
}
pub fn rs_correct_errors(codeword: &mut [u8]) -> Result<usize, RsError> {
let codeword = gf256::slice_from_slice_mut(codeword);
let S = rs_find_syndromes(codeword);
if S.iter().all(|s| *s == gf256(0)) {
return Ok(0);
}
let Λ = rs_find_error_locator(&S);
let error_count = Λ.len() - 1;
if error_count*2 > ECC_SIZE {
return Err(RsError::TooManyErrors);
}
let error_locations = rs_find_error_locations(codeword, &Λ);
let error_magnitudes = rs_find_error_magnitudes(
codeword,
&S,
&Λ,
&error_locations,
);
for (&Xj, Yj) in error_locations.iter().zip(error_magnitudes) {
codeword[Xj] += Yj;
}
let S = rs_find_syndromes(codeword);
if !S.iter().all(|s| *s == gf256(0)) {
return Err(RsError::TooManyErrors);
}
Ok(error_locations.len())
}
pub fn rs_correct(
codeword: &mut [u8],
erasures: &[usize]
) -> Result<usize, RsError> {
let codeword = gf256::slice_from_slice_mut(codeword);
if erasures.len() > ECC_SIZE {
return Err(RsError::TooManyErrors);
}
let S = rs_find_syndromes(codeword);
if S.iter().all(|s| *s == gf256(0)) {
return Ok(0);
}
let forney_S = rs_find_forney_syndromes(codeword, &S, &erasures);
let Λ = rs_find_error_locator(&forney_S);
let error_count = Λ.len() - 1;
let erasure_count = erasures.len();
if error_count*2 + erasure_count > ECC_SIZE {
return Err(RsError::TooManyErrors);
}
let mut error_locations = rs_find_error_locations(codeword, &Λ);
error_locations.extend_from_slice(&erasures);
let Λ = rs_find_erasure_locator(codeword, &error_locations);
let error_magnitudes = rs_find_error_magnitudes(
codeword,
&S,
&Λ,
&error_locations,
);
for (&Xj, Yj) in error_locations.iter().zip(error_magnitudes) {
codeword[Xj] += Yj;
}
let S = rs_find_syndromes(codeword);
if !S.iter().all(|s| *s == gf256(0)) {
return Err(RsError::TooManyErrors);
}
Ok(error_locations.len())
}
fn main() {
fn hex(xs: &[u8]) -> String {
xs.iter()
.map(|x| format!("{:02x}", x))
.collect()
}
fn ascii(xs: &[u8]) -> String {
xs.iter()
.map(|x| {
if *x < b' ' || *x > b'~' {
'.'
} else {
char::from(*x)
}
})
.collect::<String>()
}
let orig_message = b"Hello World!";
println!();
println!("testing rs({:?})", ascii(orig_message));
println!("dimension = ({},{}), {} errors, {} erasures",
BLOCK_SIZE,
DATA_SIZE,
ECC_SIZE / 2,
ECC_SIZE
);
println!("generator = {}",
hex(&GENERATOR_POLY.iter()
.map(|b| u8::from(*b))
.collect::<Vec<_>>())
);
let mut message = vec![0u8; orig_message.len()+ECC_SIZE];
message[..orig_message.len()].copy_from_slice(&orig_message[..]);
rs_encode(&mut message);
println!("{:<19} => {} {}",
"rs_encode",
ascii(&message),
hex(&message)
);
let mut rng = rand::thread_rng();
let errors = rand::seq::index::sample(&mut rng, message.len(), ECC_SIZE).into_vec();
for error in errors.iter() {
message[*error] = b'x';
}
println!("{:<19} => {} {}",
format!("corrupted ({},{})", ECC_SIZE, 0),
ascii(&message),
hex(&message)
);
rs_correct_erasures(&mut message, &errors).unwrap();
println!("{:<19} => {} {}",
"rs_correct_erasures",
ascii(&message),
hex(&message)
);
assert_eq!(
&message[0..orig_message.len()],
orig_message
);
let mut rng = rand::thread_rng();
let errors = rand::seq::index::sample(&mut rng, message.len(), ECC_SIZE/2).into_vec();
for error in errors.iter() {
message[*error] = b'x';
}
println!("{:<19} => {} {}",
format!("corrupted ({},{})", 0, ECC_SIZE/2),
ascii(&message),
hex(&message)
);
rs_correct_errors(&mut message).unwrap();
println!("{:<19} => {} {}",
"rs_correct_errors",
ascii(&message),
hex(&message)
);
assert_eq!(
&message[0..orig_message.len()],
orig_message
);
let mut rng = rand::thread_rng();
let erasure_count = rng.gen_range(0..ECC_SIZE);
let errors = rand::seq::index::sample(&mut rng, message.len(),
erasure_count + (ECC_SIZE-erasure_count)/2
).into_vec();
for error in errors.iter() {
message[*error] = b'x';
}
println!("{:<19} => {} {}",
format!("corrupted ({},{})", erasure_count, (ECC_SIZE-erasure_count)/2),
ascii(&message),
hex(&message)
);
rs_correct(&mut message, &errors[..erasure_count]).unwrap();
println!("{:<19} => {} {}",
"rs_correct",
ascii(&message),
hex(&message)
);
assert_eq!(
&message[0..orig_message.len()],
orig_message
);
println!();
fn dots<'a>(width: usize, dots: &'a [u8]) -> impl Iterator<Item=String> + 'a {
fn todots(d: u8) -> String {
fn tod(d: u8) -> char {
match d & 0x3 {
0x0 => ' ',
0x1 => '\'',
0x2 => '.',
0x3 => ':',
_ => unreachable!(),
}
}
[tod(d >> 6), tod(d >> 4), tod(d >> 2), tod(d >> 0)]
.iter()
.collect::<String>()
}
(0..dots.len())
.step_by(width)
.map(move |d| {
dots[d..d+width]
.iter()
.map(|d| todots(*d))
.collect::<String>()
})
}
let width = 16;
let image = [
0x00, 0x00, 0x00, 0x00, 0x0a, 0xff, 0xff, 0xfe, 0xa0, 0x00, 0x00, 0x00, 0x00, 0xbf, 0xaa, 0xfe,
0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf4,
0x00, 0x00, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x23, 0xff, 0xff, 0xc0,
0x00, 0x00, 0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x2a, 0x3c, 0xdf, 0xff, 0x40,
0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x4a, 0x1f, 0xc4, 0x00, 0x54, 0x00,
0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5a, 0xff, 0xc4, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x52, 0xbf, 0xf5, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x52, 0xbf, 0xf5, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x2b, 0x3f, 0xff, 0xff, 0xfd, 0x52, 0xbf, 0xf5, 0x2b, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0a, 0xfd, 0x0f, 0xff, 0xfd, 0x4a, 0xff, 0xf5, 0x2b, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
0x3f, 0x40, 0x07, 0xf5, 0x2a, 0xff, 0xf5, 0xab, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00,
0xf4, 0x00, 0x00, 0xab, 0xff, 0xd4, 0xaf, 0xff, 0xff, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x2a, 0xbf, 0xff, 0x52, 0xbf, 0xff, 0xff, 0xff, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7f, 0xff, 0xd5, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0xff, 0xff, 0xfd, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let block = 64;
let ecc_width = ((block+ECC_SIZE) / (block / width)) - width;
let stripe = |encoded: &mut [u8], i: usize, slice: &[u8]| {
for j in 0 .. block/width {
encoded[i*(width+ecc_width) + j*(width+ecc_width)*image.len()/block
.. i*(width+ecc_width) + j*(width+ecc_width)*image.len()/block + width]
.copy_from_slice(&slice[j*width .. (j+1)*width]);
encoded[i*(width+ecc_width) + j*(width+ecc_width)*image.len()/block + width
.. i*(width+ecc_width) + j*(width+ecc_width)*image.len()/block + width+ecc_width]
.copy_from_slice(&slice[block + j*ecc_width .. block + (j+1)*ecc_width]);
}
};
let unstripe = |encoded: &[u8], i: usize| -> Vec<u8> {
let mut slice = vec![0; block+ECC_SIZE];
for j in 0 .. block/width {
slice[j*width .. (j+1)*width]
.copy_from_slice(&encoded[i*(width+ecc_width) + j*(width+ecc_width)*image.len()/block
.. i*(width+ecc_width) + j*(width+ecc_width)*image.len()/block + width]);
slice[block + j*ecc_width .. block + (j+1)*ecc_width]
.copy_from_slice(&encoded[i*(width+ecc_width) + j*(width+ecc_width)*image.len()/block + width
.. i*(width+ecc_width) + j*(width+ecc_width)*image.len()/block + width+ecc_width]);
}
slice
};
let correct = |encoded: &mut [u8]| -> Result<(), RsError> {
for i in 0 .. encoded.len()/(block+ECC_SIZE) {
let mut slice = unstripe(encoded, i);
rs_correct_errors(&mut slice)?;
stripe(encoded, i, &slice);
}
Ok(())
};
let mut encoded = vec![0; image.len() + (image.len()/block)*ECC_SIZE];
for i in 0 .. image.len()/block {
let mut slice = vec![0; block + ECC_SIZE];
for j in 0 .. block/width {
slice[j*width .. (j+1)*width]
.copy_from_slice(&image[i*width + j*width*image.len()/block
.. i*width + j*width*image.len()/block + width]);
}
rs_encode(&mut slice);
stripe(&mut encoded, i, &slice);
}
let orig = encoded.clone();
let mut prev_errored = encoded.clone();
let mut errors = 0;
let mut rng = rand::thread_rng();
loop {
for _ in 0..errors {
let error = rng.gen_range(0..8*encoded.len());
let coord = error / 8;
let bit = error % 8;
encoded[coord] ^= 1 << bit;
}
let errored = encoded.clone();
match correct(&mut encoded) {
Ok(()) => {}
Err(RsError::TooManyErrors) => {
break
}
}
prev_errored = errored;
errors += 1;
}
println!("bit corrupted image (errors = {}/{}, {:.2}%):",
errors,
8*encoded.len(),
100.0 * (errors as f64 / ((8*encoded.len()) as f64)));
println!();
for line in dots(width+ecc_width, &prev_errored) {
println!(" {}", line);
}
println!();
encoded = orig.clone();
let mut prev_errored = encoded.clone();
let mut errors = 0;
let mut rng = rand::thread_rng();
loop {
for _ in 0..errors {
let error = rng.gen_range(0..encoded.len());
if encoded[error].count_ones() > 4 {
encoded[error] = 0x00;
} else {
encoded[error] = 0xff;
}
}
let errored = encoded.clone();
match correct(&mut encoded) {
Ok(()) => {}
Err(RsError::TooManyErrors) => {
break
}
}
prev_errored = errored;
errors += 1;
}
println!("byte corrupted image (errors = {}/{}, {:.2}%):",
errors,
encoded.len(),
100.0 * (errors as f64 / (encoded.len() as f64)));
println!();
for line in dots(width+ecc_width, &prev_errored) {
println!(" {}", line);
}
println!();
correct(&mut prev_errored).unwrap();
println!("corrected:");
println!();
for line in dots(width+ecc_width, &prev_errored) {
println!(" {}", line);
}
println!();
let encoded_size = encoded.len();
let mkparity = |slice: &mut [u8]| {
let encoded = &slice[..encoded_size];
let parity = (0..encoded_size/8)
.map(|i| {
let mut p = 0;
for j in 0..8 {
let coord = i + j*(encoded_size/8);
p |= (encoded[coord].count_ones() as u8 & 1) << j;
}
p
})
.collect::<Vec<_>>();
slice[encoded_size..].copy_from_slice(&parity);
};
let chparity = |slice: &[u8]| -> Vec<usize> {
let mut erasures = vec![];
let encoded = &slice[..encoded_size];
let parity = &slice[encoded_size..];
for i in 0..encoded_size/8 {
for j in 0..8 {
let coord = i + j*(encoded_size/8);
if (encoded[coord].count_ones() as u8 & 1)
!= ((parity[i] >> j) & 1)
{
erasures.push(coord);
}
}
}
erasures
};
let correct = |encoded: &mut [u8]| -> Result<(), RsError> {
let erasures = chparity(encoded);
let mut encoded_erasures = vec![0; encoded_size];
for i in erasures {
encoded_erasures[i] = 1;
}
for i in 0 .. encoded_size/(block+ECC_SIZE) {
let mut slice = unstripe(&encoded, i);
let slice_erasures = unstripe(&encoded_erasures, i);
let erasures = slice_erasures.iter()
.enumerate()
.filter(|(_, erasure)| **erasure != 0)
.map(|(i, _)| i)
.collect::<Vec<_>>();
rs_correct(&mut slice, &erasures)?;
stripe(encoded, i, &slice);
mkparity(encoded);
}
Ok(())
};
encoded = orig.clone();
encoded.resize(encoded_size + encoded_size/8, 0);
mkparity(&mut encoded);
let orig = encoded.clone();
let mut prev_errored = encoded.clone();
let mut errors = 0;
let mut rng = rand::thread_rng();
loop {
for _ in 0..errors {
let error = rng.gen_range(0..8*encoded.len());
let coord = error / 8;
let bit = error % 8;
encoded[coord] ^= 1 << bit;
}
let errored = encoded.clone();
match correct(&mut encoded) {
Ok(()) => {}
Err(RsError::TooManyErrors) => {
break
}
}
prev_errored = errored;
errors += 1;
}
println!("bit corrupted image (errors = {}/{}, {:.2}%):",
errors,
8*encoded.len(),
100.0 * (errors as f64 / ((8*encoded.len()) as f64)));
println!();
for line in dots(width+ecc_width, &prev_errored) {
println!(" {}", line);
}
println!();
encoded = orig.clone();
let mut prev_errored = encoded.clone();
let mut errors = 0;
let mut rng = rand::thread_rng();
loop {
for _ in 0..errors {
let error = rng.gen_range(0..encoded_size);
if encoded[error].count_ones() > 4 {
encoded[error] = 0x00;
encoded[encoded_size + error%(encoded_size/8)]
&= !(1 << (error/(encoded_size/8)));
} else {
encoded[error] = 0xff;
encoded[encoded_size + error%(encoded_size/8)]
|= 1 << (error/(encoded_size/8));
}
}
let errored = encoded.clone();
match correct(&mut encoded) {
Ok(()) => {}
Err(RsError::TooManyErrors) => {
break
}
}
prev_errored = errored;
errors += 1;
}
println!("byte corrupted image (errors = {}/{}, {:.2}%):",
errors,
(encoded.len()*8/9),
100.0 * (errors as f64 / ((encoded.len()*8/9) as f64)));
println!();
for line in dots(width+ecc_width, &prev_errored) {
println!(" {}", line);
}
println!();
correct(&mut prev_errored).unwrap();
println!("corrected:");
println!();
for line in dots(width+ecc_width, &prev_errored) {
println!(" {}", line);
}
println!();
}