Deserializer::getOffset()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Sysbot\Bin;
6
7
use ArithmeticError;
8
use BigInteger;
9
use Exception;
10
use OutOfRangeException;
11
use Sysbot\Bin\Common\BinaryUtils;
12
13
/**
14
 * Class Deserializer
15
 * @package Sysbot\Bin
16
 */
17
class Deserializer
18
{
19
20
    use BinaryUtils;
21
22
    /**
23
     * @var int
24
     */
25
    protected int $offset = 0;
26
27
    /**
28
     * @param string $bytes
29
     */
30
    public function __construct(protected string $bytes)
31
    {
32
    }
33
34
    /**
35
     * @return int
36
     */
37
    public function getOffset(): int
38
    {
39
        return $this->offset;
40
    }
41
42
    /**
43
     * @param int $offset
44
     * @param int $whence
45
     * @return $this
46
     */
47
    public function seek(int $offset, int $whence = SEEK_SET): self
48
    {
49
        switch ($whence) {
50
            case SEEK_CUR:
51
                $offset += $this->offset;
52
                break;
53
            case SEEK_END:
54
                if ($offset > 0) {
55
                    $offset = 0;
56
                }
57
                $offset = strlen($this->bytes) + $offset;
58
                break;
59
            default:
60
                break;
61
        }
62
        $this->offset = $offset;
63
        return $this;
64
    }
65
66
    /**
67
     * Resets the offset.
68
     * @return Deserializer
69
     */
70
    public function rewind(): self
71
    {
72
        return $this->seek(0);
73
    }
74
75
    /**
76
     * @param int $length
77
     * @param bool $bigEndian
78
     * @return string
79
     * @throws OutOfRangeException
80
     */
81
    public function getBytes(int $length, bool $bigEndian = false): string
82
    {
83
        $data = substr($this->bytes, $this->offset, $length);
84
        $actual = strlen($data);
85
        if ($actual < $length) {
86
            $message = sprintf('Not enough data to read: expected %d bytes, but only %d received', $length, $actual);
87
            throw new OutOfRangeException($message);
88
        }
89
        $this->offset += $length;
90
        if ($bigEndian) {
91
            $data = strrev($data);
92
        }
93
        return $data;
94
    }
95
96
    /**
97
     * @param bool $bigEndian
98
     * @return int
99
     * @throws OutOfRangeException
100
     */
101
    public function readLong(bool $bigEndian = false): int
102
    {
103
        $data = $this->getBytes(4, $bigEndian);
104
        $result = self::unpack('V', $data);
105
        if (null === $result) {
106
            throw new ArithmeticError('Cannot de-serialize long');
107
        }
108
        return $result;
109
    }
110
111
    /**
112
     * @param bool $bigEndian
113
     * @return int|BigInteger
114
     * @throws OutOfRangeException
115
     * @throws Exception
116
     * @throws ArithmeticError
117
     */
118
    public function readLongLong(bool $bigEndian = false): int|BigInteger
119
    {
120
        $data = $this->getBytes(8, $bigEndian);
121
        $result = BigInteger::fromBytes(IS_BIG_ENDIAN ? $data : strrev($data));
122
        if (IS_32_BIT) {
123
            return $result;
124
        }
125
        return $result->toInt();
126
    }
127
128
    /**
129
     * @return string
130
     * @throws OutOfRangeException
131
     */
132
    public function readString(): string
133
    {
134
        $len = ord($this->getBytes(1));
135
        if (254 < $len) {
136
            $message = sprintf('Length too big: %d (max 254)', $len);
137
            throw new OutOfRangeException($message);
138
        }
139
        if (254 == $len) {
140
            $data = $this->getBytes(3);
141
            $len = self::unpack('V', $data . chr(0)) + 1;
142
        }
143
        $result = $len ? $this->getBytes($len) : '';
144
        $this->offset += self::positiveModulo(-$len - 1, 4);
145
        return $result;
146
    }
147
148
    /**
149
     * @param string $format
150
     * @param string $data
151
     * @return mixed
152
     */
153
    public static function unpack(string $format, string $data): mixed
154
    {
155
        $result = @unpack($format, $data);
156
        if (false === $result) {
157
            return null;
158
        }
159
        return $result[1] ?? null;
160
    }
161
162
}