Passed
Push — master ( 87c83f...ac88b3 )
by Sam
03:00
created

DHCID   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 233
Duplicated Lines 0 %

Test Coverage

Coverage 80%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 53
dl 0
loc 233
ccs 52
cts 65
cp 0.8
rs 10
c 1
b 0
f 0
wmc 24

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getDigest() 0 3 1
A setFqdn() 0 6 2
A getIdentifierType() 0 3 1
A getFqdn() 0 3 1
A getHtype() 0 3 1
A setIdentifier() 0 4 1
A setIdentifierType() 0 6 2
A fromText() 0 12 2
A setHtype() 0 6 2
A getDigestType() 0 3 1
A setDigest() 0 3 1
A toText() 0 3 1
A toWire() 0 7 2
A calculateDigest() 0 13 4
A fromWire() 0 10 1
A getIdentifier() 0 3 1
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\Rdata;
15
16
use Badcow\DNS\Validator;
17
18
/**
19
 * {@link https://tools.ietf.org/html/rfc4701}.
20
 */
21
class DHCID implements RdataInterface
22
{
23
    use RdataTrait;
24
25
    const TYPE = 'DHCID';
26
    const TYPE_CODE = 49;
27
28
    /**
29
     * 16-bit DHCID RR Identifier Type Code specifies what data from the DHCP
30
     * client's request was used as input into the hash function.
31
     *
32
     * @var int
33
     */
34
    private $identifierType;
35
36
    /**
37
     * The 1-octet 'htype' followed by 'hlen' octets of 'chaddr' from a DHCPv4
38
     * client's DHCPREQUEST.
39
     *
40
     * The data octets (i.e., the Type and Client-Identifier fields) from a
41
     * DHCPv4 client's Client Identifier option.
42
     *
43
     * The client's DUID (i.e., the data octets of a DHCPv6 client's Client
44
     * Identifier option or the DUID field from a DHCPv4 client's Client
45
     * Identifier option).
46
     *
47
     * @var string
48
     */
49
    private $identifier;
50
51
    /**
52
     * Hardware Type {@link https://tools.ietf.org/html/rfc2131}.
53
     *
54
     * @var int Hardware type used if identifier is DHCPv4 DHCPREQUEST carrying client hardware address (chaddr or MAC)
55
     */
56
    private $htype = 1;
57
58
    /**
59
     * The Fully Qualified Domain Name of the DHCP client.
60
     *
61
     * @var string
62
     */
63
    private $fqdn;
64
65
    /**
66
     * The digest type. Only one type is defined by IANA, SHA256 with value 1.
67
     *
68
     * @var int
69
     */
70
    private $digestType = 1;
71
72
    /**
73
     * @var string
74
     */
75
    private $digest;
76
77
    /**
78
     * @param int $identifierType
79
     */
80 12
    public function setIdentifierType(int $identifierType): void
81
    {
82 12
        if (!Validator::isUnsignedInteger($identifierType, 16)) {
83
            throw new \InvalidArgumentException('Identifier type must be a 16-bit integer.');
84
        }
85 12
        $this->identifierType = $identifierType;
86 12
    }
87
88
    /**
89
     * @return int
90
     */
91 6
    public function getIdentifierType(): int
92
    {
93 6
        return $this->identifierType;
94
    }
95
96
    /**
97
     * @return string
98
     */
99 3
    public function getIdentifier(): string
100
    {
101 3
        return $this->identifier;
102
    }
103
104
    /**
105
     * @return int
106
     */
107
    public function getHtype(): int
108
    {
109
        return $this->htype;
110
    }
111
112
    /**
113
     * @param int $htype
114
     */
115
    public function setHtype(int $htype): void
116
    {
117
        if (!Validator::isUnsignedInteger($htype, 8)) {
118
            throw new \InvalidArgumentException('HType must be an 8-bit integer.');
119
        }
120
        $this->htype = $htype;
121
    }
122
123
    /**
124
     * @param int    $identifierType
125
     * @param string $identifier
126
     *
127
     * @throws \InvalidArgumentException
128
     */
129 12
    public function setIdentifier(int $identifierType, string $identifier): void
130
    {
131 12
        $this->setIdentifierType($identifierType);
132 12
        $this->identifier = $identifier;
133 12
    }
134
135
    /**
136
     * @return string
137
     */
138
    public function getFqdn(): string
139
    {
140
        return $this->fqdn;
141
    }
142
143
    /**
144
     * @param string $fqdn
145
     */
146 12
    public function setFqdn(string $fqdn): void
147
    {
148 12
        if (!Validator::fullyQualifiedDomainName($fqdn)) {
149
            throw new \InvalidArgumentException(sprintf('"%s" is not a fully qualified domain name.', $fqdn));
150
        }
151 12
        $this->fqdn = $fqdn;
152 12
    }
153
154
    /**
155
     * @return int
156
     */
157 6
    public function getDigestType(): int
158
    {
159 6
        return $this->digestType;
160
    }
161
162
    /**
163
     * @return string Digest as hexadecimal string
164
     */
165 9
    public function getDigest(): string
166
    {
167 9
        return $this->digest;
168
    }
169
170
    /**
171
     * @param string $digest Digest as hexadecimal string
172
     */
173 9
    public function setDigest(string $digest): void
174
    {
175 9
        $this->digest = $digest;
176 9
    }
177
178
    /**
179
     * Calculate the digest from the identifier and fully qualified domain name already set on the object.
180
     *
181
     * @throws \BadMethodCallException
182
     */
183 12
    public function calculateDigest(): void
184
    {
185 12
        if (null === $this->identifier || null === $this->fqdn) {
186
            throw new \BadMethodCallException('Identifier and Fully Qualified Domain Name (FQDN) must both be set on DHCID object before calling calculateDigest().');
187
        }
188
189 12
        $fqdn = self::encodeName($this->fqdn);
190 12
        $identifier = pack('H*', str_replace(':', '', strtolower($this->identifier)));
191 12
        if (0 === $this->identifierType) {
192 4
            $identifier = chr($this->htype).$identifier;
193
        }
194
195 12
        $this->digest = hash('sha256', $identifier.$fqdn);
196 12
    }
197
198
    /**
199
     * {@inheritdoc}
200
     *
201
     * @throws \BadMethodCallException
202
     */
203 12
    public function toText(): string
204
    {
205 12
        return base64_encode($this->toWire());
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211 12
    public function toWire(): string
212
    {
213 12
        if (null === $this->digest) {
214 9
            $this->calculateDigest();
215
        }
216
217 12
        return pack('nCH*', $this->identifierType, $this->digestType, $this->digest);
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     *
223
     * @return DHCID
224
     *
225
     * @throws \Exception
226
     */
227 3
    public static function fromText(string $text): RdataInterface
228
    {
229 3
        if (false === $decoded = base64_decode($text, true)) {
230
            throw new \Exception(sprintf('Unable to base64 decode text "%s".', $text));
231
        }
232
233 3
        $rdata = unpack('nIdentifierType/CDigestType/H*Digest', $decoded);
234 3
        $dhcid = new self();
235 3
        $dhcid->setIdentifierType((int) $rdata['IdentifierType']);
236 3
        $dhcid->setDigest((string) $rdata['Digest']);
237
238 3
        return $dhcid;
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244 3
    public static function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): RdataInterface
245
    {
246 3
        $rdLength = $rdLength ?? strlen($rdata);
247 3
        $rdata = unpack('nIdentifierType/CDigestType/H*Digest', substr($rdata, $offset, $rdLength));
248 3
        $dhcid = new self();
249 3
        $dhcid->setIdentifierType((int) $rdata['IdentifierType']);
250 3
        $dhcid->setDigest((string) $rdata['Digest']);
251 3
        $offset += $rdLength;
252
253 3
        return $dhcid;
254
    }
255
}
256