Completed
Push — master ( 8c517a...d7fd0c )
by Sam
01:59
created

Decoder::decodeFlags()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 1
dl 0
loc 11
ccs 9
cts 9
cp 1
crap 1
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/*
3
 * This file is part of PHP DNS Server.
4
 *
5
 * (c) Yif Swery <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace yswery\DNS;
12
13
class Decoder
14
{
15
    /**
16
     * @param string $message
17
     *
18
     * @return Message
19
     */
20 7
    public static function decodeMessage(string $message): Message
21
    {
22 7
        $offset = 0;
23 7
        $header = self::decodeHeader($message, $offset);
24 7
        $messageObject = new Message($header);
25 7
        $messageObject->setQuestions(self::decodeResourceRecords($message, $header->getQuestionCount(), $offset, true));
26 7
        $messageObject->setAnswers(self::decodeResourceRecords($message, $header->getAnswerCount(), $offset));
27 7
        $messageObject->setAuthoritatives(self::decodeResourceRecords($message, $header->getNameServerCount(), $offset));
28 7
        $messageObject->setAdditionals(self::decodeResourceRecords($message, $header->getAdditionalRecordsCount(), $offset));
29
30 7
        return $messageObject;
31
    }
32
33
    /**
34
     * @param string $string
35
     * @param int    $offset
36
     *
37
     * @return string
38
     */
39 11
    public static function decodeDomainName(string $string, int &$offset = 0): string
40
    {
41 11
        $len = ord($string[$offset]);
42 11
        ++$offset;
43
44 11
        if (0 === $len) {
45 1
            return '.';
46
        }
47
48 11
        $domainName = '';
49 11
        while (0 !== $len) {
50 11
            $domainName .= substr($string, $offset, $len).'.';
51 11
            $offset += $len;
52 11
            $len = ord($string[$offset]);
53 11
            ++$offset;
54
        }
55
56 11
        return $domainName;
57
    }
58
59
    /**
60
     * @param string $pkt
61
     * @param int    $offset
62
     * @param int    $count      The number of resource records to decode
63
     * @param bool   $isQuestion Is the resource record from the question section
64
     *
65
     * @return ResourceRecord[]
66
     */
67 9
    public static function decodeResourceRecords(string $pkt, int $count = 1, int &$offset = 0, bool $isQuestion = false): array
68
    {
69 9
        $resourceRecords = [];
70
71 9
        for ($i = 0; $i < $count; ++$i) {
72 8
            ($rr = new ResourceRecord())
73 8
                ->setQuestion($isQuestion)
74 8
                ->setName(self::decodeDomainName($pkt, $offset));
75
76 8
            if ($rr->isQuestion()) {
77 7
                $values = unpack('ntype/nclass', substr($pkt, $offset, 4));
78 7
                $rr->setType($values['type'])->setClass($values['class']);
79 7
                $offset += 4;
80
            } else {
81 4
                $values = unpack('ntype/nclass/Nttl/ndlength', substr($pkt, $offset, 10));
82 4
                $rr->setType($values['type'])->setClass($values['class'])->setTtl($values['ttl']);
83 4
                $offset += 10;
84
85
                //Ignore unsupported types.
86
                try {
87 4
                    $rr->setRdata(self::decodeRdata($rr->getType(), substr($pkt, $offset, $values['dlength'])));
88
                } catch (UnsupportedTypeException $e) {
89
                    $offset += $values['dlength'];
90
                    continue;
91
                }
92 4
                $offset += $values['dlength'];
93
            }
94
95 8
            $resourceRecords[] = $rr;
96
        }
97
98 9
        return $resourceRecords;
99
    }
100
101
    /**
102
     * @param int    $type
103
     * @param string $rdata
104
     *
105
     * @return array|string|null
106
     *
107
     * @throws UnsupportedTypeException
108
     */
109 6
    public static function decodeRdata(int $type, string $rdata)
110
    {
111
        switch ($type) {
112 6
            case RecordTypeEnum::TYPE_A:
113 6
            case RecordTypeEnum::TYPE_AAAA:
114 5
                return inet_ntop($rdata);
115 6
            case RecordTypeEnum::TYPE_NS:
116 5
            case RecordTypeEnum::TYPE_CNAME:
117 5
            case RecordTypeEnum::TYPE_PTR:
118 2
                return self::decodeDomainName($rdata);
119 5
            case RecordTypeEnum::TYPE_SOA:
120 1
                $offset = 0;
121
122 1
                return array_merge(
123
                    [
124 1
                        'mname' => self::decodeDomainName($rdata, $offset),
125 1
                        'rname' => self::decodeDomainName($rdata, $offset),
126
                    ],
127 1
                    unpack('Nserial/Nrefresh/Nretry/Nexpire/Nminimum', substr($rdata, $offset))
128
                );
129 5
            case RecordTypeEnum::TYPE_MX:
130
                return [
131 3
                    'preference' => unpack('npreference', $rdata)['preference'],
132 3
                    'exchange' => self::decodeDomainName(substr($rdata, 2)),
133
                ];
134 3
            case RecordTypeEnum::TYPE_TXT:
135 1
                $len = ord($rdata[0]);
136 1
                if ((strlen($rdata) + 1) < $len) {
137
                    return null;
138
                }
139
140 1
                return substr($rdata, 1, $len);
141 2
            case RecordTypeEnum::TYPE_SRV:
142 2
                $offset = 6;
143 2
                $values = unpack('npriority/nweight/nport', $rdata);
144 2
                $values['target'] = self::decodeDomainName($rdata, $offset);
145
146 2
                return $values;
147
            case RecordTypeEnum::TYPE_AXFR:
148
            case RecordTypeEnum::TYPE_ANY:
149
                return null;
150
            default:
151
                throw new UnsupportedTypeException(
152
                    sprintf('Record type "%s" is not a supported type.', RecordTypeEnum::getName($type))
153
                );
154
        }
155
    }
156
157
    /**
158
     * @param string $pkt
159
     * @param int    $offset
160
     *
161
     * @return Header
162
     */
163 8
    public static function decodeHeader(string $pkt, int &$offset = 0): Header
164
    {
165 8
        $data = unpack('nid/nflags/nqdcount/nancount/nnscount/narcount', $pkt);
166 8
        $flags = self::decodeFlags($data['flags']);
167 8
        $offset += 12;
168
169 8
        return (new Header())
170 8
            ->setId($data['id'])
171 8
            ->setResponse($flags['qr'])
172 8
            ->setOpcode($flags['opcode'])
173 8
            ->setAuthoritative($flags['aa'])
174 8
            ->setTruncated($flags['tc'])
175 8
            ->setRecursionDesired($flags['rd'])
176 8
            ->setRecursionAvailable($flags['ra'])
177 8
            ->setZ($flags['z'])
178 8
            ->setRcode($flags['rcode'])
179 8
            ->setQuestionCount($data['qdcount'])
180 8
            ->setAnswerCount($data['ancount'])
181 8
            ->setNameServerCount($data['nscount'])
182 8
            ->setAdditionalRecordsCount($data['arcount']);
183
    }
184
185
    /**
186
     * @param string $flags
187
     *
188
     * @return array
189
     */
190 8
    private static function decodeFlags($flags): array
191
    {
192
        return [
193 8
            'qr' => $flags >> 15 & 0x1,
194 8
            'opcode' => $flags >> 11 & 0xf,
195 8
            'aa' => $flags >> 10 & 0x1,
196 8
            'tc' => $flags >> 9 & 0x1,
197 8
            'rd' => $flags >> 8 & 0x1,
198 8
            'ra' => $flags >> 7 & 0x1,
199 8
            'z' => $flags >> 4 & 0x7,
200 8
            'rcode' => $flags & 0xf,
201
        ];
202
    }
203
}
204