Passed
Push — master ( 4c571a...c0376f )
by Thomas
02:30
created

CborEncoder::sortAndEncodeMapEntries()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 8
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 15
ccs 9
cts 9
cp 1
crap 3
rs 10
1
<?php
2
3
namespace MadWizard\WebAuthn\Format;
4
5
use MadWizard\WebAuthn\Exception\CborException;
6
7
final class CborEncoder
8
{
9 5
    public static function encodeInteger(int $i): string
10
    {
11 5
        if ($i < 0) {
12 4
            $i = -($i + 1);
13 4
            $major = Cbor::MAJOR_NEGATIVE_INT;
14
        } else {
15 5
            $major = Cbor::MAJOR_UNSIGNED_INT;
16
        }
17
18 5
        $bytes = self::integerBytes($i, $minorVal);
19
20 5
        return chr(($major << 5) | $minorVal) . $bytes;
21
    }
22
23 4
    public static function encodeTextString(string $text): string
24
    {
25 4
        $lengthBytes = self::integerBytes(\strlen($text), $minorVal);
26 4
        return chr((Cbor::MAJOR_TEXT_STRING << 5) | $minorVal) . $lengthBytes . $text;
27
    }
28
29 5
    public static function encodeByteString(ByteBuffer $bytes): string
30
    {
31 5
        $lengthBytes = self::integerBytes($bytes->getLength(), $minorVal);
32 5
        return chr((Cbor::MAJOR_BYTE_STRING << 5) | $minorVal) . $lengthBytes . $bytes->getBinaryString();
33
    }
34
35 5
    private static function sortAndEncodeMapEntries(array $map): string
36
    {
37
        // Use canonical sorting. Shorter keys (as CBOR bytes) always go before longer keys. When length is the same
38
        // a byte for byte comparison is done.
39
        uksort($map, function (string $key1, string $key2) {
40 5
            $cmp = \strlen($key1) <=> \strlen($key2);
41 5
            return ($cmp === 0) ? ($key1 <=> $key2) : $cmp;
42 5
        });
43
44 5
        $mapContent = '';
45 5
        foreach ($map as $k => $v) {
46 5
            $mapContent .= $k . $v;
47
        }
48 5
        $lengthBytes = self::integerBytes(count($map), $minorVal);
49 5
        return chr((Cbor::MAJOR_MAP << 5) | $minorVal) . $lengthBytes . $mapContent;
50
    }
51
52 5
    public static function encodeMap(CborMap $map): string
53
    {
54 5
        $mapValues = [];
55
56 5
        foreach ($map->getEntries() as $entry) {
57 5
            $mapValues[self::encodeSingleValue($entry[0])] = self::encodeSingleValue($entry[1]);
58
        }
59
60 5
        return self::sortAndEncodeMapEntries($mapValues);
61
    }
62
63 8
    private static function integerBytes(int $i, ?int &$minorVal): string
64
    {
65 8
        if ($i < 24) {
66 8
            $minorVal = $i;
67 8
            return '';
68
        }
69
70 4
        if ($i < 2 ** 8) {
71 3
            $minorVal = 24;
72 3
            return pack('C', $i);
73
        }
74
75 2
        if ($i < 2 ** 16) {
76 2
            $minorVal = 25;
77 2
            return pack('n', $i);
78
        }
79
80 1
        if (PHP_INT_SIZE < 8 || $i < 2 ** 32) {
81 1
            $minorVal = 26;
82 1
            return pack('N', $i);
83
        }
84
85 1
        $minorVal = 27;
86 1
        return pack('J', $i);
87
    }
88
89
    /**
90
     * @param int|string|ByteBuffer|CborMap|bool|null $value
91
     *
92
     * @throws CborException
93
     */
94 5
    public static function encodeSingleValue($value): string
95
    {
96 5
        if (\is_int($value)) {
97 4
            return self::encodeInteger($value);
98
        }
99 5
        if (\is_string($value)) {
100 3
            return self::encodeTextString($value);
101
        }
102 5
        if ($value instanceof ByteBuffer) {
103 4
            return self::encodeByteString($value);
104
        }
105 2
        if ($value instanceof CborMap) {
106 1
            return self::encodeMap($value);
107
        }
108 1
        if ($value === false) {
109 1
            return chr(0xF4);
110
        }
111 1
        if ($value === true) {
0 ignored issues
show
introduced by
The condition $value === true is always true.
Loading history...
112
            return chr(0xF5);
113
        }
114 1
        if ($value === null) {
115 1
            return chr(0xF6);
116
        }
117
        // @phpstan-ignore-next-line
118
        throw new CborException(sprintf('Unsupported type "%s" for encodeSingleValue', gettype($value)));
119
    }
120
}
121