Completed
Push — master ( 97a3dc...2473ff )
by Carlos
01:32
created

Serializer::deserializeResponse()   C

Complexity

Conditions 11
Paths 18

Size

Total Lines 115
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 53
CRAP Score 11.0007

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 11
eloc 61
c 3
b 0
f 0
nc 18
nop 1
dl 0
loc 115
ccs 53
cts 54
cp 0.9815
crap 11.0007
rs 6.7042

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
                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_AAAA:
152 3
                    $packed = '';
153 3
                    for ($i = 0; $i < 16; $i++) {
154 3
                        $packed .= chr($bytes[$ptr++]);
155
                    }
156 3
                    $value = inet_ntop($packed);
157 3
                    break;
158
                default:
159
                    throw new \RuntimeException("Reading responses for resource type '{$type}' is not implemented");
160
            }
161
162 21
            $record = new ResourceRecord($name, $type, $ttl, $value);
163 21
            $responseObj->addResourceRecord($record);
164
        }
165
166
        /*
167
         * Authority
168
         */
169
        // Intentionally skipped
170
171
        /*
172
         * Additional
173
         */
174
        // Intentionally skipped
175
176 21
        return $responseObj;
177
    }
178
179 21
    private function readNameField(array $bytes, int $ptr): array
180
    {
181 21
        while (true) {
182
            // Zero byte means we hit end of name field. Break the loop.
183 21
            if ($bytes[$ptr] == 0) {
184 21
                $ptr++;
185 21
                break;
186
            }
187
188 21
            $format = $bytes[$ptr] & 0xC0;
189 21
            $length = $offset = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $length is dead and can be removed.
Loading history...
190
191 21
            if ($format == 0xC0) { // Pointer format
192 21
                $offset = ($bytes[$ptr++] << 8 | $bytes[$ptr++]) & 0x3F;
193 21
                [$_, $label] = $this->readNameField($bytes, $offset);
194 21
                $labels[] = $label;
195
                // Pointer ends the name field. Break the loop.
196 21
                break;
197 21
            } elseif ($format == 0x00) {  // Label format
198 21
                $length = $bytes[$ptr++] & 0x3F;
199 21
                $labels[] = implode('', array_map('chr', array_slice($bytes, $ptr, $length)));
200 21
                $ptr += $length;
201
            } else {
202
                throw new \RuntimeException("Unrecognized format '${format}' in response");
203
            }
204
        }
205
206 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...
207
    }
208
209
    /**
210
     * Reads and returns an integer from the given byte array, at the specified
211
     * offset and with the specified length in bytes.
212
     *
213
     * @param int[] $byteArray
214
     * @param int $ptr
215
     * @param int $bytes
216
     * @return int[] An array of size 2, the first item being the updated
217
     * pointer (after the read operation) and the second item being the actual
218
     * integer that was read.
219
     */
220 21
    private function readInt(array $byteArray, int $ptr, int $bytes): array
221
    {
222 21
        $int = 0;
223 21
        for ($i = $bytes - 1; $i >= 0; $i--) {
224 21
            $int |= $byteArray[$ptr++] << 8 * $i;
225
        }
226 21
        return [$ptr, $int];
227
    }
228
}
229