WikifitaGitHub live67e8de5
outro · camdom/camdom-scope-and-delivery

CAMDOM — Scope and Delivery

Scope management analysis: what was planned, what was cut, what shipped, and why. Features added late, features removed, zombie code, Felipe's scope philosophy, and the final delivery.

Baixar raw

CAMDOM — Scope and Delivery

An analysis of scope management in a real-world BLE mesh app delivery: what was designed, what was built, what shipped, what was cut, and the philosophy that kept the project alive.


1. Original Scope (from case study documentation)

The original CAMDOM concept encompassed:

FeatureDescriptionPriority
BLE mesh communicationReal-time pairing between 2+ devices via Bluetooth Low EnergyCore
Cross-platformAndroid + iOS, including Android-to-iOS cross-connectionsCore
Camera/microphone blockingPrevent the paired device from accessing camera/microphoneCore
Alarm on tamperSound alarm if either device is tampered with (app killed, backgrounded, swiped away)Core
Privacy by designNo data collection, no servers, no accounts beyond what stores requireCore

The camera/microphone blocking was the original differentiating feature. The idea: when two phones are paired via CAMDOM, neither phone should be able to activate its camera or microphone without the other phone being alerted.


2. What Was Actually Built

BLE Mesh Communication -- SHIPPED

The BLE mesh was built from scratch using Expo Modules API (JSI) for sub-millisecond native calls. The architecture uses a GATT server/client model:

  • One device acts as GATT peripheral (server)
  • Other devices connect as GATT centrals (clients)
  • Server election is deterministic: Android always wins over iOS (IDs 126-250 for Android, 0-124 for iOS; server gets 251 or 125)
  • Packet protocol handles: sync, alarm, disconnect, migration, RSSI, ping-pong

Cross-Platform -- SHIPPED (with caveats)

Android-to-Android, iOS-to-iOS, and Android-to-iOS all work. The Android-to-iOS case required the most engineering because iOS cannot act as GATT server for Android clients. The server election algorithm was the key breakthrough (Aug 20).

Caveat: The app works best with 2 devices. 3+ devices introduces edge cases around server migration, disconnect consensus, and BLE radio contention that were never fully resolved.

Camera/Microphone Blocking -- EVOLVED INTO ALARM

This is the most significant scope change in the project. The original plan was to actively block the camera and microphone on the paired device. The actual implementation evolved into an alarm-based approach:

Original vision: Device A pairs with Device B. If Device B tries to open its camera, Device A is alerted or Device B is blocked.

What shipped: Device A pairs with Device B. If Device B backgrounds the app, tries to swipe away, or disconnects, both devices sound an alarm. The alarm alerts rather than blocks.

Why the change:

  1. iOS has no API to programmatically disable the camera or microphone for another app
  2. Android's camera blocking requires Device Admin or Accessibility Service permissions, which Google Play would reject
  3. The alarm approach achieves the same user-facing goal (deterrence) without requiring impossible system-level permissions
  4. The alarm is louder and more immediate than a silent camera block

Alarm on Tamper -- SHIPPED (simplified)

The alarm system that shipped covers these triggers:

  1. App backgrounding while synced: Both devices alarm
  2. Disconnect attempt by any device: If the other device has fireAlarmOnDisconnect enabled, alarm fires
  3. Manual alarm via UI button: Press-and-hold mechanism
  4. Screen lock (Android): Treated same as backgrounding

What was cut from the alarm system:

  • RSSI proximity alarm (disabled 14 days after implementation)
  • "Fire alarm on button press" (disabled by default, too sensitive)
  • Automatic re-arm after alarm dismissal (caused false positives)

Privacy by Design -- SHIPPED (with store-required additions)

No data collection, no analytics (except OpenReplay session replay, added stealthily), no third-party servers. The BLE protocol is entirely local. The app does store user credentials via Firebase/Auth (later removed) and uses MMKV for local key-value storage.

Store-mandated additions:

  • Sign-out button (Apple requirement)
  • Delete account button (Apple requirement)
  • Privacy policy page (Google Play family policy requirement)
  • Terms of usage (client request)

3. Features That Were Cut

RSSI Proximity Alarm (disabled Sep 3, 2024)

Added: August 20 (commit 749d1c8, v2.33.0) Disabled: September 3 (commit 2f701d2)

The proximity system used BLE RSSI (Received Signal Strength Indicator) as a distance proxy. A 15ms tick loop on the client read RSSI values and sent them to the server. The server aggregated readings from all clients, computed a rolling window average, and compared against a configurable threshold.

Why it was cut:

  • RSSI values fluctuate wildly based on phone orientation, body proximity, environmental reflections
  • Device hardware differences made cross-device comparison meaningless (iPhone RSSI base: -25; Android range: -15 to -45)
  • False positives from momentary signal dips triggered alarms during normal use
  • 15ms tick rate (66fps) on both devices doubled BLE radio traffic and drained battery
  • The sensitivity negotiation protocol had a bug: hasAnyoneHigherSensibility could become undefined

What remains in code: 109 lines of commented-out RSSI code in BleManager.ts, the rssiSense storage key initialized to 60, and the sensitivity slider UI element (which has no effect).

NFC Integration (removed Aug 21, 2024)

Added: ~August 14 (NFC disable attempt) Removed: August 21

The NFC issue: when two iPhones were close together for BLE pairing, the iOS NFC system would trigger the Apple Pay/contact sharing popup, disrupting the user experience. Alefita researched the com.apple.developer.passkit.pass-presentation-suppression entitlement but it required iOS 17.4+ and direct Apple Developer contact.

Felipe's decision: "So desabilita. Nao tenta usar. Vai dar ruim."

The NFC-related code and the external-accessory background mode were removed from Info.plist. Apple still complained about NFC in review, requiring explanation that the app does not use NFC.

Sign-Out and Delete Account Buttons (commented out)

Added: ~August 18 (Apple requirement) Status: Commented out in Options.tsx (lines 117-144)

Apple required these buttons for App Store compliance. They were implemented in the Options menu but commented out because the client didn't want them visible to users. When the sign-out button was accidentally left in a build, it crashed the app (September 13) because the sign-out logic was never fully implemented.

Sensitivity Slider (exists but non-functional)

The Options screen has UI elements for sensitivity adjustment. The rssiSense storage key exists in StorageKeys.ts with a default value of 60. The constructor initializes it. But nothing in the UI reads or writes it from any user-facing interface. The slider was part of the RSSI proximity feature and became orphaned when that feature was disabled.

German Localization (partially done)

Mikael contributed to the German translations. Some were completed and shipped. Others were translated via automated tools and had errors:

  • "Wie man CAMDOM benutzt" was incorrectly translated as "Wie man das CAMDOM benutzt"
  • Some text strings were inconsistent between the Figma and the implementation
  • The German layout had padding/spacing issues due to word length differences

The German localization was eventually completed via OTA updates, but was never fully polished.

Ledger Wallet Replacer (stub only)

The analysis/ directory contains stage4_ledger.sh — a 34-byte stub that was dynamically filled by the C2 server. This was part of the ClickFix attack analysis, not a CAMDOM feature. (Note: this was from the separate ClickFix forensic project, not CAMDOM itself.)


4. Features That Were Added Late

OpenReplay Session Replay (stealth deployment, Aug 15)

Alefita deployed OpenReplay without telling anyone: "Eu adicionei algo em segredo na versao de ontem. Temos session replay habilitado desde a ultima versao."

Victor Hugo set up the self-hosted instance at openreplay.pay4brain.com.br on a GCP server. The cloud version had a 1000-user monthly limit; the self-hosted version had no limit.

Purpose: Debug the intermittent "cache" bugs by watching actual user sessions. When the client reported errors, Alefita could replay the exact session to see what happened.

Felipe's reaction: "Boaaaa. Melhor coisa."

FireAlarmOnDisconnect (added per client request)

The client requested that if one device tried to disconnect alone, the alarm should fire on all devices. This was a consensus mechanism: both devices must agree to disconnect, otherwise it's treated as tampering.

Implementation complexity: This was one of the most complex features to implement. The server had to track which clients had requested disconnect (askedDisconnectionDevices), compare against total connected clients, and only allow disconnect when all devices agreed. If any device had fireAlarmOnDisconnect enabled, a solo disconnect attempt would trigger the alarm.

后来 reduced sensitivity: The initial implementation was too aggressive — pressing the disconnect button briefly would trigger the alarm. Felipe asked for a 15-second hold threshold, but Alefita ultimately made it a menu toggle that defaults to off.

Device-to-Device Alarm on Disconnect Attempt

Added as a core security feature: when any device attempts to disconnect (swipe away, kill app, background), the alarm fires on all connected devices. This is the primary "protection" mechanism that shipped.

Expo Updates / Force Update System

Added late in development (Aug 29-30). The system checks for updates on the splash screen, downloads them, and applies them before the app fully loads. This allowed Alefita to push fixes without going through store review.

Felipe's reaction to the splash screen timing: "Ideal ter uma tela de force update ne? Ou podemos forcar pela loja."


5. The Scope Creep Pattern

The project followed a consistent pattern of scope expansion driven by client feedback:

Pattern 1: Client asks for multi-device -> Alefita implements -> breaks other things

The multi-device support (3+ phones) was not in the original scope. The client asked for it, Alefita implemented server migration logic, but this broke 2-device connections intermittently. Each fix for multi-device introduced new edge cases in 2-device scenarios.

Pattern 2: Client asks for alarm on disconnect -> complex consensus -> too sensitive -> reduced

The disconnect alarm evolved through multiple iterations:

  1. Original: alarm fires on any disconnect
  2. Added: consensus mechanism (all must agree)
  3. Client feedback: too sensitive, fires on accidental touches
  4. Reduced: toggle in menu, disabled by default
  5. Final: alarm fires only if fireAlarmOnDisconnect is enabled per-device

Pattern 3: Client asks for NFC suppression -> weeks of research -> removed entirely

The NFC issue consumed approximately 5 days of research and implementation before being abandoned. The entitlement required Apple Developer contact, the API was deprecated, and the fix didn't actually solve the problem (the OS-level NFC trigger was outside app control).

Pattern 4: Client asks for delete account -> "implementar e uma linha de codigo, mas aonde?"

The delete account button was trivial to implement but had no logical place in the UI. The app had no profile page, no settings hierarchy that included account management. It was eventually placed in the Options menu but commented out because the client didn't want users to see it.


6. Felipe's Scope Management Philosophy

Extracted from WhatsApp messages:

"Nada a mais" -- Do only what they complain about

"Lembrando o que combinamos -- vamos fazer somente o que eles reclamarem"

Felipe's core principle: don't proactively add features. Wait for client complaints, then fix only those specific items. This prevented scope creep from the development side while still accommodating client requests.

"Foca na conexao e na estabilidade. ESQUECE o resto."

When the client was upset about crashes (Aug 19), Felipe's instruction was absolute: connection stability first, everything else second. This was repeated multiple times during the crunch period.

"Nao entra muito em detalhes, se tentarem diz que essa parte ta comigo"

Felipe shielded Alefita from client-facing complexity. Store submission, review processes, and legal requirements were "his" responsibility. Alefita's job was code.

"Vamos fazer somente o que eles reclamarem"

Repeated multiple times across the project. This was Felipe's way of saying: stop polishing, stop improving, stop adding. Ship what works and wait for feedback.

"Precisamos de sangue frio a partir de agora"

After multiple crises, Felipe recognized that panic was the real enemy. Cold blood, methodical testing, and clear communication were more valuable than fast coding.


7. What Shipped in 3.3.0 (Final Store Version)

Core Features

  • BLE mesh connection (2-4 devices, best with 2)
  • Cross-platform (Android + iOS)
  • Server election: Android always wins over iOS
  • Consensus-based disconnect (all devices must agree)
  • Alarm on app backgrounding while synced
  • Alarm on disconnect attempt (when fireAlarmOnDisconnect enabled)
  • Manual alarm via press-and-hold button
  • Force update system (checks on splash screen)

UI/UX

  • Onboarding flow with Bluetooth permission request
  • Main screen with animated "ball" (Skia canvas)
  • Lateral menu (About, How To Use, Buy A Condom, Options)
  • Alarm screen with stop button
  • Connection state animations (scanning, connecting, synced)
  • Sound effects (5 types: connect, disconnect, alarm, tap, sync)
  • Haptic feedback

Localization

  • English (complete)
  • German (partial, corrected via OTA)

Privacy

  • No data collection
  • No analytics (OpenReplay was stealth-deployed but not disclosed)
  • No third-party servers for core functionality
  • Privacy policy page (Google Play requirement)
  • Terms of usage (client request)

Platform-Specific

  • iOS: Background BLE modes (central + peripheral), CoreAudio for alarm
  • Android: WakeLock for background BLE, Do Not Disturb bypass (with permission), notification channel

8. What Was Post-Launch (OTA Updates)

After the store versions were approved, these improvements were delivered via Expo Updates without store resubmission:

UpdateChanges
Animation improvementsMenu appearance, button fade-in, icon pulsing, state transitions
Keyboard UXLogin/signup keyboard behavior fixed for iOS
Layout refinementsSpacing, padding, font sizes across screen sizes
German translationsCorrected translations, layout adjustments for German text length
Text correctionsAbout screen capitalization, punctuation, Figma alignment
Proximity warningSensor proximity warning adjusted

9. The 3.3.0 Decision

Why 3.3.0 Was the Final Store Version

The version numbering tells the story: the app went from 2.17.0 to 3.3.0 in the store, but internally the version counter reached 3.23.0+. The gap between store version and internal version grew because:

  1. Expo Updates allowed rapid iteration without store review
  2. Store review took 2-3 weeks for iOS, making it the bottleneck
  3. Most post-3.3.0 changes were UI/animations that didn't affect core functionality
  4. Felipe's boundary: "ja investimos demais nessa app. Precisamos parar."

The OTA Strategy

Alefita proposed and implemented a two-track release strategy:

  1. Store version (3.3.0): Contains all core functionality, tested extensively
  2. OTA updates: Animation, layout, text corrections — applied automatically on app launch

This meant users who downloaded from the store would immediately get the latest version without a store update cycle.

Alefita's Desire to Keep Improving

Despite Felipe's "stop" instruction, Alefita continued pushing improvements:

  • 45fps to 60fps animation targets
  • Ping-pong keepalive for BLE connections
  • Edge case fixes for multi-device scenarios
  • Bluetooth state validation before packet sends

Each improvement was small but cumulative, reaching version 3.23.0 internally before the project was considered complete.


10. Scope Summary

CategoryOriginal ScopeShippedCutAdded Late
BLE meshYesYes--
Cross-platformYesYes--
Camera/mic blockingYesEvolved to alarmActive blocking-
Alarm on tamperYesYes (simplified)--
Privacy by designYesYes-Store-mandated additions
RSSI proximityNot specifiedNoYes (14 days)-
NFC suppressionNot specifiedNoYes (7 days)-
Multi-deviceNot specifiedPartial (2-4)-Client request
Session replayNot specifiedYes (stealth)-Alefita's initiative
Force updateNot specifiedYes-Client request
German localizationNot specifiedPartial-Client request
AnimationsNot specifiedBasic-Client request
Sign-out/deleteNot specifiedCommented outUI visibilityApple requirement
Sensitivity sliderNot specifiedUI exists, no effectFunctionality-

The gap between "what was designed" and "what shipped" is the honest measure of any software project. CAMDOM shipped a working BLE mesh consent protection app that achieved its core goal: two phones paired, alarm on tamper, privacy by design. Everything else was scope negotiation.