BLE characteristic values swapped

Several of my users are reporting on at lest recent and current versions of iOS the value from one characteristic can be swapped with another.

Originally I thought this was a library issue but it doesn't happen on Android and now a user with two iPhones using the exact same app and iOS 26.3 on both has the issue on one phone but not the other.

I've gone into more detail here which also includes a little data dumping to prove the values between characteristics can be mixed up.

https://github.com/dotintent/react-native-ble-plx/issues/1316

One user reported cycling Bluetooth on/off fixed the issue but the latest user says it does not.

For the peripheral the services can only change if the device enters firmware update mode (in which case the service UUID is different). Otherwise the characteristics on a service never change. This would make it a strange one to be caching related since the cache should always be correct.

Reading through your GitHub issues, I see no indication that this would be an iOS bug, but rather a timing/race issue in your code.

A bug like this that is in the Bluetooth stack would have caused many such complaints, and at this point, this is the first time I am hearing this.

The only other explanation that would be beyond your code can be an issue with the GATT table on the accessory, perhaps due to mixing up of attribute handles.

The only way to understand whether the value sent by the accessory is the correct one vs. the wrong one reported by CoreBluetooth to your app would be to observe the traffic with a packet logger/air trace to see.

If you are able to reproduce this on your end, what would be useful is to install the Bluetooth logging profile from https://developer.apple.com/feedback-assistant/profiles-and-logs/ and then examine the Console logs for your app, or use the PacketLogger app (available at Additional Tools for Xcode) to see the traffic between your app and the peripheral, if you don't have access to a Sniffer tool.

These should help you pinpoint what read request and response you are getting between your app and the peripheral, and if what is being sent is not what you see in your app, then try to understand the root cause of the issue, where the data received might be getting mangled.

My initial thought right at the start were it a race condition in (my) code. While I was confident my JavaScript was correct the translation that ReactNative does is under-the-hood and I couldn't validate this directly. This is why I then added additional logging into production to dump the characteristics/values, which proved it was not a race issue (with my code or its translation) but what is coming back from the library or iOS/CoreBluetooth.

I then moved onto it being an issue with the react-native library, which is still a possibility, but the odds have reduced somewhat by the behaviour below.

What made me increasingly suspect it's an iOS issue is two iPhones, one a 13 and the other a SE (later model) running 26.3 running the same App at the same time try to connect to the peripheral (which supports multiple connections). One iPhone is successful, the other swaps the values between the two characteristics and so fails.

I haven't been able to replicate this myself, it's an error in production and coming from iPhone users so I'm limited in what I can inspect and debug.

I've also asked the BLE chipset (Nordic) if they've ever had anything like this reported for their SoftDevice stack. It seems very unlikely as it would have to rewrite the Gatt or iterate over it differently for a second connection. Besides which this bug never occurs on Android.

This method of collecting the Bluetooth Log looks interesting, I'll ask the user to carry it out and report back.

In the meantime I'm looking at the ReactNative module to see if there's a race possibility within it.

My code is calling this when it receives the wrong characteristic value.

https://github.com/dotintent/react-native-ble-plx/blob/master/src/BleManager.js#L708

The _callPromise is a means to abort all in-flight promises and can be ignored

https://github.com/dotintent/react-native-ble-plx/blob/71016d405844b9439225eb0a0bab1ebe7cc8a829/src/BleManager.js#L189

This calls a method in https://github.com/dotintent/react-native-ble-plx/blob/71016d405844b9439225eb0a0bab1ebe7cc8a829/src/BleModule.js#L855

At which point I assume is the bridging to iOS. I'm not a ReactNative expert but seems a reasonable assumption. The Swift code for this is at

https://github.com/dotintent/react-native-ble-plx/tree/71016d405844b9439225eb0a0bab1ebe7cc8a829/ios

Now all I see are headers. To my eyes (I have zero Apple development experience) there's nothing here to suggest any processing is occurring other than a pass-through from iOS/CoreBluetooth.

BLE characteristic values swapped
 
 
Q