Completed
Push — master ( 97184e...2b435f )
by Carlos
01:30
created

Serializer::deserializeResponse()   C

Complexity

Conditions 12
Paths 20

Size

Total Lines 122
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 59
CRAP Score 12.0006

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 12
eloc 68
c 3
b 0
f 0
nc 20
nop 1
dl 0
loc 122
ccs 59
cts 60
cp 0.9833
crap 12.0006
rs 6.2714

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 Request::RR_TYPE_A:
128 3
                    $value = $bytes[$ptr++] . '.' . $bytes[$ptr++] . '.' . $bytes[$ptr++] . '.' . $bytes[$ptr++];
129 3
                    break;
130
                case Request::RR_TYPE_NS:
131
                case Request::RR_TYPE_CNAME:
132
                case Request::RR_TYPE_PTR:
133 9
                    [$ptr, $value] = $this->readNameField($bytes, $ptr);
134 9
                    break;
135
                case Request::RR_TYPE_SOA:
136 3
                    [$ptr, $primaryNs] = $this->readNameField($bytes, $ptr);
137 3
                    [$ptr, $adminMb] = $this->readNameField($bytes, $ptr);
138 3
                    [$ptr, $serialNo] = $this->readInt($bytes, $ptr, 4);
139 3
                    [$ptr, $refreshInterval] = $this->readInt($bytes, $ptr, 4);
140 3
                    [$ptr, $retryInterval] = $this->readInt($bytes, $ptr, 4);
141 3
                    [$ptr, $expirationLimit] = $this->readInt($bytes, $ptr, 4);
142 3
                    [$ptr, $minimumTtl] = $this->readInt($bytes, $ptr, 4);
143 3
                    $value = "{$primaryNs} {$adminMb} {$serialNo} {$refreshInterval}"
144 3
                        . " {$retryInterval} {$expirationLimit} {$minimumTtl}";
145 3
                    break;
146
                case Request::RR_TYPE_MX:
147 3
                    [$ptr, $preference] = $this->readInt($bytes, $ptr, 2);
148 3
                    [$ptr, $exchanger] = $this->readNameField($bytes, $ptr);
149 3
                    $value = "{$preference} {$exchanger}";
150 3
                    break;
151
                case Request::RR_TYPE_SRV:
152 3
                    [$ptr, $priority] = $this->readInt($bytes, $ptr, 2);
153 3
                    [$ptr, $weight] = $this->readInt($bytes, $ptr, 2);
154 3
                    [$ptr, $port] = $this->readInt($bytes, $ptr, 2);
155 3
                    [$ptr, $target] = $this->readNameField($bytes, $ptr);
156 3
                    $value = "{$priority} {$weight} {$port} {$target}";
157 3
                    break;
158
                case Request::RR_TYPE_AAAA:
159 3
                    $packed = '';
160 3
                    for ($i = 0; $i < 16; $i++) {
161 3
                        $packed .= chr($bytes[$ptr++]);
162
                    }
163 3
                    $value = inet_ntop($packed);
164 3
                    break;
165
                default:
166
                    throw new \RuntimeException("Reading responses for resource type '{$type}' is not implemented");
167
            }
168
169 24
            $record = new ResourceRecord($name, $type, $ttl, $value);
170 24
            $responseObj->addResourceRecord($record);
171
        }
172
173
        /*
174
         * Authority
175
         */
176
        // Intentionally skipped
177
178
        /*
179
         * Additional
180
         */
181
        // Intentionally skipped
182
183 24
        return $responseObj;
184
    }
185
186 24
    private function readNameField(array $bytes, int $ptr): array
187
    {
188 24
        while (true) {
189
            // Zero byte means we hit end of name field. Break the loop.
190 24
            if ($bytes[$ptr] == 0) {
191 24
                $ptr++;
192 24
                break;
193
            }
194
195 24
            $format = $bytes[$ptr] & 0xC0;
196 24
            $length = $offset = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $length is dead and can be removed.
Loading history...
197
198 24
            if ($format == 0xC0) { // Pointer format
199 24
                $offset = ($bytes[$ptr++] << 8 | $bytes[$ptr++]) & 0x3F;
200 24
                [$_, $label] = $this->readNameField($bytes, $offset);
201 24
                $labels[] = $label;
202
                // Pointer ends the name field. Break the loop.
203 24
                break;
204 24
            } elseif ($format == 0x00) {  // Label format
205 24
                $length = $bytes[$ptr++] & 0x3F;
206 24
                $labels[] = implode('', array_map('chr', array_slice($bytes, $ptr, $length)));
207 24
                $ptr += $length;
208
            } else {
209
                throw new \RuntimeException("Unrecognized format '${format}' in response");
210
            }
211
        }
212
213 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...
214
    }
215
216
    /**
217
     * Reads and returns an integer from the given byte array, at the specified
218
     * offset and with the specified length in bytes.
219
     *
220
     * @param int[] $byteArray
221
     * @param int $ptr
222
     * @param int $bytes
223
     * @return int[] An array of size 2, the first item being the updated
224
     * pointer (after the read operation) and the second item being the actual
225
     * integer that was read.
226
     */
227 24
    private function readInt(array $byteArray, int $ptr, int $bytes): array
228
    {
229 24
        $int = 0;
230 24
        for ($i = $bytes - 1; $i >= 0; $i--) {
231 24
            $int |= $byteArray[$ptr++] << 8 * $i;
232
        }
233 24
        return [$ptr, $int];
234
    }
235
}
236