• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

azjezz / psl / 23101178730

15 Mar 2026 02:02AM UTC coverage: 95.57%. Remained the same
23101178730

push

github

web-flow
bc: use $camelCase variable naming everywhere (#631)

930 of 1013 new or added lines in 219 files covered. (91.81%)

10 existing lines in 8 files now uncovered.

10462 of 10947 relevant lines covered (95.57%)

32.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.0
/src/Psl/Encoding/Hex/DecodingReadHandle.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Psl\Encoding\Hex;
6

7
use Psl;
8
use Psl\Async\CancellationTokenInterface;
9
use Psl\Async\NullCancellationToken;
10
use Psl\Encoding\Exception;
11
use Psl\IO;
12

13
use function strlen;
14
use function strpos;
15
use function substr;
16

17
/**
18
 * A read handle that decodes hex-encoded data from an inner readable handle.
19
 *
20
 * Reads chunks from the inner handle and decodes complete 2-byte hex pairs.
21
 * Buffers any odd trailing character for the next read.
22
 * On EOF, throws if there is an incomplete hex pair remaining.
23
 */
24
final class DecodingReadHandle implements IO\BufferedReadHandleInterface
25
{
26
    use IO\ReadHandleConvenienceMethodsTrait;
27

28
    private string $buffer = '';
29
    private string $remainder = '';
30
    private bool $eof = false;
31

32
    public function __construct(
33
        private readonly IO\ReadHandleInterface $handle,
34
    ) {}
17✔
35

36
    /**
37
     * {@inheritDoc}
38
     */
39
    public function reachedEndOfDataSource(): bool
40
    {
41
        return $this->eof && $this->buffer === '';
6✔
42
    }
43

44
    /**
45
     * {@inheritDoc}
46
     */
47
    public function tryRead(null|int $maxBytes = null): string
48
    {
49
        if ($this->buffer === '' && !$this->eof) {
×
50
            $this->fillBuffer();
×
51
        }
52

53
        if ($this->buffer === '') {
×
54
            return '';
×
55
        }
56

NEW
57
        if (null === $maxBytes || $maxBytes >= strlen($this->buffer)) {
×
58
            $result = $this->buffer;
×
59
            $this->buffer = '';
×
60
            return $result;
×
61
        }
62

NEW
63
        $result = substr($this->buffer, 0, $maxBytes);
×
NEW
64
        $this->buffer = substr($this->buffer, $maxBytes);
×
UNCOV
65
        return $result;
×
66
    }
67

68
    /**
69
     * {@inheritDoc}
70
     */
71
    public function read(
72
        null|int $maxBytes = null,
73
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
74
    ): string {
75
        if ($this->eof && $this->buffer === '') {
7✔
76
            return '';
×
77
        }
78

79
        if ($this->buffer === '') {
7✔
80
            $this->fillBuffer($cancellation);
7✔
81
        }
82

83
        if ($this->buffer === '') {
6✔
84
            return '';
6✔
85
        }
86

87
        if (null === $maxBytes || $maxBytes >= strlen($this->buffer)) {
5✔
88
            $result = $this->buffer;
5✔
89
            $this->buffer = '';
5✔
90
            return $result;
5✔
91
        }
92

93
        $result = substr($this->buffer, 0, $maxBytes);
2✔
94
        $this->buffer = substr($this->buffer, $maxBytes);
2✔
95
        return $result;
2✔
96
    }
97

98
    public function readByte(CancellationTokenInterface $cancellation = new NullCancellationToken()): string
99
    {
100
        if ($this->buffer === '' && !$this->eof) {
4✔
101
            $this->fillBuffer($cancellation);
2✔
102
        }
103

104
        if ($this->buffer === '') {
4✔
105
            throw new IO\Exception\RuntimeException('Reached EOF without any more data.');
1✔
106
        }
107

108
        $ret = $this->buffer[0];
3✔
109
        if ($ret === $this->buffer) {
3✔
110
            $this->buffer = '';
1✔
111
            return $ret;
1✔
112
        }
113

114
        $this->buffer = substr($this->buffer, 1);
3✔
115
        return $ret;
3✔
116
    }
117

118
    public function readLine(CancellationTokenInterface $cancellation = new NullCancellationToken()): null|string
119
    {
120
        $line = $this->readUntil("\n", $cancellation);
4✔
121
        if ($line !== null) {
4✔
122
            if ($line !== '' && $line[-1] === "\r") {
3✔
123
                return substr($line, 0, -1);
1✔
124
            }
125

126
            return $line;
2✔
127
        }
128

129
        // No EOL found; return whatever remains, or null if empty
130
        if ($this->buffer === '' && !$this->eof) {
3✔
131
            $this->fillBuffer($cancellation);
×
132
        }
133

134
        if ($this->buffer === '') {
3✔
135
            return null;
3✔
136
        }
137

138
        $result = $this->buffer;
3✔
139
        $this->buffer = '';
3✔
140
        return $result;
3✔
141
    }
142

143
    public function readUntil(
144
        string $suffix,
145
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
146
    ): null|string {
147
        $suffixLen = strlen($suffix);
6✔
148
        $idx = strpos($this->buffer, $suffix);
6✔
149
        if ($idx !== false) {
6✔
150
            $result = substr($this->buffer, 0, $idx);
1✔
151
            $this->buffer = substr($this->buffer, $idx + $suffixLen);
1✔
152
            return $result;
1✔
153
        }
154

155
        while (!$this->eof) {
6✔
156
            $offset = strlen($this->buffer) - $suffixLen + 1;
6✔
157
            $offset = $offset > 0 ? $offset : 0;
6✔
158

159
            $this->fillBuffer($cancellation);
6✔
160

161
            $idx = strpos($this->buffer, $suffix, $offset);
6✔
162
            if ($idx !== false) {
6✔
163
                $result = substr($this->buffer, 0, $idx);
4✔
164
                $this->buffer = substr($this->buffer, $idx + $suffixLen);
4✔
165
                return $result;
4✔
166
            }
167
        }
168

169
        return null;
5✔
170
    }
171

172
    public function readUntilBounded(
173
        string $suffix,
174
        int $maxBytes,
175
        CancellationTokenInterface $cancellation = new NullCancellationToken(),
176
    ): null|string {
177
        $suffixLen = strlen($suffix);
3✔
178
        $idx = strpos($this->buffer, $suffix);
3✔
179
        if ($idx !== false) {
3✔
NEW
180
            if ($idx > $maxBytes) {
×
181
                throw new IO\Exception\OverflowException(Psl\Str\format(
×
182
                    'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
NEW
183
                    $maxBytes,
×
184
                    $suffix,
×
185
                ));
×
186
            }
187

188
            $result = substr($this->buffer, 0, $idx);
×
NEW
189
            $this->buffer = substr($this->buffer, $idx + $suffixLen);
×
190
            return $result;
×
191
        }
192

193
        if (strlen($this->buffer) > $maxBytes) {
3✔
194
            throw new IO\Exception\OverflowException(Psl\Str\format(
×
195
                'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
NEW
196
                $maxBytes,
×
197
                $suffix,
×
198
            ));
×
199
        }
200

201
        while (!$this->eof) {
3✔
202
            $offset = strlen($this->buffer) - $suffixLen + 1;
3✔
203
            $offset = $offset > 0 ? $offset : 0;
3✔
204

205
            $this->fillBuffer($cancellation);
3✔
206

207
            $idx = strpos($this->buffer, $suffix, $offset);
3✔
208
            if ($idx !== false) {
3✔
209
                if ($idx > $maxBytes) {
2✔
210
                    throw new IO\Exception\OverflowException(Psl\Str\format(
1✔
211
                        'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
1✔
212
                        $maxBytes,
1✔
213
                        $suffix,
1✔
214
                    ));
1✔
215
                }
216

217
                $result = substr($this->buffer, 0, $idx);
1✔
218
                $this->buffer = substr($this->buffer, $idx + $suffixLen);
1✔
219
                return $result;
1✔
220
            }
221

222
            if (strlen($this->buffer) > $maxBytes) {
1✔
223
                throw new IO\Exception\OverflowException(Psl\Str\format(
×
224
                    'Exceeded maximum byte limit (%d) before encountering the suffix ("%s").',
×
NEW
225
                    $maxBytes,
×
226
                    $suffix,
×
227
                ));
×
228
            }
229
        }
230

231
        return null;
1✔
232
    }
233

234
    /**
235
     * @throws Exception\RangeException If the hex data contains invalid characters or has an odd length at EOF.
236
     */
237
    private function fillBuffer(CancellationTokenInterface $cancellation = new NullCancellationToken()): void
238
    {
239
        if ($this->eof) {
17✔
240
            return;
×
241
        }
242

243
        $chunk = $this->handle->read(4096, $cancellation);
17✔
244
        if ($chunk === '' && $this->handle->reachedEndOfDataSource()) {
17✔
245
            $this->eof = true;
13✔
246
            // Decode any remaining bytes.
247
            if ($this->remainder !== '') {
13✔
248
                $this->buffer .= decode($this->remainder);
×
249
                $this->remainder = '';
×
250
            }
251

252
            return;
13✔
253
        }
254

255
        $data = $this->remainder . $chunk;
15✔
256

257
        // Decode complete 2-byte hex pairs only.
258
        $length = strlen($data);
15✔
259
        $usable = $length - ($length % 2);
15✔
260

261
        if ($usable > 0) {
15✔
262
            $this->buffer .= decode(substr($data, 0, $usable));
15✔
263
            $this->remainder = substr($data, $usable);
14✔
264
        } else {
265
            $this->remainder = $data;
×
266
        }
267
    }
268
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc