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 ( 888a68...c84b74 )
by Charlotte
05:53
created

ProtocolParser::getHandshakeMessage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 16
    function __construct(\Plasma\Drivers\MySQL\Driver $driver, \React\Socket\ConnectionInterface $connection) {
132 16
        $this->driver = $driver;
133 16
        $this->connection = $connection;
134
        
135 16
        $this->buffer = new \Plasma\BinaryBuffer();
136 16
        $this->messageBuffer = new \Plasma\BinaryBuffer();
137
        
138 16
        $this->addEvents();
139 16
    }
140
    
141
    /**
142
     * Invoke a command to execute.
143
     * @param \Plasma\CommandInterface|null  $command
144
     * @return void
145
     */
146 16
    function invokeCommand(?\Plasma\CommandInterface $command): void {
147 16
        if($command === null) {
148
            return;
149
        }
150
        
151 16
        $this->currentCommand = $command;
152 16
        $this->processCommand();
153 16
    }
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 14
    function getHandshakeMessage(): ?\Plasma\Drivers\MySQL\Messages\HandshakeMessage {
191 14
        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 16
    function sendPacket(string $packet): void {
199 16
        $length = \Plasma\BinaryBuffer::writeInt3(\strlen($packet));
200 16
        $sequence = \Plasma\BinaryBuffer::writeInt1((++$this->sequenceID));
201
        
202 16
        $this->connection->write($length.$sequence.$packet);
203 16
    }
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 16
    protected function processCommand(?\Plasma\CommandInterface $command = null) {
220 16
        if($command === null && $this->currentCommand instanceof \Plasma\CommandInterface) {
221 16
            $command = $this->currentCommand;
222
            
223 16
            if($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface) {
224 16
                $state = $command->setParserState();
225 16
                if($state !== -1) {
226 16
                    $this->state = $state;
227
                }
228
            }
229
        }
230
        
231 16
        if($command === null) {
232
            return;
233
        }
234
        
235 16
        if($command instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface && $command->resetSequence()) {
236
            $this->sequenceID = -1;
237
        }
238
        
239 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Processing command '.get_class($command)) || true));
240
        
241 16
        $this->sendPacket($command->getEncodedMessage());
242
        
243 16
        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 16
    }
252
    
253
    /**
254
     * Processes the buffer.
255
     * @return void
256
     */
257 16
    protected function processBuffer() {
258 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('ProtocolParser::processBuffer called') || true));
259
        
260 16
        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 16
        $buffer = clone $this->buffer;
266
        
267 16
        $length = $buffer->readInt3();
268 16
        $this->sequenceID = $buffer->readInt1();
269
        
270 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('First 10 bytes: '.implode(', ', unpack('C*', \substr($this->buffer->getContents(), 0, 10)))) || true));
271 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Read packet header length ('.$length.') and sequence ('.$this->sequenceID.')') || true));
272
        
273 16
        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 16
        } 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 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('regular packet') || true));
286
        
287 16
        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 16
        if($length > 0) {
293 16
            $this->buffer->read(($length + 4));
294 16
            $buffer->slice(0, $length);
295
        } else {
296
            $this->buffer->slice($buffer->getSize());
297
        }
298
        
299 16
        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 16
        $message = null;
306
        
307 16
        if($this->state === static::STATE_INIT) {
308 16
            $message = new \Plasma\Drivers\MySQL\Messages\HandshakeMessage($this);
309
        } else {
310 14
            $firstChar = $buffer->read(1);
311 14
            \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 14
                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 13
                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 13
                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 13
                case ($firstChar === "\x00"):
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
324 13
                    $message = new \Plasma\Drivers\MySQL\Messages\OkResponseMessage($this);
325 13
                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
                    } elseif($this->currentCommand !== null) {
1 ignored issue
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);
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
                    
342
                    if($this->buffer->getSize() > 0) {
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
343
                        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read with '.$this->buffer->getSize().' bytes') || true));
344
                        
345
                        $this->driver->getLoop()->futureTick(function () {
346
                            $this->processBuffer();
347
                        });
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
348
                    }
1 ignored issue
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
349
                    
350
                    return;
351
                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...
352
            }
353
        }
354
        
355 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message '.\get_class($message)) || true));
356
        
357 16
        $state = $message->setParserState();
358 16
        if($state !== -1) {
359 16
            $this->state = $state;
360
        }
361
        
362 16
        if($message instanceof \Plasma\Drivers\MySQL\Messages\HandshakeMessage) {
363 16
            $this->handshakeMessage = $message;
364
        }
365
        
366 16
        $this->handleMessage($buffer, $message);
367 16
    }
368
    
369
    /**
370
     * Handles an incoming message.
371
     * @param \Plasma\BinaryBuffer                             $buffer
372
     * @param \Plasma\Drivers\MySQL\Messages\MessageInterface  $message
373
     * @return void
374
     */
375 16
    function handleMessage(\Plasma\BinaryBuffer $buffer, \Plasma\Drivers\MySQL\Messages\MessageInterface $message) {
376
        try {
377 16
            $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

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