Completed
Push — master ( 282f78...6dce89 )
by Sam
05:20
created

Decoder::decodeRdata()   C

Complexity

Conditions 13
Paths 13

Size

Total Lines 44
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 34
nc 13
nop 2
dl 0
loc 44
rs 6.6166
c 0
b 0
f 0

How to fix   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
 * 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
     * @throws UnsupportedTypeException
21
     */
22
    public static function decodeMessage(string $message): Message
23
    {
24
        $offset = 0;
25
        $header = self::decodeHeader($message, $offset);
26
27
        return (new Message($header))
28
            ->setQuestions(self::decodeResourceRecords($message, $header->getQuestionCount(), $offset, true))
29
            ->setAnswers(self::decodeResourceRecords($message, $header->getAnswerCount(), $offset))
30
            ->setAuthoritatives(self::decodeResourceRecords($message, $header->getNameServerCount(), $offset))
31
            ->setAdditionals(self::decodeResourceRecords($message, $header->getAdditionalRecordsCount(), $offset));
32
    }
33
34
    /**
35
     * @param string $string
36
     * @param int    $offset
37
     *
38
     * @return string
39
     */
40
    public static function decodeDomainName(string $string, int &$offset = 0): string
41
    {
42
        $len = ord($string[$offset]);
43
        ++$offset;
44
45
        if (0 === $len) {
46
            return '.';
47
        }
48
49
        $domainName = '';
50
        while (0 !== $len) {
51
            $domainName .= substr($string, $offset, $len).'.';
52
            $offset += $len;
53
            $len = ord($string[$offset]);
54
            ++$offset;
55
        }
56
57
        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
     * @throws UnsupportedTypeException
69
     */
70
    public static function decodeResourceRecords(string $pkt, int $count = 1, int &$offset = 0, bool $isQuestion = false): array
71
    {
72
        $resourceRecords = [];
73
74
        for ($i = 0; $i < $count; ++$i) {
75
            ($rr = new ResourceRecord())
76
                ->setQuestion($isQuestion)
77
                ->setName(self::decodeDomainName($pkt, $offset));
78
79
            if ($rr->isQuestion()) {
80
                $values = unpack('ntype/nclass', substr($pkt, $offset, 4));
81
                $rr->setType($values['type'])->setClass($values['class']);
82
                $offset += 4;
83
            } else {
84
                $values = unpack('ntype/nclass/Nttl/ndlength', substr($pkt, $offset, 10));
85
                $rr->setType($values['type'])->setClass($values['class'])->setTtl($values['ttl']);
86
                $offset += 10;
87
                $rr->setRdata(self::decodeRdata($rr->getType(), substr($pkt, $offset, $values['dlength'])));
88
                $offset += $values['dlength'];
89
            }
90
91
            $resourceRecords[] = $rr;
92
        }
93
94
        return $resourceRecords;
95
    }
96
97
    /**
98
     * @param int    $type
99
     * @param string $rdata
100
     *
101
     * @return array|string|null
102
     *
103
     * @throws UnsupportedTypeException
104
     */
105
    public static function decodeRdata(int $type, string $rdata)
106
    {
107
        switch ($type) {
108
            case RecordTypeEnum::TYPE_A:
109
            case RecordTypeEnum::TYPE_AAAA:
110
                return inet_ntop($rdata);
111
            case RecordTypeEnum::TYPE_NS:
112
            case RecordTypeEnum::TYPE_CNAME:
113
            case RecordTypeEnum::TYPE_PTR:
114
                return self::decodeDomainName($rdata);
115
            case RecordTypeEnum::TYPE_SOA:
116
                $offset = 0;
117
118
                return array_merge(
119
                    [
120
                        'mname' => self::decodeDomainName($rdata, $offset),
121
                        'rname' => self::decodeDomainName($rdata, $offset),
122
                    ],
123
                    unpack('Nserial/Nrefresh/Nretry/Nexpire/Nminimum', substr($rdata, $offset))
124
                );
125
            case RecordTypeEnum::TYPE_MX:
126
                return [
127
                    'preference' => unpack('npreference', $rdata)['preference'],
128
                    'exchange' => self::decodeDomainName(substr($rdata, 2)),
129
                ];
130
            case RecordTypeEnum::TYPE_TXT:
131
                $len = ord($rdata[0]);
132
                if ((strlen($rdata) + 1) < $len) {
133
                    return null;
134
                }
135
136
                return substr($rdata, 1, $len);
137
            case RecordTypeEnum::TYPE_SRV:
138
                $offset = 6;
139
                $values = unpack('npriority/nweight/nport', $rdata);
140
                $values['target'] = self::decodeDomainName($rdata, $offset);
141
142
                return $values;
143
            case RecordTypeEnum::TYPE_AXFR:
144
            case RecordTypeEnum::TYPE_ANY:
145
                return null;
146
            default:
147
                throw new UnsupportedTypeException(
148
                    sprintf('Record type "%s" is not a supported type.', RecordTypeEnum::getName($type))
149
                );
150
        }
151
    }
152
153
    /**
154
     * @param string $pkt
155
     * @param int    $offset
156
     *
157
     * @return Header
158
     */
159
    public static function decodeHeader(string $pkt, int &$offset = 0): Header
160
    {
161
        $data = unpack('nid/nflags/nqdcount/nancount/nnscount/narcount', $pkt);
162
        $flags = self::decodeFlags($data['flags']);
163
        $offset += 12;
164
165
        return (new Header())
166
            ->setId($data['id'])
167
            ->setResponse($flags['qr'])
168
            ->setOpcode($flags['opcode'])
169
            ->setAuthoritative($flags['aa'])
170
            ->setTruncated($flags['tc'])
171
            ->setRecursionDesired($flags['rd'])
172
            ->setRecursionAvailable($flags['ra'])
173
            ->setZ($flags['z'])
174
            ->setRcode($flags['rcode'])
175
            ->setQuestionCount($data['qdcount'])
176
            ->setAnswerCount($data['ancount'])
177
            ->setNameServerCount($data['nscount'])
178
            ->setAdditionalRecordsCount($data['arcount']);
179
    }
180
181
    /**
182
     * @param string $flags
183
     *
184
     * @return array
185
     */
186
    private static function decodeFlags($flags): array
187
    {
188
        return [
189
            'qr' => $flags >> 15 & 0x1,
190
            'opcode' => $flags >> 11 & 0xf,
191
            'aa' => $flags >> 10 & 0x1,
192
            'tc' => $flags >> 9 & 0x1,
193
            'rd' => $flags >> 8 & 0x1,
194
            'ra' => $flags >> 7 & 0x1,
195
            'z' => $flags >> 4 & 0x7,
196
            'rcode' => $flags & 0xf,
197
        ];
198
    }
199
}
200