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.
Test Failed
Push — master ( d54200...f2da15 )
by Charlotte
03:55
created

ProtocolParser::processBuffer()   F

Complexity

Conditions 17
Paths 254

Size

Total Lines 95
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 19.6955

Importance

Changes 0
Metric Value
cc 17
eloc 60
nc 254
nop 0
dl 0
loc 95
ccs 45
cts 57
cp 0.7895
crap 19.6955
rs 3.7083
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 string
95
     */
96
    protected $buffer = '';
97
    
98
    /**
99
     * The sequence ID is incremented with each packet and may wrap around.
100
     * It starts at 0 and is reset to 0 when a new command begins in the Command Phase.
101
     * @var int
102
     * @see https://dev.mysql.com/doc/internals/en/sequence-id.html
103
     */
104
    protected $sequenceID = 0;
105
    
106
    /**
107
     * @var \Plasma\Drivers\MySQL\Messages\HandshakeMessage|null
108
     */
109
    protected $handshakeMessage;
110
    
111
    /**
112
     * @var \Plasma\CommandInterface|null
113
     */
114
    protected $currentCommand;
115
    
116
    /**
117
     * @var callable|null
118
     */
119
    protected $parseCallback;
120
    
121
    /**
122
     * Constructor.
123
     * @param \Plasma\Drivers\MySQL\Driver       $driver
124
     * @param \React\Socket\ConnectionInterface  $connection
125
     */
126 13
    function __construct(\Plasma\Drivers\MySQL\Driver $driver, \React\Socket\ConnectionInterface $connection) {
127 13
        $this->driver = $driver;
128 13
        $this->connection = $connection;
129
        
130 13
        $this->addEvents();
131 13
    }
132
    
133
    /**
134
     * Invoke a command to execute.
135
     * @param \Plasma\CommandInterface|null  $command
136
     * @return void
137
     */
138 11
    function invokeCommand(?\Plasma\CommandInterface $command): void {
139 11
        if($command === null) {
140 3
            return;
141
        }
142
        
143 11
        $this->currentCommand = $command;
144 11
        $this->processCommand();
145 11
    }
146
    
147
    /**
148
     * Executes a command, without handling any aftermath.
149
     * The `onComplete` callback will be immediately invoked, regardless of the `waitForCompletion` value.
150
     * @param \Plasma\CommandInterface  $command
151
     * @return void
152
     */
153
    function executeCommand(\Plasma\CommandInterface $command): void {
154
        $this->processCommand($command);
155
    }
156
    
157
    /**
158
     * Marks the command itself as finished, if currently running.
159
     * @param \Plasma\Drivers\MySQL\Commands\CommandInterface  $command
160
     * @return void
161
     */
162 2
    function markCommandAsFinished(\Plasma\CommandInterface $command): void {
163 2
        if($command === $this->currentCommand) {
164 2
            $this->currentCommand = null;
165
        }
166
        
167 2
        $command->onComplete();
168 2
    }
169
    
170
    /**
171
     * Get the parser state.
172
     * @return int
173
     */
174 1
    function getState(): int {
175 1
        return $this->state;
176
    }
177
    
178
    /**
179
     * Get the handshake message, or null.
180
     * @return \Plasma\Drivers\MySQL\Messages\HandshakeMessage|null
181
     */
182 11
    function getHandshakeMessage(): ?\Plasma\Drivers\MySQL\Messages\HandshakeMessage {
183 11
        return $this->handshakeMessage;
184
    }
185
    
186
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $packet should have a doc-comment as per coding-style.
Loading history...
187
     * Sends a packet to the server.
188
     * @return void
189
     */
190 11
    function sendPacket(string $packet): void {
191 11
        $length = \Plasma\Drivers\MySQL\Messages\MessageUtility::writeInt3(\strlen($packet));
192 11
        $sequence = \Plasma\Drivers\MySQL\Messages\MessageUtility::writeInt1((++$this->sequenceID));
193
        
194 11
        $this->connection->write($length.$sequence.$packet);
195 11
    }
196
    
197
    /**
198
     * Sets the parse callback.
199
     * @param callable $callback
200
     * @return void
201
     */
202
    function setParseCallback(callable $callback): void {
203
        $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

203
        $this->/** @scrutinizer ignore-call */ 
204
               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...
204
    }
205
    
206
    /**
207
     * Processes a command.
208
     * @param \Plasma\CommandInterface|null  $command
209
     * @return void
210
     */
211 11
    protected function processCommand(?\Plasma\CommandInterface $command = null) {
212 11
        if($command === null && $this->currentCommand instanceof \Plasma\CommandInterface) {
213 11
            $command = $this->currentCommand;
214
            
215 11
            if($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface) {
216 11
                $state = $command->setParserState();
217 11
                if($state !== -1) {
218 11
                    $this->state = $state;
219
                }
220
            }
221
        }
222
        
223 11
        if($command === null) {
224
            return;
225
        }
226
        
227 11
        if($command instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface && $command->resetSequence()) {
228 6
            $this->sequenceID = -1;
229
        }
230
        
231 11
        \Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Processing command '.get_class($command));
232
        
233 11
        $this->sendPacket($command->getEncodedMessage());
234
        
235 11
        if($command !== $this->currentCommand || !$command->waitForCompletion()) {
236
            \Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Mark command as completed');
237
            $command->onComplete();
238
            
239
            if($command === $this->currentCommand) {
240
                $this->currentCommand = null;
241
            }
242
        }
243 11
    }
244
    
245
    /**
246
     * Processes the buffer.
247
     * @return void
248
     */
249 13
    protected function processBuffer() {
250 13
        \Plasma\Drivers\MySQL\Messages\MessageUtility::debug('ProtocolParser::processBuffer called');
251
        
252 13
        $buffer = $this->buffer;
253
        
254 13
        $length = \Plasma\Drivers\MySQL\Messages\MessageUtility::readInt3($buffer);
255 13
        $this->sequenceID = \Plasma\Drivers\MySQL\Messages\MessageUtility::readInt1($buffer);
256
        
257 13
        $stmtExecuteCmd = ($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\StatementExecuteCommand);
258 13
        $isOKresponse = ($buffer[0] === "\x00" && $this->buffer[0] !== "\x00");
259
        
260 13
        if(!$stmtExecuteCmd) {
261 13
            if(\strlen($buffer) < $length) {
262
                \Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned, insufficent length: '.\strlen($buffer).', '.$length.' required');
263 13
                return;
264
            }
265
        } elseif(!$isOKresponse) {
266
            $buffer = $this->buffer;
267
        }
268
        
269 13
        \Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Read length and sequence');
270
        
271
        //var_dump(unpack('C*', substr($this->buffer, 10)));
272
        
273
        /** @var \Plasma\Drivers\MySQL\Messages\MessageInterface  $message */
274 13
        $message = null;
275
        
276 13
        if($this->state === static::STATE_INIT) {
277 13
            $message = new \Plasma\Drivers\MySQL\Messages\HandshakeMessage($this);
278
        } else {
279 11
            $firstChar = \Plasma\Drivers\MySQL\Messages\MessageUtility::readBuffer($buffer, 1);
280 11
            \Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message char "'.$firstChar.'" (0x'.\dechex(\ord($firstChar)).') - buffer length: '.\strlen($buffer));
281
            
282
            switch(true) {
283 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...
284 1
                    $message = new \Plasma\Drivers\MySQL\Messages\ErrResponseMessage($this);
285 1
                break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
286 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...
287
                    $message = new \Plasma\Drivers\MySQL\Messages\EOFMessage($this);
288
                break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
289 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...
290 1
                    $message = new \Plasma\Drivers\MySQL\Messages\PrepareStatementOkMessage($this);
291 1
                break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
292 10
                case ($isOKresponse):
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
293 10
                    $message = new \Plasma\Drivers\MySQL\Messages\OkResponseMessage($this);
294 10
                break;
1 ignored issue
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
295
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
296 2
                    if($this->parseCallback !== null) {
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
297
                        $buffer = $firstChar.$buffer;
298
                        
299
                        $parse = $this->parseCallback;
300
                        $this->parseCallback = null;
301
                        
302
                        $buffer = $firstChar.$buffer;
303
                        
304
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
305
                        $parse($caller);
306
                        
307
                        $buffer = $caller->getBuffer();
308
                    } else {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
309 2
                        $buffer = $firstChar.$buffer;
310
                        
311 2
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
312 2
                        $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

312
                        $this->currentCommand->/** @scrutinizer ignore-call */ 
313
                                               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...
313
                        
314 2
                        $buffer = $caller->getBuffer();
315
                    }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
316 2
                break;
0 ignored issues
show
Coding Style introduced by
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
317
            }
318
        }
319
        
320 13
        if(!($message instanceof \Plasma\Drivers\MySQL\Messages\MessageInterface)) {
0 ignored issues
show
introduced by
$message is always a sub-type of Plasma\Drivers\MySQL\Messages\MessageInterface.
Loading history...
321 2
            $this->buffer = $buffer;
322
            
323 2
            if(\strlen($this->buffer) > 0) {
324
                $this->driver->getLoop()->futureTick(function () {
325 2
                    $this->processBuffer();
326 2
                });
327
            }
328
            
329 2
            return;
330
        }
331
        
332 13
        \Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message '.\get_class($message));
333
        
334 13
        $state = $message->setParserState();
335 13
        if($state !== -1) {
336 13
            $this->state = $state;
337
        }
338
        
339 13
        if($message instanceof \Plasma\Drivers\MySQL\Messages\HandshakeMessage) {
340 13
            $this->handshakeMessage = $message;
341
        }
342
        
343 13
        $this->handleMessage($buffer, $message);
344 13
    }
345
    
346
    /**
347
     * Handles an incoming message.
348
     * @param string                                           $buffer
349
     * @param \Plasma\Drivers\MySQL\Messages\MessageInterface  $message
350
     * @return void
351
     */
352 13
    function handleMessage(string &$buffer, \Plasma\Drivers\MySQL\Messages\MessageInterface $message) {
353
        try {
354 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

354
            /** @scrutinizer ignore-call */ 
355
            $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...
355 13
            if($buffer === false) {
356
                \Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned handle (unsufficent buffer length)');
357
                return;
358
            }
359
            
360 13
            $this->buffer = $buffer;
0 ignored issues
show
Documentation Bug introduced by
It seems like $buffer can also be of type true. However, the property $buffer is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
361
            
362 13
            if($this->currentCommand !== null) {
363
                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...
364 11
                    ($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...
365 11
                    && $this->currentCommand->hasFinished()
366
                ) {
367 10
                    $command = $this->currentCommand;
368 10
                    $this->currentCommand = null;
369
                    
370 10
                    $command->onComplete();
371 4
                } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
372 1
                    $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
373
                    
374 1
                    $command = $this->currentCommand;
375 1
                    $this->currentCommand = null;
376
                    
377 1
                    $command->onError($error);
378
                } else {
379 3
                    $command = $this->currentCommand;
380 3
                    $command->onNext($message);
381
                    
382 3
                    if($command->hasFinished()) {
383 2
                        if($this->currentCommand === $command) {
384
                            $this->currentCommand = null;
385
                        }
386
                        
387 11
                        $command->onComplete();
388
                    }
389
                }
390 13
            } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
391
                $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
392
                $this->emit('error', array($error));
393
            }
394
            
395 13
            $this->emit('message', array($message));
396
        } catch (\Plasma\Drivers\MySQL\Messages\ParseException $e) {
397
            $state = $e->getState();
398
            if($state !== null) {
1 ignored issue
show
introduced by
The condition $state !== null is always true.
Loading history...
399
                $this->state = $state;
400
            }
401
            
402
            $buffer = $e->getBuffer();
403
            if($buffer !== null) {
1 ignored issue
show
introduced by
The condition $buffer !== null is always true.
Loading history...
404
                $this->buffer = $buffer;
405
            }
406
            
407
            if($this->currentCommand !== null) {
408
                $this->currentCommand->onError($e);
409
            }
410
            
411
            $this->emit('error', array($e));
412
            $this->connection->close();
413
        }
414
        
415 13
        if(\strlen($this->buffer) > 0) {
0 ignored issues
show
Bug introduced by
It seems like $this->buffer can also be of type true; however, parameter $string of strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

415
        if(\strlen(/** @scrutinizer ignore-type */ $this->buffer) > 0) {
Loading history...
416
            $this->driver->getLoop()->futureTick(function () {
417 1
                $this->processBuffer();
418 1
            });
419
        }
420 13
    }
421
    
422
    /**
423
     * Adds the events to the connection.
424
     * @return void
425
     */
426 13
    protected function addEvents() {
427
        $this->connection->on('data', function ($chunk) {
428 13
            $this->buffer .= $chunk;
429 13
            $this->processBuffer();
430 13
        });
431
        
432
        $this->connection->on('close', function () {
433 5
            $this->handleClose();
434 13
        });
435 13
    }
436
    
437
    /**
438
     * Connection close handler.
439
     * @return void
440
     */
441 5
    protected function handleClose() {
442 5
        if($this->state === static::STATE_AUTH || $this->state === static::STATE_AUTH_SENT) {
443
            $this->state = static::STATE_AUTH_ERROR;
444
        }
445 5
    }
446
}
447