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