ukhsa-collaboration / COVID-19-app-iOS-BETA

Source code of the Beta of the NHS COVID-19 iOS app

Home Page:https://covid19.nhs.uk/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Discovery fails when both devices are locked

lukeredpath opened this issue · comments

Describe the bug
I've been trying to see how the app works around the issue where an iOS app central cannot discover an iOS app peripheral on another device if they are both in the background (and/or the device is locked).

I did a lot of initial testing of this with an Apple sample project yesterday and could not find a way to make it work and am able to reproduce this problem with the COVID-19 app as well.

A few notes, I've had to make a few small mods to get the app running on my device to the point where it's actively scanning and broadcasting:

  • Disabled remoteNotificationManager.configure() in the app delegate to bypass Firebase
  • Faked registration by creating my own Registration, using the device advertising identifier as the UUID and a fake secret/SecKey.
  • Commented out the peripheral state restoration code in the broadcaster so my two test devices always appear as new devices to each other.

To Reproduce

  1. Install app on two iOS devices. In my test I'm using an iPad and an iPhone.
  2. Install the app on the iPhone, launch the app and make sure its working. Kill the app, turn on Airplane mode.
  3. Install the app on the iPad, launch the app, make sure its working (I'm running this one attached to the debugger so I can watch the log output). Background the app (verify using logs) and lock the device.
  4. Move out of range of the iPad with the iPhone, wake the iPhone up, disable Airplane mode, launch the app, close the app, lock the device.
  5. Move in to range of the iPad and observe no log output - the didDiscover delegate method is not being called on the iPad.
  6. Wake the iPad screen by pressing the home button - the didDiscover delegate is called and it springs to life.

Expected behaviour
This was the behaviour I expected based on my previous testing however I was lead to believe that a way had been found around this limitation.

My understanding of the problem is thus:

  • iOS apps acting as a peripheral do not broadcast their service UUID in the advertising packet while they are broadcasting in the background. The service UUID is stored in an "overflow area" to quote the Apple docs.
  • iOS apps acting as a central can only find other iOS peripherals by scanning for a specific service UUID if when they are running in the background - because the other device is not broadcasting it's service UUID, it doesn't find it.
  • If either app is in the foreground, the connection is made - either the central is in the foreground and is able to find the peripheral without relying on the service UUID being advertised or the peripheral is in the foreground and is broadcasting it's service UUID so the backgrounded central can find it.

Smartphone (please complete the following information):
I'm using an iPhone XS and an iPad mini 4 running the latest version of iOS 13.

EDIT - some further detailed scenarios below.

I appreciate my testing of this is limited at the moment and would be grateful if anyone else is able to reproduce. I've pushed the branch I was running with my small modifications to a fork which might make it easier for people to get up and running:

https://github.com/lukeredpath/COVID-19-app-Documentation-BETA/tree/mods-to-run-on-local-device

You might still need to copy the example GoogleService-Info.plist file to .secret/, update the team ID and configure provisioning.

Hi - not sure how helpful this might be to your particular question, but this article goes into detail about the technical details and workarounds used in the app: https://reincubate.com/blog/staying-alive-covid-19-background-tracing/

Sadly I don't think the above article really addresses the core issue. I've heard some suggestions that it might be relying on the Android app to wake iOS apps up but I'm not convinced that would work - even if an Android app connects to the iOS app (which is running in the background), it won't bring the app to the foreground, iOS will wake the app up in the background briefly (for about 10 seconds) and the device will of course remain locked.

Not ruling it out - I don't have an Android device to test this theory, but if this is the case then it seems like a pretty shonky workaround.

Before we speculate how they are working around this it would be good to establish if what I'm seeing is right, a known issue and that there isn't anything special going on in the app itself to bypass this issue.

I worked on a similar problem a couple of years ago and saw similar behaviour to what you're describing. Apple are very strict on restricting this kind of stuff so I'd be amazed if a trivial workaround could allow iOS devices to detect each other when locked. Having said all that, a practical test beats all else (so kudos to original poster). Someone else needs to repeat this test to add more data points.

commented

I believe they have a workaround which is feasible for use in the wild. We're writing it up now!

Some quite interesting stuff around how they might be getting around the background iOS restrictions can be found in the Android source.

Looking at the Apple documentation on background peripheral bluetooth, they state "All service UUIDs contained in the value of the CBAdvertisementDataServiceUUIDsKey advertisement key are placed in a special “overflow” area; they can be discovered only by an iOS device that is explicitly scanning for them"

I wonder if they have managed to reverse engineer this overflow area so it is accessible via Android?

Someone did some looking into this and it seems at least feasible: https://crownstone.rocks/2018/06/27/ios-advertisements-in-the-background

Will be very interesting to see if we can get some more details on how this operates from the developers.

It definitely appears to be the case that they're using Android to bypass this feature, but the key question remains: what happens if two iOS devices, both locked (inactive) encounter each other when there are no android devices in the vicinity

Yes, based on that comment in the Android source and the Crownstone stuff which I was reading yesterday, it does seem like it is possible for Androids to detect the iOS app even if its in the background (although we're all aware of the risks in relying on reverse-engineering a private, undocumented implementation detail like this).

Whether or not an Android device connecting to a background iOS peripheral on a locked device is enough to make that device visible to other locked iOS devices is what I'm not convinced by, until somebody can demonstrate it working and explain how it's doing it. And if that is the workaround, as @jdmwood2 says, that isn't much help if there are no Android devices around - how many encounters will be missed in this scenario?

Just a thought having read a bunch of threads on Twitter about this. Anyone trying to reproduce this must absolutely account for a) new devices finding each other (the OPs point about peripheral state restoration and b) finding each other when both devices are locked before coming into range.

I think it would be very easy to get false positives if you're testing with devices which had already discovered each other (even in a previous test).

The gold standard is: can two locked iOS devices (as in, phone is in your pocket) find each other when they come into range for the for first time.

I've continued to do some testing and I'll try and capture this on video tomorrow, but these are the scenarios I've tried. Interestingly it looks like there's more to this than the apps simply being in the background but it could also be related to the power state of the device.

In all scenarios I am starting with the following test conditions:

  • The iPad app is running with the peripheral state restoration code in ConcreteBTLEListener commented out - the behaviour I'm seeing only seems to occur if two devices have not seen each other. If they have seen each other the central restores them from cache during state restoration (I don't know when this expires). I'm aware that this source code modification potentially invalidates these tests, but I think it's a reasonable assumption that doing this is equivalent to using two devices that have never seen each other before.
  • The iPad app is connected to Xcode so I can observe the logs.
  • The app on my iPhone is not connected to Xcode and at the start of each test I ensure that a) the app is running and b) my phone is in Airplane mode.

Scenario 1: App on Device 1 is in foreground, Device 2 app is in background

  1. Launch the app on the iPad from Xcode, observe it is I the foreground.
  2. Unlock my iPhone and turn off Airplane mode.

Result: iPad discovers the iPhone immediately.

Scenario 2: App on Device 1 is in background (screen on), Device 2 app is in background (screen on)

  1. Launch the app on the iPad from Xcode, press the home button and wait for log output to confirm app is in background mode.
  2. Unlock iPhone and turn off Airplane mode.

Result: iPad discovers the iPhone immediately.

#### Scenario 3: App on Device 1 is in background (screen off), Device 2 app is in background (screen on)

  1. Launch the app on the iPad from Xcode, press the home button and wait for log output to confirm app is in background mode.
  2. Lock the iPad.
  3. Unlock iPhone and turn off Airplane mode.

Result: iPad does not discover iPhone (waited for up to 2 minutes keeping iPhone screen on throughout).

After waiting to see that the devices do not discover each other, discovery can be triggered immediately in one of two ways:

  1. Launching the app into the foreground on the iPhone (iPad still locked)
  2. Waking the iPad by pressing the home button to turn the screen on. This works with the iPhone both awake and locked.

In my earlier testing the above scenarios behaved the same if instead of using Airplane mode, I went out of range before launching the app on my iPhone and then came back into range. I'm using Airplane mode for convenience here so I don't have to walk up and down my driveway all night.

So it seems clear to me that the issue isn't solely related to the apps being in the background, but also to do with whether or not the screen is on. Some potential confounding variables that could be throwing off my tests:

  • I'm using an iPad to test instead of iPhones (an iPad mini 3). Could there be something different about the Bluetooth stack and power management compared to an iPhone?
  • Could the iPad being tethered in some way affect the behaviour?
  • Would the results be different if I were to swap the iPad and iPhone in the above scenarios?
  • Could the removed state restoration code be affecting this test, as noted above?

Would be great if somebody fancies reproducing my tests above with two iPhones.

I have had strange behaviour in the past with background behaviour while using Xcode to read logs.

I did discover that the app has a debug screen that allows you to see logs while not connected to Xcode. If you shake the device you will get a development menu with a number of useful options.

Good spot - just tried scenario 3 untethered from Xcode - I put my iPhone back on Airplane mode again before unlocking the iPad, launching the app and using the debug screen to send the logs to my iMac. Log output confirms the same result.

It also sends over the contact events in plist format so I thought I'd take a look at that with a clean slate.

Deleting the app and re-installing on my iPad was also useful as it seemed to clear the cached state restoration data which means I was able to run the test with the state restoration code intact. I observed the same behaviour as before, here's a snippet of the log from the point I backgrounded the app:

23:40:04 info: Application AppDelegate.swift:183:applicationWillResignActive(_:) – Will Resign Active
23:40:05 info: Application AppDelegate.swift:194:applicationDidEnterBackground(_:) – Did Enter Background
2020-05-07 23:40:05.943201+0100 Sonar[784:192358] Can't end BackgroundTask: no background task exists with identifier 2 (0x2), or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.

**** TURNED OFF AIRPORT MODE ON MY IPHONE HERE ****

23:40:18 error: Registration RegistratonService.swift:94:register() – Registration did not complete within 20.0 seconds
23:40:18 error: Registration RegistratonService.swift:177:fail(withError:reason:) – Registration failed: Sonar.(unknown context at $100eef8ac).RegistrationTimeoutError
23:40:18 info: BTLE BTLEBroadcaster.swift:217:peripheralManager(_:didReceiveRead:) – responding to read request with empty payload
2020-05-07 23:40:18.836045+0100 Sonar[784:192428] [AppCenter] WARNING: -[MSChannelUnitDefault enqueueItem:flags:]_block_invoke/141 Channel Analytics disabled in log discarding mode, discard this log.
23:40:19 info: BTLE BTLEBroadcaster.swift:217:peripheralManager(_:didReceiveRead:) – responding to read request with empty payload

**** TURNED ON AIRPORT MODE ON MY IPHONE HERE ****

23:42:16 info: Application AppDelegate.swift:198:applicationWillEnterForeground(_:) – Will Enter Foreground
2020-05-07 23:42:16.524522+0100 Sonar[784:192438] [AppCenter] WARNING: -[MSChannelUnitDefault enqueueItem:flags:]_block_invoke/141 Channel Analytics disabled in log discarding mode, discard this log.
23:42:16 info: Application AppDelegate.swift:187:applicationDidBecomeActive(_:) – Did Become Active

As you can see there's no discovery messages in the logs and on inspection the contactEvents.plist file was empty.

commented

As promised, here is the blog post explaining how the iOS <-> iOS workaround works. https://reincubate.com/blog/nhs-covid-19-background-tracing-details/

Should hopefully explain the behaviour you’ve been seeing.

If I'm reading your blog post right, you're saying that, in order for scenario 3 in my tests above to work effectively it requires either:

  • Existing proximity to an Android device or,
  • Existing proximity with an already established connection to a third iOS device?

If this is the case, that still means the scenario where any two iOS users with their phones locked in their pockets, who have not seen each other before and who are not in range of any Android devices, will still fail?

Also, if the issue seems related to the screen being off more than the app being in a background/suspended state, then where does that fit in to this?

commented

Providing the device has a managed to establish a connection with any device (whether it be Android or iOS), it appears as though the device is able to effectively poll as though it is in the foreground even when the device is locked.

If this is the case, that still means the scenario where any two iOS users with their phones locked in their pockets, who have not seen each other before and who are not in range of any Android devices, will still fail?

This would depend on the time it last saw an Android or iOS device, because there is a grace period before the app is suspended again (and thus falls back into limited BT-polling mode)

OK, I will re-test scenario three with a third device tomorrow. Is this what you'd expect to see?

  1. iPad app in background, screen locked.
  2. Turn off Airplane mode on iPhone 1, leave app in background - discovery fails as per the above test.
  3. Turn off Airplane mode on iPhone 2 - still no discovery of this one yet.
  4. Bring app on iPhone 2 into foreground - iPad should discover iPhone 2 and iPhone 1?

And for completeness:

  1. iPad app in background, screen locked.
  2. Leave iPhone 1 in Airplane mode.
  3. Turn off Airplane mode on iPhone 2 and run the app so it is discovered by the iPad.
  4. Turn on Airplane mode on iPhone 2 to disconnect it from the iPad.
  5. Within some undefined window after this, turn off Airplane mode on iPhone 1 and it should be discovered?

So what happens with the app if no android devices are around to wakeup?

commented

@lukeredpath I’m not sure what I’d expect to see under those scenarios at the moment if I’m honest as I’m exhausted and have a headache (it’s all that reversing!). I’ll get back to you in the morning, but hey there’s no harm in trying.

commented

@Jeff-Stelling and there’s no other iOS devices around for it to talk to? The device would go silent until woken by an Android device

For those interested in how the keep-alive works, it's effectively a ping-pong between the central/peripheral running on each device:

  1. After discovery, the central on each device subscribes to changes in the keep-alive characteristic of the other device's peripheral.

  2. Device 1 central (the listener) gets an RSSI ping from Device 2's peripheral and triggers the didReadRSSI delegate callback.

  3. Device 1 uses the above ping as a trigger to update the value of its the keep alive peripheral characteristic.

  4. Device 2 is notified of the change to Device 1's keep-alive value and triggers the didUpdateValueFor delegate callback where it in turn triggers an update of its keep-alive characteristic, notifying Device 1.

  5. The above process also happens in reverse, with Device 2 updating the its keep-alive value when when it gets an RSSI ping from Device 1 and Device 1 updating its keep-alive value when it receives the updated value from Device 2.

  6. There's some throttling in place to ensure each device only updates it's keep-alive value no more than every 8 seconds.

Thanks to Jamie for his investigation into this. If the final conclusion is that a) the behaviour I'm seeing is indeed a genuine issue and b) the workaround is as Jamie describes then I'm not very confident it will be an effective workaround, although I do applaud whoever came up with it because it's probably the best possible workaround you could manage. I hope in wide-scale testing I'm proved wrong (although if you're hoping this approach is abandoned completely in favour of the decentralised Apple/Google solution, then perhaps you're hoping I'm right).

What happens when Apple fix this bug that allows Android devices to wake background processes on iOS, this is clearly a battery drain and should be fixed?

What happens when Apple fix this bug that allows Android devices to wake background processes on iOS, this is clearly a battery drain and should be fixed?

Yeah, if only Apple and Google had provided a proper solution to this problem ...

What happens when Apple fix this bug that allows Android devices to wake background processes on iOS, this is clearly a battery drain and should be fixed?

Read through this thread, the whole time thinking exactly that. Surely Apple are gonna close the workaround.

If Jamie is right then it’s not just Android devices that are the key to this - the idea is that any other device actively scanning for the service UUID should in theory wake the locked device - so Android devices or other unlocked iPhones. I don’t have any Android to test but will test this with an unlocked iPhone later. It’s hard to say if this is a bug in iOS or just expected behaviour.

commented

It's not a "workaround" or "bug", it's using a legitimate Apple Framework that allows your app to function as a peripheral.

This functionality is not limited to use by Android phones, it's also used by other iOS devices (where the app is running in the foreground) to detect the advertising iPhone . Hence why an iOS app that is open can also act as a bridge between two devices where the app is in the background. "Fixing" this could break many other apps that use this for good reasons.

The difference is in how iOS chooses to advertise services (requiring an extra step to discover them) and how it scans for peripherals in foreground (taking the extra step) vs background (just passively listening without taking the extra step).

commented

Of course the Exposure Notification framework from Apple and Google fixes this by advertising the relevant service without requiring any extra steps to discover it. The method is uses to do this is not available for regular apps.

Since the privacy preserving features from that framework or not compatible with the backend architecture of the NHS it will not be useable however for this app.

@thoutbeckers it absolutely is a workaround if you're using an API in the manner that the owners didn't intend. I can't believe Apple intended the peripheral API (something intended for communicating with devices) to be constantly scanning in the background. If they did, they wouldn't have put in the restriction in the first place. Apple absolutely don't care about breaking existing apps if they feel the user experience of their customers (battery life, security) is an issue.

Firstly, a good way to understand the behaviour of the App is to use PacketLogger (available in additional tools for Xcode), connect to one device (must have Bluetooth profile installed), and filter on "85bf" (the start of the identifier characteristic, which must be read when one device is discovering another). In this way you can determine if the released App is working in a given scenario.

If there is an Android in range it will wake the iOS App most of the time (unless advertising has been stopped by iOS after 1h50m of inactivity). However, it seems there is a bug in this which will prevent the Android App discovering iOS devices that have multiple background advertisements, although that is easily fixed.

The way the Android App discovers the iOS App is the way backgrounded advertising in iOS works, and preventing it would mean breaking the functionality between iOS devices. But I could see Apple going this far, because they are very careful about this stuff.

My testing showed that the one scenario it won't work is when there are only iOS devices in range and screens are off. Even tapping the screen to wake it is sufficient for the devices to communicate. People use their phones a lot, and there is probably an Android phone around most of the time, but this is one place where it will fail some of the time.

Another scenario is if the screen is on and the App has been in the background for a long time (>1h50m, perhaps overnight) then it will not reconnect to another backgrounded iOS device. There must be no Android phones in range for this to occur. I witnessed this with two iOS devices with the App were in range overnight. Presumably this is because the screens were off and so the Apps were not able to discover and connect to each other, so eventually the iOS background scanning/advertising timeout was triggered. If either device had been woken up before the timeout it may have been sufficient to refresh the App. iOS seems to give it 6540 seconds (1h50m) of background advertising time.

In summary it might work "well enough", but some contacts will definitely be missed. Will they accept that and continue anyway because the data they can obtain is "more useful"? Or will they switch to the Apple-Google model being standardised in several other countries in Europe? It seems like they may be reconsidering their position...

commented

@jdmwood2 That's true, but unintentional usage of an API would lead to an Appstore rejection, not a removal or "fix" of that API.

There are many (especially -but not only- background related) APIs that can be abused. You have to opt-in to them to use them, after which Apple is supposed to review if your usage is justified.

Apple seems to have chosen to allow this usage. Of course they can change their mind at a later point.

@thoutbeckers I've found Apple's approval process to be mercurial and inconsistent so under normal circumstances I wouldn't count on passing being a explicit approval. Having said that, this a high profile app so we'd hope they'd dig a bit deeper then usual. And yes, they might change their mind either intentionally or as a side effect of unrelated fixes

Apple of course have regularly approved apps only to reject them in a subsequent update.

I think it’s reasonable to say that the background peripheral functionality is being used in a way that is unintended - whether you’d go as far as saying it’s being misused or abused is down to Apple.

The background execution guide clearly states that a background app acting as a peripheral does not broadcast its service UUID in the advertising packet and is therefore only intended to be discovered by other iOS devices actively scanning for that service UUID (in the foreground). The fact that this keep-alive and the reverse engineering of the advertising packet for Android is necessary shows it’s not really intended usage - iOS apps aren’t really intended to spend all day advertising and scanning for others.

It’s also true to say that using the exposure notification API that Apple are working on isn’t a “fix” for this problem except for in a roundabout way. This could only be used with a complete switch from a centralised to a decentralised model which is a bit more work than simply swapping out the Bluetooth code. The two approaches are fundamentally incompatible with each other.

From Apple's docs:

  • Apps should be session based and provide an interface that allows the user to decide when to start and stop the delivery of Bluetooth-related events.
  • Upon being woken up, an app has around 10 seconds to complete a task. Ideally, it should complete the task as fast as possible and allow itself to be suspended again. Apps that spend too much time executing in the background can be throttled back by the system or killed.
  • Apps should not use being woken up as an opportunity to perform extraneous tasks that are unrelated to why the app was woken up by the system.

This App seems to fall foul of all of these, because of the keep alive mechanism which is an extraneous task that is unrelated to the reason it was woken up. But Apple does use its descretion with some Apps, and turns a blind eye. Particularly in this case you could see them continuing to let it slide unless it is having a significant impact on battery life. I imagine they will be actively testing that now.

commented

Rejecting you or me with a poorly written brief note is not something that will happen to the NHS I imagine. Also, it's pretty much known they are in contact, I can hardly imagine this being unknown to Apple.

I would more worry that the technical solution itself is far from sufficient. It's reliance on connecting devices (rather than just scanning them) will easily work in testing a situation with a few devices, but will likely fail with dozen of devices around you. Android devices will not properly handle dozens of simultaneous connection requests.

The lack of any support/documentation for automating testing such a scenario in the client strongly suggests to me it has not been done.

@nrbrook good to hear somebody else has been able to reproduce the third scenario in my testing above - it definitely seems related to a combination of the all being in the background and the device power stage. Were you testing on two iPhones? If so that rules out any possibility of me using an iOS affecting the test.

The issue with the advertising stopping completely after a period of time is concerning - it’s not something I’ve tested for but seems like yet another potential issue that could lead to missed exposure events.

The of course there’s the question of whether or not relying on the RSSI and TxPower of the devices as a measure of distance will even work correctly in the real world especially when there are obstacles and lots of other devices in the vicinity - imagine two people in a crowded train station. This is an issue regardless of what framework and approach you use.

Ultimately none of us can be sure how big of an impact this will have on the reliability of the overall process without real world testing - nobody has done this before. I’m just concerned that by that point it’s too late and any goodwill you might have had with getting the public to use this will be lost.

If they pull the plug on this app later and go with the decentralised approach (which isn’t without its challenges either) will it be too late to make a difference?

@lukeredpath I was testing with iPads and an iPhone, but I don't think the iOS background implementation is any different on iPad vs iPhone.

It is difficult to do "real world testing" with this, even in a small area like the Isle of Wight, even if you can get a good proportion of the population to install it, and even if people are still not too scared to go outside for the test to be useful. @thoutbeckers They could and should be doing automated testing yes, although perhaps have simply not had the time yet, based on the dates in the files they have only been building this for a month.

Ultimately this is a hacky solution and nothing is going to beat using Apple-Google's solution in terms of reliability. They are taking a real risk trying to roll their own, particularly if they are putting any kind of weight on it working successfully. However, I imagine they could just swap out the entire Bluetooth infrastructure in the current App with the Apple framework if (when?) they decide that will be a better route, so users would not need to install a different App.

So to summarize this thread NHSX have spent a month with GCHQ's help building a series workaround/hacks to the iOS <-> iOS backgrounding issue hoping Apple will give them a pass on background/keep-alive abuse (which they'll probably get) but without actually solving the problem? Anyone who's actually tried to build a cross-platform (iOS + Android) Bluetooth based app (I have) could have told them this right from the start and I'm sure the GCHQ engineers did.

Knowing this they've then assumed they'll get still mass adoption on something storing every UK citizen's data centrally and whilst also reducing battery level on their phones?

At least someone seems to have finally seen sense:

https://www.theguardian.com/technology/2020/may/07/uk-may-ditch-nhs-contact-tracing-app-for-apple-and-google-model

but the mind actually boggles at the decision making process on this project.

Please give up on the workaround / hacks and switch to the Apple + Google API's - the app may have half a chance of a) working reliably in the first place and b) being adopted.

So to summarize this thread NHSX have spent a month with GCHQ's help building a series workaround/hacks to the iOS <-> iOS backgrounding issue hoping Apple will keep them a pass on background/keep-alive abuse (which they'll probably get) but without actually solving the problem? Anyone who's actually tried to build a cross-platform (iOS + Android) Bluetooth based app (I have) could have told them this right from the start and I'm sure the GCHQ engineers did.

Knowing this they've then assumed they'll get still mass adoption on something storing every UK citizen's data centrally and whilst also reducing battery level on their phones?

At least someone seems to have finally seen sense:

https://www.theguardian.com/technology/2020/may/07/uk-may-ditch-nhs-contact-tracing-app-for-apple-and-google-model

but the mind actually boggles at the decision making process on this project.

Please give up on the workaround / hacks and switch to the Apple + Google API's - the app may have half a chance of a) working reliably in the first place and b) being adopted.

An admirably succinct summary.

@sanvean yes, it seems that way. I imagine the engineers got excited about the prospect of trying to hack around limitations, NCSC just focussed on the security aspect, politicians and scientists wanted the data, and nobody stopped to think "Is this actually the best way to save lives?"

Too busy asking themselves ‘can we do this’ and never stopped to ask, ‘should we do this ?’

I understand the lust for the data as I imagine it’ll be incredibly useful. In my opinion it’s usefulness doesn’t outweigh the risk of sacrificing users privacy. I don’t believe privacy should be a commodity and the longer we allow it, the worse it’ll become.

@sanvean yes, it seems that way. I imagine the engineers got excited about the prospect of trying to hack around limitations, NCSC just focussed on the security aspect, politicians wanted the data, and nobody was pragmatic enough to say "Is this actually the best way to save lives?"

Yeah I'm sure some hubris mixed in with the classic tech mindset of there's got to be a workaround along with no doubt some key decision maker somewhere objecting to be told what to do by Apple + Google (I wager that was a politician though not the development team).

No doubt that Big Tech currently hold far too much sway and power in the modern world but is right here, right now really the time to tackle that problem?

@sanvean In this situation the fact there are only two players in the mobile space meant that they could come together and quickly deliver a solution. Thankfully the solution they have developed is privacy preserving. Ideally, this should have had democratic and expert oversight too, because Apple and Google are effectively dictating how one of the main tools to get out of this crisis will work for the whole world.

I’m a bit concerned the thread is going a bit off-topic - I appreciate the discussion around whether or not the centralised approach and rolling their own was the right approach to take is an important one to have but would be good if we could keep this issue focussed on the technical problem raised.

Hi Luke,

Yes I guess it has but the issue is that there isn't a reliable solution to the problem you've identified (even keep-alive workarounds) which is exactly why other countries have abandoned this approach. It's not actually a debate on the centralised vs. decentralised approach - the discovery method will not work reliably for iOS <-> iOS background discovery.

Apologies if I'm coming across as strongly opinated on the topic, it's because a) I have actually built an app attempting to solve a similar problem cross platform (think Happn but using Bluetooth) and b) my mother is currently working on the NHS Frontline and directly impacted by the decision making on this project.

Anyways, I very much appreciate the effort here by you on investigating the app in-depth and highlighting this technical issue to the dev team, as I was planning to spend today doing the same now the code was open source as I couldn't believe they'd figured out a way to solve the problem unless Apple had changed something - they haven't based on your analysis.

Thanks - your input has still been very helpful. I’m still hopeful we might get some input from either the devs or decision makers on this issue - if the code is going to be open-sourced this way then it has to be expected that people will raise issues for discussion and I hope that they are able to engage with us.

Thanks - your input has still been very helpful. I’m still hopeful we might get some input from either the devs or decision makers on this issue - if the code is going to be open-sourced this way then it has to be expected that people will raise issues for discussion and I hope that they are able to engage with us.

I couldn't agree more and I applaud your efforts here. However, if there is no real response or solution presented in the coming days I honestly think this thread will need to be published or shared more widely until it does receive the attention it deserves.

This is not a normal Issue you have raised here, the UK's economic recovery and lives are at stake. The app needs to work reliably in all common circumstances and as you have clearly highlighted -it doesn't currently.

Thanks to everyone for their hard work looking into this. It was one of my biggest questions when I heard about the app so it has been great to see everyone dig into the code and piece it together.

I put together a quick matrix showing the possible situations and where it works/doesn't work based on this issue. Does it look correct? I was getting a bit confused with all the possible scenarios, hopefully if it is correct then it will be useful for other folks to understand the limitations.

btle-matrix

Nice work all!

I would also just add that beyond the fact that the app definitely seems to not work in the case shown in the above matrix (nice summary @stephenheron ), it's also worth challenging whether it will work in practice even for the green blocks in that matrix.

I've tried to capture my thoughts in a comment in #7 (not sure if that was the best place). But in brief, will using devices as a peripheral actually work when there are dozens of devices all making concurrent connections with each other (sounds like a exponential problem to me). Will my device stop connecting if > N people are nearby? How will ancient Android devices loaded with manufacturer bloatware cope?

The peripheral API was not presumably designed for this, unlike, say iBeacon (though as others have pointed out, that would still suffer from even worse iOS backround issues).

I think Android (Foreground) vs iOS (Background) is Y in first column, also debatable whether Android (Background) vs iOS (Foreground) is a workaround you as just depend on the Android device discovering the iOS Device it should work pretty reliably.

Bottom right is the Workaround/Non-Reliable box. As I understand it if an Android device is near 2 backgrounded iOS devices it can wake them up to discover them (and they discover each other).

https://www.theguardian.com/world/2020/may/06/critical-mass-of-android-users-needed-for-success-of-nhs-coronavirus-contact-tracing-app

However say 2 random people with backgrounded iOS devices decide to sit next to each other on a park bench with no-one else around, the devices can't discover each other reliably.

Just a thought - could it be kept alive by doing something, e.g. playing blank music?

Nice work all!

I would also just add that beyond the fact that the app definitely seems to not work in the case shown in the above matrix (nice summary @stephenheron ), it's also worth challenging whether it will work in practice even for the green blocks in that matrix.

I've tried to capture my thoughts in a comment in #7 (not sure if that was the best place). But in brief, will using devices as a peripheral actually work when there are dozens of devices all making concurrent connections with each other (sounds like a exponential problem to me). Will my device stop connecting if > N people are nearby? How will ancient Android devices loaded with manufacturer bloatware cope?

The peripheral API was not presumably designed for this, unlike, say iBeacon (though as others have pointed out, that would still suffer from even worse iOS backround issues).

For iOS there is a concurrent limit not sure on Android:

https://stackoverflow.com/questions/13469502/maximum-number-of-peripherals-on-corebluetooth

If the iOS Bluetooth code is well written you should still be able to cycle through a higher number of devices quickly it's just a concurrent connection limit. Not sure what happens when you put several hundred devices close together (but I'm sure that what's Apple and Google will have been testing and optimising for in the last month, hopefully NHSX too).

@sanvean in my experience having done similar stuff for a startup, reality doesn't often match theory. As you say, let's hope NHSX have done scientific tests to validate precision/recall in realistic situations and not just said "works in the lab so should be fine". Not sure the Devs have released any info on how they have/are/will validate the accuracy.

btle-matrix

It's not quite as simple as iOS (background). It does work with both iOS devices in the background without workarounds if the advertising timeout has not occurred and the user has not quit the App, as long as the process is running. If the process is not running, it might not work if both screens are off, because sometimes iOS stops scanning or has a large scanning/advertising interval. But even waking the screen without unlocking is sometimes sufficient to make it work again.

Just a thought - could it be kept alive by doing something, e.g. playing blank music?

You could but you'd have to convince Apple why you needed the "play audio" permission. Again, maybe they'll allow this under the circumstances

I have summarised my findings and understanding so far in this blog post https://www.nrbtech.io/blog/2020/5/8/understanding-the-nhs-contact-tracing-app
I will continue to attempt to understand when the advertising timeout is reset.

The matrix is close but in my observations the screen being off is also a key variable - see the scenarios tested above.

Also, the iOS background - Android foreground/background cells should be yellow - this only works because they are reverse engineering the advertising packet that iOS used in the background (the one where the service UUID is hidden). Any reliance on an undocumented feature should be considered a workaround.

Just a thought - could it be kept alive by doing something, e.g. playing blank music?

This would almost certainly fall foul of App Store rules.

I have summarised my findings and understanding so far in this blog post https://www.nrbtech.io/blog/2020/5/8/understanding-the-nhs-contact-tracing-app
I will continue to attempt to understand when the advertising timeout is reset.

Small correction to your post - there is a local notification scheduled to ask the user to relaunch the app. It’s scheduled in the willTerminate app delegate method though and I’m not sure how reliably this will work. It seemed to work consistently for me when I manually killed the app.

Just a thought - could it be kept alive by doing something, e.g. playing blank music?

This would prevent the app from working while using another app to play music? Now if they embedded a free Spotify into the app, that would solve the backgrounding issue and also increase uptake... ;-)

@lukeredpath @nrbrook Thank you for the feedback on the matrix, I have added a new column for "iOS Worst case scenario" to try and cover things like "Screen off, App Killed, Advertising window expired. Do we think that more accurately describes what we are seeing? Once an iOS device falls into this "Worst case scenario" can any bluetooth iteraction bring it back into life or does a user need to unlock their device for example?

image

commented

It's not quite as simple as iOS (background). It does work with both iOS devices in the background without workarounds if the advertising timeout has not occurred and the user has not quit the App, as long as the process is running.

This is a bit of a semantic discussion that occurs on this topic. It's true while running in the background it will detect it. But "background" for most people colloquially stands for suspended in the background. Passive scanning will still work while suspended, but not for other iOS devices also suspended in the background, even within the advertising timeout. This is confirmed by the tests people have done in this issue.

To complicate things further, there is a narrow case I've not tested (I've also not tested this app specifically as others have) where both iOS devices support extended advertising (a Bluetooth 5.0 feature). As far as I know that's only the iPhone XS, the latest iPad Pro, and I assume the new iPhone SE. Apple kind of promised this would work, but I've not seen that confirmed.

@stephenheron I'd split it into iOS (process running in background), iOS (advertising before timeout) and iOS (not advertising). The first column would be the same as iOS (Background). The second column is "maybe" for all other device cases, because there is no guarantee about advertising or scanning frequency (I can see advertisements go up to 5 minute intervals, and I think the reason waking the screen sometimes works is because it decreases the interval substantially). The third column is "no" for all.

To complicate things further, there is a narrow case I've not tested (I've also not tested this app specifically as others have) where both iOS devices support extended advertising (a Bluetooth 5.0 feature). As far as I know that's only the iPhone XS, the latest iPad Pro, and I assume the new iPhone SE. Apple kind of promised this would work, but I've not seen that confirmed.

I am testing with the iPhone 11 Pro and an iPad pro which are sending extended advertisements, but I don't think this changes anything.

commented

@stephenheron an unqualified statement that iOS background <-> iOS background works (green) is disproven by the original post in this issue. Regardless of the advertising timeout. But this is also disproven by every other non-Apple/Google tracing app (dp3t, TCN, etc) I know of.

All the tricks in the code are to unsuspend the app using an external source (like an Android device being nearby), but the "natural" state when in the background is that the app is suspended, only passively scanning, and unable to detect other iOS apps suspended in the background.

@thoutbeckers The App does work in the background on two iOS devices when the process is running or advertisements are still active, however if advertisement intervals are large it may not work in practice. But I can see it working by putting the app in the background and then toggling bluetooth on one device and watching for the data sent between the devices on the other device.

commented

Our observations were that the app worked in the background for iOS <-> iOS devices after ~30 minutes of not seeing each other.

commented

There are of course failure cases, but in our experience, it should work fine if enough people use it.

@jdmwood2 Yes – removing and reinstalling the App should simulate this I think, if the devices are not also linked by an existing pairing or your Apple ID (which may cause connections regardless of App state). Doing that while bluetooth is off, and then enabling bluetooth while the App is in the background I can see that the other device with the App in the background does discover it.

@jamiebishop do wake-ups propagate as a network effect? E.g. if my iOS device is "woken" by an Android device, but is still in background, assuming it's alive for the timeout period can it wake up other background iOS devices, and those can keep alive others and so on?

commented

I assume you have the screen on when you toggle Bluetooth @nrbrook

The evidence is either replicating the actual user situation as described by @jdmwood2 , or for a technical proof an HCI packet dump of an iOS device with the screen of, and the app suspended in the background showing it's somehow advertising the service id or some other information that wakes up the second device, or another iOS device with the screen off and the app suspended requesting doing a scanrequest for a scanrecord with the additional service information.

AFAIK no one disputes there are many tricks are in the codebases to try and take advantage of some deviation of that test scenario, but that's what they are.

commented

@jdmwood2 I haven’t had a chance to test that and I don’t think I will. Unfortunately I’ve got to move onto other work now, but I’m sure someone here probably will try that out.

My concern here is that even if it can be shown that background works under certain circumstances (and I feel like the exact answer is still under debate in this thread), I'm far from clear whether it works under realistic situations. And this question is important: the media message in many publications is "background works on iOS". If that is based on some simple lab experiments for which we've only heard second hand reports, it not hugely reassuring (@lukeredpath work is the first which seems to go into detail and with code). I would love to hear from the developers exactly under what circumstances they think it should work and how they verified that.

There are of course failure cases, but in our experience, it should work fine if enough people use it.

Failure cases that are completely avoidable if NHSX choose to use the new APIs Apple + Google are providing rather than relying on iOS background grey areas and/or hoping an Android device happens to be in vicinity when two backgrounded iOS device meet each other.

commented

@sanvean iOS 13.5 isn’t even available yet, and may not be for another week or two. NHSX are ready to ship now.

Multiple media reports already point to the fact that they’re investigating moving. This is probably a fine solution in the meantime

@sanvean iOS 13.5 isn’t even available yet, and may not be for another week or two. NHSX are ready to ship now.

Multiple media reports already point to the fact that they’re investigating moving. This is probably a fine solution in the meantime

I think we'll have to disagree on the definition of fine here, there's a common real world interaction where the app does not function as intended or as people will expect it to based on media reports. For example amongst my family + friends in London I don't actually personally know a single Android device owner. I'm a minority? Yes but a significant one in a major global Coronavirus hotspot.

As mentioned in my original comment above this iOS <-> iOS Background detection issue is well known to any developer who has built a half decent iOS Bluetooth app. The dev team will have known this a month ago and they could have decided to switch over to Apple / Google API's sometime ago after some basic due diligence rather than sticking to testing and rolling out this ultimately flawed approach.

Also while NHSX may technically be ready to ship now but I doubt parliament will let them ship this out in its current state given the separate privacy issues.

I agree with @sanvean. There is also a public trust issue: releasing an app which kinda works is not great even if it gets fixed in a few weeks. People may well be adjusting their behaviour assuming the app will give them some measure of protection (or at least clarity) and won't know that it might not be working at all for them. This is a huge deal for a health app.

The government will also get exactly one (1) go at this. If there is negative feedback for a month
due to battery usage or failure to work it's going to be hard to persuade people to try again because "now it works properly, we promise".

@jdmwood2 I agree: although I can see it does work in some specific test cases it’s much harder to know how well it would work at an event of 50+ people. The theory looks ok but the practical usability of it is unknown and there is no guarantee they will get any good information about how well it has worked based on a limited study on IOW. How will they know if contacts failed to be detected?

From what we know they have not started work on an Apple-Google decentralised solution yet (would seem sensible to be doing this in parallel) so we would be behind other countries if we decide to switch.

commented

As mentioned in my original comment above this iOS <-> iOS Background detection issue is well known to any developer who has built a half decent iOS Bluetooth app.

Sure, but they have a workaround and we've observed it working under a lot of scenarios. iOS 13.5 doesn't exist yet, so do you just want them to do nothing in the meantime?

Sure, but they have a workaround and we've observed it working under a lot of scenarios. iOS 13.5 doesn't exist yet, so do you just want them to do nothing in the meantime?

Yes! That it works in some scenarios is no comfort to someone who falls in the cracks of "not working" (I'm still not clear exactly when this would be. If it really is the iOS <-> iOS background case - and I appreciate there is disagreement about that - then that is a huge hole).

Sometimes the least harmful act is to do nothing (isn't that a medical saying?)

Just a couple of extra observations to add:

  • I know some other people have been testing this working based on observations of the device logs but it seems to me that just because bluetoothd might be doing some stuff on each device, it doesn't mean that its propagating up to the app layer in the form of a didDiscover delegate call - that's why I really wanted to test this with breakpoints/logging with the real source code.

  • One odd behaviour I noticed is that with the iPad app in the foreground, it would discover the iPhone app even if I manually killed the iPhone app. My understanding that terminating the app in this way would stop all Bluetooth advertising but it didn't seem to.

  • I know some other people have been testing this working based on observations of the device logs but it seems to me that just because bluetoothd might be doing some stuff on each device, it doesn't mean that its propagating up to the app layer in the form of a didDiscover delegate call - that's why I really wanted to test this with breakpoints/logging with the real source code.

I was watching reads from the characteristic - iOS wouldn’t do that on its own.

  • One odd behaviour I noticed is that with the iPad app in the foreground, it would discover the iPhone app even if I manually killed the iPhone app. My understanding that terminating the app in this way would stop all Bluetooth advertising but it didn't seem to.

This doesn’t match my observations, killing the app prevented it from working

Call me crazy, but perhaps we could improve the situation greatly by buying a load of cheap Android phones and putting them in key locations. An Android phone at a supermarket entrance that woke up all the iPhones is a lot better than relying on customers to wake each other.

Call me crazy, but perhaps we could improve the situation greatly by buying a load of cheap Android phones and putting them in key locations. An Android phone at a supermarket entrance that woke up all the iPhones is a lot better than relying on customers to wake each other.

Given one of the theoretical privacy risks with a centralised model is re-identification and tracking of location through the use of a network of Bluetooth beacons, this would probably be a bad idea.

True, and I understand that, and I think it's worth it, but in this example your location is already known via your card payment.

Call me crazy, but perhaps we could improve the situation greatly by buying a load of cheap Android phones and putting them in key locations.

Also, then you run the risk of people who have never been in contact getting exposure notifications from each other, simply because there was a non-human Android device that linked them both

Good point, it would need an "I don't count" flag.

True, and I understand that, and I think it's worth it, but in this example your location is already known via your card payment.

That's a common misconception. Stores are banned by MasterCard and visa from using the card number for anything other than card payment. They aren't allowed to hash it and use that to link purchase to a single customer. That's what store loyalty cards are all about.

I didn't say stores did that. Just pointing out that an Android phone performing that specific function wouldn't be the only way for someone to know you were in a supermarket.

commented

What happens when Apple fix this bug that allows Android devices to wake background processes on iOS, this is clearly a battery drain and should be fixed?

I think Apple would suffer a lot of bad publicity if they chose this particular time to make any changes to this area.

Hi all. Just to let you know, we are reading this thread carefully. One of the aims of the Beta is to see how this works in the real world. We ran several test at RAF Leeming and updated the code based on that.

As we learn more about how this performs - we will get an idea of how likely people are to never see an unlocked iPhone and never see an Android phone. And all the other edge cases.

I want to thank you all for this illuminating, and well-mannered discussion. We're doing our best to get this right, and it's brilliant to see that so many passionate and committed people also care.

Have a great weekend.

@edent thanks for taking time out to keep us updated - it’s good to know that are concerns are being addressed.

This problem isn’t unique to the NHSX app of course. Competing frameworks like DP-3T will have the same Bluetooth issues (until they switch to the Apple exposure framework anyway).

It’s also not the only potential issue - nobody can be sure how effective Bluetooth will be at accurately determining proximity on a reliable enough basis in real world scenarios. Only time will tell with that one.

I think we would all welcome an explanation/some kind of confirmation from the development team that the app is relying on seeing other actively scanning devices to keep them alive enough to detect other locked iOS devices and how well this workaround performed in testing.

Thanks @edent. We hugely appreciate your input and from the sounds of things the app is well written considering the challenges you must have faced.

It would amazing to get details about the results of the RAF test and how it was measured. For example, did it simulate carriers of the virus and measure a precision/recall score for how well those carriers were detected by other phones?

Perhaps related to this is #7: a beta test in the wild is great but but how will you know how well it is actually performing? What would the ground truth be in the Beta and how do you plan to measure against it?

This particular issue (whether the iOS reliably detects) is important, but I think equally important is the broader question about how reliably detections are made overall (i.e. false positives vs. false negatives).

If, for example, the app regularly falsely detects someone who walked past me 20m away, then the fact that is reliably running in the background on iOS is a moot point.

EDIT: perhaps I should start a new ticket to keep the broader accuracy question separate from this issue?

The risk of releasing the app to the UK then being impacted by an iOS/Android update is worrying.

We know updates are coming as Apple/Google roll out their contact-tracing API. It's likely that these updates will change the way this issue works, presumably the whole world would have the same problem if they didn't change it somehow.

We know a substantial percentage of phones don't receive updates anymore, so both patched/unpatched devices need to be supported. We should do everything possible to improve the ecosystem for the next time we need it.

@billysielu, you make an important point about the incoming updates to iOS and Android.

This definitely needs to support as many devices as possible. If the contact-tracing APIs roll out and are adopted here and in the Android app, we need to make sure to keep the current tracing systems in here for older phones that do not receive patches anymore.

We also need to make sure that any security/other issues that arise in the current system are dealt with, even if the system changes to the contact-tracing API (to continue the support to older devices).

OK, finally getting around to testing this scenario where a third device can potentially wake up two sleeping iOS devices - using my failing scenario 3 above as the starting point. I don't have any Android device so I'm using a second iPhone as the additional test device.

Initial setup:

  • A fresh install of the app on both iPhones - each one is built, run, set up (go through the activation screens), confirmed that the Bluetooth is working and scanning before killing the app and turning on Airplane mode. Neither iPhone is connected to Xcode.

  • A fresh install of the app on the iPad, connected to Xcode. Set up as above, confirmed Bluetooth is scanning and that it has not yet seen either iPhone.

Test process and results:

  1. Close (not kill) the app on the iPad, confirm from logs that the didEnterBackground app delegate method has been called. Lock the device.

  2. Unlock iPhone 1 and turn off Airplane mode. Confirm no discovery or any Bluetooth chatter happening on iPad from log output. Locked iPhone 1 - so far as expected.

  3. Unlock iPhone 2. Turn off Airplane mode. As above, no discovery made or any Bluetooth chatter happening on iPad. The iPad has not seen either iPhone at this point. I cannot at this point confirm if the two iPhones have seen each other.

  4. Bring the app on iPhone 2 to the foreground. Can confirm from iPad log that it has discovered iPhone 2 and is now sending the keep-alive ping-pong.

  5. Crucially, the iPad has still not discovered iPhone 1.

  6. Turn on Airplane mode on iPhone 2 again, I can see from the iPad logs that iPhone 2 has disconnected from the iPad. No further Bluetooth activity is seen.

  7. With Airplane mode enabled on both iPhones I re-enable Wifi only (no Bluetooth), so I can safely download the app container in Xcode without any further Bluetooth communications interfering with the test.

I've included the contact events below, output from contactEvents.plist using plutil - each element in the root array represents a contact event with a single device:

iPad (locked):

[
  0 => "05485B5B-A0A2-D028-B39D-74EBFD79A4EF"
  1 => {
    "duration" => 16.20907604694366
    "rssiIntervals" => [
      0 => 0.1024460792541504
      1 => 1.082057952880859
      2 => 15.02457201480865
    ]
    "rssiValues" => [
      0 => -34
      1 => -59
      2 => -40
    ]
    "timestamp" => 2020-05-11 13:13:51 +0000
    "txPower" => 12
  }
]

iPhone 1 (locked):

[
  0 => "6D63FE18-70AF-A52E-A439-1C86173E3993"
  1 => {
    "duration" => 30.2186450958252
    "rssiIntervals" => [
      0 => 0.4501190185546875
      1 => 1.030978083610535
      2 => 7.027959942817688
      3 => 8.256847023963928
      4 => 13.45274102687836
    ]
    "rssiValues" => [
      0 => -46
      1 => -48
      2 => -51
      3 => -50
      4 => -51
    ]
    "timestamp" => 2020-05-11 13:13:50 +0000
    "txPower" => 12
  }
]

iPhone 2 (unlocked, app in foreground):

[
  0 => "C692F3FA-735C-4E5F-F081-20942CE88E99"
  1 => {
    "duration" => 64.41311502456665
    "rssiIntervals" => [
      0 => 1.257708072662354
      1 => 1.007640957832336
      2 => 6.151409029960632
      3 => 16.07540798187256
      4 => 39.92094898223877
    ]
    "rssiValues" => [
      0 => -47
      1 => -58
      2 => -60
      3 => -53
      4 => -41
    ]
    "timestamp" => 2020-05-11 13:13:51 +0000
    "txPower" => 12
  }
  2 => "5DD6B3F9-F01E-880F-E0E4-BA70E7FB77AE"
  3 => {
    "duration" => 24.76079702377319
    "rssiIntervals" => [
      0 => 0.4329550266265869
      1 => 1.080896019935608
      2 => 1.006325960159302
      3 => 6.165926933288574
      4 => 16.07469308376312
    ]
    "rssiValues" => [
      0 => -52
      1 => -47
      2 => -41
      3 => -53
      4 => -51
    ]
    "timestamp" => 2020-05-11 13:13:50 +0000
    "txPower" => 12
  }
]

Summary

What does this show:

  • iPad has recorded a single contact event for iPhone 2.
  • iPhone 1 has recorded a single contact event for iPhone 2.
  • iPhone 2 has recorded two contact events, one for the iPad and one for iPhone 1.
  • The iPad and iPhone 1 have not recorded a contact event for each other, nor is there any sign in the logs that either discovered the other one.

On the basis above, it seems to me that the theory that another actively scanning device (in this case another iPhone with the screen unlocked and the app in the foreground) will cause two sleeping devices to see each other is false. It may still be the case that an actively scanning Android device will trigger this but I'm unable to test this.

Personally I've always been skeptical that this workaround actually worked. If you think about it, even if another device (the second iPhone in my test, or an Android) establishes a connection to one sleeping iOS device, why would that mean it suddenly sees another sleeping iOS device when it didn't before?

Just because the first iOS device has a Bluetooth connection to the third device, its screen is still locked and the app will still be running in a background state. Nothing has really changed. It is true that if the app on the first device has entered a suspended state that the Bluetooth connection will wake the app up and re-launch it into a background state but the issue of two devices not seeing each other is happening when the app is already launched in a background state (I'm running my tests before the apps have had enough time to be suspended).

Nice work @lukeredpath. I shared your skepticism that this could ever work. Others have reported that it does work, but they didn't show their workings as you have here (and it's so easy to miss an important step) so I remain skeptical (of the iOS <-> iOS workaround) until they can share more details.

Just a thought, it would be great to rule out iPad and xcode by running this same test with 3 iPhones, none connected to xcode. When I've done this approach in the past, I've dumped debug info to an on-screen text box to allow debugging without an xcode connection. But obviously this is a lot more work. Just a thought.

PS I'm not an iOS hardware expert so perhaps some might say that an iPad is identical enough and that xcode debugging shouldn't make a difference. Not sure...

@lukeredpath What iPhone/iPad models were you using? What was the battery level of the phones? Did they have other Apps running? Did they have other Apps advertising in the background? How long was the delay between backgrounding the App and performing a test? How long did you wait before assuming the device could not be discovered? All these are important factors in determining if a device is working in the background, because background scanning and advertising is variable based on some algorithm that Apple doesn't disclose but will take in to account all these factors in deciding intervals and therefore discovery time.
As a 'real world' test, I'd suggest leaving them for at least 5 minutes before assuming that it isn't working. I prefer testing the App store App (I detailed how in previous comments) as there could be code improvements not released on github, or there could be some issue with how you are building and running the app (particularly the ability to test with the real notifications).
Personally I can't test with devices which haven't seen each other before, but if we assume that deleting the App is sufficient to replicate that then it does work in the background for me with two iOS 13 devices almost immediately when the process is still running in the background on both devices.

@nrbrook could you share your experiment details in a similar manner to above so others can replicate?