Message   D
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 473
Duplicated Lines 0.63 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 59
lcom 1
cbo 2
dl 3
loc 473
ccs 187
cts 187
cp 1
rs 4.08
c 0
b 0
f 0

44 Methods

Rating   Name   Duplication   Size   Complexity  
A encodeName() 0 15 3
B decodeName() 0 38 6
A getId() 0 4 1
A setId() 0 4 1
A isResponse() 0 4 1
A setResponse() 0 4 1
A isQuery() 0 4 1
A setQuery() 0 4 1
A getOpcode() 0 4 1
A setOpcode() 0 4 1
A isAuthoritative() 0 4 1
A setAuthoritative() 0 4 1
A isTruncated() 0 4 1
A setTruncated() 0 4 1
A isRecursionDesired() 0 4 1
A setRecursionDesired() 0 4 1
A isRecursionAvailable() 0 4 1
A setRecursionAvailable() 0 4 1
A getBit9() 0 4 1
A setBit9() 0 4 1
A isAuthenticData() 0 4 1
A setAuthenticData() 0 4 1
A isCheckingDisabled() 0 4 1
A setCheckingDisabled() 0 4 1
A getRcode() 0 4 1
A setRcode() 0 4 1
A getQuestions() 0 4 1
A addQuestion() 0 4 1
A setQuestions() 0 7 2
A getAnswers() 0 4 1
A addAnswer() 0 4 1
A setAnswers() 0 7 2
A getAuthoritatives() 0 4 1
A addAuthoritative() 0 4 1
A setAuthoritatives() 0 7 2
A getAdditionals() 0 4 1
A addAdditional() 0 4 1
A setAdditionals() 0 7 2
A countQuestions() 0 4 1
A countAnswers() 0 4 1
A countAuthoritatives() 0 4 1
A countAdditionals() 0 4 1
A toWire() 0 31 2
A fromWire() 3 37 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Message often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Message, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Badcow DNS Library.
7
 *
8
 * (c) Samuel Williams <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Badcow\DNS;
15
16
use Badcow\DNS\Rdata\DecodeException;
17
use Badcow\DNS\Rdata\UnsupportedTypeException;
18
19
class Message
20
{
21
    /**
22
     * ID.
23
     *
24
     * @var int
25
     */
26
    private $id;
27
28
    /**
29
     * QR.
30
     *
31
     * @var bool
32
     */
33
    private $isResponse;
34
35
    /**
36
     * OPCODE.
37
     *
38
     * @var int
39
     */
40
    private $opcode;
41
42
    /**
43
     * AA.
44
     *
45
     * @var bool
46
     */
47
    private $isAuthoritative;
48
49
    /**
50
     * TC.
51
     *
52
     * @var bool
53
     */
54
    private $isTruncated;
55
56
    /**
57
     * RD.
58
     *
59
     * @var bool
60
     */
61
    private $isRecursionDesired;
62
63
    /**
64
     * RA.
65
     *
66
     * @var bool
67
     */
68
    private $isRecursionAvailable;
69
70
    /**
71
     * Bit 9 of the header flags.
72
     *
73
     * @var int
74
     */
75
    private $bit9 = 0;
76
77
    /**
78
     * AD.
79
     *
80
     * {@link https://tools.ietf.org/html/rfc4035#section-3.2.3}
81
     *
82
     * @var bool
83
     */
84
    private $isAuthenticData;
85
86
    /**
87
     * CD.
88
     *
89
     * {@link https://tools.ietf.org/html/rfc4035#section-3.2.2}
90
     *
91
     * @var bool
92
     */
93
    private $isCheckingDisabled;
94
95
    /**
96
     * RCODE.
97
     *
98
     * @var int
99
     */
100
    private $rcode;
101
102
    /**
103
     * @var Question[]
104
     */
105
    private $questions = [];
106
107
    /**
108
     * @var ResourceRecord[]
109
     */
110
    private $answers = [];
111
112
    /**
113
     * @var ResourceRecord[]
114
     */
115
    private $authoritatives = [];
116
117
    /**
118
     * @var ResourceRecord[]
119
     */
120
    private $additionals = [];
121
122
    /**
123
     * Encode a domain name as a sequence of labels.
124 43
     */
125
    public static function encodeName(string $name): string
126 43
    {
127 3
        if ('.' === $name) {
128
            return chr(0);
129
        }
130 40
131 40
        $name = rtrim($name, '.').'.';
132
        $res = '';
133 40
134 40
        foreach (explode('.', $name) as $label) {
135
            $res .= chr(strlen($label)).$label;
136
        }
137 40
138
        return $res;
139
    }
140 30
141
    public static function decodeName(string $string, int &$offset = 0): string
142 30
    {
143 30
        $len = ord($string[$offset]);
144
        ++$offset;
145 30
146 30
        $isCompressed = (bool) (0b11000000 & $len);
147
        $_offset = 0;
148 30
149 7
        if ($isCompressed) {
150 7
            $_offset = $offset + 1;
151 7
            $offset = (0b00111111 & $len) * 256 + ord($string[$offset]);
152 7
            $len = ord($string[$offset]);
153
            ++$offset;
154
        }
155 30
156 3
        if (0 === $len) {
157
            return '.';
158
        }
159 27
160 27
        $name = '';
161 27
        while (0 !== $len) {
162 27
            $name .= substr($string, $offset, $len).'.';
163 27
            $offset += $len;
164 27
            $len = ord($string[$offset]);
165 7
            if ($len & 0b11000000) {
166 7
                $name .= self::decodeName($string, $offset);
167
                break;
168
            }
169 27
170
            ++$offset;
171
        }
172 27
173 7
        if ($isCompressed) {
174
            $offset = $_offset;
175
        }
176 27
177
        return $name;
178
    }
179 2
180
    public function getId(): int
181 2
    {
182
        return $this->id;
183
    }
184 8
185
    public function setId(int $id): void
186 8
    {
187 8
        $this->id = $id;
188
    }
189 2
190
    public function isResponse(): bool
191 2
    {
192
        return $this->isResponse;
193
    }
194 8
195
    public function setResponse(bool $isResponse): void
196 8
    {
197 8
        $this->isResponse = $isResponse;
198
    }
199 1
200
    public function isQuery(): bool
201 1
    {
202
        return !$this->isResponse;
203
    }
204 1
205
    public function setQuery(bool $query): void
206 1
    {
207 1
        $this->setResponse(!$query);
208
    }
209 2
210
    public function getOpcode(): int
211 2
    {
212
        return $this->opcode;
213
    }
214 7
215
    public function setOpcode(int $opcode): void
216 7
    {
217 7
        $this->opcode = $opcode;
218
    }
219 2
220
    public function isAuthoritative(): bool
221 2
    {
222
        return $this->isAuthoritative;
223
    }
224 7
225
    public function setAuthoritative(bool $isAuthoritative): void
226 7
    {
227 7
        $this->isAuthoritative = $isAuthoritative;
228
    }
229 2
230
    public function isTruncated(): bool
231 2
    {
232
        return $this->isTruncated;
233
    }
234 7
235
    public function setTruncated(bool $isTruncated): void
236 7
    {
237 7
        $this->isTruncated = $isTruncated;
238
    }
239 2
240
    public function isRecursionDesired(): bool
241 2
    {
242
        return $this->isRecursionDesired;
243
    }
244 7
245
    public function setRecursionDesired(bool $isRecursionDesired): void
246 7
    {
247 7
        $this->isRecursionDesired = $isRecursionDesired;
248
    }
249 2
250
    public function isRecursionAvailable(): bool
251 2
    {
252
        return $this->isRecursionAvailable;
253
    }
254 7
255
    public function setRecursionAvailable(bool $isRecursionAvailable): void
256 7
    {
257 7
        $this->isRecursionAvailable = $isRecursionAvailable;
258
    }
259 2
260
    public function getBit9(): int
261 2
    {
262
        return $this->bit9;
263
    }
264 7
265
    public function setBit9(int $bit9): void
266 7
    {
267 7
        $this->bit9 = $bit9;
268
    }
269 2
270
    public function isAuthenticData(): bool
271 2
    {
272
        return $this->isAuthenticData;
273
    }
274 7
275
    public function setAuthenticData(bool $isAuthenticData): void
276 7
    {
277 7
        $this->isAuthenticData = $isAuthenticData;
278
    }
279 2
280
    public function isCheckingDisabled(): bool
281 2
    {
282
        return $this->isCheckingDisabled;
283
    }
284 7
285
    public function setCheckingDisabled(bool $isCheckingDisabled): void
286 7
    {
287 7
        $this->isCheckingDisabled = $isCheckingDisabled;
288
    }
289 2
290
    public function getRcode(): int
291 2
    {
292
        return $this->rcode;
293
    }
294 7
295
    public function setRcode(int $rcode): void
296 7
    {
297 7
        $this->rcode = $rcode;
298
    }
299
300
    /**
301
     * @return Question[]
302 3
     */
303
    public function getQuestions(): array
304 3
    {
305
        return $this->questions;
306
    }
307 8
308
    public function addQuestion(Question $question): void
309 8
    {
310 8
        $this->questions[] = $question;
311
    }
312
313
    /**
314
     * @param Question[] $questions
315 1
     */
316
    public function setQuestions(array $questions): void
317 1
    {
318 1
        $this->questions = [];
319 1
        foreach ($questions as $question) {
320
            $this->addQuestion($question);
321 1
        }
322
    }
323
324
    /**
325
     * @return ResourceRecord[]
326 3
     */
327
    public function getAnswers(): array
328 3
    {
329
        return $this->answers;
330
    }
331 7
332
    public function addAnswer(ResourceRecord $answer): void
333 7
    {
334 7
        $this->answers[] = $answer;
335
    }
336
337
    /**
338
     * @param ResourceRecord[] $answers
339 7
     */
340
    public function setAnswers(array $answers): void
341 7
    {
342 7
        $this->answers = [];
343 7
        foreach ($answers as $answer) {
344
            $this->addAnswer($answer);
345 7
        }
346
    }
347
348
    /**
349
     * @return ResourceRecord[]
350 2
     */
351
    public function getAuthoritatives(): array
352 2
    {
353
        return $this->authoritatives;
354
    }
355 5
356
    public function addAuthoritative(ResourceRecord $authoritative): void
357 5
    {
358 5
        $this->authoritatives[] = $authoritative;
359
    }
360
361
    /**
362
     * @param ResourceRecord[] $authoritatives
363 7
     */
364
    public function setAuthoritatives(array $authoritatives): void
365 7
    {
366 7
        $this->authoritatives = [];
367 5
        foreach ($authoritatives as $authoritative) {
368
            $this->addAuthoritative($authoritative);
369 7
        }
370
    }
371
372
    /**
373
     * @return ResourceRecord[]
374 3
     */
375
    public function getAdditionals(): array
376 3
    {
377
        return $this->additionals;
378
    }
379 7
380
    public function addAdditional(ResourceRecord $additional): void
381 7
    {
382 7
        $this->additionals[] = $additional;
383
    }
384
385
    /**
386
     * @param ResourceRecord[] $additionals
387 7
     */
388
    public function setAdditionals(array $additionals): void
389 7
    {
390 7
        $this->additionals = [];
391 7
        foreach ($additionals as $additional) {
392
            $this->addAdditional($additional);
393 7
        }
394
    }
395 6
396
    public function countQuestions(): int
397 6
    {
398
        return count($this->questions);
399
    }
400 6
401
    public function countAnswers(): int
402 6
    {
403
        return count($this->answers);
404
    }
405 6
406
    public function countAuthoritatives(): int
407 6
    {
408
        return count($this->authoritatives);
409
    }
410 6
411
    public function countAdditionals(): int
412 6
    {
413
        return count($this->additionals);
414
    }
415
416
    /**
417
     * @throws UnsetValueException
418 3
     */
419
    public function toWire(): string
420
    {
421 3
        $flags = 0x0 |
422 3
        ($this->isResponse & 0x1) << 15 |
423 3
        ($this->opcode & 0xf) << 11 |
424 3
        ($this->isAuthoritative & 0x1) << 10 |
425 3
        ($this->isTruncated & 0x1) << 9 |
426 3
        ($this->isRecursionDesired & 0x1) << 8 |
427 3
        ($this->isRecursionAvailable & 0x1) << 7 |
428 3
        ($this->bit9 & 0x1) << 6 |
429 3
        ($this->isAuthenticData & 0x1) << 5 |
430 3
        ($this->isCheckingDisabled & 0x1) << 4 |
431
        ($this->rcode & 0xf);
432 3
433 3
        $encoded = pack(
434 3
            'nnnnnn',
435
            $this->id,
436 3
            $flags,
437 3
            $this->countQuestions(),
438 3
            $this->countAnswers(),
439 3
            $this->countAuthoritatives(),
440
            $this->countAdditionals()
441
        );
442 3
443
        foreach (array_merge($this->questions, $this->answers, $this->authoritatives, $this->additionals) as $resource) {
444 3
            /* @var ResourceRecord|Question $resource */
445
            $encoded .= $resource->toWire();
446
        }
447 3
448
        return $encoded;
449
    }
450
451
    /**
452
     * @throws UnsupportedTypeException
453 7
     */
454
    public static function fromWire(string $encoded): Message
455 7
    {
456 7
        $message = new self();
457 7
        $offset = 0;
458 7 View Code Duplication
        if (false === $header = unpack('nid/nflags/nqdcount/nancount/nnscount/narcount', $encoded, $offset)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
459 7
            throw new \UnexpectedValueException(sprintf('Malformed header encountered. "%s"', DecodeException::binaryToHex($encoded)));
460
        }
461 7
        $offset += 12;
462 7
        $flags = $header['flags'];
463 7
464 7
        $message->setId($header['id']);
465 7
        $message->setResponse((bool) ($flags >> 15 & 0x1));
466 7
        $message->setOpcode($flags >> 11 & 0xf);
467 7
        $message->setAuthoritative((bool) ($flags >> 10 & 0x1));
468 7
        $message->setTruncated((bool) ($flags >> 9 & 0x1));
469 7
        $message->setRecursionDesired((bool) ($flags >> 8 & 0x1));
470 7
        $message->setRecursionAvailable((bool) ($flags >> 7 & 0x1));
471 7
        $message->setBit9($flags >> 6 & 0x1);
472
        $message->setAuthenticData((bool) ($flags >> 5 & 0x1));
473 7
        $message->setCheckingDisabled((bool) ($flags >> 4 & 0x1));
474 7
        $message->setRcode($flags & 0xf);
475
476
        for ($i = 0; $i < $header['qdcount']; ++$i) {
477 7
            $message->addQuestion(Question::fromWire($encoded, $offset));
478 7
        }
479 7
480
        $rrs = [];
481
        while ($offset < strlen($encoded)) {
482 7
            $rrs[] = ResourceRecord::fromWire($encoded, $offset);
483 7
        }
484 7
485
        $message->setAnswers(array_splice($rrs, 0, $header['ancount']));
486 7
        $message->setAuthoritatives(array_splice($rrs, 0, $header['nscount']));
487
        $message->setAdditionals(array_splice($rrs, 0, $header['arcount']));
488
489
        return $message;
490
    }
491
}
492