If SQLite clearAll fails mid-panic, in-memory state was already cleared
but a process restart could reload encrypted history from disk (#699).
Fall back to deleting the database files before reporting failure.
A relay that fails is retried on an exponential backoff, then abandoned
for the lifetime of the process. Nothing brings it back: the relay layer
registers no connectivity callback, the periodic subscription validator
only repairs subscriptions on sockets that are already open (and returns
immediately when connectedRelayCount is 0, which is exactly the state
after an outage), and connect() runs once from NostrClient.initialize().
The remaining paths that reset reconnectAttempts are a manual retry, a
Tor state change, and a successful open.
Two ways a relay died permanently:
- Any error whose message mentioned DNS returned before scheduling
anything at all. "Unable to resolve host" is what this device reports
when it simply has no network, so a moment in a tunnel killed every
relay at once, with no retry ever.
- Otherwise the schedule stopped at MAX_RECONNECT_ATTEMPTS. With
INITIAL=1s and MULTIPLIER=2 that is nine waits totalling about eight
and a half minutes, after which the relay was dead. MAX_BACKOFF_INTERVAL
was unreachable: attempt 9 asks for 256s and attempt 10 gave up, so the
five-minute ceiling the constant defines never applied to anything.
Let the backoff saturate at MAX_BACKOFF_INTERVAL and keep retrying there.
A name-resolution failure now backs off like any other error. Steady state
costs one connection attempt per relay per five minutes; the previous
behaviour cost the user every internet DM, delivery receipt and geohash
channel until they noticed and restarted the app.
The schedule moves into RelayReconnectPolicy so it is unit-testable
without OkHttp or a Context.
Asserts every domain is excluded in both sections and in the legacy
rules, that nothing is opted back in, and that the manifest still wires
both files up. Against the previous rules three of the five fail.
Whole-domain exclusion is the invariant worth pinning: unlike a list of
file names, it cannot go stale when storage is renamed.
The extraction rules excluded exactly two SharedPreferences files, and
one of them is no longer written: EncryptionService moved its key store
from "bitchat_crypto" to "bitchat_crypto_secure". Because these lists are
allow-by-default, everything not named is eligible — which is the live
key store, SecureIdentityStateManager's "bitchat_identity", the geohash
alias and conversation registries, and private_conversations.db, the
whole private message history.
android:allowBackup="false" covers cloud backup. It does not settle
device-to-device migration: for apps targeting Android 12 or higher the
platform documents that D2D transfer cannot be disabled on some
manufacturers' devices, so the <device-transfer> section is the only
thing between a phone migration and a copy of the identity keys and
every private message.
Make both files deny-by-default across every storage domain instead of
naming individual files. A name-based list has to be updated every time
storage is renamed or added, and this one already wasn't.
The collision case fails on main: two packets sharing a 64-byte prefix and
a timestamp, where the second was silently dropped.
The other two pass before and after on purpose. Replay of an identical
packet must still be caught, and the same packet arriving from two
different peers must still be tracked separately — strengthening the
identity must not quietly weaken either.