diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/aprs/AprsReporter.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/aprs/AprsReporter.kt index 5a2cd0e7..c089630b 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/aprs/AprsReporter.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/aprs/AprsReporter.kt @@ -127,10 +127,13 @@ class AprsReporter( } val packetLine = (beacon as AprsBeacon.Result.Line).text - // Receive-only is a deliberate choice, not a mistake, and has to be carried through: - // it logs in with -1 exactly as a wrong passcode does, and the server answers - // "unverified" to both. - val wantsReceiveOnly = !AprsPasscode.canTransmit(cfg.callsign, cfg.passcode) + // Receive-only is a deliberate choice and has to be told apart from a typo, because + // both log in with -1 and the server answers "unverified" to each. Classifying rather + // than asking canTransmit: that collapses ReceiveOnly and Mismatch into one boolean, + // so a mistyped passcode would be told it is in receive-only mode - the same + // mis-diagnosis as before, pointing the other way. + val wantsReceiveOnly = + AprsPasscode.classify(cfg.callsign, cfg.passcode) is AprsPasscode.Entry.ReceiveOnly val c = client ?: AprsIsClient( host = cfg.server, port = cfg.port, diff --git a/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/aprs/AprsPasscodeTest.kt b/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/aprs/AprsPasscodeTest.kt index c32f65ec..f779e47e 100644 --- a/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/aprs/AprsPasscodeTest.kt +++ b/core/domain/src/test/java/com/rtbishop/look4sat/core/domain/aprs/AprsPasscodeTest.kt @@ -64,6 +64,31 @@ class AprsPasscodeTest { assertEquals(AprsPasscode.RECEIVE_ONLY, AprsPasscode.loginValue(callsign, wrong)) } + /** + * The three-way distinction the reporter depends on, and why it must not use canTransmit for + * it: that collapses a deliberate receive-only choice and a typo into one boolean. Telling an + * operator who mistyped their passcode that they are in receive-only mode is the same + * mis-diagnosis as telling a deliberate receive-only user their passcode is wrong, pointing + * the other way. + */ + @Test + fun `a deliberate receive-only entry is distinguishable from a typo`() { + val deliberate = AprsPasscode.classify(callsign, "-1") + val blank = AprsPasscode.classify(callsign, "") + val typo = AprsPasscode.classify(callsign, (correct + 1).toString()) + val garbage = AprsPasscode.classify(callsign, "abcde") + + assertTrue(deliberate is AprsPasscode.Entry.ReceiveOnly) + assertTrue(blank is AprsPasscode.Entry.ReceiveOnly) + assertFalse("a typo must not read as receive-only", typo is AprsPasscode.Entry.ReceiveOnly) + assertFalse("garbage must not read as receive-only", garbage is AprsPasscode.Entry.ReceiveOnly) + + // All four are equally unable to transmit, which is why the boolean is not enough. + for (entry in listOf("-1", "", (correct + 1).toString(), "abcde")) { + assertFalse(AprsPasscode.canTransmit(callsign, entry)) + } + } + @Test fun `something that is not a number is its own case`() { assertEquals(AprsPasscode.Entry.NotANumber, AprsPasscode.classify(callsign, "abcde"))