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 * `asn1tools` compiled from the real ETSI modules in `asn1/`: it decoded this * 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) } }