Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@

All notable changes to mobileGF2logger are documented here.

## 2.4.1 - 2026-08-25

### Changed

- Require captured packets, roster imports, parsed-packet history, and backups
to enter an explicit identity-backed Platoon scope; legacy unscoped data is
no longer exposed as a selectable production profile.
- Refine the Home selector and capture card, use cohesive rounded dialogs,
compact packet-history settings, and prevent repeated transient messages
from accumulating in the interface.

### Fixed

- Keep profile registration, active selection, reset routing, backup restore,
and retained Activity state consistent across isolated Platoons.
- Remove metadata for non-parsed VPN flows on every close path and prevent a
delayed open callback from restoring metadata after a rejected close.
- Make packet-history option rows fully tappable and let weekly evidence badges
expand safely with the user's system font size.
- Cap the pre-identity quarantine at 32 decoded payloads per flow and 128 in
total, with synchronized accounting across parser and native-close threads.

## 2.4.0 - 2026-08-25

### Added
Expand All @@ -21,40 +43,25 @@ All notable changes to mobileGF2logger are documented here.

### Changed

- Refine the Home capture card and active-Platoon selector, use cohesive
rounded dialogs, compact packet-history settings, and replace queued transient
messages so repeated actions do not leave stale feedback behind.
- Quarantine up to 32 decoded payloads per TCP flow until both its supported
Android client and valid Platoon identity are known; unverified flows never
enter management storage.
- Keep one-time-capture completion evidence isolated per detected Platoon so
two clients cannot accidentally complete one checklist.
- Leave unscoped v2.3.x files untouched but remove them from selectable
production profiles; captured packets require a confirmed `21905` identity
before they can create or enter an immutable data scope.
- Require roster CSV imports to target an explicitly selected existing
profile or a newly user-declared client/server/name/ID profile, name that
destination in the preview, and explain that `21917` cannot identify or
verify a Platoon by itself.
- Scope recent and saved parsed-packet history to the admitted profile and
keep unconfirmed flow payloads out of history as well as management storage.
- Allow a scoped v3 backup to recreate its embedded Platoon profile on an empty
installation, while continuing to reject identity-free legacy archives.
- Preserve v2.3.x data as an unmoved legacy profile while new Platoons use
immutable private databases and retained-evidence directories.
- Replace the arbitrary timezone list with the six supported server presets;
an unconfigured client must be selected once before its first profile is admitted.

### Fixed

- Improve weekly evidence guidance and restore consistent spacing around weekly
notes and member-detail fields in both light and dark themes.
- Resolve all six review findings from v2.3.3: clean flow metadata without a
parser, order known same-day joins by instant, bound manual weekly notes,
preserve captured member names, replace timezone-derived history atomically,
and accept `21905` checklist evidence only after identity validation.
- Bound the profile registry, profile metadata, and pre-identity quarantine with
both per-flow and aggregate decoded-payload caps; reject invalid restores before
metadata changes, preserve the selected import scope through preview/apply, and
restore the matching client-region routing.
- Bound the profile registry, profile metadata, and pre-identity flow buffer;
reject invalid restores before metadata changes, preserve the selected import
scope through preview/apply, and restore the matching client-region routing.
- Quarantine identity-changing or admission-failed flows, admit at most one new
profile per client per user-started capture, and let users forget selector
metadata without deleting the isolated Platoon data.
Expand Down
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ android {
applicationId = 'dev.gf2log'
minSdk = 26
targetSdk = 36
versionCode = 20400
versionName = '2.4.0'
versionCode = 20401
versionName = '2.4.1'
testInstrumentationRunner = 'androidx.test.runner.AndroidJUnitRunner'

ndk {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package dev.gf2log.app.capture

/** Bounded pre-identity quarantine; overflow permanently rejects that flow until closure. */
/**
* Bounded pre-identity quarantine; overflow permanently rejects that flow until closure.
* All state access is serialized because rejected-close cleanup can run off the parser executor.
*/
internal class BoundedFlowPayloadBuffer<T>(
private val maxItemsPerFlow: Int,
private val maxTotalItems: Int,
Expand All @@ -14,6 +17,7 @@ internal class BoundedFlowPayloadBuffer<T>(
require(maxTotalItems > 0)
}

@Synchronized
fun offer(flowId: Long, item: T): OfferResult {
if (flowId in rejected) return OfferResult.REJECTED
val items = pending.getOrPut(flowId, ::ArrayDeque)
Expand All @@ -26,24 +30,29 @@ internal class BoundedFlowPayloadBuffer<T>(
return OfferResult.ACCEPTED
}

@Synchronized
fun take(flowId: Long): List<T> {
val items = pending.remove(flowId) ?: return emptyList()
totalItems -= items.size
return items.toList()
}

@Synchronized
fun isRejected(flowId: Long): Boolean = flowId in rejected

@Synchronized
fun reject(flowId: Long) {
totalItems -= pending.remove(flowId)?.size ?: 0
rejected += flowId
}

@Synchronized
fun remove(flowId: Long) {
totalItems -= pending.remove(flowId)?.size ?: 0
rejected.remove(flowId)
}

@Synchronized
fun clear() {
pending.clear()
rejected.clear()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.gf2log.app.capture

import java.lang.reflect.Modifier
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
Expand Down Expand Up @@ -60,4 +61,19 @@ class BoundedFlowPayloadBufferTest {
assertEquals(BoundedFlowPayloadBuffer.OfferResult.ACCEPTED, buffer.offer(4, "four"))
assertEquals(listOf("four"), buffer.take(4))
}

@Test
fun everyStatefulOperationIsSerializedAcrossParserAndCloseThreads() {
val synchronizedMethods = setOf("offer", "take", "isRejected", "reject", "remove", "clear")
val methodsByName = BoundedFlowPayloadBuffer::class.java.declaredMethods
.filter { it.name in synchronizedMethods }
.associateBy { it.name }

assertEquals(synchronizedMethods, methodsByName.keys)
synchronizedMethods.forEach { name ->
assertTrue("$name must synchronize quarantine bookkeeping", Modifier.isSynchronized(
requireNotNull(methodsByName[name]).modifiers,
))
}
}
}
Loading