qr_reader 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.txt +21 -0
- data/NOTICE +14 -0
- data/README.md +222 -0
- data/exe/qr_reader +66 -0
- data/lib/qr_reader/binarizer.rb +170 -0
- data/lib/qr_reader/bit_matrix.rb +120 -0
- data/lib/qr_reader/decoder/bitstream_parser.rb +307 -0
- data/lib/qr_reader/decoder/character_set.rb +163 -0
- data/lib/qr_reader/decoder/codewords.rb +164 -0
- data/lib/qr_reader/decoder/data_mask.rb +57 -0
- data/lib/qr_reader/decoder/format_reader.rb +204 -0
- data/lib/qr_reader/decoder/function_patterns.rb +62 -0
- data/lib/qr_reader/decoder/galois_field.rb +69 -0
- data/lib/qr_reader/decoder/reed_solomon.rb +312 -0
- data/lib/qr_reader/decoder.rb +241 -0
- data/lib/qr_reader/detector/alignment_grid.rb +127 -0
- data/lib/qr_reader/detector/alignment_pattern_finder.rb +191 -0
- data/lib/qr_reader/detector/candidate/dimension.rb +237 -0
- data/lib/qr_reader/detector/candidate/strokes.rb +126 -0
- data/lib/qr_reader/detector/candidate.rb +436 -0
- data/lib/qr_reader/detector/finder_pattern.rb +63 -0
- data/lib/qr_reader/detector/finder_pattern_finder.rb +199 -0
- data/lib/qr_reader/detector/geometry.rb +118 -0
- data/lib/qr_reader/detector/grid_sampler.rb +350 -0
- data/lib/qr_reader/detector/pattern_sets.rb +235 -0
- data/lib/qr_reader/detector/perspective_transform.rb +117 -0
- data/lib/qr_reader/detector/pure_detector.rb +197 -0
- data/lib/qr_reader/detector.rb +215 -0
- data/lib/qr_reader/errors.rb +14 -0
- data/lib/qr_reader/format_information.rb +79 -0
- data/lib/qr_reader/formats/bmp.rb +434 -0
- data/lib/qr_reader/formats/ccitt.rb +81 -0
- data/lib/qr_reader/formats/gif.rb +272 -0
- data/lib/qr_reader/formats/jpeg.rb +152 -0
- data/lib/qr_reader/formats/pdf.rb +2935 -0
- data/lib/qr_reader/formats/png.rb +311 -0
- data/lib/qr_reader/formats/pnm.rb +286 -0
- data/lib/qr_reader/formats/tiff.rb +1345 -0
- data/lib/qr_reader/formats.rb +279 -0
- data/lib/qr_reader/image.rb +231 -0
- data/lib/qr_reader/kernels/binarizer.rb +475 -0
- data/lib/qr_reader/kernels/ccitt.rb +554 -0
- data/lib/qr_reader/kernels/detector.rb +587 -0
- data/lib/qr_reader/kernels/image.rb +65 -0
- data/lib/qr_reader/kernels/jpeg.rb +3421 -0
- data/lib/qr_reader/kernels/lzw.rb +173 -0
- data/lib/qr_reader/kernels/pixels.rb +116 -0
- data/lib/qr_reader/kernels/png.rb +1138 -0
- data/lib/qr_reader/kernels.rb +14 -0
- data/lib/qr_reader/native_kernels.rb +130 -0
- data/lib/qr_reader/reader.rb +348 -0
- data/lib/qr_reader/result.rb +49 -0
- data/lib/qr_reader/symbol_version.rb +173 -0
- data/lib/qr_reader/version.rb +5 -0
- data/lib/qr_reader.rb +77 -0
- metadata +103 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Decoder
|
|
5
|
+
# Reads the format information (EC level + mask) and version information
|
|
6
|
+
# words from a module grid. Bit order is exactly that of ISO/IEC 18004
|
|
7
|
+
# §7.9/§7.10 as implemented by ZXing's BitMatrixParser: the first module
|
|
8
|
+
# read becomes the most significant bit.
|
|
9
|
+
#
|
|
10
|
+
# +bits+ is the symbol's module String (0/1 bytes, row-major), +n+ its
|
|
11
|
+
# dimension. With +mirrored+ the symbol is read transposed.
|
|
12
|
+
module FormatReader
|
|
13
|
+
# Copy 1 around the top-left finder, as [x, y].
|
|
14
|
+
FORMAT_COPY1 = [[0, 8], [1, 8], [2, 8], [3, 8], [4, 8], [5, 8], [7, 8], [8, 8],
|
|
15
|
+
[8, 7], [8, 5], [8, 4], [8, 3], [8, 2], [8, 1], [8, 0]].freeze
|
|
16
|
+
|
|
17
|
+
# Version information code words for versions 7..40.
|
|
18
|
+
VERSION_CODES = (7..40).map { |v| SymbolVersion[v].version_info_bits }.freeze
|
|
19
|
+
POPCOUNT9 = Array.new(512) { |i| i.to_s(2).count("1") }.freeze
|
|
20
|
+
NO_CANDIDATES = [].freeze
|
|
21
|
+
private_constant :POPCOUNT9, :NO_CANDIDATES
|
|
22
|
+
|
|
23
|
+
@format_table = nil
|
|
24
|
+
@positions = {}
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
# The two 15-bit format information words [copy1, copy2]. Copy 2 runs
|
|
28
|
+
# up column 8 from the bottom-left corner (7 modules, above them is the
|
|
29
|
+
# dark module), then along row 8 to the right edge (8 modules).
|
|
30
|
+
def format_bits(bits, n, mirrored: false)
|
|
31
|
+
copy1, copy2 = positions(n, mirrored)
|
|
32
|
+
[read_word(bits, copy1), read_word(bits, copy2)]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The two 18-bit version information words [top_right, bottom_left].
|
|
36
|
+
def version_bits(bits, n, mirrored: false)
|
|
37
|
+
top_right, bottom_left = positions(n, mirrored)[2]
|
|
38
|
+
[read_word(bits, top_right), read_word(bits, bottom_left)]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Decodes the two format words like FormatInformation.decode (nearest
|
|
42
|
+
# code word within Hamming distance 3 of either copy, ties to the lower
|
|
43
|
+
# code; then the same for copies lacking the 0x5412 mask), but with one
|
|
44
|
+
# Array lookup per word. Returns [FormatInformation, distance] or nil.
|
|
45
|
+
def decode_format(copy1, copy2)
|
|
46
|
+
table = format_table
|
|
47
|
+
mask = FormatInformation::MASK
|
|
48
|
+
entry = closer(table[copy1 & 0x7FFF], table[copy2 & 0x7FFF]) ||
|
|
49
|
+
closer(table[(copy1 ^ mask) & 0x7FFF], table[(copy2 ^ mask) & 0x7FFF])
|
|
50
|
+
entry && [FormatInformation::ALL[entry >> 2], entry & 3]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Every format information worth trying for the two copies, most
|
|
54
|
+
# likely first: Array of up to two [FormatInformation, distance].
|
|
55
|
+
#
|
|
56
|
+
# Each copy proposes the code word within Hamming distance 3 of it
|
|
57
|
+
# (as in #decode_format, masked code words first, then copies lacking
|
|
58
|
+
# the 0x5412 mask). When the copies propose different code words, the
|
|
59
|
+
# one needing fewer bit errors in both copies together goes first
|
|
60
|
+
# (ties: the smaller distance of a single copy, then the lower code):
|
|
61
|
+
# ranking by one copy alone picks the wrong one when both copies are
|
|
62
|
+
# damaged, and trying the runner-up rescues the symbol when it is.
|
|
63
|
+
# +distance+ is the smaller distance of the two copies.
|
|
64
|
+
def format_candidates(copy1, copy2)
|
|
65
|
+
table = format_table
|
|
66
|
+
copy1 &= 0x7FFF
|
|
67
|
+
copy2 &= 0x7FFF
|
|
68
|
+
a = table[copy1]
|
|
69
|
+
b = table[copy2]
|
|
70
|
+
unless a || b
|
|
71
|
+
mask = FormatInformation::MASK
|
|
72
|
+
copy1 ^= mask
|
|
73
|
+
copy2 ^= mask
|
|
74
|
+
a = table[copy1]
|
|
75
|
+
b = table[copy2]
|
|
76
|
+
return NO_CANDIDATES unless a || b
|
|
77
|
+
end
|
|
78
|
+
return [candidate(a, copy2)] unless b
|
|
79
|
+
return [candidate(b, copy1)] if a.nil? || a >> 2 == b >> 2
|
|
80
|
+
|
|
81
|
+
first = candidate(a, copy2)
|
|
82
|
+
second = candidate(b, copy1)
|
|
83
|
+
first_rank = [(a & 3) + popcount15(copy2 ^ first[0].bits), a & 3, a >> 2]
|
|
84
|
+
second_rank = [(b & 3) + popcount15(copy1 ^ second[0].bits), b & 3, b >> 2]
|
|
85
|
+
(second_rank <=> first_rank).negative? ? [second, first] : [first, second]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Version number (7..40) whose code word is nearest to the 18-bit
|
|
89
|
+
# +word+ within Hamming distance 3 (ties to the lower version), or nil.
|
|
90
|
+
# Same result as SymbolVersion.decode_version_info, without allocation.
|
|
91
|
+
def decode_version(word)
|
|
92
|
+
best = nil
|
|
93
|
+
best_distance = 4
|
|
94
|
+
i = 0
|
|
95
|
+
while i < 34
|
|
96
|
+
x = VERSION_CODES[i] ^ word
|
|
97
|
+
return i + 7 if x.zero?
|
|
98
|
+
|
|
99
|
+
distance = POPCOUNT9[x & 511] + POPCOUNT9[(x >> 9) & 511]
|
|
100
|
+
if distance < best_distance
|
|
101
|
+
best = i + 7
|
|
102
|
+
best_distance = distance
|
|
103
|
+
end
|
|
104
|
+
i += 1
|
|
105
|
+
end
|
|
106
|
+
best
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
# Word made of the modules at +indices+, first index = most significant bit.
|
|
112
|
+
def read_word(bits, indices)
|
|
113
|
+
word = 0
|
|
114
|
+
i = 0
|
|
115
|
+
count = indices.size
|
|
116
|
+
while i < count
|
|
117
|
+
word = (word << 1) | bits.getbyte(indices[i])
|
|
118
|
+
i += 1
|
|
119
|
+
end
|
|
120
|
+
word
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Module String indices, in reading order, of format copy 1, format
|
|
124
|
+
# copy 2 and the two version information blocks for dimension +n+,
|
|
125
|
+
# cached per dimension and orientation.
|
|
126
|
+
def positions(n, mirrored)
|
|
127
|
+
@positions[mirrored ? -n : n] ||= begin
|
|
128
|
+
index = mirrored ? ->(x, y) { x * n + y } : ->(x, y) { y * n + x }
|
|
129
|
+
copy2 = (n - 1).downto(n - 7).map { |y| [8, y] } + (n - 8).upto(n - 1).map { |x| [x, 8] }
|
|
130
|
+
top_right = 5.downto(0).flat_map { |y| (n - 9).downto(n - 11).map { |x| [x, y] } }
|
|
131
|
+
bottom_left = 5.downto(0).flat_map { |x| (n - 9).downto(n - 11).map { |y| [x, y] } }
|
|
132
|
+
words = [FORMAT_COPY1, copy2, top_right, bottom_left].map do |coords|
|
|
133
|
+
coords.map { |x, y| index.call(x, y) }.freeze
|
|
134
|
+
end
|
|
135
|
+
[words[0], words[1], words[2, 2].freeze].freeze
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# [FormatInformation, distance] for a format table +entry+ proposed by
|
|
140
|
+
# one copy; +other+ is the other copy (it may be closer).
|
|
141
|
+
def candidate(entry, other)
|
|
142
|
+
info = FormatInformation::ALL[entry >> 2]
|
|
143
|
+
distance = entry & 3
|
|
144
|
+
other_distance = popcount15(other ^ info.bits)
|
|
145
|
+
[info, other_distance < distance ? other_distance : distance]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def popcount15(word)
|
|
149
|
+
POPCOUNT9[word & 511] + POPCOUNT9[word >> 9]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def closer(a, b)
|
|
153
|
+
return b unless a
|
|
154
|
+
return a unless b
|
|
155
|
+
|
|
156
|
+
da = a & 3
|
|
157
|
+
db = b & 3
|
|
158
|
+
return a if da < db
|
|
159
|
+
return b if db < da
|
|
160
|
+
|
|
161
|
+
a < b ? a : b
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def format_table
|
|
165
|
+
@format_table ||= build_format_table
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# 32768 entries: (format data bits << 2 | distance) for every 15-bit
|
|
169
|
+
# word within distance 3 of a format code word, else nil. The code has
|
|
170
|
+
# minimum distance 7, so these neighbourhoods never overlap. (Plain
|
|
171
|
+
# loops in a method that runs once: YJIT would spend more time
|
|
172
|
+
# compiling blocks here than running them.)
|
|
173
|
+
def build_format_table
|
|
174
|
+
table = Array.new(1 << 15)
|
|
175
|
+
data = 0
|
|
176
|
+
while data < 32
|
|
177
|
+
code = FormatInformation::ALL[data].bits
|
|
178
|
+
base = data << 2
|
|
179
|
+
table[code] = base
|
|
180
|
+
a = 0
|
|
181
|
+
while a < 15
|
|
182
|
+
one = code ^ (1 << a)
|
|
183
|
+
table[one] = base | 1
|
|
184
|
+
b = 0
|
|
185
|
+
while b < a
|
|
186
|
+
two = one ^ (1 << b)
|
|
187
|
+
table[two] = base | 2
|
|
188
|
+
c = 0
|
|
189
|
+
while c < b
|
|
190
|
+
table[two ^ (1 << c)] = base | 3
|
|
191
|
+
c += 1
|
|
192
|
+
end
|
|
193
|
+
b += 1
|
|
194
|
+
end
|
|
195
|
+
a += 1
|
|
196
|
+
end
|
|
197
|
+
data += 1
|
|
198
|
+
end
|
|
199
|
+
table.freeze
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Decoder
|
|
5
|
+
# Which modules of a symbol are function modules (finder patterns with
|
|
6
|
+
# separators, format and version information, timing patterns, alignment
|
|
7
|
+
# patterns, dark module) and therefore carry no data. Same regions as
|
|
8
|
+
# ZXing's Version#buildFunctionPattern.
|
|
9
|
+
module FunctionPatterns
|
|
10
|
+
@cache = Array.new(41)
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# Binary String of dimension**2 bytes, row-major, "\x01" for function
|
|
14
|
+
# modules. Cached per version (1..40).
|
|
15
|
+
def for_version(number)
|
|
16
|
+
@cache[number] ||= build(SymbolVersion[number])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# (Plain loops: this runs once per version, and YJIT would spend more
|
|
22
|
+
# time compiling blocks here than running them.)
|
|
23
|
+
def build(version)
|
|
24
|
+
n = version.dimension
|
|
25
|
+
grid = "\0".b * (n * n)
|
|
26
|
+
fill(grid, n, 0, 0, 9, 9) # top-left finder + separator + format info
|
|
27
|
+
fill(grid, n, n - 8, 0, 8, 9) # top-right finder + separator + format info
|
|
28
|
+
fill(grid, n, 0, n - 8, 9, 8) # bottom-left finder + separator + format info + dark module
|
|
29
|
+
centers = version.alignment_centers
|
|
30
|
+
last = centers.size - 1
|
|
31
|
+
iy = 0
|
|
32
|
+
while iy <= last
|
|
33
|
+
ix = 0
|
|
34
|
+
while ix <= last
|
|
35
|
+
# the three corners occupied by finder patterns carry none
|
|
36
|
+
finder = (ix.zero? && (iy.zero? || iy == last)) || (ix == last && iy.zero?)
|
|
37
|
+
fill(grid, n, centers[ix] - 2, centers[iy] - 2, 5, 5) unless finder
|
|
38
|
+
ix += 1
|
|
39
|
+
end
|
|
40
|
+
iy += 1
|
|
41
|
+
end
|
|
42
|
+
fill(grid, n, 6, 9, 1, n - 17) # vertical timing pattern
|
|
43
|
+
fill(grid, n, 9, 6, n - 17, 1) # horizontal timing pattern
|
|
44
|
+
if version.number >= 7
|
|
45
|
+
fill(grid, n, n - 11, 0, 3, 6) # version info, top right
|
|
46
|
+
fill(grid, n, 0, n - 11, 6, 3) # version info, bottom left
|
|
47
|
+
end
|
|
48
|
+
grid.freeze
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def fill(grid, n, left, top, width, height)
|
|
52
|
+
row = "\x01".b * width
|
|
53
|
+
y = top
|
|
54
|
+
while y < top + height
|
|
55
|
+
grid[y * n + left, width] = row
|
|
56
|
+
y += 1
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Decoder
|
|
5
|
+
# Arithmetic in GF(256) as used by QR codes: field polynomial
|
|
6
|
+
# x^8 + x^4 + x^3 + x^2 + 1 (0x11D), primitive element alpha = 2.
|
|
7
|
+
# Addition is XOR; multiplication goes through log/antilog tables.
|
|
8
|
+
module GaloisField
|
|
9
|
+
PRIMITIVE = 0x11D
|
|
10
|
+
|
|
11
|
+
exp = Array.new(512, 0)
|
|
12
|
+
log = Array.new(256, 0)
|
|
13
|
+
x = 1
|
|
14
|
+
255.times do |i|
|
|
15
|
+
exp[i] = x
|
|
16
|
+
log[x] = i
|
|
17
|
+
x <<= 1
|
|
18
|
+
x ^= PRIMITIVE if x > 255
|
|
19
|
+
end
|
|
20
|
+
255.upto(511) { |i| exp[i] = exp[i - 255] }
|
|
21
|
+
|
|
22
|
+
# EXP[i] = alpha**i for 0 <= i < 512. Doubled in length so that
|
|
23
|
+
# EXP[LOG[a] + LOG[b]] needs no modulo.
|
|
24
|
+
EXP = exp.freeze
|
|
25
|
+
# LOG[a] = discrete log of a (1..255). LOG[0] is undefined (stored as 0).
|
|
26
|
+
LOG = log.freeze
|
|
27
|
+
|
|
28
|
+
# MULTIPLY_BY_ALPHA_POWER[j][s] = s * alpha**j, for j < 31 (enough for the
|
|
29
|
+
# largest QR block's 30 error correction codewords). Lets a syndrome
|
|
30
|
+
# Horner step be one Array lookup: s = table[s] ^ codeword.
|
|
31
|
+
MULTIPLY_BY_ALPHA_POWER = Array.new(31) do |j|
|
|
32
|
+
Array.new(256) { |s| s.zero? ? 0 : exp[log[s] + j] }.freeze
|
|
33
|
+
end.freeze
|
|
34
|
+
|
|
35
|
+
@multiplication_table = nil
|
|
36
|
+
|
|
37
|
+
# Full multiplication table: multiplication_table[a][b] = a * b. Built on
|
|
38
|
+
# first use because only error correction needs it. Row a is EXP
|
|
39
|
+
# shifted by LOG[a] and permuted by LOG, gathered at C speed.
|
|
40
|
+
def self.multiplication_table
|
|
41
|
+
@multiplication_table ||= begin
|
|
42
|
+
logs = LOG[1, 255]
|
|
43
|
+
zero = Array.new(256, 0).freeze
|
|
44
|
+
Array.new(256) do |a|
|
|
45
|
+
next zero if a.zero?
|
|
46
|
+
|
|
47
|
+
EXP[LOG[a], 255].values_at(*logs).unshift(0).freeze
|
|
48
|
+
end.freeze
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module_function
|
|
53
|
+
|
|
54
|
+
def multiply(a, b)
|
|
55
|
+
a.zero? || b.zero? ? 0 : EXP[LOG[a] + LOG[b]]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# a / b for b != 0.
|
|
59
|
+
def divide(a, b)
|
|
60
|
+
a.zero? ? 0 : EXP[LOG[a] + 255 - LOG[b]]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# alpha**power for any integer power (negative allowed).
|
|
64
|
+
def power(power)
|
|
65
|
+
EXP[power % 255]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QrReader
|
|
4
|
+
module Decoder
|
|
5
|
+
# Reed-Solomon error correction for QR code blocks over GF(256) (0x11D).
|
|
6
|
+
#
|
|
7
|
+
# The generator polynomial of an EC block with +ec+ codewords has the roots
|
|
8
|
+
# alpha**0 .. alpha**(ec-1). Decoding computes the syndromes, finds the
|
|
9
|
+
# error locator with Berlekamp-Massey, its roots with a Chien search and the
|
|
10
|
+
# error values with Forney's formula, then re-checks the syndromes of the
|
|
11
|
+
# corrected block. At most floor(ec/2) errors are corrected; anything that
|
|
12
|
+
# does not lead to a verified codeword is rejected.
|
|
13
|
+
#
|
|
14
|
+
# Polynomials are Arrays of coefficients; a block's codewords[0] is the
|
|
15
|
+
# coefficient of x**(n-1), so codeword i sits at "degree position" n-1-i.
|
|
16
|
+
module ReedSolomon
|
|
17
|
+
EXP = GaloisField::EXP
|
|
18
|
+
LOG = GaloisField::LOG
|
|
19
|
+
MULTIPLY_BY_ALPHA_POWER = GaloisField::MULTIPLY_BY_ALPHA_POWER
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# Corrects +codewords+ (Array of Integers 0..255, data followed by EC
|
|
24
|
+
# codewords, first element = highest-degree coefficient) in place.
|
|
25
|
+
# Returns the number of corrected codewords, or nil when the block is
|
|
26
|
+
# uncorrectable (the Array is then left unchanged).
|
|
27
|
+
def decode(codewords, ec_count)
|
|
28
|
+
n = codewords.size
|
|
29
|
+
return nil if ec_count < 1 || ec_count >= n || n > 255 || ec_count >= MULTIPLY_BY_ALPHA_POWER.size
|
|
30
|
+
|
|
31
|
+
syndromes = syndromes(codewords, ec_count)
|
|
32
|
+
return 0 unless syndromes
|
|
33
|
+
|
|
34
|
+
locator = berlekamp_massey(syndromes)
|
|
35
|
+
errors = locator.size - 1
|
|
36
|
+
return nil if errors.zero? || 2 * errors > ec_count
|
|
37
|
+
|
|
38
|
+
positions = chien_search(locator, n)
|
|
39
|
+
return nil unless positions
|
|
40
|
+
|
|
41
|
+
magnitudes = forney(syndromes, locator, positions)
|
|
42
|
+
return nil unless magnitudes && corrects_syndromes?(syndromes, positions, magnitudes)
|
|
43
|
+
|
|
44
|
+
positions.each_with_index { |e, k| codewords[n - 1 - e] ^= magnitudes[k] }
|
|
45
|
+
errors
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Syndromes S_j = r(alpha**j) for j = 0...ec_count, or nil when all are
|
|
49
|
+
# zero (the block is a valid codeword). This is the hot path for clean
|
|
50
|
+
# symbols, so the cheaper of two equivalent methods is used: with a JIT,
|
|
51
|
+
# Horner's rule over all codewords (allocation-free table lookups);
|
|
52
|
+
# in the plain interpreter, the remainder r(x) mod g(x) computed with
|
|
53
|
+
# one Bignum step per codeword, whose syndromes are those of r(x)
|
|
54
|
+
# because g(alpha**j) = 0 (about 3x faster there).
|
|
55
|
+
def syndromes(codewords, ec_count)
|
|
56
|
+
jit? ? horner_syndromes(codewords, ec_count) : remainder_syndromes(codewords, ec_count)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Syndromes by Horner's rule: one lookup and one XOR per codeword and
|
|
60
|
+
# syndrome. Four syndromes share each pass over the codewords, which
|
|
61
|
+
# roughly halves the loop overhead.
|
|
62
|
+
def horner_syndromes(codewords, ec_count)
|
|
63
|
+
n = codewords.size
|
|
64
|
+
result = nil
|
|
65
|
+
j = 0
|
|
66
|
+
while j + 4 <= ec_count
|
|
67
|
+
t0 = MULTIPLY_BY_ALPHA_POWER[j]
|
|
68
|
+
t1 = MULTIPLY_BY_ALPHA_POWER[j + 1]
|
|
69
|
+
t2 = MULTIPLY_BY_ALPHA_POWER[j + 2]
|
|
70
|
+
t3 = MULTIPLY_BY_ALPHA_POWER[j + 3]
|
|
71
|
+
s0 = s1 = s2 = s3 = 0
|
|
72
|
+
i = 0
|
|
73
|
+
while i < n
|
|
74
|
+
c = codewords[i]
|
|
75
|
+
s0 = t0[s0] ^ c
|
|
76
|
+
s1 = t1[s1] ^ c
|
|
77
|
+
s2 = t2[s2] ^ c
|
|
78
|
+
s3 = t3[s3] ^ c
|
|
79
|
+
i += 1
|
|
80
|
+
end
|
|
81
|
+
if (s0 | s1 | s2 | s3) != 0
|
|
82
|
+
result ||= Array.new(ec_count, 0)
|
|
83
|
+
result[j] = s0
|
|
84
|
+
result[j + 1] = s1
|
|
85
|
+
result[j + 2] = s2
|
|
86
|
+
result[j + 3] = s3
|
|
87
|
+
end
|
|
88
|
+
j += 4
|
|
89
|
+
end
|
|
90
|
+
while j < ec_count
|
|
91
|
+
table = MULTIPLY_BY_ALPHA_POWER[j]
|
|
92
|
+
s = 0
|
|
93
|
+
i = 0
|
|
94
|
+
while i < n
|
|
95
|
+
s = table[s] ^ codewords[i]
|
|
96
|
+
i += 1
|
|
97
|
+
end
|
|
98
|
+
if s != 0
|
|
99
|
+
result ||= Array.new(ec_count, 0)
|
|
100
|
+
result[j] = s
|
|
101
|
+
end
|
|
102
|
+
j += 1
|
|
103
|
+
end
|
|
104
|
+
result
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Syndromes via the remainder r(x) mod g(x), kept as an Integer of
|
|
108
|
+
# ec_count bytes (coefficient of x**(ec-1) in the top byte).
|
|
109
|
+
def remainder_syndromes(codewords, ec_count)
|
|
110
|
+
table = remainder_table(ec_count)
|
|
111
|
+
shift = 8 * (ec_count - 1)
|
|
112
|
+
r = 0
|
|
113
|
+
i = 0
|
|
114
|
+
n = codewords.size
|
|
115
|
+
while i < n
|
|
116
|
+
r = ((r << 8) | codewords[i]) ^ table[r >> shift]
|
|
117
|
+
i += 1
|
|
118
|
+
end
|
|
119
|
+
return nil if r.zero?
|
|
120
|
+
|
|
121
|
+
horner_syndromes([r.to_s(16).rjust(2 * ec_count, "0")].pack("H*").bytes, ec_count)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# True when running under a JIT compiler (or a non-CRuby VM).
|
|
125
|
+
def jit?
|
|
126
|
+
return true unless RUBY_ENGINE == "ruby"
|
|
127
|
+
|
|
128
|
+
yjit = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
|
|
129
|
+
zjit = defined?(RubyVM::ZJIT) && RubyVM::ZJIT.enabled?
|
|
130
|
+
yjit || zjit ? true : false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
@remainder_tables = []
|
|
134
|
+
|
|
135
|
+
# remainder_table(ec)[b] = b * g(x) as an Integer of ec + 1 bytes, where
|
|
136
|
+
# g(x) = (x - alpha**0) ... (x - alpha**(ec-1)) is the monic generator.
|
|
137
|
+
# The map b -> b * g(x) is GF(2)-linear, so only the eight single-bit
|
|
138
|
+
# rows need field multiplications; the others are XORs of those.
|
|
139
|
+
def remainder_table(ec_count)
|
|
140
|
+
@remainder_tables[ec_count] ||= begin
|
|
141
|
+
generator = [1]
|
|
142
|
+
ec_count.times do |i|
|
|
143
|
+
next_generator = generator + [0]
|
|
144
|
+
generator.each_with_index { |c, k| next_generator[k + 1] ^= GaloisField.multiply(c, EXP[i]) }
|
|
145
|
+
generator = next_generator
|
|
146
|
+
end
|
|
147
|
+
table = Array.new(256, 0)
|
|
148
|
+
8.times do |bit|
|
|
149
|
+
b = 1 << bit
|
|
150
|
+
table[b] = generator.inject(0) { |acc, c| (acc << 8) | GaloisField.multiply(b, c) }
|
|
151
|
+
end
|
|
152
|
+
b = 1
|
|
153
|
+
while b < 256
|
|
154
|
+
low = b & -b # lowest set bit; table[b ^ low] is already filled
|
|
155
|
+
table[b] = table[low] ^ table[b ^ low] unless b == low
|
|
156
|
+
b += 1
|
|
157
|
+
end
|
|
158
|
+
table.freeze
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Error locator polynomial Lambda (coefficients, Lambda[0] = 1) of the
|
|
163
|
+
# shortest LFSR generating the syndrome sequence. Its length - 1 is the
|
|
164
|
+
# number of errors assumed (its degree).
|
|
165
|
+
def berlekamp_massey(syndromes)
|
|
166
|
+
mul = GaloisField.multiplication_table
|
|
167
|
+
current = [1]
|
|
168
|
+
previous = [1]
|
|
169
|
+
length = 0
|
|
170
|
+
shift = 1
|
|
171
|
+
previous_discrepancy = 1
|
|
172
|
+
count = syndromes.size
|
|
173
|
+
n = 0
|
|
174
|
+
while n < count
|
|
175
|
+
d = syndromes[n]
|
|
176
|
+
i = 1
|
|
177
|
+
while i <= length
|
|
178
|
+
d ^= mul[current[i]][syndromes[n - i]]
|
|
179
|
+
i += 1
|
|
180
|
+
end
|
|
181
|
+
if d.zero?
|
|
182
|
+
shift += 1
|
|
183
|
+
else
|
|
184
|
+
row = mul[EXP[LOG[d] + 255 - LOG[previous_discrepancy]]] # d / previous_discrepancy
|
|
185
|
+
replaced = 2 * length <= n ? current.dup : nil
|
|
186
|
+
size = previous.size
|
|
187
|
+
current << 0 while current.size < size + shift
|
|
188
|
+
k = 0
|
|
189
|
+
while k < size
|
|
190
|
+
current[k + shift] ^= row[previous[k]]
|
|
191
|
+
k += 1
|
|
192
|
+
end
|
|
193
|
+
if replaced
|
|
194
|
+
length = n + 1 - length
|
|
195
|
+
previous = replaced
|
|
196
|
+
previous_discrepancy = d
|
|
197
|
+
shift = 1
|
|
198
|
+
else
|
|
199
|
+
shift += 1
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
n += 1
|
|
203
|
+
end
|
|
204
|
+
current.pop while current.size > length + 1
|
|
205
|
+
current << 0 while current.size < length + 1 # degree below length: no valid locator
|
|
206
|
+
current
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Degree positions e (0...n) of the errors: the e with
|
|
210
|
+
# Lambda(alpha**-e) == 0, by Horner evaluation. Returns nil unless
|
|
211
|
+
# exactly deg(Lambda) roots lie inside the block.
|
|
212
|
+
def chien_search(locator, n)
|
|
213
|
+
mul = GaloisField.multiplication_table
|
|
214
|
+
errors = locator.size - 1
|
|
215
|
+
return nil if locator[errors].zero?
|
|
216
|
+
|
|
217
|
+
roots = []
|
|
218
|
+
e = 0
|
|
219
|
+
while e < n
|
|
220
|
+
row = mul[EXP[255 - e]] # x = alpha**-e
|
|
221
|
+
s = locator[errors]
|
|
222
|
+
k = errors - 1
|
|
223
|
+
while k >= 0
|
|
224
|
+
s = row[s] ^ locator[k]
|
|
225
|
+
k -= 1
|
|
226
|
+
end
|
|
227
|
+
if s.zero?
|
|
228
|
+
roots << e
|
|
229
|
+
return roots if roots.size == errors
|
|
230
|
+
end
|
|
231
|
+
e += 1
|
|
232
|
+
end
|
|
233
|
+
nil
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Error values by Forney's formula (first consecutive root alpha**0):
|
|
237
|
+
# e_k = X_k * Omega(X_k^-1) / Lambda'(X_k^-1), Omega = S * Lambda mod x^v.
|
|
238
|
+
# Returns nil if a value comes out as zero (no valid correction).
|
|
239
|
+
def forney(syndromes, locator, positions)
|
|
240
|
+
mul = GaloisField.multiplication_table
|
|
241
|
+
errors = locator.size - 1
|
|
242
|
+
omega = Array.new(errors, 0)
|
|
243
|
+
i = 0
|
|
244
|
+
while i < errors
|
|
245
|
+
v = 0
|
|
246
|
+
k = 0
|
|
247
|
+
while k <= i
|
|
248
|
+
v ^= mul[locator[k]][syndromes[i - k]]
|
|
249
|
+
k += 1
|
|
250
|
+
end
|
|
251
|
+
omega[i] = v
|
|
252
|
+
i += 1
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
count = positions.size
|
|
256
|
+
magnitudes = Array.new(count)
|
|
257
|
+
p = 0
|
|
258
|
+
while p < count
|
|
259
|
+
e = positions[p]
|
|
260
|
+
x_inverse = EXP[255 - e]
|
|
261
|
+
row = mul[x_inverse]
|
|
262
|
+
numerator = omega[errors - 1]
|
|
263
|
+
i = errors - 2
|
|
264
|
+
while i >= 0
|
|
265
|
+
numerator = row[numerator] ^ omega[i]
|
|
266
|
+
i -= 1
|
|
267
|
+
end
|
|
268
|
+
# Lambda'(x) has only the odd-degree terms (characteristic 2).
|
|
269
|
+
x_squared = row[x_inverse]
|
|
270
|
+
denominator = 0
|
|
271
|
+
power = 1
|
|
272
|
+
k = 1
|
|
273
|
+
while k <= errors
|
|
274
|
+
denominator ^= mul[locator[k]][power]
|
|
275
|
+
power = mul[power][x_squared]
|
|
276
|
+
k += 2
|
|
277
|
+
end
|
|
278
|
+
return nil if numerator.zero? || denominator.zero?
|
|
279
|
+
|
|
280
|
+
magnitudes[p] = EXP[(e + LOG[numerator] + 255 - LOG[denominator]) % 255]
|
|
281
|
+
p += 1
|
|
282
|
+
end
|
|
283
|
+
magnitudes
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# True if subtracting the error values at the positions zeroes every
|
|
287
|
+
# syndrome, i.e. the corrected block is a codeword.
|
|
288
|
+
def corrects_syndromes?(syndromes, positions, magnitudes)
|
|
289
|
+
mul = GaloisField.multiplication_table
|
|
290
|
+
count = positions.size
|
|
291
|
+
locators = Array.new(count) { |k| EXP[positions[k]] }
|
|
292
|
+
terms = magnitudes.dup # magnitude_k * X_k**j, starting at j = 0
|
|
293
|
+
j = 0
|
|
294
|
+
ec_count = syndromes.size
|
|
295
|
+
while j < ec_count
|
|
296
|
+
s = syndromes[j]
|
|
297
|
+
k = 0
|
|
298
|
+
while k < count
|
|
299
|
+
term = terms[k]
|
|
300
|
+
s ^= term
|
|
301
|
+
terms[k] = mul[term][locators[k]]
|
|
302
|
+
k += 1
|
|
303
|
+
end
|
|
304
|
+
return false unless s.zero?
|
|
305
|
+
|
|
306
|
+
j += 1
|
|
307
|
+
end
|
|
308
|
+
true
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|