feat(ble): negotiate an explicit per-peer fragment ceiling - #1671
Open
Chessing234 wants to merge 3 commits into
Open
feat(ble): negotiate an explicit per-peer fragment ceiling#1671Chessing234 wants to merge 3 commits into
Chessing234 wants to merge 3 commits into
Conversation
The `.privateMedia` capability bit tells a sender that a peer understands encrypted media. It says nothing about how much of it that peer can hold, so senders have had to infer the ceiling from the packet type instead. Add TLV 0x03 to the authenticated peer-state packet: a 2-byte big-endian count of fragments the peer will reassemble for one packet. Absent means "not advertised", which is what every already-released client sends, so old peers keep decoding unchanged. An unreadable 0x03 (zero, wrong width, duplicated) is rejected rather than skipped like an unknown TLV. Skipping it would read as "said nothing", and "said nothing" falls back to the permissive proxy — the opposite of what a peer sending the field is asking for. Name the receiver's own 10,000-fragment guard while here, so the number we advertise and the number we enforce cannot drift apart.
Turn the inline type check in broadcastPacket into a value: given the packet type, whether it is directed, and whatever the recipient advertised, decide the fragment bound and say which of the three reasons produced it. An advertised ceiling replaces the proxy in both directions. Below 256 is the case the proxy gets wrong silently — we plan fragments the peer drops and see no local failure. Above 256 is a peer telling us the deployed Android assumption behind that cap does not describe it. Every decision is clamped to what we would reassemble ourselves, so one side raising its configuration cannot raise the other side's memory exposure. No caller yet; this commit is the decision and its tests.
Advertise our own reassembler's bound in authenticated peer state, keep each peer's advertised bound pinned to the generation that stated it, and size outbound transfers against it. Two behaviour changes fall out of this: The preflight no longer only inspects directed `fileTransfer`. An advertised ceiling constrains encrypted media too, and a small reassembler behind the 0x20 path is exactly the case the type proxy cannot see. Peers that advertise nothing keep the old classification, so released clients are unaffected. The rejection message carries the real limit instead of a hardcoded 256, which is no longer always the number in force. The existing key keeps every locale's translation with only the numeral parameterized. Retires TODO(permissionlesstech#1434).
Contributor
Author
|
one allocation note while this is open, since i only spotted it reading the rotation spec.
worth writing the number down somewhere either way — the packet's TLV space currently has its assignments only in the struct itself, which is how two features end up reaching for the same byte. (the |
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Closes the
TODO(#1434)left inbroadcastPacketwhen the blanket 256-fragment preflight was narrowed.The gap
Today the fragment bound for a directed transfer is inferred from the packet type: a directed
fileTransferis assumed to be the raw migration fallback aimed at current Android (256), and anything else is assumed to be aimed at a client whose reassembler is as large as ours. That proxy holds only while "implements the encrypted0x20path" and "has a large reassembler" describe the same population.A client that adopts
0x20with a smaller buffer breaks it in the silent direction: we plan fragments the peer drops, and the sender sees no local failure..privateMediasays a peer understands encrypted media; it says nothing about how much of it that peer can hold.The change
0x03onAuthenticatedPeerStatePacket— a 2-byte big-endian count of fragments the peer will reassemble for one packet, stated inside the established Noise session. Absent means "not advertised", which is what every released client sends, so old peers decode unchanged.0x03(zero, wrong width, duplicated) is rejected rather than skipped like an unknown TLV. Skipping would read as "said nothing", and "said nothing" falls back to the permissive proxy — the opposite of what a peer sending the field is asking for.BLEFragmentCeilingPolicyturns the inline type check into a value: packet type + directedness + whatever the peer advertised, out comes the bound and which of three reasons produced it. Framework-free, so every branch is tested directly.10_000guard is now a named constant used for both the guard and the advertisement, so they cannot drift.Two things worth reviewing closely
The preflight is no longer restricted to
fileTransfer. It could not stay restricted and still do its job: an advertised ceiling constrains encrypted media too, and that is the case the proxy misses. Peers that advertise nothing keep the exact old classification, so this is a no-op for every released client.The rejection copy is now parameterized. It said "more than 256 mesh fragments", which stops being true once the ceiling is negotiated. The existing key is kept and only the numeral replaced, so all 30 locales keep their existing translation. One judgment call I want flagged: Turkish attached a vowel-harmonised ablative suffix to the numeral (
256'dan), which no format specifier gets right for every value, so I rephrased that locale to a suffix-free construction (mesh parça sınırı: %lld). That is my edit, not a translator's — please have someone who reads Turkish check it.Verification
beginAuthenticatedGenerationdrops the observation, and the accessor refuses one pinned to a superseded generation. Removing either alone still passes; the test fails once both are gone, and it was the only failure in that run.ChatViewModelInitializationTests,BLEServiceCoreTests) failed once each on runs that took 47s and 63s while the machine was loaded, and did not reproduce in four subsequent clean runs of the full suite or in isolation. Timeout-sensitive, not this diff — but I am flagging them rather than filtering them out.--strictrules by hand against.swiftlint.yml; onetrailing_commaviolation found and fixed before pushing.What I did not verify
No device or simulator run, so the negotiation is verified by unit tests and by reading the wiring, not by two real handsets agreeing on a ceiling. Android does not send
0x03yet, so in the field today every peer takes the unchanged fallback path — which is the safe way for this to land, but it also means the negotiated branch has no cross-platform exercise until an Android client advertises one.