Passed
Push — main ( 565416...37b783 )
by Oscar
12:09
created

Crockford::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 1
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of ocubom/base-convert
5
 *
6
 * © Oscar Cubo Medina <https://ocubom.github.io>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ocubom\Math\Base;
13
14
use Ocubom\Math\AbstractBase;
15
use Ocubom\Math\Base;
16
use Ocubom\Math\Exception\InvalidArgumentException;
17
18
/**
19
 * A base for Douglas Crockford Base 32 encoding.
20
 *
21
 * @see https://www.crockford.com/base32.html
22
 */
23
class Crockford extends AbstractBase
24
{
25
    /**
26
     * Generic map of symbols.
27
     */
28
    const MAP = [
29
        '' => '0123456789ABCDEFGHJKMNPQRSTVWXYZ',
30
        0 => 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A' => 10, 'B' => 11,
31
        'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17,
32
        'I' => 01, 'J' => 18, 'K' => 19, 'L' => 01, 'M' => 20, 'N' => 21,
0 ignored issues
show
Bug introduced by
A parse error occurred: The alleged octal '1' is invalid
Loading history...
33
        'O' => 00, 'P' => 22, 'Q' => 23, 'R' => 24, 'S' => 25, 'T' => 26,
34
        'V' => 27, 'W' => 28, 'X' => 29, 'Y' => 30, 'Z' => 31, 'a' => 10,
35
        'b' => 11, 'c' => 12, 'd' => 13, 'e' => 14, 'f' => 15, 'g' => 16,
36
        'h' => 17, 'i' => 01, 'j' => 18, 'k' => 19, 'l' => 01, 'm' => 20,
37
        'n' => 21, 'o' => 00, 'p' => 22, 'q' => 23, 'r' => 24, 's' => 25,
38
        't' => 26, 'v' => 27, 'w' => 28, 'x' => 29, 'y' => 30, 'z' => 31,
39
    ];
40
41
    /**
42
     * Map for symbols used only on checksums.
43
     */
44
    const CHECKSUM = [
45
        '' => '*~$=U',
46
        '*' => 32,
47
        '~' => 33,
48
        '$' => 34,
49
        '=' => 35,
50
        'U' => 36,
51
        'u' => 36,
52
    ];
53
54
    /** @var bool */
55
    private $checksum;
56
57
    /**
58
     * @param bool $checksum The number includes an extra digit for checksum
59
     */
60 51
    public function __construct(bool $checksum = false)
61
    {
62 51
        $this->checksum = $checksum;
63
    }
64
65 51
    public function getMap(): array
66
    {
67 51
        return self::MAP;
68
    }
69
70 51
    public function filterValue($value): string
71
    {
72 51
        $number = (is_numeric($value) ? strval($value) : $value) ?: ($this->checksum ? '00' : '0');
73
74
        // Ignore hyphens (improving readability)
75 51
        $number = str_replace('-', '', $number);
76
77 51
        return $this->checksum
78 26
            ? $this->filterValueWithChecksum($number)
79 50
            : parent::filterValue($number);
80
    }
81
82 16
    public function returnValue($value): string
83
    {
84 16
        $value = parent::returnValue($value);
85
86 16
        return $value.($this->checksum ? self::checksum($value) : '');
87
    }
88
89 26
    private function filterValueWithChecksum(string $value): string
90
    {
91 26
        $number = parent::filterValue(substr($value, 0, -1));
92 26
        $check = substr($value, -1);
93
94
        // Verify checksum character validity
95 26
        if (!isset(self::MAP[$check]) && !isset(self::CHECKSUM[$check])) {
96 1
            throw new InvalidArgumentException(sprintf(
97 1
                'Invalid %s check symbol "%s" found on "%s"',
98 1
                (string) $this,
99 1
                $check,
100 1
                $value
101 1
            ));
102
        }
103
104
        // Verify checksum value
105 25
        if ($check !== $valid = $this->checksum($number)) {
106 1
            throw new InvalidArgumentException(sprintf(
107 1
                'Invalid %s checksum for "%s", found "%s" must be "%s"',
108 1
                (string) $this,
109 1
                $value,
110 1
                $check,
111 1
                $valid
112 1
            ));
113
        }
114
115 24
        return $number;
116
    }
117
118
    /**
119
     * Calculate crockford checksum for number.
120
     *
121
     * @param string $number The crockford number
122
     *
123
     * @return string The crockford encoded checksum
124
     */
125 25
    private static function checksum(string $number): string
126
    {
127 25
        $value = str_replace('-', '', $number);
128 25
        $value = Base::convert($value, new Crockford(false), 10);
129 25
        $check = self::mod($value, 37);
130
131 25
        return substr(self::MAP[''].self::CHECKSUM[''], $check, 1);
132
    }
133
134
    /**
135
     * Remainder (modulo) of the division of the arguments.
136
     *
137
     * @param string $num1 The dividend (base-10 string)
138
     * @param int    $num2 The divisor
139
     *
140
     * @return int Remainder of $num1/$num2
141
     */
142 25
    private static function mod(string $num1, int $num2): int
143
    {
144 25
        $mod = 0;
145 25
        foreach (str_split($num1) as $digit) {
146 25
            $mod = (10 * $mod + (int) $digit) % $num2;
147
        }
148
149 25
        return $mod;
150
    }
151
}
152