fix(aprs): the login line was malformed, and the refusal was invisible

An auditor ran the plan's own release gate against live APRS-IS servers. It failed at
the login step, on every server tried:

    sent: user N0CALL pass -1 vers Look4Sat-4.5.4
    got:  # Invalid login: software name and version are not separated by a space

Reproduced on euro.aprs2.net and noam.aprs2.net, aprsc 2.1.21. `vers` takes TWO tokens,
a software name and a version. An earlier commit read the rule "softwarename must not
contain a space" as "the field must be one token" and hyphenated the space between them
- and the unit test asserted that as correct, so the mistake was frozen in place.

Worse than the malformed line was what happened next. `# Invalid login:` is a comment
but not a logresp, so parse skipped it as keepalive chatter; the login then timed out
into Unknown, which is deliberately treated as "may be working"; so `ok = sent &&
!refused` was true and the operator was shown "APRS: report sent OK" for a login the
server had refused. That is v4.6.0's defining defect - every send reported successful
regardless of outcome - still live on the exact path every operator takes. The rebuild
narrowed it rather than closing it.

Both halves are fixed: the name and version stay separate tokens with whitespace
collapsed within each, and a refusal comment is classified as a refusal before the
logresp test. A socket test now replays the server's actual bytes.

Three smaller things from the same review:

The foreground service type goes back to dataSync. The previous commit chose location
to escape dataSync's six-hour cap, but a location-typed service is refused outright
unless a location runtime permission has already been granted, and the settings card
requests only notifications - so it would have failed silently for anyone who declined
location access. The cap that prompted the switch applies only when targetSdk is 35 or
higher, which this project does not declare. A test now reads the manifest and the
service source and fails if they disagree, which is the only way this class of defect
is visible from a JVM test.

The version string in the login was 4.5.4 while the app was 4.6.0. Now split into name
and version and corrected, though it is still hardcoded - core:data has no BuildConfig,
so passing it in properly is a separate change.

The passcode hint said "empty = auto-computed from callsign" in all five locales. The
app stopped doing that two commits ago; it now connects receive-only, and the hint says
so. It was the first thing an operator read next to the field, promising the behaviour
that was deliberately removed.

Not fixed, and known: the notification body is rebuilt from the previous cycle's state
so it can show a stale verdict, a deliberate receive-only choice is still styled as an
error, and no last-success timestamp exists - so an operator still cannot establish
whether their station has ever reached the network.
This commit is contained in:
mckero committed 2026-08-25 16:04:47 +00:00
1 parent 0208a577c4
commit 321cd8f2fa
13 files changed
+297 -56

No files matched your search

+8 -6
View File
@@ -16,12 +16,14 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--
location, not dataSync: dataSync foreground services are capped at six hours in any
24-hour window and then stopped by the system, which would silently end a beacon meant
to run all day. The service reads the station position, falling back to GPS, so location
describes what it actually does.
dataSync rather than location. The location type is refused outright unless a location
runtime permission has already been granted - startForeground throws SecurityException -
and this service reads the station position the operator typed into settings, so demanding
location access to beacon a fixed QTH is both wrong and a way to fail silently for anyone
who declined it. The dataSync six-hour cap that prompted the earlier switch applies only
when targetSdk is 35 or higher, which this project does not declare.
-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<!--
Doze suspends network access and ignores wake locks even for a foreground service, so a
coroutine delay wakes up on time and then cannot reach the network. An exact alarm with
@@ -64,6 +66,6 @@
<service
android:name="com.rtbishop.look4sat.app.AprsForegroundService"
android:exported="false"
android:foregroundServiceType="location" />
android:foregroundServiceType="dataSync" />
</application>
</manifest>
@@ -152,13 +152,12 @@ class AprsForegroundService : Service() {
try {
val notif = buildNotification(cfg)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Must match the manifest attribute or the platform refuses the call: AOSP checks the
// passed type is a subset of the declared one, and location (0x08) does not contain
// dataSync (0x01). Changing the manifest without changing this line stopped the
// service dead on Android 10 and later - the IllegalArgumentException was caught
// below and turned into stopSelf(), so APRS did nothing and reported nothing while
// the settings switch stayed on.
startForeground(NOTIF_ID, notif, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
// Must match the manifest attribute exactly: AOSP checks the passed type is a
// subset of the declared one and throws otherwise, which the catch below turns
// into a silent stopSelf(). Declaring location instead would additionally require
// a granted location permission before this call, and the settings card asks only
// for notifications - so that combination fails silently too.
startForeground(NOTIF_ID, notif, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
} else {
startForeground(NOTIF_ID, notif)
}