Completed
Branch Version3 (6fba0e)
by Sam
01:26
created

LOC::fromWire()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 10
cts 10
cp 1
rs 9.7998
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 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\Parser\Tokens;
17
18
/**
19
 * Class LocRdata.
20
 *
21
 * Mechanism to allow the DNS to carry location
22
 * information about hosts, networks, and subnets.
23
 *
24
 * @see http://tools.ietf.org/html/rfc1876
25
 */
26
class LOC implements RdataInterface
27
{
28
    use RdataTrait;
29
30
    const TYPE = 'LOC';
31
    const TYPE_CODE = 29;
32
    const LATITUDE = 'LATITUDE';
33
    const LONGITUDE = 'LONGITUDE';
34
    const FORMAT_DECIMAL = 'DECIMAL';
35
    const FORMAT_DMS = 'DMS';
36
37
    /**
38
     * @var float|null
39
     */
40
    private $latitude;
41
42
    /**
43
     * @var float|null
44
     */
45
    private $longitude;
46
47
    /**
48
     * @var float
49
     */
50
    private $altitude = 0.0;
51
52
    /**
53
     * @var float
54
     */
55
    private $size = 1.0;
56
57
    /**
58
     * @var float
59
     */
60
    private $horizontalPrecision = 10000.0;
61
62
    /**
63
     * @var float
64
     */
65
    private $verticalPrecision = 10.0;
66
67
    /**
68
     * @param float $latitude
69
     */
70 13
    public function setLatitude(float $latitude): void
71
    {
72 13
        $this->latitude = (float) $latitude;
73 13
    }
74
75
    /**
76
     * @param string $format
77
     *
78
     * @return float|string|null
79
     */
80 6
    public function getLatitude(string $format = self::FORMAT_DECIMAL)
81
    {
82 6
        if (self::FORMAT_DMS === $format) {
83 6
            return $this->toDms($this->latitude ?? 0, self::LATITUDE);
84
        }
85
86 1
        return $this->latitude;
87
    }
88
89
    /**
90
     * @param float $longitude
91
     */
92 13
    public function setLongitude(float $longitude): void
93
    {
94 13
        $this->longitude = (float) $longitude;
95 13
    }
96
97
    /**
98
     * @param string $format
99
     *
100
     * @return float|string|null
101
     */
102 6
    public function getLongitude(string $format = self::FORMAT_DECIMAL)
103
    {
104 6
        if (self::FORMAT_DMS === $format) {
105 6
            return $this->toDms($this->longitude ?? 0, self::LONGITUDE);
106
        }
107
108 1
        return $this->longitude;
109
    }
110
111
    /**
112
     * @param float $altitude
113
     *
114
     * @throws \OutOfRangeException
115
     */
116 15
    public function setAltitude(float $altitude): void
117
    {
118 15
        if ($altitude < -100000.00 || $altitude > 42849672.95) {
119 2
            throw new \OutOfRangeException('The altitude must be on [-100000.00, 42849672.95].');
120
        }
121
122 13
        $this->altitude = (float) $altitude;
123 13
    }
124
125
    /**
126
     * @return float
127
     */
128 4
    public function getAltitude(): float
129
    {
130 4
        return $this->altitude;
131
    }
132
133
    /**
134
     * @param float $horizontalPrecision
135
     *
136
     * @throws \OutOfRangeException
137
     */
138 15
    public function setHorizontalPrecision(float $horizontalPrecision): void
139
    {
140 15
        if ($horizontalPrecision < 0 || $horizontalPrecision > 9e9) {
141 2
            throw new \OutOfRangeException('The horizontal precision must be on [0, 9e9].');
142
        }
143
144 13
        $this->horizontalPrecision = (float) $horizontalPrecision;
145 13
    }
146
147
    /**
148
     * @return float
149
     */
150 4
    public function getHorizontalPrecision(): float
151
    {
152 4
        return $this->horizontalPrecision;
153
    }
154
155
    /**
156
     * @param float $size
157
     *
158
     * @throws \OutOfRangeException
159
     */
160 15
    public function setSize(float $size): void
161
    {
162 15
        if ($size < 0 || $size > 9e9) {
163 2
            throw new \OutOfRangeException('The size must be on [0, 9e9].');
164
        }
165
166 13
        $this->size = (float) $size;
167 13
    }
168
169
    /**
170
     * @return float
171
     */
172 4
    public function getSize(): float
173
    {
174 4
        return $this->size;
175
    }
176
177
    /**
178
     * @param float $verticalPrecision
179
     *
180
     * @throws \OutOfRangeException
181
     */
182 15
    public function setVerticalPrecision(float $verticalPrecision): void
183
    {
184 15
        if ($verticalPrecision < 0 || $verticalPrecision > 9e9) {
185 2
            throw new \OutOfRangeException('The vertical precision must be on [0, 9e9].');
186
        }
187
188 13
        $this->verticalPrecision = $verticalPrecision;
189 13
    }
190
191
    /**
192
     * @return float
193
     */
194 4
    public function getVerticalPrecision(): float
195
    {
196 4
        return $this->verticalPrecision;
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202 5
    public function toText(): string
203
    {
204 5
        return sprintf(
205 5
                '%s %s %.2fm %.2fm %.2fm %.2fm',
206 5
                $this->getLatitude(self::FORMAT_DMS),
207 5
                $this->getLongitude(self::FORMAT_DMS),
208 5
                $this->altitude,
209 5
                $this->size,
210 5
                $this->horizontalPrecision,
211 5
                $this->verticalPrecision
212
        );
213
    }
214
215
    /**
216
     * Determine the degree minute seconds value from decimal.
217
     *
218
     * @param float  $decimal
219
     * @param string $axis
220
     *
221
     * @return string
222
     */
223 7
    private function toDms(float $decimal, string $axis = self::LATITUDE): string
224
    {
225 7
        $d = (int) floor(abs($decimal));
226 7
        $m = (int) floor((abs($decimal) - $d) * 60);
227 7
        $s = ((abs($decimal) - $d) * 60 - $m) * 60;
228 7
        if (self::LATITUDE === $axis) {
229 6
            $h = ($decimal < 0) ? 'S' : 'N';
230
        } else {
231 6
            $h = ($decimal < 0) ? 'W' : 'E';
232
        }
233
234 7
        return sprintf('%d %d %.3f %s', $d, $m, $s, $h);
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240 1
    public function toWire(): string
241
    {
242 1
        return pack('CCCClll',
243 1
            0,
244 1
            self::numberToExponentValue($this->size),
245 1
            self::numberToExponentValue($this->horizontalPrecision),
246 1
            self::numberToExponentValue($this->verticalPrecision),
247 1
            (int) floor($this->latitude * 3600000),
248 1
            (int) floor($this->longitude * 3600000),
249 1
            (int) floor($this->altitude)
250
        );
251
    }
252
253 1
    private static function numberToExponentValue(float $num): int
254
    {
255 1
        $exponent = (int) floor(log($num, 10));
256 1
        $base = (int) ceil($num / (10 ** $exponent));
257
258 1
        return $base * 16 + $exponent;
259
    }
260
261 1
    private static function exponentValueToNumber(int $val): float
262
    {
263 1
        $base = ($val & 0b11110000) / 16;
264 1
        $exponent = ($val & 0b00001111);
265
266 1
        return $base * 10 ** $exponent;
267
    }
268
269
    /**
270
     * Transform a DMS string to a decimal representation. Used for LOC records.
271
     *
272
     * @param int    $deg        Degrees
273
     * @param int    $min        Minutes
274
     * @param float  $sec        Seconds
275
     * @param string $hemisphere Either 'N', 'S', 'E', or 'W'
276
     *
277
     * @return float
278
     */
279 1
    public static function dmsToDecimal(int $deg, int $min, float $sec, string $hemisphere): float
280
    {
281 1
        $multiplier = ('S' === $hemisphere || 'W' === $hemisphere) ? -1 : 1;
282
283 1
        return $multiplier * ($deg + ($min / 60) + ($sec / 3600));
284
    }
285
286
    /**
287
     * {@inheritdoc}
288
     *
289
     * @return LOC
290
     */
291 1
    public static function fromText(string $text): RdataInterface
292
    {
293 1
        $rdata = explode(Tokens::SPACE, $text);
294 1
        $lat = self::dmsToDecimal((int) array_shift($rdata), (int) array_shift($rdata), (float) array_shift($rdata), (string) array_shift($rdata));
295 1
        $lon = self::dmsToDecimal((int) array_shift($rdata), (int) array_shift($rdata), (float) array_shift($rdata), (string) array_shift($rdata));
296
297 1
        return Factory::LOC(
298 1
            $lat,
299 1
            $lon,
300 1
            (float) array_shift($rdata),
301 1
            (float) array_shift($rdata),
302 1
            (float) array_shift($rdata),
303 1
            (float) array_shift($rdata)
304
        );
305
    }
306
307
    /**
308
     * {@inheritdoc}
309
     *
310
     * @return LOC
311
     */
312 1
    public static function fromWire(string $rdata): RdataInterface
313
    {
314 1
        $values = unpack('C<version>/C<size>/C<hp>/C<vp>/l<lat>/l<lon>/l<alt>', $rdata);
315 1
        $loc = new LOC();
316
317 1
        $loc->setSize(self::exponentValueToNumber($values['<size>']));
318 1
        $loc->setHorizontalPrecision(self::exponentValueToNumber($values['<hp>']));
319 1
        $loc->setVerticalPrecision(self::exponentValueToNumber($values['<vp>']));
320 1
        $loc->setLatitude($values['<lat>'] / 3600000);
321 1
        $loc->setLongitude($values['<lon>'] / 3600000);
322 1
        $loc->setAltitude($values['<alt>']);
323
324 1
        return $loc;
325
    }
326
}
327