1<?php
2/**
3 * BaconQrCode
4 *
5 * @link      http://github.com/Bacon/BaconQrCode For the canonical source repository
6 * @copyright 2013 Ben 'DASPRiD' Scholzen
7 * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
8 */
9
10namespace BaconQrCode\Common;
11
12use BaconQrCode\Exception;
13use SplFixedArray;
14
15/**
16 * Reed-Solomon codec for 8-bit characters.
17 *
18 * Based on libfec by Phil Karn, KA9Q.
19 */
20class ReedSolomonCodec
21{
22    /**
23     * Symbol size in bits.
24     *
25     * @var integer
26     */
27    protected $symbolSize;
28
29    /**
30     * Block size in symbols.
31     *
32     * @var integer
33     */
34    protected $blockSize;
35
36    /**
37     * First root of RS code generator polynomial, index form.
38     *
39     * @var integer
40     */
41    protected $firstRoot;
42
43    /**
44     * Primitive element to generate polynomial roots, index form.
45     *
46     * @var integer
47     */
48    protected $primitive;
49
50    /**
51     * Prim-th root of 1, index form.
52     *
53     * @var integer
54     */
55    protected $iPrimitive;
56
57    /**
58     * RS code generator polynomial degree (number of roots).
59     *
60     * @var integer
61     */
62    protected $numRoots;
63
64    /**
65     * Padding bytes at front of shortened block.
66     *
67     * @var integer
68     */
69    protected $padding;
70
71    /**
72     * Log lookup table.
73     *
74     * @var SplFixedArray
75     */
76    protected $alphaTo;
77
78    /**
79     * Anti-Log lookup table.
80     *
81     * @var SplFixedArray
82     */
83    protected $indexOf;
84
85    /**
86     * Generator polynomial.
87     *
88     * @var SplFixedArray
89     */
90    protected $generatorPoly;
91
92    /**
93     * Creates a new reed solomon instance.
94     *
95     * @param  integer $symbolSize
96     * @param  integer $gfPoly
97     * @param  integer $firstRoot
98     * @param  integer $primitive
99     * @param  integer $numRoots
100     * @param  integer $padding
101     * @throws Exception\InvalidArgumentException
102     * @throws Exception\RuntimeException
103     */
104    public function __construct($symbolSize, $gfPoly, $firstRoot, $primitive, $numRoots, $padding)
105    {
106        if ($symbolSize < 0 || $symbolSize > 8) {
107            throw new Exception\InvalidArgumentException('Symbol size must be between 0 and 8');
108        }
109
110        if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) {
111            throw new Exception\InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize));
112        }
113
114        if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) {
115            throw new Exception\InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize));
116        }
117
118        if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) {
119            throw new Exception\InvalidArgumentException('Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots));
120        }
121
122        $this->symbolSize = $symbolSize;
123        $this->blockSize  = (1 << $symbolSize) - 1;
124        $this->padding    = $padding;
125        $this->alphaTo    = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
126        $this->indexOf    = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
127
128        // Generate galous field lookup table
129        $this->indexOf[0]                = $this->blockSize;
130        $this->alphaTo[$this->blockSize] = 0;
131
132        $sr = 1;
133
134        for ($i = 0; $i < $this->blockSize; $i++) {
135            $this->indexOf[$sr] = $i;
136            $this->alphaTo[$i]  = $sr;
137
138            $sr <<= 1;
139
140            if ($sr & (1 << $symbolSize)) {
141                $sr ^= $gfPoly;
142            }
143
144            $sr &= $this->blockSize;
145        }
146
147        if ($sr !== 1) {
148            throw new Exception\RuntimeException('Field generator polynomial is not primitive');
149        }
150
151        // Form RS code generator polynomial from its roots
152        $this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false);
153        $this->firstRoot     = $firstRoot;
154        $this->primitive     = $primitive;
155        $this->numRoots      = $numRoots;
156
157        // Find prim-th root of 1, used in decoding
158        for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize);
159        $this->iPrimitive = intval($iPrimitive / $primitive);
160
161        $this->generatorPoly[0] = 1;
162
163        for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; $i++, $root += $primitive) {
164            $this->generatorPoly[$i + 1] = 1;
165
166            for ($j = $i; $j > 0; $j--) {
167                if ($this->generatorPoly[$j] !== 0) {
168                    $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root)];
169                } else {
170                    $this->generatorPoly[$j] = $this->generatorPoly[$j - 1];
171                }
172            }
173
174            $this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)];
175        }
176
177        // Convert generator poly to index form for quicker encoding
178        for ($i = 0; $i <= $numRoots; $i++) {
179            $this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]];
180        }
181    }
182
183    /**
184     * Encodes data and writes result back into parity array.
185     *
186     * @param  SplFixedArray $data
187     * @param  SplFixedArray $parity
188     * @return void
189     */
190    public function encode(SplFixedArray $data, SplFixedArray $parity)
191    {
192        for ($i = 0; $i < $this->numRoots; $i++) {
193            $parity[$i] = 0;
194        }
195
196        $iterations = $this->blockSize - $this->numRoots - $this->padding;
197
198        for ($i = 0; $i < $iterations; $i++) {
199            $feedback = $this->indexOf[$data[$i] ^ $parity[0]];
200
201            if ($feedback !== $this->blockSize) {
202                // Feedback term is non-zero
203                $feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback);
204
205                for ($j = 1; $j < $this->numRoots; $j++) {
206                    $parity[$j] = $parity[$j] ^ $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j])];
207                }
208            }
209
210            for ($j = 0; $j < $this->numRoots - 1; $j++) {
211                $parity[$j] = $parity[$j + 1];
212            }
213
214            if ($feedback !== $this->blockSize) {
215                $parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])];
216            } else {
217                $parity[$this->numRoots - 1] = 0;
218            }
219        }
220    }
221
222    /**
223     * Decodes received data.
224     *
225     * @param  SplFixedArray      $data
226     * @param  SplFixedArray|null $erasures
227     * @return null|integer
228     */
229    public function decode(SplFixedArray $data, SplFixedArray $erasures = null)
230    {
231        // This speeds up the initialization a bit.
232        $numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false);
233        $numRoots        = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false);
234
235        $lambda    = clone $numRootsPlusOne;
236        $b         = clone $numRootsPlusOne;
237        $t         = clone $numRootsPlusOne;
238        $omega     = clone $numRootsPlusOne;
239        $root      = clone $numRoots;
240        $loc       = clone $numRoots;
241
242        $numErasures = ($erasures !== null ? count($erasures) : 0);
243
244        // Form the Syndromes; i.e., evaluate data(x) at roots of g(x)
245        $syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false);
246
247        for ($i = 1; $i < $this->blockSize - $this->padding; $i++) {
248            for ($j = 0; $j < $this->numRoots; $j++) {
249                if ($syndromes[$j] === 0) {
250                    $syndromes[$j] = $data[$i];
251                } else {
252                    $syndromes[$j] = $data[$i] ^ $this->alphaTo[
253                        $this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive)
254                    ];
255                }
256            }
257        }
258
259        // Convert syndromes to index form, checking for nonzero conditions
260        $syndromeError = 0;
261
262        for ($i = 0; $i < $this->numRoots; $i++) {
263            $syndromeError |= $syndromes[$i];
264            $syndromes[$i]  = $this->indexOf[$syndromes[$i]];
265        }
266
267        if (!$syndromeError) {
268            // If syndrome is zero, data[] is a codeword and there are no errors
269            // to correct, so return data[] unmodified.
270            return 0;
271        }
272
273        $lambda[0] = 1;
274
275        if ($numErasures > 0) {
276            // Init lambda to be the erasure locator polynomial
277            $lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))];
278
279            for ($i = 1; $i < $numErasures; $i++) {
280                $u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i]));
281
282                for ($j = $i + 1; $j > 0; $j--) {
283                    $tmp = $this->indexOf[$lambda[$j - 1]];
284
285                    if ($tmp !== $this->blockSize) {
286                        $lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)];
287                    }
288                }
289            }
290        }
291
292        for ($i = 0; $i <= $this->numRoots; $i++) {
293            $b[$i] = $this->indexOf[$lambda[$i]];
294        }
295
296        // Begin Berlekamp-Massey algorithm to determine error+erasure locator
297        // polynomial
298        $r  = $numErasures;
299        $el = $numErasures;
300
301        while (++$r <= $this->numRoots) {
302            // Compute discrepancy at the r-th step in poly form
303            $discrepancyR = 0;
304
305            for ($i = 0; $i < $r; $i++) {
306                if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) {
307                    $discrepancyR ^= $this->alphaTo[$this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1])];
308                }
309            }
310
311            $discrepancyR = $this->indexOf[$discrepancyR];
312
313            if ($discrepancyR === $this->blockSize) {
314                $tmp = $b->toArray();
315                array_unshift($tmp, $this->blockSize);
316                array_pop($tmp);
317                $b = SplFixedArray::fromArray($tmp, false);
318            } else {
319                $t[0] = $lambda[0];
320
321                for ($i = 0; $i < $this->numRoots; $i++) {
322                    if ($b[$i] !== $this->blockSize) {
323                        $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])];
324                    } else {
325                        $t[$i + 1] = $lambda[$i + 1];
326                    }
327                }
328
329                if (2 * $el <= $r + $numErasures - 1) {
330                    $el = $r + $numErasures - $el;
331
332                    for ($i = 0; $i <= $this->numRoots; $i++) {
333                        $b[$i] = (
334                            $lambda[$i] === 0
335                            ? $this->blockSize
336                            : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize)
337                        );
338                    }
339                } else {
340                    $tmp = $b->toArray();
341                    array_unshift($tmp, $this->blockSize);
342                    array_pop($tmp);
343                    $b = SplFixedArray::fromArray($tmp, false);
344                }
345
346                $lambda = clone $t;
347            }
348        }
349
350        // Convert lambda to index form and compute deg(lambda(x))
351        $degLambda = 0;
352
353        for ($i = 0; $i <= $this->numRoots; $i++) {
354            $lambda[$i] = $this->indexOf[$lambda[$i]];
355
356            if ($lambda[$i] !== $this->blockSize) {
357                $degLambda = $i;
358            }
359        }
360
361        // Find roots of the error+erasure locator polynomial by Chien search.
362        $reg    = clone $lambda;
363        $reg[0] = 0;
364        $count  = 0;
365
366        for ($i = 1, $k = $this->iPrimitive - 1; $i <= $this->blockSize; $i++, $k = $this->modNn($k + $this->iPrimitive)) {
367            $q = 1;
368
369            for ($j = $degLambda; $j > 0; $j--) {
370                if ($reg[$j] !== $this->blockSize) {
371                    $reg[$j]  = $this->modNn($reg[$j] + $j);
372                    $q       ^= $this->alphaTo[$reg[$j]];
373                }
374            }
375
376            if ($q !== 0) {
377                // Not a root
378                continue;
379            }
380
381            // Store root (index-form) and error location number
382            $root[$count] = $i;
383            $loc[$count]  = $k;
384
385            if (++$count === $degLambda) {
386                break;
387            }
388        }
389
390        if ($degLambda !== $count) {
391            // deg(lambda) unequal to number of roots: uncorreactable error
392            // detected
393            return null;
394        }
395
396        // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo
397        // x**numRoots). In index form. Also find deg(omega).
398        $degOmega = $degLambda - 1;
399
400        for ($i = 0; $i <= $degOmega; $i++) {
401            $tmp = 0;
402
403            for ($j = $i; $j >= 0; $j--) {
404                if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) {
405                    $tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])];
406                }
407            }
408
409            $omega[$i] = $this->indexOf[$tmp];
410        }
411
412        // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
413        // inv(X(l))**(firstRoot-1) and den = lambda_pr(inv(X(l))) all in poly
414        // form.
415        for ($j = $count - 1; $j >= 0; $j--) {
416            $num1 = 0;
417
418            for ($i = $degOmega; $i >= 0; $i--) {
419                if ($omega[$i] !== $this->blockSize) {
420                    $num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])];
421                }
422            }
423
424            $num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)];
425            $den  = 0;
426
427            // lambda[i+1] for i even is the formal derivativelambda_pr of
428            // lambda[i]
429            for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) {
430                if ($lambda[$i + 1] !== $this->blockSize) {
431                    $den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])];
432                }
433            }
434
435            // Apply error to data
436            if ($num1 !== 0 && $loc[$j] >= $this->padding) {
437                $data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ (
438                    $this->alphaTo[
439                        $this->modNn(
440                            $this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den]
441                        )
442                    ]
443                );
444            }
445        }
446
447        if ($erasures !== null) {
448            if (count($erasures) < $count) {
449                $erasures->setSize($count);
450            }
451
452            for ($i = 0; $i < $count; $i++) {
453                $erasures[$i] = $loc[$i];
454            }
455        }
456
457        return $count;
458    }
459
460    /**
461     * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow
462     * divide.
463     *
464     * @param  itneger $x
465     * @return integer
466     */
467    protected function modNn($x)
468    {
469        while ($x >= $this->blockSize) {
470            $x -= $this->blockSize;
471            $x  = ($x >> $this->symbolSize) + ($x & $this->blockSize);
472        }
473
474        return $x;
475    }
476}
477