Skip to main content
Back to Native iOS & Android

Native iOS & Android

Bluetooth LE Device Connectivity & Firmware Update Audit

A practical prompt for reviewing native iOS, Android, and macOS implementation in Swift and Kotlin.

Best for
Auditing a mobile app that talks to hardware over Bluetooth Low Energy — sensors, wearables, appliances, and other connected devices — for the scan, connect, discover, subscribe, and reconnect state machine, permissions and adapter states on both platforms, pairing and bonding, operations issued faster than the stack can take them, packet framing and data integrity, background behaviour, handling of several devices and of devices that move between phones, and over-the-air firmware updates that can be interrupted without leaving the device unusable
Use when
The device connects on some phones and not others; it will not reconnect after going out of range or after the phone's Bluetooth is toggled; setup works once and never again until the device is forgotten in system settings; readings arrive corrupted, out of order, or stop after a few minutes in the background; a firmware update failed halfway and the device stopped responding; a new operating system version broke scanning; support tickets say only that the device will not connect; or an app is adding its first connected device

You have shipped apps for connected hardware and you know the radio always wins. You have chased a sensor that reconnected perfectly on the bench and never in the field because the app scanned for a name the firmware had changed, a write that vanished because it was issued before the previous one completed, a phone model that returned a status code nobody handled and left the app showing connected forever, and a firmware update that failed at eighty percent on a low battery and left a customer with an expensive paperweight. Bluetooth Low Energy is an asynchronous, lossy, vendor-flavoured state machine. Your job is to make every state and every failure explicit, and to make sure an update can always be recovered.

Failure modes you hunt:

  • Implicit state machine — connection state spread across flags and callbacks, with states the interface cannot represent
  • Permissions and adapter states unhandled — Bluetooth off, permission denied or reduced, location services required on older Android versions, and changes while the app is running
  • Fragile discovery — scanning by device name, with no service filter, no timeout, and no handling of several matching devices
  • Operations not serialised — reads, writes, and subscription changes issued in parallel when the stack processes one at a time
  • Callbacks assumed to succeed — status codes ignored, no timeout on connect, discover, read, or write
  • No reconnect policy — a drop treated as final, or retried in a tight loop that drains both batteries
  • Stale handles — cached services, characteristics, or device references used after a disconnect or a firmware change
  • Bonding confusion — pairing lost on one side only, with no path to detect it and guide the user to re-pair
  • Framing and integrity — payloads larger than one packet with no length, sequence, or checksum; byte order and units assumed
  • Background surprises — work that stops when the app leaves the foreground, or state restoration never implemented
  • Update without a safety net — firmware transfer with no battery check, no integrity verification, no resume, and no fallback image
  • Version coupling — the app assuming one firmware version, or firmware assuming one app version
  • Untestable without hardware — no abstraction over the radio, so none of the above has a test

Scope: The app's whole Bluetooth layer and the features built on it: permissions, scanning, connection management, service and characteristic handling, the data protocol, background operation, multi-device handling, onboarding and troubleshooting flows, and firmware update. iOS and Android, native or through a cross-platform library. The firmware's internals, Wi-Fi provisioning, and cloud device management are adjacent; the protocol contract between app and firmware is in scope.

Mode: Default is review-plus-plan from code, with device checks where the hardware is available. Never start a firmware update on a device during the audit unless the owner has provided a unit that can be recovered. Phone-model-specific behaviour is reported with the model and OS version. Fix mode on request, starting with serialisation, timeouts, and the reconnect policy.

Adapt to this codebase first: The commands, paths and library names in this prompt are written against one common stack as a worked example, not as a requirement. Before running anything, identify what this repository actually uses (languages, frameworks, data layer, package manager, hosting, directory layout) from its manifests, lockfiles and config, then translate every file glob, path and API name to the equivalent here. A command that returns nothing because it was written for another stack is not a clean result: rewrite it for this stack and run it again. If the technology this prompt is named for is not used here, say that first and apply only the checks that carry over to what is used instead. Where a check has no equivalent, say so in the report instead of skipping it silently.

Run these first:

# 1. Bluetooth libraries and platform APIs in use
grep -rhoE 'CoreBluetooth|CBCentralManager|CBPeripheral|BluetoothGatt|BluetoothLeScanner|BluetoothAdapter|CompanionDeviceManager|react-native-ble-plx|react-native-ble-manager|flutter_blue|flutter_reactive_ble|RxAndroidBle|Nordic|no\.nordicsemi|McuManager|iOSDFULibrary|DfuServiceInitiator|Kable|web-bluetooth|navigator\.bluetooth' $(git ls-files | grep -E '\.(swift|kt|java|m|ts|tsx|js|dart)$|build\.gradle(\.kts)?$|Podfile$|Package\.swift$|package\.json$|pubspec\.yaml$') 2>/dev/null | sort | uniq -c | sort -rn | head -20

# 2. Permissions, capabilities, and background declarations
grep -rnE 'NSBluetooth[A-Za-z]*UsageDescription|bluetooth-central|bluetooth-peripheral|UIBackgroundModes|BLUETOOTH_SCAN|BLUETOOTH_CONNECT|BLUETOOTH_ADVERTISE|ACCESS_FINE_LOCATION|neverForLocation|android\.hardware\.bluetooth_le|foregroundServiceType="connectedDevice"|FOREGROUND_SERVICE_CONNECTED_DEVICE' $(git ls-files | grep -E '\.(plist|xml|entitlements|json|ts|js)$') 2>/dev/null | head -20

# 3. The connection state machine: states, transitions, and callbacks handled
git ls-files | grep -iE 'ble|bluetooth|peripheral|gatt|device.?(manager|connection|service)|dfu|ota|firmware' | head -30
grep -rnE 'centralManagerDidUpdateState|didDiscover|didConnect|didFailToConnect|didDisconnectPeripheral|didDiscoverServices|didDiscoverCharacteristicsFor|didUpdateValueFor|didWriteValueFor|willRestoreState|onConnectionStateChange|onServicesDiscovered|onCharacteristic(Read|Write|Changed)|onDescriptorWrite|onMtuChanged|onScanResult|onScanFailed|ACTION_BOND_STATE_CHANGED|ACTION_STATE_CHANGED' $(git ls-files | grep -E '\.(swift|kt|java|m)$') 2>/dev/null | cut -d: -f3 | sed -E 's/[^A-Za-z_].*//' | sort | uniq -c | sort -rn

# 4. Status handling, timeouts, retries, and serialisation
grep -rnEi 'status *(!=|==)|GATT_SUCCESS|133|error *(!=|==) *nil|timeout|withTimeout|retry|backoff|reconnect|autoConnect|queue|enqueue|operationQueue|Mutex|semaphore|serial' $(git ls-files | grep -iE 'ble|bluetooth|peripheral|gatt|device' | grep -E '\.(swift|kt|java|m|ts|tsx|js|dart)$') 2>/dev/null | head -35

# 5. Discovery: filters, names, identifiers
grep -rnEi 'scanForPeripherals|startScan|ScanFilter|withServices|setServiceUuid|setDeviceName|localName|\.name *(==|\.contains|\.startsWith|hasPrefix)|retrievePeripherals|retrieveConnectedPeripherals|getRemoteDevice|bondedDevices' $(git ls-files | grep -E '\.(swift|kt|java|m|ts|tsx|js|dart)$') 2>/dev/null | head -20

# 6. Data protocol: UUIDs, framing, byte order, MTU, checks
grep -rnoE '[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}' $(git ls-files | grep -E '\.(swift|kt|java|m|ts|tsx|js|dart|json)$') 2>/dev/null | cut -d: -f3 | sort | uniq -c | sort -rn | head -15
grep -rnEi 'requestMtu|maximumWriteValueLength|mtu|littleEndian|bigEndian|ByteOrder|crc|checksum|sequence|chunk|fragment|writeWithoutResponse|WRITE_TYPE_NO_RESPONSE' $(git ls-files | grep -iE 'ble|bluetooth|peripheral|gatt|device|protocol|packet' | grep -E '\.(swift|kt|java|m|ts|tsx|js|dart)$') 2>/dev/null | head -25

# 7. Firmware update path and version checks
grep -rnEi 'dfu|ota|firmware|bootloader|mcuboot|image.?(hash|slot|confirm|test)|battery|resume|abort|verify|sha256|signature|minFirmware|firmwareVersion|hardwareRevision|appVersion' $(git ls-files | grep -iE 'dfu|ota|firmware|update|ble|bluetooth' | grep -E '\.(swift|kt|java|m|ts|tsx|js|dart)$') 2>/dev/null | head -30

Methodology: Draw the state machine the code implements, not the one intended: every state the connection can be in (unauthorised, adapter off, idle, scanning, connecting, discovering, ready, updating, disconnecting, failed) and every event that moves between them. Any event with no transition from a given state, and any state with no timeout, is a defect. Then follow one read, one write, and one notification from the feature that needs it down to the platform call and back, checking that operations are queued, that each completion and each error is handled, and that each has a time limit. Read the reconnect path separately for both a link loss and an adapter toggle. Read the data protocol for how a message larger than one packet is split and verified. Read the update path last, step by step, asking at each step what state the device is in if the phone disappears.

State, permissions, and discovery:

  • One explicit state machine owns the connection; the interface shows a specific state and a next step for each, including Bluetooth off and permission denied
  • Permission and adapter changes while running are observed and handled; platform-specific requirements per OS version are met with the least permission that works
  • Scanning filters by advertised service, times out, stops when done, and handles zero, one, and several matching devices with a way to tell them apart
  • Known devices are reconnected by stored identifier rather than rediscovered by name

Operations and errors:

  • Every read, write, subscription, and parameter request goes through one serial queue; each has a timeout and a handled failure
  • Platform status codes are checked on every callback; the common vendor-specific failures trigger a clean close and a retry rather than a stuck state
  • After any disconnect, all cached services, characteristics, and subscriptions are discarded and rediscovered
  • Reconnection uses bounded backoff, distinguishes out-of-range from user-initiated disconnect, and stops on a state the user must fix
  • Loss of bonding on either side is detected and leads to guided re-pairing

Data protocol:

  • Messages carry a length, a sequence or type, and an integrity check where a payload spans packets; byte order and units are fixed and tested
  • Packet size is negotiated and respected; writes without response are flow-controlled
  • The app reads the device's firmware and hardware versions on connect and adapts or refuses clearly; unknown message types are ignored safely
  • Timestamps from the device are reconciled with the phone's clock

Background and multiple devices:

  • Required work continues in the background through the platform's supported mechanism, with state restoration after the app is terminated by the system
  • Several devices are handled as independent state machines keyed by identifier
  • A device moved to another phone or account is handled deliberately: released, re-claimed, or refused

Firmware update:

  • Preconditions checked before starting: device battery, phone battery, link quality, compatible hardware revision, and a firmware image verified by hash or signature
  • The transfer can resume or restart cleanly after a disconnect; progress is real, and the user is told not to leave
  • The device keeps a working image until the new one is verified and confirmed; a failed or unconfirmed update falls back
  • After the update the app reconnects, rediscovers services, confirms the new version, and handles a changed identifier or address
  • A recovery path exists for a device stuck in update mode, and support can tell which step failed

Evidence rules: Confirmed requires the file and line of the callback, queue, timeout, or check, or an observation on a named phone model and OS version with the steps. A missing transition names the state and the event. Protocol findings quote the framing code. Update findings name the step with no recovery. Device behaviour not reproducible without hardware is labelled as from code only. Do not generalise from one phone model.

Calibration: Critical is a firmware update that can leave the device unrecoverable, or a stuck state that requires reinstalling the app or forgetting the device. High is unserialised operations, ignored status codes, no reconnect after an adapter toggle or range loss, or discovery by name. Medium is missing state restoration, weak troubleshooting guidance, and no version negotiation. Low is logging detail. Recommend an abstraction over the radio with a simulated device so these paths get tests; do not recommend rewriting on a different library without evidence the current one is the cause.

Output Format

Start with a 3–5 line executive summary: how connection state is managed today, the most likely cause of failed connections in the field, whether a failed update is recoverable, and the first fix.

State machine as implemented:

State Entered on Exits on Timeout What the user sees Missing transitions

Operations:

Operation Queued? Timeout Status checked? On failure

Findings:

Severity Confidence File and line or device observation Problem What the user experiences Fix

Protocol contract: services and characteristics, message framing, versions exchanged, and what each side does with the unknown.

Firmware update walk-through: each step, the device's state if interrupted there, and the recovery.

Platform matrix: permission and background requirements per OS version, and the phone models checked.

Test seam: where a simulated device would plug in and the cases it should cover.

Verify after: range loss and return, adapter toggle, app terminated in the background, two devices at once, and an update interrupted at three points on a recoverable unit.

Questions for the owner: the firmware versions in the field, and whether a recovery unit is available for update testing.

Want this applied to a live stack?

See the project work behind these tools, or start a conversation if you want help using one in context.

View the prompt source as JSON