Passed
Push — v5 ( 93618e...84eb31 )
by smiley
01:52
created

ReedSolomonDecoder   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 69
c 1
b 0
f 0
dl 0
loc 151
rs 10
wmc 22

4 Methods

Rating   Name   Duplication   Size   Complexity  
A findErrorMagnitudes() 0 23 5
A runEuclideanAlgorithm() 0 39 5
B decode() 0 40 6
A findErrorLocations() 0 23 6
1
<?php
2
/**
3
 * Class ReedSolomonDecoder
4
 *
5
 * @created      24.01.2021
6
 * @author       ZXing Authors
7
 * @author       Smiley <[email protected]>
8
 * @copyright    2021 Smiley
9
 * @license      Apache-2.0
10
 */
11
12
namespace chillerlan\QRCode\Common;
13
14
use RuntimeException;
15
use function array_fill, count;
16
17
/**
18
 * <p>Implements Reed-Solomon decoding, as the name implies.</p>
19
 *
20
 * <p>The algorithm will not be explained here, but the following references were helpful
21
 * in creating this implementation:</p>
22
 *
23
 * <ul>
24
 * <li>Bruce Maggs.
25
 * <a href="http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps">
26
 * "Decoding Reed-Solomon Codes"</a> (see discussion of Forney's Formula)</li>
27
 * <li>J.I. Hall. <a href="www.mth.msu.edu/~jhall/classes/codenotes/GRS.pdf">
28
 * "Chapter 5. Generalized Reed-Solomon Codes"</a>
29
 * (see discussion of Euclidean algorithm)</li>
30
 * </ul>
31
 *
32
 * <p>Much credit is due to William Rucklidge since portions of this code are an indirect
33
 * port of his C++ Reed-Solomon implementation.</p>
34
 *
35
 * @author Sean Owen
36
 * @author William Rucklidge
37
 * @author sanfordsquires
38
 */
39
final class ReedSolomonDecoder{
40
41
	/**
42
	 * <p>Decodes given set of received codewords, which include both data and error-correction
43
	 * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
44
	 * in the input.</p>
45
	 *
46
	 * @param array $received        data and error-correction codewords
47
	 * @param int   $numEccCodewords number of error-correction codewords available
48
	 *
49
	 * @return int[]
50
	 * @throws \RuntimeException if decoding fails for any reason
51
	 */
52
	public function decode(array $received, int $numEccCodewords):array{
53
		$poly                 = new GenericGFPoly($received);
54
		$syndromeCoefficients = [];
55
		$noError              = true;
56
57
		for($i = 0, $j = $numEccCodewords - 1; $i < $numEccCodewords; $i++, $j--){
58
			$eval                     = $poly->evaluateAt(GF256::exp($i));
59
			$syndromeCoefficients[$j] = $eval;
60
61
			if($eval !== 0){
62
				$noError = false;
63
			}
64
		}
65
66
		if($noError){
0 ignored issues
show
introduced by
The condition $noError is always true.
Loading history...
67
			return $received;
68
		}
69
70
		[$sigma, $omega] = $this->runEuclideanAlgorithm(
71
			GF256::buildMonomial($numEccCodewords, 1),
72
			new GenericGFPoly($syndromeCoefficients),
73
			$numEccCodewords
74
		);
75
76
		$errorLocations      = $this->findErrorLocations($sigma);
77
		$errorMagnitudes     = $this->findErrorMagnitudes($omega, $errorLocations);
78
		$errorLocationsCount = count($errorLocations);
79
		$receivedCount       = count($received);
80
81
		for($i = 0; $i < $errorLocationsCount; $i++){
82
			$position = $receivedCount - 1 - GF256::log($errorLocations[$i]);
83
84
			if($position < 0){
85
				throw new RuntimeException('Bad error location');
86
			}
87
88
			$received[$position] ^= $errorMagnitudes[$i];
89
		}
90
91
		return $received;
92
	}
93
94
	/**
95
	 * @return \chillerlan\QRCode\Common\GenericGFPoly[] [sigma, omega]
96
	 * @throws \RuntimeException
97
	 */
98
	private function runEuclideanAlgorithm(GenericGFPoly $a, GenericGFPoly $b, int $R):array{
99
		// Assume a's degree is >= b's
100
		if($a->getDegree() < $b->getDegree()){
101
			$temp = $a;
102
			$a    = $b;
103
			$b    = $temp;
104
		}
105
106
		$rLast = $a;
107
		$r     = $b;
108
		$tLast = new GenericGFPoly([0]);
109
		$t     = new GenericGFPoly([1]);
110
111
		// Run Euclidean algorithm until r's degree is less than R/2
112
		while($r->getDegree() >= $R / 2){
113
			$rLastLast = $rLast;
114
			$tLastLast = $tLast;
115
			$rLast     = $r;
116
			$tLast     = $t;
117
118
			// Divide rLastLast by rLast, with quotient in q and remainder in r
119
			[$q, $r] = $rLastLast->divide($rLast);
120
121
			$t = $q->multiply($tLast)->addOrSubtract($tLastLast);
122
123
			if($r->getDegree() >= $rLast->getDegree()){
124
				throw new RuntimeException('Division algorithm failed to reduce polynomial?');
125
			}
126
		}
127
128
		$sigmaTildeAtZero = $t->getCoefficient(0);
129
130
		if($sigmaTildeAtZero === 0){
131
			throw new RuntimeException('sigmaTilde(0) was zero');
132
		}
133
134
		$inverse = GF256::inverse($sigmaTildeAtZero);
135
136
		return [$t->multiplyInt($inverse), $r->multiplyInt($inverse)];
137
	}
138
139
	/**
140
	 * @throws \RuntimeException
141
	 */
142
	private function findErrorLocations(GenericGFPoly $errorLocator):array{
143
		// This is a direct application of Chien's search
144
		$numErrors = $errorLocator->getDegree();
145
146
		if($numErrors === 1){ // shortcut
147
			return [$errorLocator->getCoefficient(1)];
148
		}
149
150
		$result = array_fill(0, $numErrors, 0);
151
		$e      = 0;
152
153
		for($i = 1; $i < 256 && $e < $numErrors; $i++){
154
			if($errorLocator->evaluateAt($i) === 0){
155
				$result[$e] = GF256::inverse($i);
156
				$e++;
157
			}
158
		}
159
160
		if($e !== $numErrors){
161
			throw new RuntimeException('Error locator degree does not match number of roots');
162
		}
163
164
		return $result;
165
	}
166
167
	private function findErrorMagnitudes(GenericGFPoly $errorEvaluator, array $errorLocations):array{
168
		// This is directly applying Forney's Formula
169
		$s      = count($errorLocations);
170
		$result = [];
171
172
		for($i = 0; $i < $s; $i++){
173
			$xiInverse   = GF256::inverse($errorLocations[$i]);
174
			$denominator = 1;
175
176
			for($j = 0; $j < $s; $j++){
177
				if($i !== $j){
178
#					$denominator = GF256::multiply($denominator, GF256::addOrSubtract(1, GF256::multiply($errorLocations[$j], $xiInverse)));
179
					// Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
180
					// Below is a funny-looking workaround from Steven Parkes
181
					$term        = GF256::multiply($errorLocations[$j], $xiInverse);
182
					$denominator = GF256::multiply($denominator, (($term & 0x1) === 0 ? $term | 1 : $term & ~1));
183
				}
184
			}
185
186
			$result[$i] = GF256::multiply($errorEvaluator->evaluateAt($xiInverse), GF256::inverse($denominator));
187
		}
188
189
		return $result;
190
	}
191
192
}
193