1 use std::fmt;
2 
3 use log::*;
4 
5 use crate::wire::*;
6 
7 
8 /// A **LOC** _(location)_ record, which points to a location on Earth using
9 /// its latitude, longitude, and altitude.
10 ///
11 /// # References
12 ///
13 /// - [RFC 1876](https://tools.ietf.org/html/rfc1876) — A Means for Expressing
14 ///   Location Information in the Domain Name System (January 1996)
15 #[derive(PartialEq, Debug, Copy, Clone)]
16 pub struct LOC {
17 
18     /// The diameter of a sphere enclosing the entity at the location, as a
19     /// measure of its size, measured in centimetres.
20     pub size: Size,
21 
22     /// The diameter of the “circle of error” that this location could be in,
23     /// measured in centimetres.
24     pub horizontal_precision: u8,
25 
26     /// The amount of vertical space that this location could be in, measured
27     /// in centimetres.
28     pub vertical_precision: u8,
29 
30     /// The latitude of the centre of the sphere. If `None`, the packet
31     /// parses, but the position is out of range.
32     pub latitude: Option<Position>,
33 
34     /// The longitude of the centre of the sphere. If `None`, the packet
35     /// parses, but the position is out of range.
36     pub longitude: Option<Position>,
37 
38     /// The altitude of the centre of the sphere, measured in centimetres
39     /// above a base of 100,000 metres below the GPS reference spheroid.
40     pub altitude: Altitude,
41 }
42 
43 /// A measure of size, in centimetres, represented by a base and an exponent.
44 #[derive(PartialEq, Debug, Copy, Clone)]
45 pub struct Size {
46     base: u8,
47     power_of_ten: u8,
48 }
49 
50 /// A position on one of the world’s axes.
51 #[derive(PartialEq, Debug, Copy, Clone)]
52 pub struct Position {
53     degrees: u32,
54     arcminutes: u32,
55     arcseconds: u32,
56     milliarcseconds: u32,
57     direction: Direction,
58 }
59 
60 /// A position on the vertical axis.
61 #[derive(PartialEq, Debug, Copy, Clone)]
62 pub struct Altitude {
63     metres: i64,
64     centimetres: i64,
65 }
66 
67 /// One of the directions a position could be in, relative to the equator or
68 /// prime meridian.
69 #[derive(PartialEq, Debug, Copy, Clone)]
70 pub enum Direction {
71     North,
72     East,
73     South,
74     West,
75 }
76 
77 impl Wire for LOC {
78     const NAME: &'static str = "LOC";
79     const RR_TYPE: u16 = 29;
80 
81     #[cfg_attr(all(test, feature = "with_mutagen"), ::mutagen::mutate)]
read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result<Self, WireError>82     fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result<Self, WireError> {
83         let version = c.read_u8()?;
84         trace!("Parsed version -> {:?}", version);
85 
86         if version != 0 {
87             return Err(WireError::WrongVersion {
88                 stated_version: version,
89                 maximum_supported_version: 0,
90             });
91         }
92 
93         if stated_length != 16 {
94             let mandated_length = MandatedLength::Exactly(16);
95             return Err(WireError::WrongRecordLength { stated_length, mandated_length });
96         }
97 
98         let size_bits = c.read_u8()?;
99         let size = Size::from_u8(size_bits);
100         trace!("Parsed size -> {:#08b} ({})", size_bits, size);
101 
102         let horizontal_precision = c.read_u8()?;
103         trace!("Parsed horizontal precision -> {:?}", horizontal_precision);
104 
105         let vertical_precision = c.read_u8()?;
106         trace!("Parsed vertical precision -> {:?}", vertical_precision);
107 
108         let latitude_num = c.read_u32::<BigEndian>()?;
109         let latitude = Position::from_u32(latitude_num, true);
110         trace!("Parsed latitude -> {:?} ({:?})", latitude_num, latitude);
111 
112         let longitude_num = c.read_u32::<BigEndian>()?;
113         let longitude = Position::from_u32(longitude_num, false);
114         trace!("Parsed longitude -> {:?} ({:?})", longitude_num, longitude);
115 
116         let altitude_num = c.read_u32::<BigEndian>()?;
117         let altitude = Altitude::from_u32(altitude_num);
118         trace!("Parsed altitude -> {:?} ({:})", altitude_num, altitude);
119 
120         Ok(Self {
121             size, horizontal_precision, vertical_precision, latitude, longitude, altitude,
122         })
123     }
124 }
125 
126 impl Size {
127 
128     /// Converts a number into the size it represents. To allow both small and
129     /// large sizes, the input octet is split into two four-bit sizes, one the
130     /// base, and one the power of ten exponent.
from_u8(input: u8) -> Self131     fn from_u8(input: u8) -> Self {
132         let base = input >> 4;
133         let power_of_ten = input & 0b_0000_1111;
134         Self { base, power_of_ten }
135     }
136 }
137 
138 impl Position {
139 
140     /// Converts a number into the position it represents. The input number is
141     /// measured in thousandths of an arcsecond (milliarcseconds), with 2^31
142     /// as the equator or prime meridian.
143     ///
144     /// Returns `None` if the input is out of range, meaning it would wrap
145     /// around to another half of the Earth once or more.
from_u32(mut input: u32, vertical: bool) -> Option<Self>146     fn from_u32(mut input: u32, vertical: bool) -> Option<Self> {
147         let max_for_direction = if vertical { 90 } else { 180 };
148         let limit = 1000 * 60 * 60 * max_for_direction;
149 
150         if input < (0x_8000_0000 - limit) || input > (0x_8000_0000 + limit) {
151             // Input is out of range
152             None
153         }
154         else if input >= 0x_8000_0000 {
155             // Input is north or east, so de-relativise it and divide into segments
156             input -= 0x_8000_0000;
157             let milliarcseconds = input % 1000;
158             let total_arcseconds = input / 1000;
159 
160             let arcseconds = total_arcseconds % 60;
161             let total_arcminutes = total_arcseconds / 60;
162 
163             let arcminutes = total_arcminutes % 60;
164             let degrees = total_arcminutes / 60;
165 
166             let direction = if vertical { Direction::North }
167                                    else { Direction::East };
168 
169             Some(Self { degrees, arcminutes, arcseconds, milliarcseconds, direction })
170         }
171         else {
172             // Input is south or west, so do the calculations for
173             let mut pos = Self::from_u32(input + (0x_8000_0000_u32 - input) * 2, vertical)?;
174 
175             pos.direction = if vertical { Direction::South }
176                                    else { Direction::West };
177             Some(pos)
178         }
179     }
180 }
181 
182 impl Altitude {
from_u32(input: u32) -> Self183     fn from_u32(input: u32) -> Self {
184         let mut input = i64::from(input);
185         input -= 10_000_000;  // 100,000m
186         let metres = input / 100;
187         let centimetres = input % 100;
188         Self { metres, centimetres }
189     }
190 }
191 
192 
193 impl fmt::Display for Size {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result194     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195         write!(f, "{}e{}", self.base, self.power_of_ten)
196     }
197 }
198 
199 impl fmt::Display for Position {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result200     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201         write!(f, "{}°{}′{}",
202             self.degrees,
203             self.arcminutes,
204             self.arcseconds,
205         )?;
206 
207         if self.milliarcseconds != 0 {
208             write!(f, ".{:03}", self.milliarcseconds)?;
209         }
210 
211         write!(f, "″ {}", self.direction)
212     }
213 }
214 
215 impl fmt::Display for Direction {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result216     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217         match self {
218             Self::North  => write!(f, "N"),
219             Self::East   => write!(f, "E"),
220             Self::South  => write!(f, "S"),
221             Self::West   => write!(f, "W"),
222         }
223     }
224 }
225 
226 impl fmt::Display for Altitude {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result227     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228         // Usually there’s a space between the number and the unit, but
229         // spaces are already used to delimit segments in the record summary
230         if self.centimetres == 0 {
231             write!(f, "{}m", self.metres)
232         }
233         else {
234             write!(f, "{}.{:02}m", self.metres, self.centimetres)
235         }
236     }
237 }
238 
239 
240 #[cfg(test)]
241 mod test {
242     use super::*;
243     use pretty_assertions::assert_eq;
244 
245     #[test]
parses()246     fn parses() {
247         let buf = &[
248             0x00,  // version
249             0x32,  // size,
250             0x00,  // horizontal precision
251             0x00,  // vertical precision
252             0x8b, 0x0d, 0x2c, 0x8c,  // latitude
253             0x7f, 0xf8, 0xfc, 0xa5,  // longitude
254             0x00, 0x98, 0x96, 0x80,  // altitude
255         ];
256 
257         assert_eq!(LOC::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(),
258                    LOC {
259                        size: Size { base: 3, power_of_ten: 2 },
260                        horizontal_precision: 0,
261                        vertical_precision: 0,
262                        latitude:  Position::from_u32(0x_8b_0d_2c_8c, true),
263                        longitude: Position::from_u32(0x_7f_f8_fc_a5, false),
264                        altitude:  Altitude::from_u32(0x_00_98_96_80),
265                    });
266     }
267 
268     #[test]
record_too_short()269     fn record_too_short() {
270         let buf = &[
271             0x00,  // version
272             0x00,  // size
273         ];
274 
275         assert_eq!(LOC::read(buf.len() as _, &mut Cursor::new(buf)),
276                    Err(WireError::WrongRecordLength { stated_length: 2, mandated_length: MandatedLength::Exactly(16) }));
277     }
278 
279     #[test]
record_too_long()280     fn record_too_long() {
281         let buf = &[
282             0x00,  // version
283             0x32,  // size,
284             0x00,  // horizontal precision
285             0x00,  // vertical precision
286             0x8b, 0x0d, 0x2c, 0x8c,  // latitude
287             0x7f, 0xf8, 0xfc, 0xa5,  // longitude
288             0x00, 0x98, 0x96, 0x80,  // altitude
289             0x12, 0x34, 0x56,  // some other stuff
290         ];
291 
292         assert_eq!(LOC::read(buf.len() as _, &mut Cursor::new(buf)),
293                    Err(WireError::WrongRecordLength { stated_length: 19, mandated_length: MandatedLength::Exactly(16) }));
294     }
295 
296     #[test]
more_recent_version()297     fn more_recent_version() {
298         let buf = &[
299             0x80,  // version
300             0x12, 0x34, 0x56,  // some data in an unknown format
301         ];
302 
303         assert_eq!(LOC::read(buf.len() as _, &mut Cursor::new(buf)),
304                    Err(WireError::WrongVersion { stated_version: 128, maximum_supported_version: 0 }));
305     }
306 
307     #[test]
record_empty()308     fn record_empty() {
309         assert_eq!(LOC::read(0, &mut Cursor::new(&[])),
310                    Err(WireError::IO));
311     }
312 
313     #[test]
buffer_ends_abruptly()314     fn buffer_ends_abruptly() {
315         let buf = &[
316             0x00,  // version
317         ];
318 
319         assert_eq!(LOC::read(16, &mut Cursor::new(buf)),
320                    Err(WireError::IO));
321     }
322 }
323 
324 
325 #[cfg(test)]
326 mod size_test {
327     use super::*;
328     use pretty_assertions::assert_eq;
329 
330     #[test]
zeroes()331     fn zeroes() {
332         assert_eq!(Size::from_u8(0b_0000_0000).to_string(),
333                    String::from("0e0"));
334     }
335 
336     #[test]
ones()337     fn ones() {
338         assert_eq!(Size::from_u8(0b_0001_0001).to_string(),
339                    String::from("1e1"));
340     }
341 
342     #[test]
schfourteen_teen()343     fn schfourteen_teen() {
344         assert_eq!(Size::from_u8(0b_1110_0011).to_string(),
345                    String::from("14e3"));
346     }
347 
348     #[test]
ones_but_bits_this_time()349     fn ones_but_bits_this_time() {
350         assert_eq!(Size::from_u8(0b_1111_1111).to_string(),
351                    String::from("15e15"));
352     }
353 }
354 
355 
356 #[cfg(test)]
357 mod position_test {
358     use super::*;
359     use pretty_assertions::assert_eq;
360 
361     // centre line tests
362 
363     #[test]
meridian()364     fn meridian() {
365         assert_eq!(Position::from_u32(0x_8000_0000, false).unwrap().to_string(),
366                    String::from("0°0′0″ E"));
367     }
368 
369     #[test]
meridian_plus_one()370     fn meridian_plus_one() {
371         assert_eq!(Position::from_u32(0x_8000_0000 + 1, false).unwrap().to_string(),
372                    String::from("0°0′0.001″ E"));
373     }
374 
375     #[test]
meridian_minus_one()376     fn meridian_minus_one() {
377         assert_eq!(Position::from_u32(0x_8000_0000 - 1, false).unwrap().to_string(),
378                    String::from("0°0′0.001″ W"));
379     }
380 
381     #[test]
equator()382     fn equator() {
383         assert_eq!(Position::from_u32(0x_8000_0000, true).unwrap().to_string(),
384                    String::from("0°0′0″ N"));
385     }
386 
387     #[test]
equator_plus_one()388     fn equator_plus_one() {
389         assert_eq!(Position::from_u32(0x_8000_0000 + 1, true).unwrap().to_string(),
390                    String::from("0°0′0.001″ N"));
391     }
392 
393     #[test]
equator_minus_one()394     fn equator_minus_one() {
395         assert_eq!(Position::from_u32(0x_8000_0000 - 1, true).unwrap().to_string(),
396                    String::from("0°0′0.001″ S"));
397     }
398 
399     // arbitrary value tests
400 
401     #[test]
some_latitude()402     fn some_latitude() {
403         assert_eq!(Position::from_u32(2332896396, true).unwrap().to_string(),
404                    String::from("51°30′12.748″ N"));
405     }
406 
407     #[test]
some_longitude()408     fn some_longitude() {
409         assert_eq!(Position::from_u32(2147024037, false).unwrap().to_string(),
410                    String::from("0°7′39.611″ W"));
411     }
412 
413     // limit tests
414 
415     #[test]
the_north_pole()416     fn the_north_pole() {
417         assert_eq!(Position::from_u32(0x8000_0000 + (1000 * 60 * 60 * 90), true).unwrap().to_string(),
418                    String::from("90°0′0″ N"));
419     }
420 
421     #[test]
the_north_pole_plus_one()422     fn the_north_pole_plus_one() {
423         assert_eq!(Position::from_u32(0x8000_0000 + (1000 * 60 * 60 * 90) + 1, true),
424                    None);
425     }
426 
427     #[test]
the_south_pole()428     fn the_south_pole() {
429         assert_eq!(Position::from_u32(0x8000_0000 - (1000 * 60 * 60 * 90), true).unwrap().to_string(),
430                    String::from("90°0′0″ S"));
431     }
432 
433     #[test]
the_south_pole_minus_one()434     fn the_south_pole_minus_one() {
435         assert_eq!(Position::from_u32(0x8000_0000 - (1000 * 60 * 60 * 90) - 1, true),
436                    None);
437     }
438 
439     #[test]
the_far_east()440     fn the_far_east() {
441         assert_eq!(Position::from_u32(0x8000_0000 + (1000 * 60 * 60 * 180), false).unwrap().to_string(),
442                    String::from("180°0′0″ E"));
443     }
444 
445     #[test]
the_far_east_plus_one()446     fn the_far_east_plus_one() {
447         assert_eq!(Position::from_u32(0x8000_0000 + (1000 * 60 * 60 * 180) + 1, false),
448                    None);
449     }
450 
451     #[test]
the_far_west()452     fn the_far_west() {
453         assert_eq!(Position::from_u32(0x8000_0000 - (1000 * 60 * 60 * 180), false).unwrap().to_string(),
454                    String::from("180°0′0″ W"));
455     }
456 
457     #[test]
the_far_west_minus_one()458     fn the_far_west_minus_one() {
459         assert_eq!(Position::from_u32(0x8000_0000 - (1000 * 60 * 60 * 180) - 1, false),
460                    None);
461     }
462 }
463 
464 
465 #[cfg(test)]
466 mod altitude_test {
467     use super::*;
468     use pretty_assertions::assert_eq;
469 
470     #[test]
base_level()471     fn base_level() {
472         assert_eq!(Altitude::from_u32(10000000).to_string(),
473                    String::from("0m"));
474     }
475 
476     #[test]
up_high()477     fn up_high() {
478         assert_eq!(Altitude::from_u32(20000000).to_string(),
479                    String::from("100000m"));
480     }
481 
482     #[test]
down_low()483     fn down_low() {
484         assert_eq!(Altitude::from_u32(0).to_string(),
485                    String::from("-100000m"));
486     }
487 
488     #[test]
with_decimal()489     fn with_decimal() {
490         assert_eq!(Altitude::from_u32(50505050).to_string(),
491                    String::from("405050.50m"));
492     }
493 }
494