loc.rs 14 KB

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