Completed
Push — master ( 0cd2cd...e2ec22 )
by Sam
02:48
created

Decoder::decodeHeader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 17
nc 1
nop 2
dl 0
loc 20
ccs 18
cts 18
cp 1
crap 1
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of PHP DNS Server.
5
 *
6
 * (c) Yif Swery <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace yswery\DNS;
13
14
class Decoder
15
{
16
    /**
17
     * @param string $message
18
     *
19
     * @return Message
20
     */
21 7
    public static function decodeMessage(string $message): Message
22
    {
23 7
        $offset = 0;
24 7
        $header = self::decodeHeader($message, $offset);
25 7
        $messageObject = new Message($header);
26 7
        $messageObject->setQuestions(self::decodeResourceRecords($message, $header->getQuestionCount(), $offset, true));
27 7
        $messageObject->setAnswers(self::decodeResourceRecords($message, $header->getAnswerCount(), $offset));
28 7
        $messageObject->setAuthoritatives(self::decodeResourceRecords($message, $header->getNameServerCount(), $offset));
29 7
        $messageObject->setAdditionals(self::decodeResourceRecords($message, $header->getAdditionalRecordsCount(), $offset));
30
31 7
        return $messageObject;
32
    }
33
34
    /**
35
     * @param string $string
36
     * @param int    $offset
37
     *
38
     * @return string
39
     */
40 11
    public static function decodeDomainName(string $string, int &$offset = 0): string
41
    {
42 11
        $len = ord($string[$offset]);
43 11
        ++$offset;
44
45 11
        if (0 === $len) {
46 1
            return '.';
47
        }
48
49 11
        $domainName = '';
50 11
        while (0 !== $len) {
51 11
            $domainName .= substr($string, $offset, $len).'.';
52 11
            $offset += $len;
53 11
            $len = ord($string[$offset]);
54 11
            ++$offset;
55
        }
56
57 11
        return $domainName;
58
    }
59
60
    /**
61
     * @param string $pkt
62
     * @param int    $offset
63
     * @param int    $count      The number of resource records to decode
64
     * @param bool   $isQuestion Is the resource record from the question section
65
     *
66
     * @return ResourceRecord[]
67
     */
68 9
    public static function decodeResourceRecords(string $pkt, int $count = 1, int &$offset = 0, bool $isQuestion = false): array
69
    {
70 9
        $resourceRecords = [];
71
72 9
        for ($i = 0; $i < $count; ++$i) {
73 8
            ($rr = new ResourceRecord())
74 8
                ->setQuestion($isQuestion)
75 8
                ->setName(self::decodeDomainName($pkt, $offset));
76
77 8
            if ($rr->isQuestion()) {
78 7
                $values = unpack('ntype/nclass', substr($pkt, $offset, 4));
79 7
                $rr->setType($values['type'])->setClass($values['class']);
80 7
                $offset += 4;
81
            } else {
82 4
                $values = unpack('ntype/nclass/Nttl/ndlength', substr($pkt, $offset, 10));
83 4
                $rr->setType($values['type'])->setClass($values['class'])->setTtl($values['ttl']);
84 4
                $offset += 10;
85
86
                //Ignore unsupported types.
87
                try {
88 4
                    $rr->setRdata(RdataDecoder::decodeRdata($rr->getType(), substr($pkt, $offset, $values['dlength'])));
89
                } catch (UnsupportedTypeException $e) {
90
                    $offset += $values['dlength'];
91
                    continue;
92
                }
93 4
                $offset += $values['dlength'];
94
            }
95
96 8
            $resourceRecords[] = $rr;
97
        }
98
99 9
        return $resourceRecords;
100
    }
101
102
    /**
103
     * @param string $pkt
104
     * @param int    $offset
105
     *
106
     * @return Header
107
     */
108 8
    public static function decodeHeader(string $pkt, int &$offset = 0): Header
109
    {
110 8
        $data = unpack('nid/nflags/nqdcount/nancount/nnscount/narcount', $pkt);
111 8
        $flags = self::decodeFlags($data['flags']);
112 8
        $offset += 12;
113
114 8
        return (new Header())
115 8
            ->setId($data['id'])
116 8
            ->setResponse($flags['qr'])
117 8
            ->setOpcode($flags['opcode'])
118 8
            ->setAuthoritative($flags['aa'])
119 8
            ->setTruncated($flags['tc'])
120 8
            ->setRecursionDesired($flags['rd'])
121 8
            ->setRecursionAvailable($flags['ra'])
122 8
            ->setZ($flags['z'])
123 8
            ->setRcode($flags['rcode'])
124 8
            ->setQuestionCount($data['qdcount'])
125 8
            ->setAnswerCount($data['ancount'])
126 8
            ->setNameServerCount($data['nscount'])
127 8
            ->setAdditionalRecordsCount($data['arcount']);
128
    }
129
130
    /**
131
     * @param string $flags
132
     *
133
     * @return array
134
     */
135 8
    private static function decodeFlags($flags): array
136
    {
137
        return [
138 8
            'qr' => $flags >> 15 & 0x1,
139 8
            'opcode' => $flags >> 11 & 0xf,
140 8
            'aa' => $flags >> 10 & 0x1,
141 8
            'tc' => $flags >> 9 & 0x1,
142 8
            'rd' => $flags >> 8 & 0x1,
143 8
            'ra' => $flags >> 7 & 0x1,
144 8
            'z' => $flags >> 4 & 0x7,
145 8
            'rcode' => $flags & 0xf,
146
        ];
147
    }
148
}
149