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

Encoder::encodeRdata()   C

Complexity

Conditions 13
Paths 14

Size

Total Lines 31
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 26
nc 14
nop 2
dl 0
loc 31
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 Encoder
14
{
15
    /**
16
     * @param Message $message
17
     *
18
     * @return string
19
     *
20
     * @throws UnsupportedTypeException
21
     */
22
    public static function encodeMessage(Message $message): string
23
    {
24
        return
25
            self::encodeHeader($message->getHeader()).
26
            self::encodeResourceRecords($message->getQuestions()).
27
            self::encodeResourceRecords($message->getAnswers()).
28
            self::encodeResourceRecords($message->getAuthoritatives()).
29
            self::encodeResourceRecords($message->getAdditionals());
30
    }
31
32
    /**
33
     * Encode a domain name as a sequence of labels.
34
     *
35
     * @param $domain
36
     *
37
     * @return string
38
     */
39
    public static function encodeDomainName($domain): string
40
    {
41
        if ('.' === $domain) {
42
            return chr(0);
43
        }
44
45
        $domain = rtrim($domain, '.').'.';
46
        $res = '';
47
48
        foreach (explode('.', $domain) as $label) {
49
            $res .= chr(strlen($label)).$label;
50
        }
51
52
        return $res;
53
    }
54
55
    /**
56
     * @param int          $type
57
     * @param string|array $rdata
58
     *
59
     * @return string
60
     *
61
     * @throws UnsupportedTypeException|\InvalidArgumentException
62
     */
63
    public static function encodeRdata(int $type, $rdata): string
64
    {
65
        switch ($type) {
66
            case RecordTypeEnum::TYPE_A:
67
            case RecordTypeEnum::TYPE_AAAA:
68
                if (!filter_var($rdata, FILTER_VALIDATE_IP)) {
69
                    throw new \InvalidArgumentException(sprintf('The IP address "%s" is invalid.', $rdata));
0 ignored issues
show
Bug introduced by
It seems like $rdata can also be of type array; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

69
                    throw new \InvalidArgumentException(sprintf('The IP address "%s" is invalid.', /** @scrutinizer ignore-type */ $rdata));
Loading history...
70
                }
71
72
                return inet_pton($rdata);
0 ignored issues
show
Bug introduced by
It seems like $rdata can also be of type array; however, parameter $address of inet_pton() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

72
                return inet_pton(/** @scrutinizer ignore-type */ $rdata);
Loading history...
73
            case RecordTypeEnum::TYPE_NS:
74
            case RecordTypeEnum::TYPE_CNAME:
75
            case RecordTypeEnum::TYPE_PTR:
76
                return self::encodeDomainName($rdata);
77
            case RecordTypeEnum::TYPE_SOA:
78
                return self::encodeSOA($rdata);
0 ignored issues
show
Bug introduced by
It seems like $rdata can also be of type string; however, parameter $soa of yswery\DNS\Encoder::encodeSOA() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
                return self::encodeSOA(/** @scrutinizer ignore-type */ $rdata);
Loading history...
79
            case RecordTypeEnum::TYPE_MX:
80
                return pack('n', (int) $rdata['preference']).self::encodeDomainName($rdata['exchange']);
81
            case RecordTypeEnum::TYPE_TXT:
82
                $rdata = substr($rdata, 0, 255);
0 ignored issues
show
Bug introduced by
It seems like $rdata can also be of type array; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

82
                $rdata = substr(/** @scrutinizer ignore-type */ $rdata, 0, 255);
Loading history...
83
84
                return chr(strlen($rdata)).$rdata;
85
            case RecordTypeEnum::TYPE_SRV:
86
                return pack('nnn', (int) $rdata['priority'], (int) $rdata['weight'], (int) $rdata['port']).
87
                    self::encodeDomainName($rdata['target']);
88
            case RecordTypeEnum::TYPE_AXFR:
89
            case RecordTypeEnum::TYPE_ANY:
90
                return '';
91
            default:
92
                throw new UnsupportedTypeException(
93
                    sprintf('Record type "%s" is not a supported type.', RecordTypeEnum::getName($type))
94
                );
95
        }
96
    }
97
98
    /**
99
     * @param ResourceRecord[] $resourceRecords
100
     *
101
     * @return string
102
     *
103
     * @throws UnsupportedTypeException
104
     */
105
    public static function encodeResourceRecords(array $resourceRecords): string
106
    {
107
        $res = '';
108
109
        foreach ($resourceRecords as $rr) {
110
            $res .= self::encodeDomainName($rr->getName());
111
            if ($rr->isQuestion()) {
112
                $res .= pack('nn', $rr->getType(), $rr->getClass());
113
                continue;
114
            }
115
116
            $data = self::encodeRdata($rr->getType(), $rr->getRdata());
117
            $res .= pack('nnNn', $rr->getType(), $rr->getClass(), $rr->getTtl(), strlen($data));
118
            $res .= $data;
119
        }
120
121
        return $res;
122
    }
123
124
    /**
125
     * @param Header $header
126
     *
127
     * @return string
128
     */
129
    public static function encodeHeader(Header $header): string
130
    {
131
        return pack(
132
            'nnnnnn',
133
            $header->getId(),
134
            self::encodeFlags($header),
135
            $header->getQuestionCount(),
136
            $header->getAnswerCount(),
137
            $header->getNameServerCount(),
138
            $header->getAdditionalRecordsCount()
139
        );
140
    }
141
142
    /**
143
     * Encode the bit field of the Header between "ID" and "QDCOUNT".
144
     *
145
     * @param Header $header
146
     *
147
     * @return int
148
     */
149
    private static function encodeFlags(Header $header): int
150
    {
151
        return 0x0 |
152
            ($header->isResponse() & 0x1) << 15 |
153
            ($header->getOpcode() & 0xf) << 11 |
154
            ($header->isAuthoritative() & 0x1) << 10 |
155
            ($header->isTruncated() & 0x1) << 9 |
156
            ($header->isRecursionDesired() & 0x1) << 8 |
157
            ($header->isRecursionAvailable() & 0x1) << 7 |
158
            ($header->getZ() & 0x7) << 4 |
159
            ($header->getRcode() & 0xf);
160
    }
161
162
    /**
163
     * @param array $soa
164
     *
165
     * @return string
166
     */
167
    private static function encodeSOA(array $soa): string
168
    {
169
        return
170
            self::encodeDomainName($soa['mname']).
171
            self::encodeDomainName($soa['rname']).
172
            pack(
173
                'NNNNN',
174
                $soa['serial'],
175
                $soa['refresh'],
176
                $soa['retry'],
177
                $soa['expire'],
178
                $soa['minimum']
179
            );
180
    }
181
}
182