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