Skip to content

Sync upstream bitchat-android (345 commits, 9fb8d4b..6ade682) - #34

Open
sarahsec wants to merge 453 commits into
mainfrom
sync-upstream-bitchat
Open

sarahsec wants to merge 453 commits into
mainfrom
sync-upstream-bitchat

Conversation

@sarahsec

Copy link
Copy Markdown
Owner

Sync with upstream permissionlesstech/bitchat-android main.

Upstream range

9fb8d4b..6ade682 (2026-03-15 → 2026-10-11): 345 non-merge commits, up to v2.0.2-wear.0.1.4. Highlights: Wi‑Fi Aware transport + MeshService/UnifiedMeshService facade, Noise PEER_STATE capabilities and the private-media preparation pipeline, live push‑to‑talk, a Wear OS module, UI redesign (palette, conversation headers, bubbles), fragment/TLV hardening, reproducible builds, dependency locking and verification.

Method

A direct merge gave 290 false conflicts because of the com.bitchat → com.cirabit rename. So I applied Cirabit's rename (paths, packages, Bitchat* → Cirabit*, bitchat1: → cirabit1:) to both the merge base and upstream, then ran a 3‑way merge. That left 56 real conflicts. The commit is a true merge, with parents main + upstream/main.

Conflicted files and how they were resolved

  • CI and docs (android-build.yml, release.yml, AGENTS.md, README.md, CHANGELOG.md, GOOGLE_PLAY.md, fetch-georelays.yml): kept Cirabit's version (its own signing/release flow and branding). Upstream's reproducible-build workflows were not adopted.
  • .gitignore: union of both. nostr_relays.csv: upstream (generated data). docs/file_transfer.md, tools/arti-build/README.md: upstream.
  • gradle/libs.versions.toml, wrapper: took the newer upstream versions (AGP 9.3.1, Kotlin 2.4.10, compileSdk 37, Gradle 9.6.1) and kept Cirabit-only entries (biometric, coroutines-test, lifecycle-process). Re-added the androidx-security-crypto alias because wear uses it. Robolectric went back to 4.15, following upstream's note.
  • app/build.gradle.kts: kept Cirabit's applicationId, versionName 1.7.3, signing and JVM 17. Added upstream's buildConfig cert field and composeCompiler block. Enabled includeAndroidResources for unit tests. build.gradle.kts: Cirabit's CVE pins plus upstream dependency locking.
  • locales_config.xml: kept Cirabit's (en-US, pt-BR).
  • strings.xml (en, pt-BR): kept Cirabit's strings and added the upstream strings that were missing. Added perm_type_wifi_aware.
  • NoiseEncrypted.kt: upstream PEER_STATE takes 0x21. Cirabit REACTION moved 0x21 → 0x22.
  • BinaryProtocol.kt: MSG_REACTION 0x23 and upstream VOICE_FRAME 0x29 both kept. Kept Cirabit's per-type payload limits; empty expanded payloads are now rejected (as upstream does).
  • AppConstants.kt, FragmentManager.kt: kept Cirabit's limits (65 535 fragments, 50 MB files) and its parametrized FragmentManager. Took upstream's createFragments(maxFragments). Added the upstream Router constants.
  • CirabitFilePacket.kt: Cirabit's multi-chunk content plus upstream's skipping of unknown TLVs.
  • SecureIdentityStateManager.kt, SecureKeyValueStore.kt: kept Cirabit's Tink store. Added an atomic Editor/commit() and a test-only plaintext constructor so the upstream peer-state/pin logic works on top of it.
  • BluetoothMeshService.kt, MeshDelegate.kt, MeshService.kt, UnifiedMeshService.kt, PacketProcessor.kt, MessageHandler.kt, SecurityManager.kt: took upstream's architecture and wired Cirabit reactions and stable (timestamp-pinned) IDs into it. Added default-arg overloads for sendMessage/sendFileBroadcast with a timestamp. Reactions over MeshCore/Wi‑Fi Aware are a no-op for now.
  • BluetoothPacketBroadcaster.kt: upstream GATT send queue. GeohashRepository.kt, LocationChannelManager.kt, SystemLocationProvider.kt, FileUtils.kt, PermissionManager.kt, OrientationAwareActivity.kt: upstream logic plus Cirabit additions (string-resource labels, Wi‑Fi Aware label).
  • MainActivity.kt: kept Cirabit's app lock and update check, and integrated upstream's Wi‑Fi Aware delegate wiring, BLE-optional check and best-effort foreground-service start.
  • ChatViewModel.kt, ChatScreen.kt, MessageComponents.kt, MeshPeerListSheet.kt, MediaSendingManager.kt, CommandProcessor.kt, AboutSheet.kt, ChatHeader.kt, ChatUserSheet.kt, GeohashViewModel.kt, SecurityVerificationSheet.kt, MeshDelegateHandler.kt, FullScreenImageViewer.kt, ImageMessageItem.kt, MeshForegroundService.kt: took the upstream UI redesign and kept Cirabit's features: reactions, app lock and update toggles, localized commands with /pay, stable public file IDs, the SecureImageDecoder bounded decode, and the non-deprecated stopForeground calls.
  • Tests: merged both sides. Renamed Cirabit's MessageHandlerTest to StableFileIdMessageHandlerTest. Removed NotificationManagerTest (upstream removed the class it tests) and the upstream "fragment set > 256 rejected" test (it contradicts Cirabit's limit). Oversized-file tests now use MAX_FILE_SIZE_BYTES.
  • Wear: moved to com/cirabit/watch and fixed the deprecated srcDir calls.

Build / tests

  • ./gradlew :app:assembleDebug: succeeded locally on an earlier pass.
  • :app:testDebugUnitTest: the earlier run had 633 tests, 13 failed. I made some fixes afterwards, but the final local test run was cut short to save time. CI needs to verify this branch. Lockfiles were regenerated with --write-locks. gradle/verification-metadata.xml was not updated, so strict dependency verification may fail until it is regenerated.
  • Expected failures on Cirabit's intentional choices: the 256-fragment cap tests (FragmentingPacketSender, PrivateMediaTransferPreparer) and LanguageCatalogContractTest (locales).
  • :wear was not built (it needs JDK 21).

Needs human review

  1. Wire change: REACTION 0x21 → 0x22. This breaks reactions with older Cirabit builds.
  2. Fragment and file limits (65 535 / 50 MB) versus upstream's iOS-interop cap (256 / ~10 MB).
  3. BITCHAT_GITHUB_RELEASE_CERT_SHA256 is blanked in gradle.properties. Fill in the Cirabit release cert fingerprint.
  4. Private files now use upstream's UUID message IDs, while the receiver computes a stable ID. Delivery acks may not match.
  5. Upstream's reproducible-build/release workflows were not adopted. verification-metadata.xml needs regenerating.
  6. Upstream's other translations exist, but the locale config is still en/pt-BR.

callebtc and others added 30 commits July 29, 2026 12:08
Addresses three issues from Codex review on permissionlesstech#808.

The Wi-Fi Aware hold could be defeated by a race. startIfPossible()
does a long stretch of async work between checking the hold and
assigning the service, so holdForHotspot() landing in that window
left an in-flight start free to resurrect NAN behind the hotspot's
back, putting every P2P attempt back on BUSY. The hold is now
rechecked before committing the service, and the freshly started
service is torn down if the hotspot claimed the radio meanwhile.

Startup error paths bypassed cleanup. A web-server failure, a null
connection info, or a throw from the outer block stopped the manager
but left the Aware hold set, blocking all mesh starts until the user
happened to retry or close the screen. Worse, an error after the
server had started left it serving the APK on port 9999 -- including
after the device reconnected to an ordinary Wi-Fi network -- because
only stopHotspot() cleared it.

Both follow from the same gap: cleanup lived at the call sites rather
than in one place. All failures now go through failWith(), which
shares teardown() with stopHotspot() and releases the server, the
manager and the hold together.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…arget (leans toward finger, blushes red with proximity, snaps on hover, REJECT haptic on cancel, firm click on enter/exit), release-anywhere tracking replaces button-owned release, timestamp left of waveform showing elapsed only, muted 1.5dp red outline, neutral grey recording pill with depth
…ain recording visualizer row to the text field's 22dp content height so the separator no longer shifts
The previous recheck narrowed the race without closing it. Testing
hotspotHold outside lifecycleLock left this interleaving:

  1. the recheck passes, hold not yet set
  2. holdForHotspot() sets the flag and calls stop(), which takes the
     lock, finds nothing published, and returns having stopped nothing
  3. this block takes the lock and publishes the service anyway

Aware ends up running while the hotspot believes it owns the radio,
which is the original failure.

The hold is now tested inside the same synchronized block that
publishes, so the check and the publication are one step against
stop(). Ordering holds because holdForHotspot() sets the flag before
calling stop(): either the publisher observes the flag and abandons,
or stop() observes the published service and tears it down. There is
no interleaving that leaves a service published with the hold set.

stopServices() stays outside the lock since it can block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…wifi-direct-reliability

fix: make Wi-Fi Direct hotspot sharing reliable
…omposed shouldCancel state could be one frame stale on a fast slide-and-lift (PR review)
…ex/persistent-conversations

Persist private conversations in People
…ex/wear-notification-stream-fix

Fix Wear OS DM notification delivery
…ce-recording-ux

Voice recording UX: slide-to-cancel, magnetic target, refined recording pill
…ex/reproducible-builds

Enable reproducible Android builds and releases
A bare `test -n` does not abort in a shell without set -e, so a failed
mktemp would let `--out ""` resolve to the repository root and write
private evidence there. Guard with an explicit exit instead.
…ex/mesh-lab-agent-skill

Add Mesh Lab agent skill
…ex/polish-persistent-conversations-v2

Polish persistent private conversations
actions-user and others added 27 commits August 30, 2026 06:16
…ex/bump-version-2.0.2

Bump Android version to 2.0.2
…ex/wear-ui-shape-accessibility

Improve Wear round-screen layouts and chat scrolling
…ex/release-wear-0.1.3

Bump Wear OS version to 0.1.3
…reconnect-recovery

fix(nostr): keep retrying relays instead of giving up on them forever
…-lookback

fix(nostr): cap DM envelope backdating at 24 hours
…ation

Use full-payload packet IDs for duplicate detection and simplify the explanatory comment.
…total-checks-race

fix: synchronize totalChecks increment in NostrEventDeduplicator
…r-ongoing-activity

Add Wear ongoing activity for the mesh service
Replace the white-background pixel bubble with the radar bubble: a green
chat bubble with two broadcast rings on a near-black ground. The rings
open toward the tail (bottom-right, like BitChatIcon) and all shapes stay
inside the 66dp adaptive-icon safe zone.

The notification small icon and ongoing-activity static icon now use the
same glyph instead of a stock chat bubble, and the monochrome layer
matches the launcher.
…ude/wearos-logo-redesign-20af78

Redesign Wear OS launcher and notification icons
…ease-wear-0.1.4

Bump Wear release to 0.1.4
Brings in 345 upstream commits (9fb8d4b..6ade682), translated to Cirabit
package/branding, with conflicts resolved preserving Cirabit customizations.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d66415ad3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +51 to +52
"https://github.com/permissionlesstech/bitchat-android/releases/latest/" +
"download/bitchat-android-universal.apk",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Point the APK source at a Cirabit release

When a user without a locally shareable APK selects the new Prepare/Download action, this is the only configured source. It serves the upstream BitChat APK, whose package is not this app's com.cirabit.android; UniversalApkManager.downloadedVersionName() rejects package mismatches, and release builds will also reject its different signer. Consequently every default download fails instead of producing a shareable Cirabit APK. Configure this source (and the matching release-metadata client) to Cirabit's own release assets.

Useful? React with 👍 / 👎.

Comment thread gradle.properties
# releases. Upstream bitchat ships its own fingerprint here; Cirabit releases are
# signed with a different key, so leave this empty until the Cirabit release
# certificate fingerprint is filled in (TODO: set to cirabit-release.jks SHA-256).
BITCHAT_GITHUB_RELEASE_CERT_SHA256=

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pin the Cirabit release certificate

For users installed through Play or another signing lineage, a legitimate Cirabit GitHub universal APK will not match ownCerts; with this value blank, BuildConfig.GITHUB_RELEASE_CERT_SHA256 contributes no trusted release signer and verifyApkSignature() rejects the download. This blocks the new prepare-for-sharing/update path precisely for the separate-release-key case it is intended to support. Ship the actual Cirabit GitHub release certificate fingerprint rather than an empty default.

Useful? React with 👍 / 👎.

Comment on lines 622 to +623
val msg = CirabitMessage(
id = stableMessageID,
id = UUID.randomUUID().toString().uppercase(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align private-media echo IDs with recipient ACKs

For every encrypted private file, the sender persists a random UUID here, but the receiver computes FileMessageIdUtil from the packet timestamp and encoded file payload and returns that deterministic value in its delivery ACK. The ACK is then used directly to update the sender's message status, so it cannot match this local echo and receiver confirmation is discarded. Persist the same deterministic ID (or carry one on the wire) for the sender echo.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T13:15:24.419954Z 5d66415 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.