ByteBuffer   F
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 449
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 126
c 1
b 0
f 0
dl 0
loc 449
rs 3.28
wmc 64

28 Methods

Rating   Name   Duplication   Size   Complexity  
A base64UrlEncode() 0 3 1
A serialize() 0 3 1
A getBinaryString() 0 3 1
A getUint64Value() 0 26 6
A __unserialize() 0 10 3
A getFloatValue() 0 15 4
A getByteValue() 0 10 3
A randomBuffer() 0 16 4
A isEmpty() 0 3 1
A unserialize() 0 8 2
A getHexValue() 0 3 1
A getUint16Value() 0 15 4
A getLength() 0 3 1
A __serialize() 0 4 1
A __construct() 0 4 1
A isUseBase64UrlEncoding() 0 3 1
A getHalfFloatValue() 0 17 5
A fromBase64Url() 0 5 1
A isEqual() 0 3 1
A useBase64UrlEncoding() 0 4 1
A getDoubleValue() 0 15 4
A getBytes() 0 11 4
A __toString() 0 3 1
A getUint32Value() 0 20 5
A jsonSerialize() 0 9 2
A fromHex() 0 8 2
A getJson() 0 9 2
A base64UrlDecode() 0 9 1

How to fix   Complexity   

Complex Class

Complex classes like ByteBuffer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ByteBuffer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Platine Webauth
5
 *
6
 * Platine Webauthn is the implementation of webauthn specifications
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Webauth
11
 * Copyright (c) Jakob Bennemann <[email protected]>
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
declare(strict_types=1);
33
34
namespace Platine\Webauthn\Helper;
35
36
use Exception;
37
use JsonSerializable;
38
use Platine\Stdlib\Helper\Json;
39
use Platine\Webauthn\Exception\WebauthnException;
40
use Serializable;
41
use Stringable;
42
43
/**
44
 * @class ByteBuffer
45
 * @package Platine\Webauthn\Helper
46
 */
47
class ByteBuffer implements JsonSerializable, Serializable, Stringable
48
{
49
    /**
50
     * Whether to use Base64 URL encoding
51
     * @var bool
52
     */
53
    protected bool $useBase64UrlEncoding = false;
54
55
    /**
56
     * The data
57
     * @var string
58
     */
59
    protected string $data;
60
61
    /**
62
     * The data length
63
     * @var int
64
     */
65
    protected int $length = 0;
66
67
    /**
68
     * Create new instance
69
     * @param string $binaryData
70
     */
71
    public function __construct(string $binaryData)
72
    {
73
        $this->data = (string)$binaryData;
74
        $this->length = strlen($binaryData);
75
    }
76
77
    /**
78
     * Create a ByteBuffer from a base64 URL encoded string
79
     * @param string $str
80
     * @return self
81
     */
82
    public static function fromBase64Url(string $str): self
83
    {
84
        $data = self::base64UrlDecode($str);
85
86
        return new self($data);
87
    }
88
89
    /**
90
     * Create a ByteBuffer from a hexadecimal string
91
     * @param string $str
92
     * @return self
93
     */
94
    public static function fromHex(string $str): self
95
    {
96
        $binary = hex2bin($str);
97
        if ($binary === false) {
98
            throw new WebauthnException('Invalid hex string for bytes buffer');
99
        }
100
101
        return new self($binary);
102
    }
103
104
    /**
105
     * create a random Byte Buffer
106
     * @param int<1, max> $length
107
     * @return self
108
     */
109
    public static function randomBuffer(int $length): self
110
    {
111
        if (function_exists('random_bytes')) {
112
            return new self(random_bytes($length));
113
        }
114
115
        if (function_exists('openssl_random_pseudo_bytes')) {
116
            $bytes = openssl_random_pseudo_bytes($length);
117
            if ($bytes === false) {
118
                throw new WebauthnException('cannot generate random bytes using openssl for bytes buffer');
119
            }
120
121
            return new self($bytes);
122
        }
123
124
        throw new WebauthnException('cannot generate random bytes for bytes buffer');
125
    }
126
127
    /**
128
     * Return the buffer bytes
129
     * @param int $offset
130
     * @param int $length
131
     * @return string
132
     */
133
    public function getBytes(int $offset, int $length): string
134
    {
135
        if ($offset < 0 || $length < 0 || ($offset + $length) > $this->length) {
136
            throw new WebauthnException(sprintf(
137
                'Invalid offset [%d] or length [%d] for bytes buffer',
138
                $offset,
139
                $length
140
            ));
141
        }
142
143
        return substr($this->data, $offset, $length);
144
    }
145
146
    /**
147
     * Return the byte value of the given offset
148
     * @param int $offset
149
     * @return int
150
     */
151
    public function getByteValue(int $offset): int
152
    {
153
        if ($offset < 0 || $offset >= $this->length) {
154
            throw new WebauthnException(sprintf(
155
                'Invalid offset [%d] for bytes buffer',
156
                $offset
157
            ));
158
        }
159
160
        return ord(substr($this->data, $offset, 1));
161
    }
162
163
    /**
164
     * Return the Uint16 value of the given offset
165
     * @param int $offset
166
     * @return int
167
     */
168
    public function getUint16Value(int $offset): int
169
    {
170
        if ($offset < 0 || ($offset + 2) > $this->length) {
171
            throw new WebauthnException(sprintf(
172
                'Invalid offset [%d] for bytes buffer',
173
                $offset
174
            ));
175
        }
176
177
        $data = unpack('n', $this->data, $offset);
178
        if ($data === false) {
179
            throw new WebauthnException('Can not unpack data for bytes buffer');
180
        }
181
182
        return $data[1];
183
    }
184
185
    /**
186
     * Return the Uint32 value of the given offset
187
     * @param int $offset
188
     * @return int
189
     */
190
    public function getUint32Value(int $offset): int
191
    {
192
        if ($offset < 0 || ($offset + 4) > $this->length) {
193
            throw new WebauthnException(sprintf(
194
                'Invalid offset [%d] for bytes buffer',
195
                $offset
196
            ));
197
        }
198
199
        $data = unpack('N', $this->data, $offset);
200
        if ($data === false) {
201
            throw new WebauthnException('Can not unpack data for bytes buffer');
202
        }
203
204
        // Signed integer overflow causes signed negative numbers
205
        if ($data[1] < 0) {
206
            throw new WebauthnException('Value out of integer range for bytes buffer');
207
        }
208
209
        return $data[1];
210
    }
211
212
    /**
213
     * Return the Uint64 value of the given offset
214
     * @param int $offset
215
     * @return int
216
     */
217
    public function getUint64Value(int $offset): int
218
    {
219
        // @codeCoverageIgnoreStart
220
        if (PHP_INT_SIZE < 8) {
221
            throw new WebauthnException('64-bit values not supported by this system');
222
        }
223
        // @codeCoverageIgnoreEnd
224
225
        if ($offset < 0 || ($offset + 8) > $this->length) {
226
            throw new WebauthnException(sprintf(
227
                'Invalid offset [%d] for bytes buffer',
228
                $offset
229
            ));
230
        }
231
232
        $data = unpack('J', $this->data, $offset);
233
        if ($data === false) {
234
            throw new WebauthnException('Can not unpack data for bytes buffer');
235
        }
236
237
        // Signed integer overflow causes signed negative numbers
238
        if ($data[1] < 0) {
239
            throw new WebauthnException('Value out of integer range for bytes buffer');
240
        }
241
242
        return $data[1];
243
    }
244
245
    /**
246
     * Return the half float value
247
     * @param int $offset
248
     * @return float
249
     */
250
    public function getHalfFloatValue(int $offset): float
251
    {
252
        //FROM spec pseudo decode_half(unsigned char *halfp)
253
        $half = $this->getUint16Value($offset);
254
255
        $exp = ($half >> 10) & 0x1f;
256
        $mant = $half & 0x3ff;
257
258
        if ($exp === 0) {
259
            $val = $mant * (2 ** -24);
260
        } elseif ($exp !== 31) {
261
            $val = ($mant + 1024) * (2 ** ($exp - 25));
262
        } else {
263
            $val = ($mant === 0) ? INF : NAN;
264
        }
265
266
        return ($half & 0x8000) ? -$val : $val;
267
    }
268
269
    /**
270
     * Return the float value of the given offset
271
     * @param int $offset
272
     * @return float
273
     */
274
    public function getFloatValue(int $offset): float
275
    {
276
        if ($offset < 0 || ($offset + 4) > $this->length) {
277
            throw new WebauthnException(sprintf(
278
                'Invalid offset [%d] for bytes buffer',
279
                $offset
280
            ));
281
        }
282
283
        $data = unpack('G', $this->data, $offset);
284
        if ($data === false) {
285
            throw new WebauthnException('Can not unpack data for bytes buffer');
286
        }
287
288
        return $data[1];
289
    }
290
291
    /**
292
     * Return the double value of the given offset
293
     * @param int $offset
294
     * @return float
295
     */
296
    public function getDoubleValue(int $offset): float
297
    {
298
        if ($offset < 0 || ($offset + 8) > $this->length) {
299
            throw new WebauthnException(sprintf(
300
                'Invalid offset [%d] for bytes buffer',
301
                $offset
302
            ));
303
        }
304
305
        $data = unpack('E', $this->data, $offset);
306
        if ($data === false) {
307
            throw new WebauthnException('Can not unpack data for bytes buffer');
308
        }
309
310
        return $data[1];
311
    }
312
313
    /**
314
     * Return the hexadecimal
315
     * @return string
316
     */
317
    public function getHexValue(): string
318
    {
319
        return bin2hex($this->data);
320
    }
321
322
    /**
323
     * Whether the buffer is empty
324
     * @return bool
325
     */
326
    public function isEmpty(): bool
327
    {
328
        return $this->length === 0;
329
    }
330
331
    /**
332
     * Whether two buffers are equal
333
     * @param ByteBuffer $data
334
     * @return bool
335
     */
336
    public function isEqual(ByteBuffer $data): bool
337
    {
338
        return $this->data === $data->getBinaryString();
339
    }
340
341
    /**
342
     * Return the JSON data
343
     * @param int $options
344
     * @return mixed
345
     */
346
    public function getJson(int $options = 0): mixed
347
    {
348
        try {
349
            $data = Json::decode($this->getBinaryString(), false, 512, $options);
350
        } catch (Exception $ex) {
351
            throw new WebauthnException($ex->getMessage());
352
        }
353
354
        return $data;
355
    }
356
357
    /**
358
     * Return the length
359
     * @return int
360
     */
361
    public function getLength(): int
362
    {
363
        return $this->length;
364
    }
365
366
        /**
367
     * Return the binary string
368
     * @return string
369
     */
370
    public function getBinaryString(): string
371
    {
372
        return $this->data;
373
    }
374
375
    /**
376
     *
377
     * @return bool
378
     */
379
    public function isUseBase64UrlEncoding(): bool
380
    {
381
        return $this->useBase64UrlEncoding;
382
    }
383
384
    /**
385
     *
386
     * @param bool $useBase64UrlEncoding
387
     * @return $this
388
     */
389
    public function useBase64UrlEncoding(bool $useBase64UrlEncoding): self
390
    {
391
        $this->useBase64UrlEncoding = $useBase64UrlEncoding;
392
        return $this;
393
    }
394
395
    /**
396
    * {@inheritdoc}
397
    * @return mixed
398
    */
399
    public function jsonSerialize(): mixed
400
    {
401
        if ($this->useBase64UrlEncoding) {
402
            return self::base64UrlEncode($this->data);
403
        }
404
405
        return sprintf(
406
            '=?BINARY?B?%s?=',
407
            base64_encode($this->data)
408
        );
409
    }
410
411
    /**
412
    * {@inheritdoc}
413
    * @return string|null
414
    */
415
    public function serialize(): ?string
416
    {
417
        return serialize($this->data);
418
    }
419
420
    /**
421
    * {@inheritdoc}
422
    * $param string $data
423
    */
424
    public function unserialize(string $data): void
425
    {
426
        $value = unserialize($data);
427
        if ($value === false) {
428
            throw new WebauthnException('Can not unserialize the data');
429
        }
430
        $this->data = $value;
431
        $this->length = strlen($this->data);
432
    }
433
434
    /**
435
     * Return string representation
436
     * @return string
437
     */
438
    public function __toString(): string
439
    {
440
        return $this->getHexValue();
441
    }
442
443
    /**
444
     * PHP 8 deprecates Serializable Interface if we don't defined this method
445
     * @param array<string, mixed> $data
446
     * @return void
447
     */
448
    public function __unserialize(array $data)
449
    {
450
        if (isset($data['data'])) {
451
            $value = unserialize($data['data']);
452
            if ($value === false) {
453
                throw new WebauthnException('Can not unserialize the data');
454
            }
455
456
            $this->data = $value;
457
            $this->length = strlen($this->data);
458
        }
459
    }
460
461
    /**
462
     * PHP 8 deprecates Serializable Interface if we don't defined this method
463
     * @return array<string, mixed>
464
     */
465
    public function __serialize(): array
466
    {
467
        return [
468
            'data' => serialize($this->data),
469
        ];
470
    }
471
472
    /**
473
     * Base 64 URL decoding
474
     * @param string $str
475
     * @return string
476
     */
477
    protected static function base64UrlDecode(string $str): string
478
    {
479
        $data = sprintf(
480
            '%s%s',
481
            strtr($str, '-_', '+/'),
482
            str_repeat('=', 3 - (3 + strlen($str)) % 4)
483
        );
484
485
        return base64_decode($data);
486
    }
487
488
    /**
489
     * Base 64 URL encoding
490
     * @param string $str
491
     * @return string
492
     */
493
    protected static function base64UrlEncode(string $str): string
494
    {
495
        return rtrim(strtr($str, '+/', '-_'), '=');
496
    }
497
}
498