Skip to content

Commit 12e936a

Browse files
v1.5.4+308: hotfix iOS SIGABRT — drop CBAdvertisementDataServiceDataKey
CBPeripheralManager.startAdvertising rejects ServiceData with a '-[CBUUID UTF8String]: unrecognized selector' SIGABRT inside Apple's XPC serialization (the inner [CBUUID:Data] dict isn't supported for 3rd-party advertisers despite the docs implying otherwise). v1.5.4+307 TestFlight caught this on iOS 26.3 / iPad. iOS hosts now advertise only the service UUID and rely on Multipeer Connectivity (already wired in MultiTransport) for iPhone-to-iPhone discovery. Adds an assert() guard so re-introducing a non-whitelisted key fails loudly during dev/beta builds.
1 parent b66485b commit 12e936a

2 files changed

Lines changed: 41 additions & 22 deletions

File tree

ios/Runner/BleAdvertiserChannel.swift

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -189,34 +189,53 @@ class BleAdvertiserChannel: NSObject {
189189
/// Actually start the BLE advertisement. Called only after the GATT
190190
/// service has been confirmed registered via `didAdd`.
191191
///
192-
/// Includes the Field Link sessionId as 16 raw bytes of service data
193-
/// keyed by the service UUID. Joiners parse this payload from
194-
/// `result.advertisementData.serviceData` (flutter_blue_plus) and use
195-
/// it as the canonical CRDT session id. Without this, joiners would
196-
/// fall back to the BLE peripheral's CoreBluetooth UUID, mismatch the
197-
/// host's UUID v4 sessionId, and silently lose all sync data.
192+
/// CRITICAL: Apple does NOT support `CBAdvertisementDataServiceDataKey`
193+
/// in `CBPeripheralManager.startAdvertising` for third-party apps —
194+
/// the documentation is misleading. Passing it causes a SIGABRT in
195+
/// Apple's XPC serialization with `-[CBUUID UTF8String]: unrecognized
196+
/// selector` (the inner `[CBUUID: Data]` dict is rejected because
197+
/// CoreBluetooth's XPC layer only accepts string keys for that
198+
/// payload). v1.5.4+307 had this and TestFlight crash logs caught it
199+
/// on iPad / iOS 26.3. Fixed in v1.5.4+308 by reverting to the
200+
/// Apple-blessed 2-key dict (ServiceUUIDs + optional LocalName).
198201
///
199-
/// NOTE: CoreBluetooth's foreground advertisement payload is limited
200-
/// to 31 bytes. Service-UUID-128 takes 18 bytes; 16 bytes of service
201-
/// data plus the 2-byte service-UUID prefix and 1-byte AD-type header
202-
/// = 19 bytes, totalling 37. iOS handles the overflow by spilling the
203-
/// service data into the scan-response packet, which flutter_blue_plus
204-
/// merges back into `result.advertisementData.serviceData` on the
205-
/// scanning side. The local name "RGL" was dropped to keep the primary
206-
/// advertisement compact and to avoid further overflow.
202+
/// The session id is therefore NOT carried in the BLE advertisement
203+
/// from iOS hosts. iOS-to-iOS discovery falls back to Multipeer
204+
/// Connectivity (wired in main.dart via MultiTransport), which uses
205+
/// Bonjour's discoveryInfo to carry the session id. iOS hosts seen
206+
/// by Android scanners still appear, but `device.sessionId` is null,
207+
/// so the joiner UI prompts for QR / Manual entry.
207208
private func beginAdvertising() {
208209
guard let pm = peripheralManager else { return }
209210

210-
var advertData: [String: Any] = [
211+
// Apple's whitelist for `startAdvertising:` is:
212+
// - CBAdvertisementDataServiceUUIDsKey (array of CBUUID)
213+
// - CBAdvertisementDataLocalNameKey (String)
214+
// Anything else either silently strips OR crashes the XPC layer.
215+
let advertData: [String: Any] = [
211216
CBAdvertisementDataServiceUUIDsKey: [BleAdvertiserChannel.serviceUUID],
212217
]
213218

214-
if let sessionId = pendingSessionId,
215-
let sessionBytes = BleAdvertiserChannel.sessionIdToBytes(sessionId) {
216-
// CBAdvertisementDataServiceDataKey is a [CBUUID: Data] dict.
217-
advertData[CBAdvertisementDataServiceDataKey] = [
218-
BleAdvertiserChannel.serviceUUID: Data(sessionBytes),
219-
]
219+
// Belt-and-braces guard: if a future change re-introduces a key
220+
// outside the whitelist (and especially one whose VALUE contains
221+
// a non-NSString-keyed dict, like CBAdvertisementDataServiceDataKey
222+
// = [CBUUID: Data]), the XPC serialization in CoreBluetooth will
223+
// SIGABRT inside `pm.startAdvertising`. Fail loudly during dev/
224+
// beta builds so the regression is caught before another TestFlight
225+
// crash report.
226+
let allowedKeys: Set<String> = [
227+
CBAdvertisementDataServiceUUIDsKey,
228+
CBAdvertisementDataLocalNameKey,
229+
]
230+
for k in advertData.keys {
231+
assert(
232+
allowedKeys.contains(k),
233+
"""
234+
BleAdvertiserChannel: advertData contains key '\(k)' which
235+
is NOT in Apple's documented whitelist for startAdvertising:.
236+
See v1.5.4+307 TestFlight crash (CBUUID UTF8String).
237+
"""
238+
)
220239
}
221240

222241
pm.startAdvertising(advertData)

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: red_grid_link
22
description: Offline-first MGRS-native proximity coordination platform
33
publish_to: 'none'
4-
version: 1.5.4+307
4+
version: 1.5.4+308
55

66
environment:
77
sdk: '>=3.2.0 <4.0.0'

0 commit comments

Comments
 (0)