Skip to content

Commit ed7fe8b

Browse files
authored
Merge pull request #83 from clue-labs/too-many-connections
Fix parsing error message during handshake (Too many connections)
2 parents a9aa011 + 83ff2d3 commit ed7fe8b

File tree

2 files changed

+64
-18
lines changed

2 files changed

+64
-18
lines changed

src/Io/Parser.php

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -138,25 +138,26 @@ public function parse($data)
138138
$this->state = self::STATE_STANDBY;
139139
//$this->stream->bufferSize = 4;
140140
if ($this->phase === 0) {
141-
$this->phase = self::PHASE_GOT_INIT;
142-
$this->protocalVersion = $this->buffer->readInt1();
143-
$this->debug(sprintf("Protocal Version: %d", $this->protocalVersion));
144-
if ($this->protocalVersion === 0xFF) { //error
145-
$fieldCount = $this->protocalVersion;
146-
$this->protocalVersion = 0;
147-
printf("Error:\n");
148-
149-
$this->rsState = self::RS_STATE_HEADER;
150-
$this->resultFields = [];
151-
if ($this->phase === self::PHASE_AUTH_SENT || $this->phase === self::PHASE_GOT_INIT) {
152-
$this->phase = self::PHASE_AUTH_ERR;
153-
}
141+
$response = $this->buffer->readInt1();
142+
if ($response === 0xFF) {
143+
// error packet before handshake means we did not exchange capabilities and error does not include SQL state
144+
$this->phase = self::PHASE_AUTH_ERR;
145+
$this->errno = $this->buffer->readInt2();
146+
$this->errmsg = $this->buffer->read($this->pctSize - $len + $this->buffer->length());
147+
$this->debug(sprintf("Error Packet:%d %s\n", $this->errno, $this->errmsg));
154148

155-
goto field;
149+
// error during init phase also means we're not currently executing any command
150+
// simply reject the first outstanding command in the queue (AuthenticateCommand)
151+
$this->currCommand = $this->executor->dequeue();
152+
$this->onError();
153+
return;
156154
}
157155

158-
$options = &$this->connectOptions;
156+
$this->phase = self::PHASE_GOT_INIT;
157+
$this->protocalVersion = $response;
158+
$this->debug(sprintf("Protocal Version: %d", $this->protocalVersion));
159159

160+
$options = &$this->connectOptions;
160161
$options['serverVersion'] = $this->buffer->readStringNull();
161162
$options['threadId'] = $this->buffer->readInt4();
162163
$this->scramble = $this->buffer->read(8); // 1st part
@@ -173,14 +174,15 @@ public function parse($data)
173174
$this->buffer->readStringNull(); // skip authentication plugin name
174175
}
175176

177+
// init completed, continue with sending AuthenticateCommand
176178
$this->nextRequest(true);
177179
} else {
178180
$fieldCount = $this->buffer->readInt1();
179-
field:
181+
180182
if ($fieldCount === 0xFF) {
181183
// error packet
182184
$this->errno = $this->buffer->readInt2();
183-
$this->buffer->skip(6); // state
185+
$this->buffer->skip(6); // skip SQL state
184186
$this->errmsg = $this->buffer->read($this->pctSize - $len + $this->buffer->length());
185187
$this->debug(sprintf("Error Packet:%d %s\n", $this->errno, $this->errmsg));
186188

tests/Io/ParserTest.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use React\MySQL\Io\Parser;
88
use React\Stream\ThroughStream;
99
use React\Tests\MySQL\BaseTestCase;
10+
use React\MySQL\Exception;
1011

1112
class ParserTest extends BaseTestCase
1213
{
@@ -33,7 +34,7 @@ public function testClosingStreamEmitsErrorForCurrentCommand()
3334
$parser = new Parser($stream, $executor);
3435
$parser->start();
3536

36-
$command = new QueryCommand($connection);
37+
$command = new QueryCommand();
3738
$command->on('error', $this->expectCallableOnce());
3839

3940
// hack to inject command as current command
@@ -43,4 +44,47 @@ public function testClosingStreamEmitsErrorForCurrentCommand()
4344

4445
$stream->close();
4546
}
47+
48+
public function testSendingErrorFrameDuringHandshakeShouldEmitErrorOnFollowingCommand()
49+
{
50+
$stream = new ThroughStream();
51+
$connection = $this->getMockBuilder('React\MySQL\ConnectionInterface')->disableOriginalConstructor()->getMock();
52+
53+
$command = new QueryCommand();
54+
$command->on('error', $this->expectCallableOnce());
55+
56+
$error = null;
57+
$command->on('error', function ($e) use (&$error) {
58+
$error = $e;
59+
});
60+
61+
$executor = new Executor($connection);
62+
$executor->enqueue($command);
63+
64+
$parser = new Parser($stream, $executor);
65+
$parser->start();
66+
67+
$stream->write("\x17\0\0\0" . "\xFF" . "\x10\x04" . "Too many connections");
68+
69+
$this->assertTrue($error instanceof Exception);
70+
$this->assertEquals(1040, $error->getCode());
71+
$this->assertEquals('Too many connections', $error->getMessage());
72+
}
73+
74+
public function testSendingIncompleteErrorFrameDuringHandshakeShouldNotEmitError()
75+
{
76+
$stream = new ThroughStream();
77+
$connection = $this->getMockBuilder('React\MySQL\ConnectionInterface')->disableOriginalConstructor()->getMock();
78+
79+
$command = new QueryCommand();
80+
$command->on('error', $this->expectCallableNever());
81+
82+
$executor = new Executor($connection);
83+
$executor->enqueue($command);
84+
85+
$parser = new Parser($stream, $executor);
86+
$parser->start();
87+
88+
$stream->write("\xFF\0\0\0" . "\xFF" . "\x12\x34" . "Some incomplete error message...");
89+
}
4690
}

0 commit comments

Comments
 (0)