Skip to content

Commit 4150016

Browse files
loks0nCopilot
andcommitted
Apply suggestion from @Copilot
Co-authored-by: Copilot <[email protected]>
1 parent ae550f2 commit 4150016

27 files changed

+2912
-1125
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ COPY composer.json /usr/local/src/
1111
RUN composer install --ignore-platform-reqs --optimize-autoloader \
1212
--no-plugins --no-scripts --prefer-dist
1313

14-
FROM appwrite/base:0.10.4 as final
14+
FROM appwrite/base:0.11.3 as final
1515

1616
LABEL maintainer="[email protected]"
1717

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
"psr-4": {"Utopia\\DNS\\": "src/DNS"}
2222
},
2323
"require": {
24-
"php": ">=8.3",
24+
"php": ">=8.4",
2525
"utopia-php/console": "0.0.*",
26-
"utopia-php/telemetry": "^0.1.1"
26+
"utopia-php/telemetry": "0.1.*"
2727
},
2828
"require-dev": {
2929
"swoole/ide-helper": "5.1.8",

composer.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ services:
33
image: dns-dev
44
build:
55
context: .
6-
command: [ "sh", "-c", "php /usr/src/code/tests/resource/server.php" ]
6+
command: [ "sh", "-c", "php /usr/src/code/tests/resources/server.php" ]
77
volumes:
88
- ./bin:/usr/src/code/bin
99
- ./src:/usr/src/code/src

src/DNS/Client.php

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Utopia\DNS;
44

55
use Exception;
6-
use Utopia\DNS\Message\Question;
76

87
class Client
98
{
@@ -33,42 +32,37 @@ public function __construct(string $server = '127.0.0.1', int $port = 53, int $t
3332
}
3433

3534
/**
36-
* @param Question $question
35+
* @param Message $message
3736
* @return Message
3837
*/
39-
public function query(Question $question): Message
38+
public function query(Message $message): Message
4039
{
41-
try {
42-
$message = Message::query($question);
43-
$packet = $message->encode();
44-
if (socket_sendto($this->socket, $packet, strlen($packet), 0, $this->server, $this->port) === false) {
45-
throw new Exception('Failed to send data: ' . socket_strerror(socket_last_error($this->socket)));
46-
}
47-
48-
$response = '';
49-
$from = '';
50-
$port = 0;
40+
$packet = $message->encode();
41+
if (socket_sendto($this->socket, $packet, strlen($packet), 0, $this->server, $this->port) === false) {
42+
throw new Exception('Failed to send data: ' . socket_strerror(socket_last_error($this->socket)));
43+
}
5144

52-
$result = socket_recvfrom($this->socket, $response, 512, 0, $from, $port);
45+
$data = '';
46+
$from = '';
47+
$port = 0;
5348

54-
if ($result === false) {
55-
$error = socket_last_error($this->socket);
56-
$errorMessage = socket_strerror($error);
57-
throw new Exception("Failed to receive data from {$this->server}: {$errorMessage} (Error code: {$error})");
58-
}
49+
$result = socket_recvfrom($this->socket, $data, 512, 0, $from, $port);
5950

60-
if (empty($response)) {
61-
throw new Exception("Empty response received from {$this->server}:{$this->port}");
62-
}
51+
if ($result === false) {
52+
$error = socket_last_error($this->socket);
53+
$errorMessage = socket_strerror($error);
54+
throw new Exception("Failed to receive data from {$this->server}: {$errorMessage} (Error code: {$error})");
55+
}
6356

64-
$response = Message::decode($response);
65-
if ($response->header->id !== $message->header->id) {
66-
throw new Exception("Mismatched DNS transaction ID. Expected {$message->header->id}, got {$response->header->id}");
67-
}
57+
if (empty($data)) {
58+
throw new Exception("Empty response received from {$this->server}:{$this->port}");
59+
}
6860

69-
return $response;
70-
} catch (Exception $e) {
71-
throw new Exception($e->getMessage(), $e->getCode(), $e);
61+
$response = Message::decode($data);
62+
if ($response->header->id !== $message->header->id) {
63+
throw new Exception("Mismatched DNS transaction ID. Expected {$message->header->id}, got {$response->header->id}");
7264
}
65+
66+
return $response;
7367
}
7468
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Utopia\DNS\Exception;
4+
5+
/**
6+
* Exception thrown when a DNS message is not decoded.
7+
*/
8+
class DecodingException extends \RuntimeException
9+
{
10+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Utopia\DNS\Exception;
4+
5+
use Utopia\DNS\Message;
6+
use Utopia\DNS\Message\Header;
7+
8+
/**
9+
* Exception thrown when a DNS message header is decoded, but the message is not fully decoded.
10+
*/
11+
final class PartialDecodingException extends DecodingException
12+
{
13+
public function __construct(
14+
private readonly Header $header,
15+
string $message = '',
16+
?\Throwable $previous = null
17+
) {
18+
parent::__construct($message, Message::RCODE_FORMERR, $previous);
19+
}
20+
21+
public function getHeader(): Header
22+
{
23+
return $this->header;
24+
}
25+
}

src/DNS/Message.php

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Utopia\DNS;
44

5+
use Utopia\DNS\Exception\DecodingException;
6+
use Utopia\DNS\Exception\PartialDecodingException;
57
use Utopia\DNS\Message\Header;
68
use Utopia\DNS\Message\Question;
79
use Utopia\DNS\Message\Record;
@@ -20,15 +22,22 @@ final class Message
2022
public const int RCODE_NOTAUTH = 9;
2123
public const int RCODE_NOTZONE = 10;
2224

25+
/**
26+
* @param Header $header The header of the message.
27+
* @param Question[] $questions The question records.
28+
* @param list<Record> $answers The answer records.
29+
* @param list<Record> $authority The authority records.
30+
* @param list<Record> $additional The additional records.
31+
*/
2332
public function __construct(
2433
public readonly Header $header,
2534
/** @var Question[] */
2635
public readonly array $questions = [],
27-
/** @var Record[] */
36+
/** @var list<Record> */
2837
public readonly array $answers = [],
29-
/** @var Record[] */
38+
/** @var list<Record> */
3039
public readonly array $authority = [],
31-
/** @var Record[] */
40+
/** @var list<Record> */
3241
public readonly array $additional = []
3342
) {
3443
if ($header->questionCount !== count($questions)) {
@@ -43,7 +52,7 @@ public function __construct(
4352
if ($header->additionalCount !== count($additional)) {
4453
throw new \InvalidArgumentException('Invalid DNS response: additional count mismatch');
4554
}
46-
if ($header->isResponse && !array_any($this->authority, fn ($record) => $record->type === Record::TYPE_SOA)) {
55+
if ($header->isResponse && $header->authoritative && !array_any($this->authority, fn ($record) => $record->type === Record::TYPE_SOA)) {
4756
if ($header->responseCode === self::RCODE_NXDOMAIN) {
4857
throw new \InvalidArgumentException('NXDOMAIN requires SOA in authority');
4958
}
@@ -83,19 +92,21 @@ public static function query(
8392
/**
8493
* Create a response message.
8594
*
86-
* @param Message $query The query message to respond to.
95+
* @param Header $header The header of the query message to respond to.
8796
* @param int $responseCode The response code.
88-
* @param array<Record> $answers The answer records.
89-
* @param array<Record> $authority The authority records.
90-
* @param array<Record> $additional The additional records.
97+
* @param array<Question> $questions The question records.
98+
* @param list<Record> $answers The answer records.
99+
* @param list<Record> $authority The authority records.
100+
* @param list<Record> $additional The additional records.
91101
* @param bool $authoritative Whether the response is authoritative.
92102
* @param bool $truncated Whether the response is truncated.
93103
* @param bool $recursionAvailable Whether recursion is available.
94104
* @return self The response message.
95105
*/
96106
public static function response(
97-
Message $query,
107+
Header $header,
98108
int $responseCode,
109+
array $questions = [],
99110
array $answers = [],
100111
array $authority = [],
101112
array $additional = [],
@@ -104,59 +115,64 @@ public static function response(
104115
bool $recursionAvailable = false
105116
): self {
106117
$header = new Header(
107-
id: $query->header->id,
118+
id: $header->id,
108119
isResponse: true,
109-
opcode: $query->header->opcode,
120+
opcode: $header->opcode,
110121
authoritative: $authoritative,
111122
truncated: $truncated,
112-
recursionDesired: $query->header->recursionDesired,
123+
recursionDesired: $header->recursionDesired,
113124
recursionAvailable: $recursionAvailable,
114125
responseCode: $responseCode,
115-
questionCount: count($query->questions),
126+
questionCount: count($questions),
116127
answerCount: count($answers),
117128
authorityCount: count($authority),
118129
additionalCount: count($additional)
119130
);
120131

121-
return new self($header, $query->questions, $answers, $authority, $additional);
132+
133+
return new self($header, $questions, $answers, $authority, $additional);
122134
}
123135

124136
public static function decode(string $packet): self
125137
{
126138
if (strlen($packet) < Header::LENGTH) {
127-
throw new \InvalidArgumentException('Invalid DNS response: header too short');
139+
throw new DecodingException('Invalid DNS response: header too short');
128140
}
129141

130142
// --- Parse header (12 bytes) ---
131143
$header = Header::decode($packet);
132144

133145
// --- Parse Question Section ---
134-
$offset = Header::LENGTH;
135-
$questions = [];
136-
for ($i = 0; $i < $header->questionCount; $i++) {
137-
$questions[] = Question::decode($packet, $offset);
138-
}
146+
try {
147+
$offset = Header::LENGTH;
148+
$questions = [];
149+
for ($i = 0; $i < $header->questionCount; $i++) {
150+
$questions[] = Question::decode($packet, $offset);
151+
}
139152

140-
// --- Decode Answer Section ---
141-
$answers = [];
142-
for ($i = 0; $i < $header->answerCount; $i++) {
143-
$answers[] = Record::decode($packet, $offset);
144-
}
153+
// --- Decode Answer Section ---
154+
$answers = [];
155+
for ($i = 0; $i < $header->answerCount; $i++) {
156+
$answers[] = Record::decode($packet, $offset);
157+
}
145158

146-
// --- Decode Authority Section ---
147-
$authority = [];
148-
for ($i = 0; $i < $header->authorityCount; $i++) {
149-
$authority[] = Record::decode($packet, $offset);
150-
}
159+
// --- Decode Authority Section ---
160+
$authority = [];
161+
for ($i = 0; $i < $header->authorityCount; $i++) {
162+
$authority[] = Record::decode($packet, $offset);
163+
}
151164

152-
// --- Decode Additional Section ---
153-
$additional = [];
154-
for ($i = 0; $i < $header->additionalCount; $i++) {
155-
$additional[] = Record::decode($packet, $offset);
156-
}
165+
// --- Decode Additional Section ---
166+
$additional = [];
167+
for ($i = 0; $i < $header->additionalCount; $i++) {
168+
$additional[] = Record::decode($packet, $offset);
169+
}
157170

158-
if ($offset !== strlen($packet)) {
159-
throw new \InvalidArgumentException('Invalid packet length');
171+
if ($offset !== strlen($packet)) {
172+
throw new DecodingException('Invalid packet length');
173+
}
174+
} catch (DecodingException $e) {
175+
throw new PartialDecodingException($header, $e->getMessage(), $e);
160176
}
161177

162178
return new self($header, $questions, $answers, $authority, $additional);

0 commit comments

Comments
 (0)