LOC   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 2
dl 0
loc 249
ccs 101
cts 101
cp 1
rs 9.52
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A setLatitude() 0 4 1
A getLatitude() 0 8 2
A setLongitude() 0 4 1
A getLongitude() 0 8 2
A setAltitude() 0 8 3
A getAltitude() 0 4 1
A setHorizontalPrecision() 0 8 3
A getHorizontalPrecision() 0 4 1
A setSize() 0 8 3
A getSize() 0 4 1
A setVerticalPrecision() 0 8 3
A getVerticalPrecision() 0 4 1
A toText() 0 12 1
A toDms() 0 13 4
A toWire() 0 12 1
A numberToExponentValue() 0 7 1
A exponentValueToNumber() 0 7 1
A dmsToDecimal() 0 6 3
A fromText() 0 13 1
A fromWire() 0 14 2
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 1
    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 14
    public function setLatitude(float $latitude): void
68
    {
69 14
        $this->latitude = (float) $latitude;
70 14
    }
71
72
    /**
73
     * @return float|string|null
74
     */
75 6
    public function getLatitude(string $format = self::FORMAT_DECIMAL)
76
    {
77 6
        if (self::FORMAT_DMS === $format) {
78 6
            return $this->toDms($this->latitude ?? 0, self::LATITUDE);
79
        }
80
81 1
        return $this->latitude;
82
    }
83
84 14
    public function setLongitude(float $longitude): void
85
    {
86 14
        $this->longitude = (float) $longitude;
87 14
    }
88
89
    /**
90
     * @return float|string|null
91
     */
92 6
    public function getLongitude(string $format = self::FORMAT_DECIMAL)
93
    {
94 6
        if (self::FORMAT_DMS === $format) {
95 6
            return $this->toDms($this->longitude ?? 0, self::LONGITUDE);
96
        }
97
98 1
        return $this->longitude;
99
    }
100
101
    /**
102
     * @throws \OutOfRangeException
103
     */
104 16
    public function setAltitude(float $altitude): void
105
    {
106 16
        if ($altitude < -100000.00 || $altitude > 42849672.95) {
107 2
            throw new \OutOfRangeException('The altitude must be on [-100000.00, 42849672.95].');
108
        }
109
110 14
        $this->altitude = (float) $altitude;
111 14
    }
112
113 4
    public function getAltitude(): float
114
    {
115 4
        return $this->altitude;
116
    }
117
118
    /**
119
     * @throws \OutOfRangeException
120
     */
121 16
    public function setHorizontalPrecision(float $horizontalPrecision): void
122
    {
123 16
        if ($horizontalPrecision < 0 || $horizontalPrecision > 9e9) {
124 2
            throw new \OutOfRangeException('The horizontal precision must be on [0, 9e9].');
125
        }
126
127 14
        $this->horizontalPrecision = (float) $horizontalPrecision;
128 14
    }
129
130 4
    public function getHorizontalPrecision(): float
131
    {
132 4
        return $this->horizontalPrecision;
133
    }
134
135
    /**
136
     * @throws \OutOfRangeException
137
     */
138 16
    public function setSize(float $size): void
139
    {
140 16
        if ($size < 0 || $size > 9e9) {
141 2
            throw new \OutOfRangeException('The size must be on [0, 9e9].');
142
        }
143
144 14
        $this->size = (float) $size;
145 14
    }
146
147 4
    public function getSize(): float
148
    {
149 4
        return $this->size;
150
    }
151
152
    /**
153
     * @throws \OutOfRangeException
154
     */
155 16
    public function setVerticalPrecision(float $verticalPrecision): void
156
    {
157 16
        if ($verticalPrecision < 0 || $verticalPrecision > 9e9) {
158 2
            throw new \OutOfRangeException('The vertical precision must be on [0, 9e9].');
159
        }
160
161 14
        $this->verticalPrecision = $verticalPrecision;
162 14
    }
163
164 4
    public function getVerticalPrecision(): float
165
    {
166 4
        return $this->verticalPrecision;
167
    }
168
169 5
    public function toText(): string
170
    {
171 5
        return sprintf(
172 5
                '%s %s %.2fm %.2fm %.2fm %.2fm',
173 5
                $this->getLatitude(self::FORMAT_DMS),
174 5
                $this->getLongitude(self::FORMAT_DMS),
175 5
                $this->altitude,
176 5
                $this->size,
177 5
                $this->horizontalPrecision,
178 5
                $this->verticalPrecision
179
        );
180
    }
181
182
    /**
183
     * Determine the degree minute seconds value from decimal.
184
     */
185 7
    private function toDms(float $decimal, string $axis = self::LATITUDE): string
186
    {
187 7
        $d = (int) floor(abs($decimal));
188 7
        $m = (int) floor((abs($decimal) - $d) * 60);
189 7
        $s = ((abs($decimal) - $d) * 60 - $m) * 60;
190 7
        if (self::LATITUDE === $axis) {
191 6
            $h = ($decimal < 0) ? 'S' : 'N';
192
        } else {
193 6
            $h = ($decimal < 0) ? 'W' : 'E';
194
        }
195
196 7
        return sprintf('%d %d %.3f %s', $d, $m, $s, $h);
197
    }
198
199 1
    public function toWire(): string
200
    {
201 1
        return pack('CCCClll',
202 1
            0,
203 1
            self::numberToExponentValue($this->size),
204 1
            self::numberToExponentValue($this->horizontalPrecision),
205 1
            self::numberToExponentValue($this->verticalPrecision),
206 1
            (int) floor($this->latitude * 3600000),
207 1
            (int) floor($this->longitude * 3600000),
208 1
            (int) floor($this->altitude)
209
        );
210
    }
211
212 1
    private static function numberToExponentValue(float $num): int
213
    {
214 1
        $exponent = (int) floor(log($num, 10));
215 1
        $base = (int) ceil($num / (10 ** $exponent));
216
217 1
        return $base * 16 + $exponent;
218
    }
219
220 1
    private static function exponentValueToNumber(int $val): float
221
    {
222 1
        $base = ($val & 0b11110000) / 16;
223 1
        $exponent = ($val & 0b00001111);
224
225 1
        return $base * 10 ** $exponent;
226
    }
227
228
    /**
229
     * Transform a DMS string to a decimal representation. Used for LOC records.
230
     *
231
     * @param int    $deg        Degrees
232
     * @param int    $min        Minutes
233
     * @param float  $sec        Seconds
234
     * @param string $hemisphere Either 'N', 'S', 'E', or 'W'
235
     */
236 1
    public static function dmsToDecimal(int $deg, int $min, float $sec, string $hemisphere): float
237
    {
238 1
        $multiplier = ('S' === $hemisphere || 'W' === $hemisphere) ? -1 : 1;
239
240 1
        return $multiplier * ($deg + ($min / 60) + ($sec / 3600));
241
    }
242
243 1
    public function fromText(string $text): void
244
    {
245 1
        $rdata = explode(Tokens::SPACE, $text);
246 1
        $lat = self::dmsToDecimal((int) array_shift($rdata), (int) array_shift($rdata), (float) array_shift($rdata), (string) array_shift($rdata));
247 1
        $lon = self::dmsToDecimal((int) array_shift($rdata), (int) array_shift($rdata), (float) array_shift($rdata), (string) array_shift($rdata));
248
249 1
        $this->setLatitude($lat);
250 1
        $this->setLongitude($lon);
251 1
        $this->setAltitude((float) array_shift($rdata));
252 1
        $this->setSize((float) array_shift($rdata));
253 1
        $this->setHorizontalPrecision((float) array_shift($rdata));
254 1
        $this->setVerticalPrecision((float) array_shift($rdata));
255 1
    }
256
257 1
    /**
258
     * @throws DecodeException
259 1
     */
260 1
    public function fromWire(string $rdata, int &$offset = 0, ?int $rdLength = null): void
261
    {
262 1
        if (false === $values = unpack('C<version>/C<size>/C<hp>/C<vp>/l<lat>/l<lon>/l<alt>', $rdata, $offset)) {
263 1
            throw new DecodeException(static::TYPE, $rdata);
264 1
        }
265 1
        $offset += 16;
266 1
267 1
        $this->setSize(self::exponentValueToNumber($values['<size>']));
268 1
        $this->setHorizontalPrecision(self::exponentValueToNumber($values['<hp>']));
269
        $this->setVerticalPrecision(self::exponentValueToNumber($values['<vp>']));
270
        $this->setLatitude($values['<lat>'] / 3600000);
271
        $this->setLongitude($values['<lon>'] / 3600000);
272
        $this->setAltitude($values['<alt>']);
273
    }
274
}
275