Completed
Push — master ( 4f6108...97a3dc )
by Carlos
01:33
created

Serializer::deserializeResponse()   C

Complexity

Conditions 11
Paths 18

Size

Total Lines 119
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 11.0006

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 11
eloc 65
c 2
b 0
f 0
nc 18
nop 1
dl 0
loc 119
ccs 57
cts 58
cp 0.9828
crap 11.0006
rs 6.6169

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
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 21
    public function deserializeResponse(string $response): Response
63
    {
64 21
        $responseObj = new Response();
65
66 21
        $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 21
        $isAuthoritative = (bool) ($bytes[2] & 0x04);
74 21
        $isRecursionAvailable = (bool) ($bytes[3] & 0x80);
75 21
        $type = $bytes[3] & 0x0F;
76
77 21
        [$_, $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 21
        [$_, $anCount] = $this->readInt($bytes, 6, 2);
79 21
        [$_, $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 21
        [$_, $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 21
        $responseObj->setAuthoritative($isAuthoritative);
83 21
        $responseObj->setRecursionAvailable($isRecursionAvailable);
84 21
        $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 21
        $ptr = 12;
89
90
        /*
91
         * Question
92
         */
93
        // QNAME
94 21
        while ($bytes[$ptr] > 0x00) {
95 21
            $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 21
            $ptr += $labelLength + 1;
99
        }
100 21
        $ptr++; // End of field
101
102
        // QTYPE
103 21
        $ptr += 2;
104
105
        // CLASS
106 21
        $ptr += 2;
107
108
        /*
109
         * Answer
110
         */
111 21
        for ($i = 0; $i < $anCount; $i++) {
112
            // NAME
113 21
            [$ptr, $name] = $this->readNameField($bytes, $ptr);
114
115
            // TYPE
116 21
            [$ptr, $type] = $this->readInt($bytes, $ptr, 2);
117
118
            // CLASS
119 21
            $ptr += 2;
120
121
            // TTL
122 21
            [$ptr, $ttl] = $this->readInt($bytes, $ptr, 4);
123
124
            // RDATA
125 21
            [$ptr, $rdLength] = $this->readInt($bytes, $ptr, 2);
126 21
            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 3
                    [$ptr, $value] = $this->readNameField($bytes, $ptr);
132 3
                    break;
133
                case Request::RR_TYPE_CNAME:
134 3
                    [$ptr, $value] = $this->readNameField($bytes, $ptr);
135 3
                    break;
136
                case Request::RR_TYPE_SOA:
137 3
                    [$ptr, $primaryNs] = $this->readNameField($bytes, $ptr);
138 3
                    [$ptr, $adminMb] = $this->readNameField($bytes, $ptr);
139 3
                    [$ptr, $serialNo] = $this->readInt($bytes, $ptr, 4);
140 3
                    [$ptr, $refreshInterval] = $this->readInt($bytes, $ptr, 4);
141 3
                    [$ptr, $retryInterval] = $this->readInt($bytes, $ptr, 4);
142 3
                    [$ptr, $expirationLimit] = $this->readInt($bytes, $ptr, 4);
143 3
                    [$ptr, $minimumTtl] = $this->readInt($bytes, $ptr, 4);
144 3
                    $value = "{$primaryNs} {$adminMb} {$serialNo} {$refreshInterval}"
145 3
                        . " {$retryInterval} {$expirationLimit} {$minimumTtl}";
146 3
                    break;
147
                case Request::RR_TYPE_PTR:
148 3
                    [$ptr, $value] = $this->readNameField($bytes, $ptr);
149 3
                    break;
150
                case Request::RR_TYPE_MX:
151 3
                    [$ptr, $preference] = $this->readInt($bytes, $ptr, 2);
152 3
                    [$ptr, $exchanger] = $this->readNameField($bytes, $ptr);
153 3
                    $value = "{$preference} {$exchanger}";
154 3
                    break;
155
                case Request::RR_TYPE_AAAA:
156 3
                    $packed = '';
157 3
                    for ($i = 0; $i < 16; $i++) {
158 3
                        $packed .= chr($bytes[$ptr++]);
159
                    }
160 3
                    $value = inet_ntop($packed);
161 3
                    break;
162
                default:
163
                    throw new \RuntimeException("Reading responses for resource type '{$type}' is not implemented");
164
            }
165
166 21
            $record = new ResourceRecord($name, $type, $ttl, $value);
167 21
            $responseObj->addResourceRecord($record);
168
        }
169
170
        /*
171
         * Authority
172
         */
173
        // Intentionally skipped
174
175
        /*
176
         * Additional
177
         */
178
        // Intentionally skipped
179
180 21
        return $responseObj;
181
    }
182
183 21
    private function readNameField(array $bytes, int $ptr): array
184
    {
185 21
        while (true) {
186
            // Zero byte means we hit end of name field. Break the loop.
187 21
            if ($bytes[$ptr] == 0) {
188 21
                $ptr++;
189 21
                break;
190
            }
191
192 21
            $format = $bytes[$ptr] & 0xC0;
193 21
            $length = $offset = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $length is dead and can be removed.
Loading history...
194
195 21
            if ($format == 0xC0) { // Pointer format
196 21
                $offset = ($bytes[$ptr++] << 8 | $bytes[$ptr++]) & 0x3F;
197 21
                [$_, $label] = $this->readNameField($bytes, $offset);
198 21
                $labels[] = $label;
199
                // Pointer ends the name field. Break the loop.
200 21
                break;
201 21
            } elseif ($format == 0x00) {  // Label format
202 21
                $length = $bytes[$ptr++] & 0x3F;
203 21
                $labels[] = implode('', array_map('chr', array_slice($bytes, $ptr, $length)));
204 21
                $ptr += $length;
205
            } else {
206
                throw new \RuntimeException("Unrecognized format '${format}' in response");
207
            }
208
        }
209
210 21
        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...
211
    }
212
213
    /**
214
     * Reads and returns an integer from the given byte array, at the specified
215
     * offset and with the specified length in bytes.
216
     *
217
     * @param int[] $byteArray
218
     * @param int $ptr
219
     * @param int $bytes
220
     * @return int[] An array of size 2, the first item being the updated
221
     * pointer (after the read operation) and the second item being the actual
222
     * integer that was read.
223
     */
224 21
    private function readInt(array $byteArray, int $ptr, int $bytes): array
225
    {
226 21
        $int = 0;
227 21
        for ($i = $bytes - 1; $i >= 0; $i--) {
228 21
            $int |= $byteArray[$ptr++] << 8 * $i;
229
        }
230 21
        return [$ptr, $int];
231
    }
232
}
233