2026-08-20 15:47:14 +02:00
|
|
|
package com.hawhamburg.micr0bu
|
|
|
|
|
|
|
|
|
|
import com.hawhamburg.micr0bu.domain.asn1.CamUperCodec
|
|
|
|
|
import com.hawhamburg.micr0bu.domain.cam.Cam
|
|
|
|
|
import com.hawhamburg.micr0bu.domain.cam.StationType
|
|
|
|
|
import org.junit.Assert.assertEquals
|
|
|
|
|
import org.junit.Test
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Golden-byte test for the CAM this app transmits.
|
|
|
|
|
*
|
|
|
|
|
* ## Why a byte-for-byte fixture
|
|
|
|
|
* This project has now shipped the same class of bug three times: a field encoded with the wrong
|
|
|
|
|
* number of bits, which both ends of this codebase then read back with the *same* wrong number.
|
|
|
|
|
* Phone and ESP32 agree perfectly with each other and with nothing else, so every internal test
|
|
|
|
|
* passes while the frames on air are malformed. It cost a hardware session each time
|
|
|
|
|
* (`CurvatureCalculationMode`, the GeoNetworking reserved bytes, and `yawRateConfidence`).
|
|
|
|
|
*
|
|
|
|
|
* A round-trip test through this codebase's own decoder cannot catch that - it shares the
|
|
|
|
|
* mistake. Only an independent implementation can. So the expected bytes below were produced by
|
2026-08-21 14:28:57 +02:00
|
|
|
* `asn1tools` compiled from the real ETSI modules in `asn1/`: it decoded this
|
2026-08-20 15:47:14 +02:00
|
|
|
* encoder's output and re-encoded it, and the result was byte-identical to what is asserted here.
|
|
|
|
|
* That is stronger than "it parses" - it means this encoder emits exactly what the reference
|
|
|
|
|
* encoder emits.
|
|
|
|
|
*
|
|
|
|
|
* If a field width is ever "tidied up", this test fails. Do not regenerate the expected value from
|
|
|
|
|
* this encoder's own output - regenerate it through asn1tools, or the test is worthless.
|
|
|
|
|
*/
|
|
|
|
|
class CamEncodeGoldenTest {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* asn1tools-verified encoding of [referenceCam]. The trap this pins down: `YawRateConfidence`
|
|
|
|
|
* has nine enumerands (0..8), so it needs 4 bits and `unavailable` is 8 - not 3 bits and 7.
|
|
|
|
|
*/
|
|
|
|
|
private val expectedHex =
|
|
|
|
|
"0202000f423f3700402ab215af6e286477dffffffc23b7743e0027ffc0d0fe0118329337feebfff6000000"
|
|
|
|
|
|
|
|
|
|
private val referenceCam = Cam(
|
|
|
|
|
stationId = 999_999L,
|
|
|
|
|
stationType = StationType.CYCLIST,
|
|
|
|
|
latitude = 53.5544955,
|
|
|
|
|
longitude = 10.0225470,
|
|
|
|
|
speedMps = 4.17,
|
|
|
|
|
headingDeg = 63.9,
|
|
|
|
|
yawRateDps = null,
|
|
|
|
|
driveDirection = 0,
|
|
|
|
|
vehicleLengthM = 1.8,
|
|
|
|
|
vehicleWidthM = 0.7,
|
|
|
|
|
accelerationMps2 = 0.4,
|
|
|
|
|
timestamp = 1_787_100_000_000L,
|
|
|
|
|
isOwn = true,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun `encodes a CAM exactly as the ETSI reference encoder does`() {
|
|
|
|
|
val encoded = CamUperCodec.encode(referenceCam)
|
|
|
|
|
.joinToString("") { "%02x".format(it) }
|
|
|
|
|
assertEquals(expectedHex, encoded)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun `own decoder agrees with the encoder on every field it reads`() {
|
|
|
|
|
// Self-consistency is necessary but NOT sufficient - see the class KDoc. This guards the
|
|
|
|
|
// decoder against drifting away from the encoder, while the golden bytes above are what
|
|
|
|
|
// guards both of them against drifting away from the standard.
|
|
|
|
|
val round = CamUperCodec.decode(CamUperCodec.encode(referenceCam), referenceCam.timestamp)
|
|
|
|
|
requireNotNull(round)
|
|
|
|
|
assertEquals(referenceCam.stationId, round.stationId)
|
|
|
|
|
assertEquals(referenceCam.stationType, round.stationType)
|
|
|
|
|
assertEquals(referenceCam.latitude, round.latitude, 1e-7)
|
|
|
|
|
assertEquals(referenceCam.longitude, round.longitude, 1e-7)
|
|
|
|
|
assertEquals(referenceCam.speedMps, round.speedMps, 1e-9)
|
|
|
|
|
assertEquals(referenceCam.headingDeg, round.headingDeg, 1e-9)
|
|
|
|
|
assertEquals(referenceCam.vehicleLengthM!!, round.vehicleLengthM!!, 1e-9)
|
|
|
|
|
assertEquals(referenceCam.vehicleWidthM!!, round.vehicleWidthM!!, 1e-9)
|
|
|
|
|
assertEquals(referenceCam.accelerationMps2!!, round.accelerationMps2!!, 1e-9)
|
|
|
|
|
}
|
|
|
|
|
}
|