GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Failed Conditions
Push — master ( f2da15...888a68 )
by Charlotte
05:28
created

ProtocolParser::processBuffer()   D

Complexity

Conditions 30
Paths 24

Size

Total Lines 113
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 123.799

Importance

Changes 0
Metric Value
cc 30
eloc 73
nc 24
nop 0
dl 0
loc 113
ccs 36
cts 68
cp 0.5294
crap 123.799
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Plasma Driver MySQL component
4
 * Copyright 2018 PlasmaPHP, All Rights Reserved
5
 *
6
 * Website: https://github.com/PlasmaPHP
7
 * License: https://github.com/PlasmaPHP/driver-mysql/blob/master/LICENSE
8
*/
9
10
namespace Plasma\Drivers\MySQL;
11
12
/**
13
 * The MySQL Protocol Parser.
14
 * @internal
15
 */
16
class ProtocolParser implements \Evenement\EventEmitterInterface {
17
    use \Evenement\EventEmitterTrait;
18
    
19
    /**
20
     * @var int
21
     */
22
    const STATE_INIT = 0;
23
    
24
    /**
25
     * @var int
26
     */
27
    const STATE_HANDSHAKE = 1;
28
    
29
    /**
30
     * @var int
31
     */
32
    const STATE_HANDSHAKE_ERROR = 2;
33
    
34
    /**
35
     * @var int
36
     */
37
    const STATE_AUTH = 5;
38
    
39
    /**
40
     * @var int
41
     */
42
    const STATE_AUTH_SENT = 6;
43
    
44
    /**
45
     * @var int
46
     */
47
    const STATE_AUTH_ERROR = 7;
48
    
49
    /**
50
     * @var int
51
     */
52
    const STATE_OK = 9;
53
    
54
    /**
55
     * @var int
56
     */
57
    const CLIENT_CAPABILITIES = (
58
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_LONG_PASSWORD |
59
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_LONG_FLAG |
60
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_LOCAL_FILES |
61
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_INTERACTIVE |
62
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_TRANSACTIONS |
63
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_SECURE_CONNECTION |
64
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_PROTOCOL_41 |
65
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_DEPRECATE_EOF
66
    );
67
    
68
    /**
69
     * @var int
70
     */
71
    const CLIENT_MAX_PACKET_SIZE = 0x1000000;
72
    
73
    /**
74
     * @var int
75
     */
76
    const CLIENT_CHARSET_NUMBER = 0x21;
77
    
78
    /**
79
     * @var \Plasma\Drivers\MySQL\Driver
80
     */
81
    protected $driver;
82
    
83
    /**
84
     * @var \React\Socket\ConnectionInterface
85
     */
86
    protected $connection;
87
    
88
    /**
89
     * @var int
90
     */
91
    protected $state = ProtocolParser::STATE_INIT;
92
    
93
    /**
94
     * @var \Plasma\BinaryBuffer
95
     */
96
    protected $buffer;
97
    
98
    /**
99
     * @var \Plasma\BinaryBuffer
100
     */
101
    protected $messageBuffer;
102
    
103
    /**
104
     * The sequence ID is incremented with each packet and may wrap around.
105
     * It starts at 0 and is reset to 0 when a new command begins in the Command Phase.
106
     * @var int
107
     * @see https://dev.mysql.com/doc/internals/en/sequence-id.html
108
     */
109
    protected $sequenceID = 0;
110
    
111
    /**
112
     * @var \Plasma\Drivers\MySQL\Messages\HandshakeMessage|null
113
     */
114
    protected $handshakeMessage;
115
    
116
    /**
117
     * @var \Plasma\CommandInterface|null
118
     */
119
    protected $currentCommand;
120
    
121
    /**
122
     * @var callable|null
123
     */
124
    protected $parseCallback;
125
    
126
    /**
127
     * Constructor.
128
     * @param \Plasma\Drivers\MySQL\Driver       $driver
129
     * @param \React\Socket\ConnectionInterface  $connection
130
     */
131 13
    function __construct(\Plasma\Drivers\MySQL\Driver $driver, \React\Socket\ConnectionInterface $connection) {
132 13
        $this->driver = $driver;
133 13
        $this->connection = $connection;
134
        
135 13
        $this->buffer = new \Plasma\BinaryBuffer();
136 13
        $this->messageBuffer = new \Plasma\BinaryBuffer();
137
        
138 13
        $this->addEvents();
139 13
    }
140
    
141
    /**
142
     * Invoke a command to execute.
143
     * @param \Plasma\CommandInterface|null  $command
144
     * @return void
145
     */
146 13
    function invokeCommand(?\Plasma\CommandInterface $command): void {
147 13
        if($command === null) {
148
            return;
149
        }
150
        
151 13
        $this->currentCommand = $command;
152 13
        $this->processCommand();
153 13
    }
154
    
155
    /**
156
     * Executes a command, without handling any aftermath.
157
     * The `onComplete` callback will be immediately invoked, regardless of the `waitForCompletion` value.
158
     * @param \Plasma\CommandInterface  $command
159
     * @return void
160
     */
161
    function executeCommand(\Plasma\CommandInterface $command): void {
162
        $this->processCommand($command);
163
    }
164
    
165
    /**
166
     * Marks the command itself as finished, if currently running.
167
     * @param \Plasma\Drivers\MySQL\Commands\CommandInterface  $command
168
     * @return void
169
     */
170
    function markCommandAsFinished(\Plasma\CommandInterface $command): void {
171
        if($command === $this->currentCommand) {
172
            $this->currentCommand = null;
173
        }
174
        
175
        $command->onComplete();
176
    }
177
    
178
    /**
179
     * Get the parser state.
180
     * @return int
181
     */
182 1
    function getState(): int {
183 1
        return $this->state;
184
    }
185
    
186
    /**
187
     * Get the handshake message, or null.
188
     * @return \Plasma\Drivers\MySQL\Messages\HandshakeMessage|null
189
     */
190 11
    function getHandshakeMessage(): ?\Plasma\Drivers\MySQL\Messages\HandshakeMessage {
191 11
        return $this->handshakeMessage;
192
    }
193
    
194
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $packet should have a doc-comment as per coding-style.
Loading history...
195
     * Sends a packet to the server.
196
     * @return void
197
     */
198 13
    function sendPacket(string $packet): void {
199 13
        $length = \Plasma\BinaryBuffer::writeInt3(\strlen($packet));
200 13
        $sequence = \Plasma\BinaryBuffer::writeInt1((++$this->sequenceID));
201
        
202 13
        $this->connection->write($length.$sequence.$packet);
203 13
    }
204
    
205
    /**
206
     * Sets the parse callback.
207
     * @param callable $callback
208
     * @return void
209
     */
210
    function setParseCallback(callable $callback): void {
211
        $this->parseCallback($callback);
0 ignored issues
show
Bug introduced by
The method parseCallback() does not exist on Plasma\Drivers\MySQL\ProtocolParser. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

211
        $this->/** @scrutinizer ignore-call */ 
212
               parseCallback($callback);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
212
    }
213
    
214
    /**
215
     * Processes a command.
216
     * @param \Plasma\CommandInterface|null  $command
217
     * @return void
218
     */
219 13
    protected function processCommand(?\Plasma\CommandInterface $command = null) {
220 13
        if($command === null && $this->currentCommand instanceof \Plasma\CommandInterface) {
221 13
            $command = $this->currentCommand;
222
            
223 13
            if($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface) {
224 13
                $state = $command->setParserState();
225 13
                if($state !== -1) {
226 13
                    $this->state = $state;
227
                }
228
            }
229
        }
230
        
231 13
        if($command === null) {
232
            return;
233
        }
234
        
235 13
        if($command instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface && $command->resetSequence()) {
236
            $this->sequenceID = -1;
237
        }
238
        
239 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Processing command '.get_class($command)) || true));
240
        
241 13
        $this->sendPacket($command->getEncodedMessage());
242
        
243 13
        if($command !== $this->currentCommand || !$command->waitForCompletion()) {
244 2
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Mark command as completed') || true));
245 2
            $command->onComplete();
246
            
247 2
            if($command === $this->currentCommand) {
248 2
                $this->currentCommand = null;
249
            }
250
        }
251 13
    }
252
    
253
    /**
254
     * Processes the buffer.
255
     * @return void
256
     */
257 13
    protected function processBuffer() {
258 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('ProtocolParser::processBuffer called') || true));
259
        
260 13
        if($this->buffer->getSize() < 4) {
261
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Not enough data received for packet header ('.$this->buffer->getSize().')') || true));
262
            return;
263
        }
264
        
265 13
        $buffer = clone $this->buffer;
266
        
267 13
        $length = $buffer->readInt3();
268 13
        $this->sequenceID = $buffer->readInt1();
269
        
270 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('First 10 bytes: '.implode(', ', unpack('C*', \substr($this->buffer->getContents(), 0, 10)))) || true));
271 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Read packet header length ('.$length.') and sequence ('.$this->sequenceID.')') || true));
272
        
273 13
        if($length === 0xFFFFFF) {
274
            $this->buffer->read(($length + 4));
275
            $this->messageBuffer->append($buffer->read($length));
276
            
277
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned, 16mb packet received, waiting for the last one to arrive') || true));
278
            return;
279 13
        } elseif($this->messageBuffer->getSize() > 0) {
280
            $this->messageBuffer->append($buffer->read($length));
281
            $buffer = $this->messageBuffer;
282
            $this->messageBuffer = new \Plasma\BinaryBuffer();
283
        }
284
        
285 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('regular packet') || true));
286
        
287 13
        if($buffer->getSize() < $length) {
288
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned, insufficent length: '.$buffer->getSize().', '.$length.' required') || true));
289
            return;
290
        }
291
        
292 13
        if($length > 0) {
293 13
            $this->buffer->read(($length + 4));
294 13
            $buffer->slice(0, $length);
295
        } else {
296
            $this->buffer->slice($buffer->getSize());
297
        }
298
        
299 13
        if($buffer->getSize() === 0) {
300
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Buffer length is 0') || true));
301
            return;
302
        }
303
        
304
        /** @var \Plasma\Drivers\MySQL\Messages\MessageInterface  $message */
305 13
        $message = null;
306
        
307 13
        if($this->state === static::STATE_INIT) {
308 13
            $message = new \Plasma\Drivers\MySQL\Messages\HandshakeMessage($this);
309
        } else {
310 11
            $firstChar = $buffer->read(1);
311 11
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message char "'.$firstChar.'" (0x'.\dechex(\ord($firstChar)).') - buffer length: '.$buffer->getSize()) || true));
312
            
313
            switch(true) {
314 11
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\ErrResponseMessage::getID()):
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
315 1
                    $message = new \Plasma\Drivers\MySQL\Messages\ErrResponseMessage($this);
316 1
                break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
317 10
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\EOFMessage::getID() && $length < 6):
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
318
                    $message = new \Plasma\Drivers\MySQL\Messages\EOFMessage($this);
319
                break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
320 10
                case ($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\PrepareCommand && $firstChar === "\x00"):
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
321
                    $message = new \Plasma\Drivers\MySQL\Messages\PrepareStatementOkMessage($this);
322
                break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
323 10
                case ($firstChar === "\x00"):
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
324 10
                    $message = new \Plasma\Drivers\MySQL\Messages\OkResponseMessage($this);
325 10
                break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
326
                default:
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
327
                    $buffer->prepend($firstChar);
328
                    
329
                    if($this->parseCallback !== null) {
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
330
                        $parse = $this->parseCallback;
331
                        $this->parseCallback = null;
332
                        
333
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
334
                        $parse($caller);
335
                    } else {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
336
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
337
                        $this->currentCommand->onNext($caller);
0 ignored issues
show
Bug introduced by
The method onNext() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

337
                        $this->currentCommand->/** @scrutinizer ignore-call */ 
338
                                               onNext($caller);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
338
                    }
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
339
                    
340
                    \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Left over buffer: '.$buffer->getSize()) || true));
341
                    /*if($buffer->getSize() > 0) {
342
                        $this->buffer->prepend($buffer->getContents());
343
                    }*/
344
                    
345
                    if($this->buffer->getSize() > 0) {
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
346
                        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read with '.$this->buffer->getSize().' bytes') || true));
347
                        
348
                        $this->driver->getLoop()->futureTick(function () {
349
                            $this->processBuffer();
350
                        });
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
351
                    }
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
352
                    
353
                    return;
354
                break;
1 ignored issue
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
355
            }
356
        }
357
        
358 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message '.\get_class($message)) || true));
359
        
360 13
        $state = $message->setParserState();
361 13
        if($state !== -1) {
362 13
            $this->state = $state;
363
        }
364
        
365 13
        if($message instanceof \Plasma\Drivers\MySQL\Messages\HandshakeMessage) {
366 13
            $this->handshakeMessage = $message;
367
        }
368
        
369 13
        $this->handleMessage($buffer, $message);
370 13
    }
371
    
372
    /**
373
     * Handles an incoming message.
374
     * @param \Plasma\BinaryBuffer                             $buffer
375
     * @param \Plasma\Drivers\MySQL\Messages\MessageInterface  $message
376
     * @return void
377
     */
378 13
    function handleMessage(\Plasma\BinaryBuffer $buffer, \Plasma\Drivers\MySQL\Messages\MessageInterface $message) {
379
        try {
380 13
            $buffer = $message->parseMessage($buffer, $this);
0 ignored issues
show
Unused Code introduced by
The call to Plasma\Drivers\MySQL\Mes...terface::parseMessage() has too many arguments starting with $this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

380
            /** @scrutinizer ignore-call */ 
381
            $buffer = $message->parseMessage($buffer, $this);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
381 13
            if(!$buffer) {
382 10
                \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned handle (unsufficent buffer length)') || true));
383 10
                return;
384
            }
385
            
386 13
            if($this->currentCommand !== null) {
387
                if(
1 ignored issue
show
Coding Style introduced by
First condition of a multi-line IF statement must directly follow the opening parenthesis
Loading history...
388 1
                    ($message instanceof \Plasma\Drivers\MySQL\Messages\OkResponseMessage || $message instanceof \Plasma\Drivers\MySQL\Messages\EOFMessage)
1 ignored issue
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
389 1
                    && $this->currentCommand->hasFinished()
390
                ) {
391
                    $command = $this->currentCommand;
392
                    $this->currentCommand = null;
393
                    
394
                    $command->onComplete();
395 1
                } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
396 1
                    $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
397
                    
398 1
                    $command = $this->currentCommand;
399 1
                    $this->currentCommand = null;
400
                    
401 1
                    $command->onError($error);
402
                } else {
403
                    $command = $this->currentCommand;
404
                    $command->onNext($message);
405
                    
406
                    if($command->hasFinished()) {
407
                        if($this->currentCommand === $command) {
408
                            $this->currentCommand = null;
409
                        }
410
                        
411 1
                        $command->onComplete();
412
                    }
413
                }
414 13
            } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
415
                $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
416
                $this->emit('error', array($error));
417
            }
418
            
419 13
            $this->emit('message', array($message));
420
        } catch (\Plasma\Drivers\MySQL\Messages\ParseException $e) {
421
            $state = $e->getState();
422
            if($state !== null) {
1 ignored issue
show
introduced by
The condition $state !== null is always true.
Loading history...
423
                $this->state = $state;
424
            }
425
            
426
            $buffer = $e->getBuffer();
427
            if($buffer !== null) {
1 ignored issue
show
introduced by
The condition $buffer !== null is always true.
Loading history...
428
                $this->buffer->clear();
429
                $this->buffer->append($buffer);
430
            }
431
            
432
            if($this->currentCommand !== null) {
433
                $this->currentCommand->onError($e);
434
            }
435
            
436
            $this->emit('error', array($e));
437
            $this->connection->close();
438
        }
439
        
440 13
        if($this->buffer->getSize() > 0) {
441
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read (msg) with '.$this->buffer->getSize().' bytes') || true));
442
            
443
            $this->driver->getLoop()->futureTick(function () {
444
                $this->processBuffer();
445
            });
446
        }
447 13
    }
448
    
449
    /**
450
     * Adds the events to the connection.
451
     * @return void
452
     */
453 13
    protected function addEvents() {
454
        $this->connection->on('data', function ($chunk) {
455 13
            $this->buffer->append($chunk);
456 13
            $this->processBuffer();
457 13
        });
458
        
459
        $this->connection->on('close', function () {
460 2
            $this->handleClose();
461 13
        });
462 13
    }
463
    
464
    /**
465
     * Connection close handler.
466
     * @return void
467
     */
468 2
    protected function handleClose() {
469 2
        if($this->state === static::STATE_AUTH || $this->state === static::STATE_AUTH_SENT) {
470
            $this->state = static::STATE_AUTH_ERROR;
471
        }
472 2
    }
473
}
474