All files / qr-code/src/encode reedSolomon.ts

100% Statements 24/24
100% Branches 8/8
100% Functions 2/2
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                  17x                     4033x 3x   4030x 4030x   80x 80x 1409x   80x 80x                     4011x   4011x 4011x   4011x 145006x 145006x   144057x 4190155x       4011x    
import { gfExp, gfMul, polyMul } from "./galois";
 
/**
 * Reed–Solomon error correction over GF(256) (ISO/IEC 18004 §7.5).
 *
 * The generator polynomial of degree `n` is `Π (x - α^i)` for `i` in `[0, n)`.
 * Because subtraction is XOR in a binary field, `-α^i` is just `α^i`.
 */
 
const generatorCache = new Map<number, Uint8Array>();
 
/**
 * The Reed–Solomon generator polynomial of the given degree, highest-degree
 * coefficient first (always 1). Cached — QR only ever uses a handful of
 * degrees (7…30).
 *
 * Returns a copy: the cache is process-wide, and handing out the live array
 * would let one caller's mutation corrupt every symbol encoded afterwards.
 */
export function generatorPoly(degree: number): Uint8Array {
  if (!Number.isInteger(degree) || degree < 1) {
    throw new RangeError(`Generator polynomial degree must be a positive integer, got ${degree}`);
  }
  const cached = generatorCache.get(degree);
  if (cached) return cached.slice();
 
  let poly: Uint8Array = new Uint8Array([1]);
  for (let i = 0; i < degree; i++) {
    poly = polyMul(poly, new Uint8Array([1, gfExp(i)]));
  }
  generatorCache.set(degree, poly);
  return poly.slice();
}
 
/**
 * The `ecLength` error-correction codewords for one data block — the remainder
 * of `data · x^ecLength` divided by the generator polynomial.
 *
 * @param data - Data codewords for a single block.
 * @param ecLength - Number of EC codewords to produce.
 */
export function rsEncode(data: Readonly<Uint8Array>, ecLength: number): Uint8Array {
  const generator = generatorPoly(ecLength);
  // Working buffer: the dividend (data shifted left by ecLength) reduced in place.
  const remainder = new Uint8Array(data.length + ecLength);
  remainder.set(data);
 
  for (let i = 0; i < data.length; i++) {
    const factor = remainder[i]!;
    if (factor === 0) continue;
    // generator[0] is 1, so this zeroes remainder[i] and folds the rest down.
    for (let j = 0; j < generator.length; j++) {
      remainder[i + j] ^= gfMul(generator[j]!, factor);
    }
  }
 
  return remainder.slice(data.length);
}