Skip to content

Commit cd3572a

Browse files
authored
Merge pull request #49 from clue-labs/simpler
Add simpler internal API
2 parents 4647d86 + 88df694 commit cd3572a

File tree

7 files changed

+193
-95
lines changed

7 files changed

+193
-95
lines changed

src/Model/Message.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace React\Dns\Model;
44

5+
use React\Dns\Query\Query;
6+
use React\Dns\Model\Record;
7+
58
class Message
69
{
710
const TYPE_A = 1;
@@ -25,6 +28,55 @@ class Message
2528
const RCODE_NOT_IMPLEMENTED = 4;
2629
const RCODE_REFUSED = 5;
2730

31+
/**
32+
* Creates a new request message for the given query
33+
*
34+
* @param Query $query
35+
* @return self
36+
*/
37+
public static function createRequestForQuery(Query $query)
38+
{
39+
$request = new Message();
40+
$request->header->set('id', self::generateId());
41+
$request->header->set('rd', 1);
42+
$request->questions[] = (array) $query;
43+
$request->prepare();
44+
45+
return $request;
46+
}
47+
48+
/**
49+
* Creates a new response message for the given query with the given answer records
50+
*
51+
* @param Query $query
52+
* @param Record[] $answers
53+
* @return self
54+
*/
55+
public static function createResponseWithAnswersForQuery(Query $query, array $answers)
56+
{
57+
$response = new Message();
58+
$response->header->set('id', self::generateId());
59+
$response->header->set('qr', 1);
60+
$response->header->set('opcode', Message::OPCODE_QUERY);
61+
$response->header->set('rd', 1);
62+
$response->header->set('rcode', Message::RCODE_OK);
63+
64+
$response->questions[] = (array) $query;
65+
66+
foreach ($answers as $record) {
67+
$response->answers[] = $record;
68+
}
69+
70+
$response->prepare();
71+
72+
return $response;
73+
}
74+
75+
private static function generateId()
76+
{
77+
return mt_rand(0, 0xffff);
78+
}
79+
2880
public $data = '';
2981

3082
public $header;

src/Protocol/Parser.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use React\Dns\Model\Message;
66
use React\Dns\Model\Record;
7+
use InvalidArgumentException;
78

89
/**
910
* DNS protocol parser
@@ -12,7 +13,32 @@
1213
*/
1314
class Parser
1415
{
16+
/**
17+
* Parses the given raw binary message into a Message object
18+
*
19+
* @param string $data
20+
* @throws InvalidArgumentException
21+
* @return Message
22+
*/
23+
public function parseMessage($data)
24+
{
25+
$message = new Message();
26+
if ($this->parse($data, $message) !== $message) {
27+
throw new InvalidArgumentException('Unable to parse binary message');
28+
}
29+
30+
return $message;
31+
}
32+
33+
/**
34+
* @deprecated unused, exists for BC only
35+
*/
1536
public function parseChunk($data, Message $message)
37+
{
38+
return $this->parse($data, $message);
39+
}
40+
41+
private function parse($data, Message $message)
1642
{
1743
$message->data .= $data;
1844

src/Query/CachedExecutor.php

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace React\Dns\Query;
44

55
use React\Dns\Model\Message;
6-
use React\Dns\Model\Record;
76

87
class CachedExecutor implements ExecutorInterface
98
{
@@ -18,15 +17,14 @@ public function __construct(ExecutorInterface $executor, RecordCache $cache)
1817

1918
public function query($nameserver, Query $query)
2019
{
21-
$that = $this;
2220
$executor = $this->executor;
2321
$cache = $this->cache;
2422

2523
return $this->cache
2624
->lookup($query)
2725
->then(
28-
function ($cachedRecords) use ($that, $query) {
29-
return $that->buildResponse($query, $cachedRecords);
26+
function ($cachedRecords) use ($query) {
27+
return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
3028
},
3129
function () use ($executor, $cache, $nameserver, $query) {
3230
return $executor
@@ -39,27 +37,17 @@ function () use ($executor, $cache, $nameserver, $query) {
3937
);
4038
}
4139

40+
/**
41+
* @deprecated unused, exists for BC only
42+
*/
4243
public function buildResponse(Query $query, array $cachedRecords)
4344
{
44-
$response = new Message();
45-
46-
$response->header->set('id', $this->generateId());
47-
$response->header->set('qr', 1);
48-
$response->header->set('opcode', Message::OPCODE_QUERY);
49-
$response->header->set('rd', 1);
50-
$response->header->set('rcode', Message::RCODE_OK);
51-
52-
$response->questions[] = new Record($query->name, $query->type, $query->class);
53-
54-
foreach ($cachedRecords as $record) {
55-
$response->answers[] = $record;
56-
}
57-
58-
$response->prepare();
59-
60-
return $response;
45+
return Message::createResponseWithAnswersForQuery($query, $cachedRecords);
6146
}
6247

48+
/**
49+
* @deprecated unused, exists for BC only
50+
*/
6351
protected function generateId()
6452
{
6553
return mt_rand(0, 0xffff);

src/Query/Executor.php

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,20 @@ public function __construct(LoopInterface $loop, Parser $parser, BinaryDumper $d
3838

3939
public function query($nameserver, Query $query)
4040
{
41-
$request = $this->prepareRequest($query);
41+
$request = Message::createRequestForQuery($query);
4242

4343
$queryData = $this->dumper->toBinary($request);
4444
$transport = strlen($queryData) > 512 ? 'tcp' : 'udp';
4545

4646
return $this->doQuery($nameserver, $transport, $queryData, $query->name);
4747
}
4848

49+
/**
50+
* @deprecated unused, exists for BC only
51+
*/
4952
public function prepareRequest(Query $query)
5053
{
51-
$request = new Message();
52-
$request->header->set('id', $this->generateId());
53-
$request->header->set('rd', 1);
54-
$request->questions[] = (array) $query;
55-
$request->prepare();
56-
57-
return $request;
54+
return Message::createRequestForQuery($query);
5855
}
5956

6057
public function doQuery($nameserver, $transport, $queryData, $name)
@@ -63,7 +60,6 @@ public function doQuery($nameserver, $transport, $queryData, $name)
6360
$parser = $this->parser;
6461
$loop = $this->loop;
6562

66-
$response = new Message();
6763
$deferred = new Deferred(function ($resolve, $reject) use (&$timer, &$conn, $name) {
6864
$reject(new CancellationException(sprintf('DNS query for %s has been cancelled', $name)));
6965

@@ -108,17 +104,19 @@ public function doQuery($nameserver, $transport, $queryData, $name)
108104
return $deferred->promise();
109105
}
110106

111-
$conn->on('data', function ($data) use ($retryWithTcp, $conn, $parser, $response, $transport, $deferred, $timer) {
112-
$responseReady = $parser->parseChunk($data, $response);
113-
114-
if (!$responseReady) {
115-
return;
116-
}
117-
107+
$conn->on('data', function ($data) use ($retryWithTcp, $conn, $parser, $transport, $deferred, $timer) {
118108
if ($timer !== null) {
119109
$timer->cancel();
120110
}
121111

112+
try {
113+
$response = $parser->parseMessage($data);
114+
} catch (\Exception $e) {
115+
$conn->end();
116+
$deferred->reject($e);
117+
return;
118+
}
119+
122120
if ($response->header->isTruncated()) {
123121
if ('tcp' === $transport) {
124122
$deferred->reject(new BadServerException('The server set the truncated bit although we issued a TCP request'));
@@ -138,6 +136,9 @@ public function doQuery($nameserver, $transport, $queryData, $name)
138136
return $deferred->promise();
139137
}
140138

139+
/**
140+
* @deprecated unused, exists for BC only
141+
*/
141142
protected function generateId()
142143
{
143144
return mt_rand(0, 0xffff);

tests/Model/MessageTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace React\Tests\Dns\Model;
4+
5+
use React\Dns\Query\Query;
6+
use React\Dns\Model\Message;
7+
8+
class MessageTest extends \PHPUnit_Framework_TestCase
9+
{
10+
public function testCreateRequestDesiresRecusion()
11+
{
12+
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
13+
$request = Message::createRequestForQuery($query);
14+
15+
$this->assertTrue($request->header->isQuery());
16+
$this->assertSame(1, $request->header->get('rd'));
17+
}
18+
19+
public function testCreateResponseWithNoAnswers()
20+
{
21+
$query = new Query('igor.io', Message::TYPE_A, Message::CLASS_IN, 1345656451);
22+
$answers = array();
23+
$request = Message::createResponseWithAnswersForQuery($query, $answers);
24+
25+
$this->assertFalse($request->header->isQuery());
26+
$this->assertTrue($request->header->isResponse());
27+
$this->assertEquals(0, $request->header->get('anCount'));
28+
}
29+
}

tests/Protocol/ParserTest.php

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77

88
class ParserTest extends \PHPUnit_Framework_TestCase
99
{
10+
public function setUp()
11+
{
12+
$this->parser = new Parser();
13+
}
14+
1015
/**
1116
* @dataProvider provideConvertTcpDumpToBinary
1217
*/
@@ -34,10 +39,7 @@ public function testParseRequest()
3439

3540
$data = $this->convertTcpDumpToBinary($data);
3641

37-
$request = new Message();
38-
39-
$parser = new Parser();
40-
$parser->parseChunk($data, $request);
42+
$request = $this->parser->parseMessage($data);
4143

4244
$header = $request->header;
4345
$this->assertSame(0x7262, $header->get('id'));
@@ -74,10 +76,7 @@ public function testParseResponse()
7476

7577
$data = $this->convertTcpDumpToBinary($data);
7678

77-
$response = new Message();
78-
79-
$parser = new Parser();
80-
$parser->parseChunk($data, $response);
79+
$response = $this->parser->parseMessage($data);
8180

8281
$header = $response->header;
8382
$this->assertSame(0x7262, $header->get('id'));
@@ -121,8 +120,7 @@ public function testParseQuestionWithTwoQuestions()
121120
$request->header->set('qdCount', 2);
122121
$request->data = $data;
123122

124-
$parser = new Parser();
125-
$parser->parseQuestion($request);
123+
$this->parser->parseQuestion($request);
126124

127125
$this->assertCount(2, $request->questions);
128126
$this->assertSame('igor.io', $request->questions[0]['name']);
@@ -148,8 +146,7 @@ public function testParseAnswerWithInlineData()
148146
$response->header->set('anCount', 1);
149147
$response->data = $data;
150148

151-
$parser = new Parser();
152-
$parser->parseAnswer($response);
149+
$this->parser->parseAnswer($response);
153150

154151
$this->assertCount(1, $response->answers);
155152
$this->assertSame('igor.io', $response->answers[0]->name);
@@ -174,10 +171,7 @@ public function testParseResponseWithCnameAndOffsetPointers()
174171

175172
$data = $this->convertTcpDumpToBinary($data);
176173

177-
$response = new Message();
178-
179-
$parser = new Parser();
180-
$parser->parseChunk($data, $response);
174+
$response = $this->parser->parseMessage($data);
181175

182176
$this->assertCount(1, $response->questions);
183177
$this->assertSame('mail.google.com', $response->questions[0]['name']);
@@ -212,10 +206,7 @@ public function testParseResponseWithTwoAnswers()
212206

213207
$data = $this->convertTcpDumpToBinary($data);
214208

215-
$response = new Message();
216-
217-
$parser = new Parser();
218-
$parser->parseChunk($data, $response);
209+
$response = $this->parser->parseMessage($data);
219210

220211
$this->assertCount(1, $response->questions);
221212
$this->assertSame('io.whois-servers.net', $response->questions[0]['name']);
@@ -237,6 +228,21 @@ public function testParseResponseWithTwoAnswers()
237228
$this->assertSame('193.223.78.152', $response->answers[1]->data);
238229
}
239230

231+
/**
232+
* @expectedException InvalidArgumentException
233+
*/
234+
public function testParseIncomplete()
235+
{
236+
$data = "";
237+
$data .= "72 62 01 00 00 01 00 00 00 00 00 00"; // header
238+
$data .= "04 69 67 6f 72 02 69 6f 00"; // question: igor.io
239+
//$data .= "00 01 00 01"; // question: type A, class IN
240+
241+
$data = $this->convertTcpDumpToBinary($data);
242+
243+
$this->parser->parseMessage($data);
244+
}
245+
240246
private function convertTcpDumpToBinary($input)
241247
{
242248
// sudo ngrep -d en1 -x port 53

0 commit comments

Comments
 (0)