fix(aprs): clamp altitude and wrap course to keep fixed-width fields
Both extensions are fixed-width decimal fields, but neither value was range
checked before formatting:
formatAltitude(-50.0) -> /A=-00164 ('-' eats a digit slot)
formatCourseSpeed(_, 360f) -> /360/... (course must be 000..359)
formatCourseSpeed(_, -1f) -> /-01/... (widens the field)
A negative altitude is reachable from a below-sea-level position or a poor GPS
fix, and the malformed extension corrupts everything after it in the comment
field. Altitude now clamps to 0..999999, course wraps modulo 360, and speed
clamps to three digits.
Found by the same locale probe that produced the previous commit.
:core:domain:test BUILD SUCCESSFUL.
This commit is contained in:
1 parent
f4e188261d
commit
5f1f90067f
2 files changed
+32
-5
No files matched your search
@@ -39,17 +39,28 @@ object AprsPacket {
|
||||
return String.format(Locale.ROOT, "r/%.3f/%.3f/%d", latitude, longitude, distKm)
|
||||
}
|
||||
|
||||
/** Altitude extension /A=00000 (feet) */
|
||||
/**
|
||||
* Altitude extension /A=000000 (feet). The field is a fixed six-digit
|
||||
* decimal, so a negative altitude (below sea level, or a bad GPS fix) must
|
||||
* be clamped: "%06d" of -164 yields "/A=-00164", which is not a valid
|
||||
* extension and corrupts the rest of the comment field.
|
||||
*/
|
||||
fun formatAltitude(altitudeMeters: Double?): String {
|
||||
if (altitudeMeters == null) return ""
|
||||
return String.format(Locale.ROOT, "/A=%06d", (altitudeMeters * 3.2808399).toInt())
|
||||
val feet = (altitudeMeters * 3.2808399).toInt().coerceIn(0, 999999)
|
||||
return String.format(Locale.ROOT, "/A=%06d", feet)
|
||||
}
|
||||
|
||||
/** Speed/course extension (knots/degrees) */
|
||||
/**
|
||||
* Speed/course extension /CCC/SSS (degrees/knots). Course wraps into
|
||||
* 0..359 and speed is clamped to three digits, because "%03d" of an
|
||||
* out-of-range value widens the field and breaks the fixed-width format.
|
||||
*/
|
||||
fun formatCourseSpeed(speedMps: Double?, bearing: Float?): String {
|
||||
if (speedMps == null || bearing == null) return ""
|
||||
val knots = (speedMps * 1.94384449).toInt()
|
||||
return String.format(Locale.ROOT, "/%03d/%03d", bearing.toInt(), knots)
|
||||
val knots = (speedMps * 1.94384449).toInt().coerceIn(0, 999)
|
||||
val course = ((bearing.toInt() % 360) + 360) % 360
|
||||
return String.format(Locale.ROOT, "/%03d/%03d", course, knots)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
@@ -62,6 +62,22 @@ class AprsPacketLocaleTest {
|
||||
assertEquals("r/39.904/116.407/100", filter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun altitude_clampsNegativeToKeepSixDigitField() {
|
||||
// "%06d" of a negative value yields "/A=-00164": the '-' takes a digit
|
||||
// slot, so the extension is no longer a valid fixed-width field.
|
||||
assertEquals("/A=000000", AprsPacket.formatAltitude(-50.0))
|
||||
assertEquals("/A=000000", AprsPacket.formatAltitude(-1.0))
|
||||
assertEquals("/A=000328", AprsPacket.formatAltitude(100.0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun courseSpeed_wrapsCourseIntoValidRange() {
|
||||
assertEquals("/000/019", AprsPacket.formatCourseSpeed(10.0, 360f))
|
||||
assertEquals("/359/019", AprsPacket.formatCourseSpeed(10.0, -1f))
|
||||
assertEquals("/090/019", AprsPacket.formatCourseSpeed(10.0, 90f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ambiguousPosition_staysAsciiUnderArabicLocale() {
|
||||
Locale.setDefault(Locale.forLanguageTag("ar-EG"))
|
||||
|
||||
Reference in new issue
Block a user