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.
Completed
Push — master ( 04e554...c06fef )
by Charlotte
03:19
created

ProtocolParser::processCommand()   C

Complexity

Conditions 13
Paths 28

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 16.6231

Importance

Changes 0
Metric Value
cc 13
eloc 17
nc 28
nop 1
dl 0
loc 30
ccs 13
cts 18
cp 0.7221
crap 16.6231
rs 6.6166
c 0
b 0
f 0

How to fix   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 12
    function __construct(\Plasma\Drivers\MySQL\Driver $driver, \React\Socket\ConnectionInterface $connection) {
132 12
        $this->driver = $driver;
133 12
        $this->connection = $connection;
134
        
135 12
        $this->buffer = new \Plasma\BinaryBuffer();
136 12
        $this->messageBuffer = new \Plasma\BinaryBuffer();
137
        
138 12
        $this->addEvents();
139 12
    }
140
    
141
    /**
142
     * Invoke a command to execute.
143
     * @param \Plasma\CommandInterface|null  $command
144
     * @return void
145
     */
146 12
    function invokeCommand(?\Plasma\CommandInterface $command): void {
147 12
        if($command === null) {
148 3
            return;
149
        }
150
        
151 12
        $this->currentCommand = $command;
152 12
        $this->processCommand();
153 12
    }
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 3
    function markCommandAsFinished(\Plasma\CommandInterface $command): void {
171 3
        if($command === $this->currentCommand) {
172 3
            $this->currentCommand = null;
173
        }
174
        
175 3
        $command->onComplete();
176 3
    }
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 12
    function getHandshakeMessage(): ?\Plasma\Drivers\MySQL\Messages\HandshakeMessage {
191 12
        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 12
    function sendPacket(string $packet): void {
199 12
        $maxSize = static::CLIENT_MAX_PACKET_SIZE - 4;
200
        
201
        do {
202 12
            $partial = \substr($packet, 0, $maxSize);
203 12
            $packet = \substr($packet, $maxSize);
204
            
205 12
            $length = \Plasma\BinaryBuffer::writeInt3(\strlen($partial));
206 12
            $sequence = \Plasma\BinaryBuffer::writeInt1((++$this->sequenceID));
207
            
208 12
            $this->connection->write($length.$sequence.$partial);
209 12
        } while(\strlen($packet) > $maxSize);
210 12
    }
211
    
212
    /**
213
     * Sets the parse callback.
214
     * @param callable $callback
215
     * @return void
216
     */
217
    function setParseCallback(callable $callback): void {
218
        $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

218
        $this->/** @scrutinizer ignore-call */ 
219
               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...
219
    }
220
    
221
    /**
222
     * Processes a command.
223
     * @param \Plasma\CommandInterface|null  $command
224
     * @return void
225
     */
226 12
    protected function processCommand(?\Plasma\CommandInterface $command = null) {
227 12
        if($command === null && $this->currentCommand instanceof \Plasma\CommandInterface) {
228 12
            $command = $this->currentCommand;
229
            
230 12
            if($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface) {
231 12
                $state = $command->setParserState();
232 12
                if($state !== -1) {
233 12
                    $this->state = $state;
234
                }
235
            }
236
        }
237
        
238 12
        if($command === null) {
239
            return;
240
        }
241
        
242 12
        if(!($command instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface) || $command->resetSequence()) {
243 9
            $this->sequenceID = -1;
244
        }
245
        
246 12
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Processing command '.get_class($command)) || true));
247
        
248 12
        $this->sendPacket($command->getEncodedMessage());
249
        
250 12
        if($command !== $this->currentCommand || !$command->waitForCompletion()) {
251
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Mark command as completed') || true));
252
            $command->onComplete();
253
            
254
            if($command === $this->currentCommand) {
255
                $this->currentCommand = null;
256
            }
257
        }
258 12
    }
259
    
260
    /**
261
     * Processes the buffer.
262
     * @return void
263
     */
264 12
    protected function processBuffer() {
265 12
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('ProtocolParser::processBuffer called') || true));
266
        
267 12
        if($this->buffer->getSize() < 4) {
268
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Not enough data received for packet header ('.$this->buffer->getSize().')') || true));
269
            return;
270
        }
271
        
272 12
        $buffer = clone $this->buffer;
273
        
274 12
        $length = $buffer->readInt3();
275 12
        $this->sequenceID = $buffer->readInt1();
276
        
277 12
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('First 10 bytes: '.implode(', ', unpack('C*', \substr($this->buffer->getContents(), 0, 10)))) || true));
278 12
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Read packet header length ('.$length.') and sequence ('.$this->sequenceID.')') || true));
279
        
280 12
        if($length === 0xFFFFFF) {
281
            $this->buffer->read(($length + 4));
282
            $this->messageBuffer->append($buffer->read($length));
283
            
284
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned, 16mb packet received, waiting for the last one to arrive') || true));
285
            return;
286 12
        } elseif($this->messageBuffer->getSize() > 0) {
287
            $this->messageBuffer->append($buffer->read($length));
288
            $buffer = $this->messageBuffer;
289
            $this->messageBuffer = new \Plasma\BinaryBuffer();
290
        }
291
        
292 12
        if($buffer->getSize() < $length) {
293
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned, insufficent length: '.$buffer->getSize().', '.$length.' required') || true));
294
            return;
295
        }
296
        
297 12
        if($length > 0) {
298 12
            $this->buffer->read(($length + 4));
299 12
            $buffer->slice(0, $length);
300
        } else {
301
            $this->buffer->slice($buffer->getSize());
302
        }
303
        
304 12
        if($buffer->getSize() === 0) {
305
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Buffer length is 0') || true));
306
            return;
307
        }
308
        
309
        /** @var \Plasma\Drivers\MySQL\Messages\MessageInterface  $message */
310 12
        $message = null;
311
        
312 12
        if($this->state === static::STATE_INIT) {
313 12
            $message = new \Plasma\Drivers\MySQL\Messages\HandshakeMessage($this);
314
        } else {
315 12
            $firstChar = $buffer->read(1);
316 12
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message char "'.$firstChar.'" (0x'.\dechex(\ord($firstChar)).') - buffer length: '.$buffer->getSize()) || true));
317
            
318 12
            $okRespID = \Plasma\Drivers\MySQL\Messages\OkResponseMessage::getID();
319
            $isOkMessage = (
320
                (
321 12
                    $firstChar === $okRespID ||
322
                    (
323 6
                        $firstChar === \Plasma\Drivers\MySQL\Messages\EOFMessage::getID() &&
324 12
                        ($this->handshakeMessage->capability & \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_DEPRECATE_EOF) !== 0
325
                    )
326
                ) &&
327 12
                !($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\StatementExecuteCommand)
328
            );
329
            
330
            switch(true) {
331 12
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\ErrResponseMessage::getID()):
332 1
                    $message = new \Plasma\Drivers\MySQL\Messages\ErrResponseMessage($this);
333 1
                break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
334 11
                case ($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\StatementPrepareCommand && $firstChar === $okRespID):
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
335 2
                    $message = new \Plasma\Drivers\MySQL\Messages\PrepareStatementOkMessage($this);
336 2
                break;
337 11
                case $isOkMessage:
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
338 11
                    $message = new \Plasma\Drivers\MySQL\Messages\OkResponseMessage($this);
339 11
                break;
340 5
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\EOFMessage::getID() && $length < 6):
341
                    $message = new \Plasma\Drivers\MySQL\Messages\EOFMessage($this);
342
                break;
343
                default:
344 5
                    $buffer->prepend($firstChar);
345
                    
346 5
                    if($this->parseCallback !== null) {
347
                        $parse = $this->parseCallback;
348
                        $this->parseCallback = null;
349
                        
350
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
351
                        $parse($caller);
352 5
                    } elseif($this->currentCommand !== null) {
353 5
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
354 5
                        $this->currentCommand->onNext($caller);
355
                    }
356
                    
357 5
                    \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Left over buffer: '.$buffer->getSize()) || true));
358
                    
359 5
                    if($this->buffer->getSize() > 0) {
360 5
                        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read with '.$this->buffer->getSize().' bytes') || true));
361
                        
362
                        $this->driver->getLoop()->futureTick(function () {
363 5
                            $this->processBuffer();
364 5
                        });
365
                    }
366
                    
367 5
                    return;
368
                break;
369
            }
370
        }
371
        
372 12
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message '.\get_class($message)) || true));
373
        
374 12
        $state = $message->setParserState();
375 12
        if($state !== -1) {
376 12
            $this->state = $state;
377
        }
378
        
379 12
        if($message instanceof \Plasma\Drivers\MySQL\Messages\HandshakeMessage) {
380 12
            $this->handshakeMessage = $message;
381
        }
382
        
383 12
        $this->handleMessage($buffer, $message);
384 12
    }
385
    
386
    /**
387
     * Handles an incoming message.
388
     * @param \Plasma\BinaryBuffer                             $buffer
389
     * @param \Plasma\Drivers\MySQL\Messages\MessageInterface  $message
390
     * @return void
391
     */
392 12
    function handleMessage(\Plasma\BinaryBuffer $buffer, \Plasma\Drivers\MySQL\Messages\MessageInterface $message) {
393
        try {
394 12
            $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

394
            /** @scrutinizer ignore-call */ 
395
            $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...
395 12
            if(!$buffer) {
396
                \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned handle (unsufficent buffer length)') || true));
397
                return;
398
            }
399
            
400 12
            if($this->currentCommand !== null) {
401
                if(
402 12
                    ($message instanceof \Plasma\Drivers\MySQL\Messages\OkResponseMessage || $message instanceof \Plasma\Drivers\MySQL\Messages\EOFMessage)
403 12
                    && $this->currentCommand->hasFinished()
404
                ) {
405 11
                    $command = $this->currentCommand;
406 11
                    $this->currentCommand = null;
407
                    
408 11
                    $command->onComplete();
409 6
                } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
410 1
                    $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
411
                    
412 1
                    $command = $this->currentCommand;
413 1
                    $this->currentCommand = null;
414
                    
415 1
                    $command->onError($error);
416
                } else {
417 5
                    $command = $this->currentCommand;
418 5
                    $command->onNext($message);
419
                    
420 5
                    if($command->hasFinished()) {
421 3
                        if($this->currentCommand === $command) {
422
                            $this->currentCommand = null;
423
                        }
424
                        
425 12
                        $command->onComplete();
426
                    }
427
                }
428 12
            } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
429
                \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug(
430
                    'Received Error Response Message with message: '.$message->errorMessage
431
                ) || true));
432
                
433
                $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
434
                $this->emit('error', array($error));
435
            }
436
            
437 12
            $this->emit('message', array($message));
438
        } catch (\Plasma\Drivers\MySQL\Messages\ParseException $e) {
439
            $state = $e->getState();
440
            if($state !== null) {
441
                $this->state = $state;
442
            }
443
            
444
            $buffer = $e->getBuffer();
445
            if($buffer !== null) {
446
                $this->buffer->clear();
447
                $this->buffer->append($buffer);
448
            }
449
            
450
            if($this->currentCommand !== null) {
451
                $this->currentCommand->onError($e);
452
            }
453
            
454
            $this->emit('error', array($e));
455
            $this->connection->close();
456
        }
457
        
458 12
        if($this->buffer->getSize() > 0) {
459 2
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read (msg) with '.$this->buffer->getSize().' bytes') || true));
460
            
461
            $this->driver->getLoop()->futureTick(function () {
462 2
                $this->processBuffer();
463 2
            });
464
        }
465 12
    }
466
    
467
    /**
468
     * Adds the events to the connection.
469
     * @return void
470
     */
471 12
    protected function addEvents() {
472
        $this->connection->on('data', function ($chunk) {
473 12
            $this->buffer->append($chunk);
474 12
            $this->processBuffer();
475 12
        });
476
        
477
        $this->connection->on('close', function () {
478 3
            $this->handleClose();
479 12
        });
480 12
    }
481
    
482
    /**
483
     * Connection close handler.
484
     * @return void
485
     */
486 3
    protected function handleClose() {
487 3
        if($this->state === static::STATE_AUTH || $this->state === static::STATE_AUTH_SENT) {
488
            $this->state = static::STATE_AUTH_ERROR;
489
        }
490 3
    }
491
}
492