Repository navigation
fix(bindings): serve the Hello characteristic from the iOS peripheral - #636
Merged
Merged
Conversation
A central that cannot be dialled back was never named by an iPhone. The iOS peripheral served Message, Device id, Identity and App tag, so a central writing to it had every fragment queued behind ensureDeviceId, which dials the central's address to read its identity. That costs seconds to tens of seconds for an Android phone whose advertising address rotated, and never completes for a central with no GATT server at all, such as a browser over Web Bluetooth. The peripheral now serves Hello, as Android and the Python service do. A write is answered through BlePeripheralHelloPolicy before anything is verified: a long write, a non-zero offset or a length outside 96 to 512 bytes is refused. An accepted hello is verified with the one verifier, binds the central to the derived address only, never rebinds a link bound to another peer, flushes the per-peer MTU before announcing the peer once, and then delivers the fragments the central wrote before it said hello. react_native_ios_peripheral_serves_hello pins the service list, the verdict routing and the verify-then-bind and MTU-then-announce orders; it fails when Hello is dropped from the service.
…ied path react_native_ble_announces_only_cross_checked_addresses required exactly one blePeerDiscovered call in BleManager.swift. The peripheral's verified hello is a second verified path in the same file (Android keeps its own in BleTransportFacade.kt, outside the file the guard counts). The guard now requires exactly two in Swift, one in completePeerHandshake after the cross-check and one in handleInboundHello after verification, and fails on a third call site anywhere.
Three guards each carried their own copy of "find this Swift signature, cut at the next function". Two of them knew that a body ends at a declaration of any visibility. The hello guard did not: it cut at the next `private func` only, and the declaration after the hello handler is a `public` delegate method. So its "body" ran on through the subscribe and unsubscribe handlers, and an assertion about the hello path would have passed on code in either. This is not great. A guard that reads more than the function it names passes on things it was written to refuse. Lift one slicer beside the Kotlin one and use it everywhere. It panics when a signature is gone, as the copies did, so a rename still fails loudly instead of slicing an empty string.
A hello makes a kind of link the iOS bridge never had: a central named by what it wrote, with no peripheral-role link behind it. The previous commit built how that link is born. Nobody built the rest. It turns out the rest was being left to code that assumed every named central had also been dialled back: - The mesh controller counted the link only from the pending- fragment drain, and that returns early when nothing was queued. A central that says hello before its first write, as the chapter orders, has nothing queued. So the link never counted against the connection budget and the advertisement never refreshed. - Every "peer lost" report comes from a central-manager disconnect. A browser has none. When it went away, the core kept it as a neighbour, kept its MTU, and went on sending to a link that was gone. Confusion ensues. - Subscribing still dialled the central back at once, racing the hello it was about to write: ten seconds of futile dial to a rotated Android address, and for a browser a dial that found no service and cancelled the connection the browser was using. - A central turned away at subscribe time for cap or budget was still named and announced, with no notify target behind it. So both verified paths now count their link through one helper, unconditionally, and that also cancels any pending loss report. A central leaving schedules a loss report 15 seconds out, as Android does, which fires only if no link to the peer survives. That means checking the departed identifier too, because it can also be our own still-connected dial, and the link-resolution helper excludes it on both roles. Subscribing waits two seconds for a hello before it dials, and the dial re-checks the binding when it runs. A hello from a central we did not keep is ignored, as Android ignores one from an address with no server connection. Every shipped central (iOS, Android, Python and the browser carrier) subscribes before it writes its hello, so the subscription check refuses nothing legitimate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
An iPhone could not name a central it cannot dial back. The iOS peripheral served Message, Device id, Identity and App tag but not Hello. Every fragment a central wrote was queued behind
ensureDeviceId, which dials the central's address to read its identity.Android and the Python service already serve Hello. The iOS central already writes it.
What changes
BlePeripheralHelloPolicy. It sits beside the central'sBleHelloPolicyand refuses a write before anything is verified when it is long, offset, or outside 96 to 512 bytes. CoreBluetooth hands a long write over as several requests in one call, so more than one Hello request in a call counts as long.handleInboundHello. It is the iOS copy of Android'shandleInboundHelloOnBleThread, in this order:Testing
swift test --package-path bindings/react-native/ios: 497 pass, three of them new for the peripheral policy.react_native_ios_peripheral_serves_hellois new. It pins the service list, the verdict routing, and the verify-then-bind and MTU-then-announce orders. It fails when Hello is dropped from the service.Not done