Completed
Push — master ( a21f6f...b758b2 )
by Carlos
01:41
created

Serializer::deserializeResponse()   C

Complexity

Conditions 12
Paths 20

Size

Total Lines 139
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 75
CRAP Score 12.0003

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 12
eloc 84
c 3
b 0
f 0
nc 20
nop 1
dl 0
loc 139
ccs 75
cts 76
cp 0.9868
crap 12.0003
rs 5.9224

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
declare(strict_types=1);
4
5
namespace Afonso\Dns;
6
7
class Serializer implements SerializerInterface
8
{
9 6
    public function serializeRequest(Request $request): string
10
    {
11 6
        $bytes = [];
12
13
        /*
14
         * Header
15
         */
16
        // ID (0x00 - 0x15)
17 6
        $bytes = array_merge($bytes, [0xAA, 0xAA]);
18
19
        // QR, OPCODE, AA, TC, RD, RA, RCODE (0x16 - 0x31)
20 6
        $bytes += array_merge($bytes, [0x01, 0x00]);
21
22
        // QDCOUNT (0x32 - 0x47)
23 6
        $bytes += array_merge($bytes, [0x00, 0x01]);
24
25
        // ANCOUNT (0x48 - 0x63)
26 6
        $bytes += array_merge($bytes, [0x00, 0x00]);
27
28
        // NSCOUNT (0x64 - 0x79)
29 6
        $bytes += array_merge($bytes, [0x00, 0x00]);
30
31
        // ARCOUNT (0x80 - 0x95)
32 6
        $bytes += array_merge($bytes, [0x00, 0x00]);
33
34
        /*
35
         * Question
36
         */
37
        // QNAME
38 6
        $qname = [];
39 6
        $labels = explode('.', $request->getName());
40 6
        foreach ($labels as $label) {
41 6
            $labelLength = strlen($label);
42 6
            if ($labelLength > 63) {
43 3
                throw new \InvalidArgumentException(
44 3
                    'At least one of the labels of the specified domain exceeds the allowed maximum length'
45
                );
46
            }
47 3
            $qname = array_merge($qname, [$labelLength]);
48 3
            $qname = array_merge($qname, array_map('ord', str_split($label)));
49
        }
50 3
        $qname = array_merge($qname, [0x00]);
51 3
        $bytes += array_merge($bytes, $qname);
52
53
        // QTYPE
54 3
        $bytes += array_merge($bytes, [0x00, $request->getType()]);
55
56
        // QCLASS
57 3
        $bytes += array_merge($bytes, [0x00, 0x01]);
58
59 3
        return implode(array_map('chr', $bytes));
60
    }
61
62 24
    public function deserializeResponse(string $response): Response
63
    {
64 24
        $responseObj = new Response();
65
66 24
        $bytes = array_map('ord', str_split($response));
67
68
        // To-Do: check that response is actually a response (QR)
69
70
        /*
71
         * Header
72
         */
73 24
        $isAuthoritative = (bool) ($bytes[2] & 0x04);
74 24
        $isRecursionAvailable = (bool) ($bytes[3] & 0x80);
75 24
        $type = $bytes[3] & 0x0F;
76
77 24
        [$_, $qdCount] = $this->readInt($bytes, 4, 2);
0 ignored issues
show
Comprehensibility Best Practice introduced by
This list assign is not used and could be removed.
Loading history...
78 24
        [$_, $anCount] = $this->readInt($bytes, 6, 2);
79 24
        [$_, $nsCount] = $this->readInt($bytes, 8, 2);
0 ignored issues
show
Comprehensibility Best Practice introduced by
This list assign is not used and could be removed.
Loading history...
80 24
        [$_, $arCount] = $this->readInt($bytes, 10, 2);
0 ignored issues
show
Comprehensibility Best Practice introduced by
This list assign is not used and could be removed.
Loading history...
81
82 24
        $responseObj->setAuthoritative($isAuthoritative);
83 24
        $responseObj->setRecursionAvailable($isRecursionAvailable);
84 24
        $responseObj->setType($type);
85
86
        // From now on there are variable-length fields, so we need to keep
87
        // track of the current byte position.
88 24
        $ptr = 12;
89
90
        /*
91
         * Question
92
         */
93
        // QNAME
94 24
        while ($bytes[$ptr] > 0x00) {
95 24
            $labelLength = $bytes[$ptr];
96
            // Just skip the label for the time being
97
            // $label = implode('', array_map('chr', array_slice($bytes, $ptr + 1, $labelLength)));
98 24
            $ptr += $labelLength + 1;
99
        }
100 24
        $ptr++; // End of field
101
102
        // QTYPE
103 24
        $ptr += 2;
104
105
        // CLASS
106 24
        $ptr += 2;
107
108
        /*
109
         * Answer
110
         */
111 24
        for ($i = 0; $i < $anCount; $i++) {
112
            // NAME
113 24
            [$ptr, $name] = $this->readNameField($bytes, $ptr);
114
115
            // TYPE
116 24
            [$ptr, $type] = $this->readInt($bytes, $ptr, 2);
117
118
            // CLASS
119 24
            $ptr += 2;
120
121
            // TTL
122 24
            [$ptr, $ttl] = $this->readInt($bytes, $ptr, 4);
123
124
            // RDATA
125 24
            [$ptr, $rdLength] = $this->readInt($bytes, $ptr, 2);
126 24
            switch ($type) {
127
                case ResourceRecord::TYPE_A:
128 3
                    $address = $bytes[$ptr++] . '.' . $bytes[$ptr++] . '.' . $bytes[$ptr++] . '.' . $bytes[$ptr++];
129 3
                    $record = new AResourceRecord($name, $ttl, $address);
130 3
                    break;
131
                case ResourceRecord::TYPE_NS:
132 3
                    [$ptr, $nameserver] = $this->readNameField($bytes, $ptr);
133 3
                    $record = new NsResourceRecord($name, $ttl, $nameserver);
134 3
                    break;
135
                case ResourceRecord::TYPE_CNAME:
136 3
                    [$ptr, $canonicalName] = $this->readNameField($bytes, $ptr);
137 3
                    $record= new CnameResourceRecord($name, $ttl, $canonicalName);
138 3
                    break;
139
                case ResourceRecord::TYPE_PTR:
140 3
                    [$ptr, $targetName] = $this->readNameField($bytes, $ptr);
141 3
                    $record = new PtrResourceRecord($name, $ttl, $targetName);
142 3
                    break;
143
                case ResourceRecord::TYPE_SOA:
144 3
                    [$ptr, $primaryNs] = $this->readNameField($bytes, $ptr);
145 3
                    [$ptr, $adminMb] = $this->readNameField($bytes, $ptr);
146 3
                    [$ptr, $serialNo] = $this->readInt($bytes, $ptr, 4);
147 3
                    [$ptr, $refreshInterval] = $this->readInt($bytes, $ptr, 4);
148 3
                    [$ptr, $retryInterval] = $this->readInt($bytes, $ptr, 4);
149 3
                    [$ptr, $expirationLimit] = $this->readInt($bytes, $ptr, 4);
150 3
                    [$ptr, $minimumTtl] = $this->readInt($bytes, $ptr, 4);
151 3
                    $record = new SoaResourceRecord(
152 3
                        $name,
153 1
                        $ttl,
154 1
                        $primaryNs,
155 1
                        $adminMb,
156 1
                        $serialNo,
157 1
                        $refreshInterval,
158 1
                        $retryInterval,
159 1
                        $expirationLimit,
160 1
                        $minimumTtl
161
                    );
162 3
                    break;
163
                case ResourceRecord::TYPE_MX:
164 3
                    [$ptr, $preference] = $this->readInt($bytes, $ptr, 2);
165 3
                    [$ptr, $exchanger] = $this->readNameField($bytes, $ptr);
166 3
                    $record= new MxResourceRecord($name, $ttl, $preference, $exchanger);
167 3
                    break;
168
                case ResourceRecord::TYPE_SRV:
169 3
                    [$ptr, $priority] = $this->readInt($bytes, $ptr, 2);
170 3
                    [$ptr, $weight] = $this->readInt($bytes, $ptr, 2);
171 3
                    [$ptr, $port] = $this->readInt($bytes, $ptr, 2);
172 3
                    [$ptr, $target] = $this->readNameField($bytes, $ptr);
173 3
                    $record = new SrvResourceRecord($name, $ttl, $priority, $weight, $port, $target);
174 3
                    break;
175
                case ResourceRecord::TYPE_AAAA:
176 3
                    $packed = '';
177 3
                    for ($i = 0; $i < 16; $i++) {
178 3
                        $packed .= chr($bytes[$ptr++]);
179
                    }
180 3
                    $address = inet_ntop($packed);
181 3
                    $record = new AaaaResourceRecord($name, $ttl, $address);
182 3
                    break;
183
                default:
184
                    throw new \RuntimeException("Reading responses for resource type '{$type}' is not implemented");
185
            }
186
187 24
            $responseObj->addResourceRecord($record);
188
        }
189
190
        /*
191
         * Authority
192
         */
193
        // Intentionally skipped
194
195
        /*
196
         * Additional
197
         */
198
        // Intentionally skipped
199
200 24
        return $responseObj;
201
    }
202
203 24
    private function readNameField(array $bytes, int $ptr): array
204
    {
205 24
        while (true) {
206
            // Zero byte means we hit end of name field. Break the loop.
207 24
            if ($bytes[$ptr] == 0) {
208 24
                $ptr++;
209 24
                break;
210
            }
211
212 24
            $format = $bytes[$ptr] & 0xC0;
213 24
            $length = $offset = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $length is dead and can be removed.
Loading history...
214
215 24
            if ($format == 0xC0) { // Pointer format
216 24
                $offset = ($bytes[$ptr++] << 8 | $bytes[$ptr++]) & 0x3F;
217 24
                [$_, $label] = $this->readNameField($bytes, $offset);
218 24
                $labels[] = $label;
219
                // Pointer ends the name field. Break the loop.
220 24
                break;
221 24
            } elseif ($format == 0x00) {  // Label format
222 24
                $length = $bytes[$ptr++] & 0x3F;
223 24
                $labels[] = implode('', array_map('chr', array_slice($bytes, $ptr, $length)));
224 24
                $ptr += $length;
225
            } else {
226
                throw new \RuntimeException("Unrecognized format '${format}' in response");
227
            }
228
        }
229
230 24
        return [$ptr, implode('.', $labels)];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $labels does not seem to be defined for all execution paths leading up to this point.
Loading history...
231
    }
232
233
    /**
234
     * Reads and returns an integer from the given byte array, at the specified
235
     * offset and with the specified length in bytes.
236
     *
237
     * @param int[] $byteArray
238
     * @param int $ptr
239
     * @param int $bytes
240
     * @return int[] An array of size 2, the first item being the updated
241
     * pointer (after the read operation) and the second item being the actual
242
     * integer that was read.
243
     */
244 24
    private function readInt(array $byteArray, int $ptr, int $bytes): array
245
    {
246 24
        $int = 0;
247 24
        for ($i = $bytes - 1; $i >= 0; $i--) {
248 24
            $int |= $byteArray[$ptr++] << 8 * $i;
249
        }
250 24
        return [$ptr, $int];
251
    }
252
}
253