Passed
Branch master (8940db)
by Sam
02:38
created

NSEC3::base32decode()   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 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Badcow DNS Library.
7
 *
8
 * (c) Samuel Williams <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Badcow\DNS\Rdata;
15
16
use Badcow\DNS\Parser\Tokens;
17
use Badcow\DNS\Validator;
18
19
/**
20
 * {@link https://tools.ietf.org/html/rfc5155}.
21
 */
22
class NSEC3 implements RdataInterface
23
{
24
    use RdataTrait;
25
26
    const TYPE = 'NSEC3';
27
    const TYPE_CODE = 50;
28
29
    /**
30
     * @var int
31
     */
32
    private $hashAlgorithm;
33
34
    /**
35
     * @var bool
36
     */
37
    private $unsignedDelegationsCovered = false;
38
39
    /**
40
     * @var int
41
     */
42
    private $iterations;
43
44
    /**
45
     * @var string Binary encoded string
46
     */
47
    private $salt;
48
49
    /**
50
     * @var string Binary encoded hash
51
     */
52
    private $nextHashedOwnerName;
53
54
    /**
55
     * @var array
56
     */
57
    private $types = [];
58
59
    /**
60
     * @var \Base2n
61
     */
62
    private static $base32;
63
64
    /**
65
     * Singleton to instantiate and return \Base2n instance for extended hex.
66
     *
67
     * @return \Base2n
68
     */
69 4
    private static function base32(): \Base2n
70
    {
71 4
        if (!isset(self::$base32)) {
72 1
            self::$base32 = new \Base2n(5, '0123456789abcdefghijklmnopqrstuv', false, true, true);
73
        }
74
75 4
        return self::$base32;
76
    }
77
78
    /**
79
     * @return int
80
     */
81
    public function getHashAlgorithm(): int
82
    {
83
        return $this->hashAlgorithm;
84
    }
85
86
    /**
87
     * @param int $hashAlgorithm
88
     *
89
     * @throws \InvalidArgumentException
90
     */
91 4
    public function setHashAlgorithm(int $hashAlgorithm): void
92
    {
93 4
        if (!Validator::isUnsignedInteger($hashAlgorithm, 8)) {
94
            throw new \InvalidArgumentException('Hash algorithm must be 8-bit integer.');
95
        }
96 4
        $this->hashAlgorithm = $hashAlgorithm;
97 4
    }
98
99
    /**
100
     * @return bool
101
     */
102
    public function isUnsignedDelegationsCovered(): bool
103
    {
104
        return $this->unsignedDelegationsCovered;
105
    }
106
107
    /**
108
     * @param bool $unsignedDelegationsCovered
109
     */
110 4
    public function setUnsignedDelegationsCovered(bool $unsignedDelegationsCovered): void
111
    {
112 4
        $this->unsignedDelegationsCovered = $unsignedDelegationsCovered;
113 4
    }
114
115
    /**
116
     * @return int
117
     */
118
    public function getIterations(): int
119
    {
120
        return $this->iterations;
121
    }
122
123
    /**
124
     * @param int $iterations
125
     *
126
     * @throws \InvalidArgumentException
127
     */
128 4
    public function setIterations(int $iterations): void
129
    {
130 4
        if (!Validator::isUnsignedInteger($iterations, 16)) {
131
            throw new \InvalidArgumentException('Hash algorithm must be 16-bit integer.');
132
        }
133 4
        $this->iterations = $iterations;
134 4
    }
135
136
    /**
137
     * @return string Base16 string
138
     */
139 2
    public function getSalt(): string
140
    {
141 2
        return bin2hex($this->salt);
142
    }
143
144
    /**
145
     * @param string $salt Hexadecimal string
146
     */
147 4
    public function setSalt(string $salt): void
148
    {
149 4
        if (false === $bin = @hex2bin($salt)) {
150
            throw new \InvalidArgumentException('Salt must be a hexadecimal string.');
151
        }
152 4
        $this->salt = $bin;
153 4
    }
154
155
    /**
156
     * @return string Base32 hashed string
157
     */
158 2
    public function getNextHashedOwnerName(): string
159
    {
160 2
        return self::base32encode($this->nextHashedOwnerName);
161
    }
162
163
    /**
164
     * @param string $nextHashedOwnerName
165
     */
166 4
    public function setNextHashedOwnerName(string $nextHashedOwnerName): void
167
    {
168 4
        if (!Validator::isBase32HexEncoded($nextHashedOwnerName)) {
169
            throw new \InvalidArgumentException('Next hashed owner name must be a base32 encoded string.');
170
        }
171
172 4
        $this->nextHashedOwnerName = self::base32decode($nextHashedOwnerName);
173 4
    }
174
175
    /**
176
     * @param string $type
177
     */
178 4
    public function addType(string $type): void
179
    {
180 4
        $this->types[] = $type;
181 4
    }
182
183
    /**
184
     * Clears the types from the RDATA.
185
     */
186
    public function clearTypes(): void
187
    {
188
        $this->types = [];
189
    }
190
191
    /**
192
     * @return array
193
     */
194
    public function getTypes(): array
195
    {
196
        return $this->types;
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202 2
    public function toText(): string
203
    {
204 2
        return sprintf('%d %d %d %s %s %s',
205 2
            $this->hashAlgorithm,
206 2
            (int) $this->unsignedDelegationsCovered,
207 2
            $this->iterations,
208 2
            $this->getSalt(),
209 2
            $this->getNextHashedOwnerName(),
210 2
            implode(Tokens::SPACE, $this->types)
211
        );
212
    }
213
214
    /**
215
     * {@inheritdoc}
216
     *
217
     * @throws UnsupportedTypeException
218
     */
219 1
    public function toWire(): string
220
    {
221 1
        $wire = pack('CCnC',
222 1
            $this->hashAlgorithm,
223 1
            (int) $this->unsignedDelegationsCovered,
224 1
            $this->iterations,
225 1
            strlen($this->salt)
226
        );
227 1
        $wire .= $this->salt;
228 1
        $wire .= chr(strlen($this->nextHashedOwnerName));
229 1
        $wire .= $this->nextHashedOwnerName;
230 1
        $wire .= NSEC::renderBitmap($this->types);
231
232 1
        return $wire;
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     *
238
     * @return NSEC3
239
     */
240 1
    public static function fromText(string $text): RdataInterface
241
    {
242 1
        $rdata = explode(Tokens::SPACE, $text);
243 1
        $nsec3 = new self();
244 1
        $nsec3->setHashAlgorithm((int) array_shift($rdata));
245 1
        $nsec3->setUnsignedDelegationsCovered((bool) array_shift($rdata));
246 1
        $nsec3->setIterations((int) array_shift($rdata));
247 1
        $nsec3->setSalt((string) array_shift($rdata));
248 1
        $nsec3->setNextHashedOwnerName((string) array_shift($rdata));
249 1
        array_map([$nsec3, 'addType'], $rdata);
250
251 1
        return $nsec3;
252
    }
253
254
    /**
255
     * {@inheritdoc}
256
     *
257
     * @return NSEC3
258
     *
259
     * @throws UnsupportedTypeException
260
     */
261 1
    public static function fromWire(string $rdata): RdataInterface
262
    {
263 1
        $offset = 0;
264 1
        $values = unpack('C<hashAlgo>/C<flags>/n<iterations>/C<saltLen>', $rdata, $offset);
265 1
        $offset += 5;
266 1
        $nsec3 = new self();
267 1
        $nsec3->setHashAlgorithm((int) $values['<hashAlgo>']);
268 1
        $nsec3->setUnsignedDelegationsCovered((bool) $values['<flags>']);
269 1
        $nsec3->setIterations((int) $values['<iterations>']);
270
271 1
        $saltLen = (int) $values['<saltLen>'];
272 1
        $salt = unpack('H*', substr($rdata, $offset, $saltLen))[1];
273 1
        $nsec3->setSalt($salt);
274 1
        $offset += $saltLen;
275
276 1
        $hashLen = ord(substr($rdata, $offset, 1));
277 1
        ++$offset;
278 1
        $hash = substr($rdata, $offset, $hashLen);
279 1
        $offset += $hashLen;
280 1
        $nsec3->setNextHashedOwnerName(self::base32encode($hash));
281
282 1
        $types = NSEC::parseBitmap($rdata, $offset);
283 1
        array_map([$nsec3, 'addType'], $types);
284
285 1
        return $nsec3;
286
    }
287
288
    /**
289
     * Encode data as a base32 string.
290
     *
291
     * @param string $data
292
     *
293
     * @return string base32 string
294
     */
295 3
    public static function base32encode(string $data): string
296
    {
297 3
        return self::base32()->encode($data);
298
    }
299
300
    /**
301
     * Decode a base32 encoded string.
302
     *
303
     * @param string $data base32 string
304
     *
305
     * @return string
306
     */
307 4
    public static function base32decode(string $data): string
308
    {
309 4
        return self::base32()->decode($data);
310
    }
311
}
312