| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 | 
							- <?php
 
- declare(strict_types = 1);
 
- namespace BaconQrCode\Common;
 
- use BaconQrCode\Exception\InvalidArgumentException;
 
- use BaconQrCode\Exception\RuntimeException;
 
- use SplFixedArray;
 
- /**
 
-  * Reed-Solomon codec for 8-bit characters.
 
-  *
 
-  * Based on libfec by Phil Karn, KA9Q.
 
-  */
 
- final class ReedSolomonCodec
 
- {
 
-     /**
 
-      * Symbol size in bits.
 
-      *
 
-      * @var int
 
-      */
 
-     private $symbolSize;
 
-     /**
 
-      * Block size in symbols.
 
-      *
 
-      * @var int
 
-      */
 
-     private $blockSize;
 
-     /**
 
-      * First root of RS code generator polynomial, index form.
 
-      *
 
-      * @var int
 
-      */
 
-     private $firstRoot;
 
-     /**
 
-      * Primitive element to generate polynomial roots, index form.
 
-      *
 
-      * @var int
 
-      */
 
-     private $primitive;
 
-     /**
 
-      * Prim-th root of 1, index form.
 
-      *
 
-      * @var int
 
-      */
 
-     private $iPrimitive;
 
-     /**
 
-      * RS code generator polynomial degree (number of roots).
 
-      *
 
-      * @var int
 
-      */
 
-     private $numRoots;
 
-     /**
 
-      * Padding bytes at front of shortened block.
 
-      *
 
-      * @var int
 
-      */
 
-     private $padding;
 
-     /**
 
-      * Log lookup table.
 
-      *
 
-      * @var SplFixedArray
 
-      */
 
-     private $alphaTo;
 
-     /**
 
-      * Anti-Log lookup table.
 
-      *
 
-      * @var SplFixedArray
 
-      */
 
-     private $indexOf;
 
-     /**
 
-      * Generator polynomial.
 
-      *
 
-      * @var SplFixedArray
 
-      */
 
-     private $generatorPoly;
 
-     /**
 
-      * @throws InvalidArgumentException if symbol size ist not between 0 and 8
 
-      * @throws InvalidArgumentException if first root is invalid
 
-      * @throws InvalidArgumentException if num roots is invalid
 
-      * @throws InvalidArgumentException if padding is invalid
 
-      * @throws RuntimeException if field generator polynomial is not primitive
 
-      */
 
-     public function __construct(
 
-         int $symbolSize,
 
-         int $gfPoly,
 
-         int $firstRoot,
 
-         int $primitive,
 
-         int $numRoots,
 
-         int $padding
 
-     ) {
 
-         if ($symbolSize < 0 || $symbolSize > 8) {
 
-             throw new InvalidArgumentException('Symbol size must be between 0 and 8');
 
-         }
 
-         if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) {
 
-             throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize));
 
-         }
 
-         if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) {
 
-             throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize));
 
-         }
 
-         if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) {
 
-             throw new InvalidArgumentException(
 
-                 'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots)
 
-             );
 
-         }
 
-         $this->symbolSize = $symbolSize;
 
-         $this->blockSize = (1 << $symbolSize) - 1;
 
-         $this->padding = $padding;
 
-         $this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
 
-         $this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
 
-         // Generate galous field lookup table
 
-         $this->indexOf[0] = $this->blockSize;
 
-         $this->alphaTo[$this->blockSize] = 0;
 
-         $sr = 1;
 
-         for ($i = 0; $i < $this->blockSize; ++$i) {
 
-             $this->indexOf[$sr] = $i;
 
-             $this->alphaTo[$i]  = $sr;
 
-             $sr <<= 1;
 
-             if ($sr & (1 << $symbolSize)) {
 
-                 $sr ^= $gfPoly;
 
-             }
 
-             $sr &= $this->blockSize;
 
-         }
 
-         if (1 !== $sr) {
 
-             throw new RuntimeException('Field generator polynomial is not primitive');
 
-         }
 
-         // Form RS code generator polynomial from its roots
 
-         $this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false);
 
-         $this->firstRoot = $firstRoot;
 
-         $this->primitive = $primitive;
 
-         $this->numRoots = $numRoots;
 
-         // Find prim-th root of 1, used in decoding
 
-         for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) {
 
-         }
 
-         $this->iPrimitive = intdiv($iPrimitive, $primitive);
 
-         $this->generatorPoly[0] = 1;
 
-         for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) {
 
-             $this->generatorPoly[$i + 1] = 1;
 
-             for ($j = $i; $j > 0; $j--) {
 
-                 if ($this->generatorPoly[$j] !== 0) {
 
-                     $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[
 
-                         $this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root)
 
-                     ];
 
-                 } else {
 
-                     $this->generatorPoly[$j] = $this->generatorPoly[$j - 1];
 
-                 }
 
-             }
 
-             $this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)];
 
-         }
 
-         // Convert generator poly to index form for quicker encoding
 
-         for ($i = 0; $i <= $numRoots; ++$i) {
 
-             $this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]];
 
-         }
 
-     }
 
-     /**
 
-      * Encodes data and writes result back into parity array.
 
-      */
 
-     public function encode(SplFixedArray $data, SplFixedArray $parity) : void
 
-     {
 
-         for ($i = 0; $i < $this->numRoots; ++$i) {
 
-             $parity[$i] = 0;
 
-         }
 
-         $iterations = $this->blockSize - $this->numRoots - $this->padding;
 
-         for ($i = 0; $i < $iterations; ++$i) {
 
-             $feedback = $this->indexOf[$data[$i] ^ $parity[0]];
 
-             if ($feedback !== $this->blockSize) {
 
-                 // Feedback term is non-zero
 
-                 $feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback);
 
-                 for ($j = 1; $j < $this->numRoots; ++$j) {
 
-                     $parity[$j] = $parity[$j] ^ $this->alphaTo[
 
-                         $this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j])
 
-                     ];
 
-                 }
 
-             }
 
-             for ($j = 0; $j < $this->numRoots - 1; ++$j) {
 
-                 $parity[$j] = $parity[$j + 1];
 
-             }
 
-             if ($feedback !== $this->blockSize) {
 
-                 $parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])];
 
-             } else {
 
-                 $parity[$this->numRoots - 1] = 0;
 
-             }
 
-         }
 
-     }
 
-     /**
 
-      * Decodes received data.
 
-      */
 
-     public function decode(SplFixedArray $data, SplFixedArray $erasures = null) : ?int
 
-     {
 
-         // This speeds up the initialization a bit.
 
-         $numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false);
 
-         $numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false);
 
-         $lambda = clone $numRootsPlusOne;
 
-         $b = clone $numRootsPlusOne;
 
-         $t = clone $numRootsPlusOne;
 
-         $omega = clone $numRootsPlusOne;
 
-         $root = clone $numRoots;
 
-         $loc = clone $numRoots;
 
-         $numErasures = (null !== $erasures ? count($erasures) : 0);
 
-         // Form the Syndromes; i.e., evaluate data(x) at roots of g(x)
 
-         $syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false);
 
-         for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) {
 
-             for ($j = 0; $j < $this->numRoots; ++$j) {
 
-                 if ($syndromes[$j] === 0) {
 
-                     $syndromes[$j] = $data[$i];
 
-                 } else {
 
-                     $syndromes[$j] = $data[$i] ^ $this->alphaTo[
 
-                         $this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive)
 
-                     ];
 
-                 }
 
-             }
 
-         }
 
-         // Convert syndromes to index form, checking for nonzero conditions
 
-         $syndromeError = 0;
 
-         for ($i = 0; $i < $this->numRoots; ++$i) {
 
-             $syndromeError |= $syndromes[$i];
 
-             $syndromes[$i] = $this->indexOf[$syndromes[$i]];
 
-         }
 
-         if (! $syndromeError) {
 
-             // If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[]
 
-             // unmodified.
 
-             return 0;
 
-         }
 
-         $lambda[0] = 1;
 
-         if ($numErasures > 0) {
 
-             // Init lambda to be the erasure locator polynomial
 
-             $lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))];
 
-             for ($i = 1; $i < $numErasures; ++$i) {
 
-                 $u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i]));
 
-                 for ($j = $i + 1; $j > 0; --$j) {
 
-                     $tmp = $this->indexOf[$lambda[$j - 1]];
 
-                     if ($tmp !== $this->blockSize) {
 
-                         $lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)];
 
-                     }
 
-                 }
 
-             }
 
-         }
 
-         for ($i = 0; $i <= $this->numRoots; ++$i) {
 
-             $b[$i] = $this->indexOf[$lambda[$i]];
 
-         }
 
-         // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial
 
-         $r  = $numErasures;
 
-         $el = $numErasures;
 
-         while (++$r <= $this->numRoots) {
 
-             // Compute discrepancy at the r-th step in poly form
 
-             $discrepancyR = 0;
 
-             for ($i = 0; $i < $r; ++$i) {
 
-                 if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) {
 
-                     $discrepancyR ^= $this->alphaTo[
 
-                         $this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1])
 
-                     ];
 
-                 }
 
-             }
 
-             $discrepancyR = $this->indexOf[$discrepancyR];
 
-             if ($discrepancyR === $this->blockSize) {
 
-                 $tmp = $b->toArray();
 
-                 array_unshift($tmp, $this->blockSize);
 
-                 array_pop($tmp);
 
-                 $b = SplFixedArray::fromArray($tmp, false);
 
-                 continue;
 
-             }
 
-             $t[0] = $lambda[0];
 
-             for ($i = 0; $i < $this->numRoots; ++$i) {
 
-                 if ($b[$i] !== $this->blockSize) {
 
-                     $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])];
 
-                 } else {
 
-                     $t[$i + 1] = $lambda[$i + 1];
 
-                 }
 
-             }
 
-             if (2 * $el <= $r + $numErasures - 1) {
 
-                 $el = $r + $numErasures - $el;
 
-                 for ($i = 0; $i <= $this->numRoots; ++$i) {
 
-                     $b[$i] = (
 
-                         $lambda[$i] === 0
 
-                         ? $this->blockSize
 
-                         : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize)
 
-                     );
 
-                 }
 
-             } else {
 
-                 $tmp = $b->toArray();
 
-                 array_unshift($tmp, $this->blockSize);
 
-                 array_pop($tmp);
 
-                 $b = SplFixedArray::fromArray($tmp, false);
 
-             }
 
-             $lambda = clone $t;
 
-         }
 
-         // Convert lambda to index form and compute deg(lambda(x))
 
-         $degLambda = 0;
 
-         for ($i = 0; $i <= $this->numRoots; ++$i) {
 
-             $lambda[$i] = $this->indexOf[$lambda[$i]];
 
-             if ($lambda[$i] !== $this->blockSize) {
 
-                 $degLambda = $i;
 
-             }
 
-         }
 
-         // Find roots of the error+erasure locator polynomial by Chien search.
 
-         $reg = clone $lambda;
 
-         $reg[0] = 0;
 
-         $count = 0;
 
-         $i = 1;
 
-         for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) {
 
-             $q = 1;
 
-             for ($j = $degLambda; $j > 0; $j--) {
 
-                 if ($reg[$j] !== $this->blockSize) {
 
-                     $reg[$j] = $this->modNn($reg[$j] + $j);
 
-                     $q ^= $this->alphaTo[$reg[$j]];
 
-                 }
 
-             }
 
-             if ($q !== 0) {
 
-                 // Not a root
 
-                 continue;
 
-             }
 
-             // Store root (index-form) and error location number
 
-             $root[$count] = $i;
 
-             $loc[$count] = $k;
 
-             if (++$count === $degLambda) {
 
-                 break;
 
-             }
 
-         }
 
-         if ($degLambda !== $count) {
 
-             // deg(lambda) unequal to number of roots: uncorrectable error detected
 
-             return null;
 
-         }
 
-         // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find
 
-         // deg(omega).
 
-         $degOmega = $degLambda - 1;
 
-         for ($i = 0; $i <= $degOmega; ++$i) {
 
-             $tmp = 0;
 
-             for ($j = $i; $j >= 0; --$j) {
 
-                 if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) {
 
-                     $tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])];
 
-                 }
 
-             }
 
-             $omega[$i] = $this->indexOf[$tmp];
 
-         }
 
-         // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and
 
-         // den = lambda_pr(inv(X(l))) all in poly form.
 
-         for ($j = $count - 1; $j >= 0; --$j) {
 
-             $num1 = 0;
 
-             for ($i = $degOmega; $i >= 0; $i--) {
 
-                 if ($omega[$i] !== $this->blockSize) {
 
-                     $num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])];
 
-                 }
 
-             }
 
-             $num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)];
 
-             $den  = 0;
 
-             // lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i]
 
-             for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) {
 
-                 if ($lambda[$i + 1] !== $this->blockSize) {
 
-                     $den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])];
 
-                 }
 
-             }
 
-             // Apply error to data
 
-             if ($num1 !== 0 && $loc[$j] >= $this->padding) {
 
-                 $data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ (
 
-                     $this->alphaTo[
 
-                         $this->modNn(
 
-                             $this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den]
 
-                         )
 
-                     ]
 
-                 );
 
-             }
 
-         }
 
-         if (null !== $erasures) {
 
-             if (count($erasures) < $count) {
 
-                 $erasures->setSize($count);
 
-             }
 
-             for ($i = 0; $i < $count; $i++) {
 
-                 $erasures[$i] = $loc[$i];
 
-             }
 
-         }
 
-         return $count;
 
-     }
 
-     /**
 
-      * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide.
 
-      */
 
-     private function modNn(int $x) : int
 
-     {
 
-         while ($x >= $this->blockSize) {
 
-             $x -= $this->blockSize;
 
-             $x = ($x >> $this->symbolSize) + ($x & $this->blockSize);
 
-         }
 
-         return $x;
 
-     }
 
- }
 
 
  |