Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
QrReedSolomonEncoder
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
4 / 4
14
100.00% covered (success)
100.00%
1 / 1
 buildGaloisField
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 buildRsGeneratorPoly
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 calculateReedSolomonEc
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 gfMultiply
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Security\Mfa\Qr;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11/**
12 * Enterprise Reed-Solomon Error Correction Code Generator for QR Codes.
13 *
14 * Implements Galois Field GF(256) polynomial arithmetic and generator polynomial
15 * generation according to ISO/IEC 18004.
16 *
17 * @package App\Core\Security\Mfa\Qr
18 */
19final readonly class QrReedSolomonEncoder
20{
21    /** @var int Galois Field GF(256) size. */
22    private const int GF_SIZE = 256;
23
24    /** @var int Primitive polynomial for GF(256): x^8 + x^4 + x^3 + x^2 + 1. */
25    private const int GF_PRIMITIVE = 0x11D;
26
27    /**
28     * Builds Galois Field GF(256) log and antilog tables.
29     *
30     * @return array{exp: array<int, int>, log: array<int, int>}
31     */
32    public function buildGaloisField(): array
33    {
34        $exp = array_fill(0, 512, 0);
35        $log = array_fill(0, 256, 0);
36        $x = 1;
37
38        for ($i = 0; $i < 255; $i++) {
39            $exp[$i] = $x;
40            $log[$x] = $i;
41            $x <<= 1;
42            if ($x >= self::GF_SIZE) {
43                $x ^= self::GF_PRIMITIVE;
44            }
45        }
46
47        for ($i = 255; $i < 512; $i++) {
48            $exp[$i] = $exp[$i - 255];
49        }
50
51        return ['exp' => $exp, 'log' => $log];
52    }
53
54    /**
55     * Computes Reed-Solomon generator polynomial for given error correction length.
56     *
57     * @param int                                               $ecLen Error correction length in bytes.
58     * @param array{exp: array<int, int>, log: array<int, int>} $gf    Galois field tables.
59     * @return array<int, int> Generator polynomial coefficients.
60     */
61    public function buildRsGeneratorPoly(int $ecLen, array $gf): array
62    {
63        $g = [1];
64        for ($i = 0; $i < $ecLen; $i++) {
65            $term = [1, $gf['exp'][$i]];
66            $next = array_fill(0, count($g) + 1, 0);
67            $gCount = count($g);
68            for ($j = 0; $j < $gCount; $j++) {
69                $next[$j] ^= $g[$j];
70                $next[$j + 1] ^= $this->gfMultiply($g[$j], $term[1], $gf);
71            }
72            $g = $next;
73        }
74        return $g;
75    }
76
77    /**
78     * Calculates Reed-Solomon parity codewords for a given data block.
79     *
80     * @param array<int, int>                                   $data      Data codewords.
81     * @param int                                               $ecLen     Error correction length in bytes.
82     * @param array<int, int>                                   $generator Generator polynomial coefficients.
83     * @param array{exp: array<int, int>, log: array<int, int>} $gf        Galois field tables.
84     * @return array<int, int> Parity codewords.
85     */
86    public function calculateReedSolomonEc(array $data, int $ecLen, array $generator, array $gf): array
87    {
88        $msg = array_merge($data, array_fill(0, $ecLen, 0));
89        $dataLen = count($data);
90        $gLen = count($generator);
91
92        for ($i = 0; $i < $dataLen; $i++) {
93            $lead = $msg[$i];
94            if ($lead !== 0) {
95                for ($j = 0; $j < $gLen; $j++) {
96                    $msg[$i + $j] ^= $this->gfMultiply($generator[$j], $lead, $gf);
97                }
98            }
99        }
100
101        return array_slice($msg, $dataLen);
102    }
103
104    /**
105     * Multiplies two elements in GF(256).
106     *
107     * @param int                                               $a  First element.
108     * @param int                                               $b  Second element.
109     * @param array{exp: array<int, int>, log: array<int, int>} $gf Galois field tables.
110     * @return int Product in GF(256).
111     */
112    public function gfMultiply(int $a, int $b, array $gf): int
113    {
114        if ($a === 0 || $b === 0) {
115            return 0;
116        }
117        return $gf['exp'][$gf['log'][$a] + $gf['log'][$b]];
118    }
119}