fix(ble): size notification chunks for the MTU that was negotiated, not 512 - #93
fix(ble): size notification chunks for the MTU that was negotiated, not 512#93ehsan6sha wants to merge 1 commit into
Conversation
…ot 512
The Bluetooth logs panel showed its two headers -- "=== Docker Logs ===" and
"=== System Logs ===" -- with nothing under them. The reply was arriving as
damaged JSON, failing to parse, and leaving an object with no `docker` and no
`system` key for the client to render.
Every chunk goes out as a D-Bus PropertiesChanged "Value", and BlueZ silently
truncates that to the NEGOTIATED ATT MTU minus 3. `BLEResponseHandler` assumed
512 -- the largest MTU anyone negotiates, not the smallest -- and nothing here
can see the real one: service.py implements none of the BlueZ APIs that report
it (no AcquireNotify, no MTU property), so there is no value to read and no
error when a chunk is cut short.
With base_overhead 47 and the old halving, chunks came out at ~278 bytes. A
phone that settles on ATT_MTU 247 -- completely ordinary on Android -- cuts
those at 244. Every chunk lands damaged, and the reply can never be
reassembled.
assumed 512, //2 chunk ~278 B truncated at 244
assumed 512, no //2 chunk ~509 B truncated at 244 (worse)
assumed 185, no //2 chunk ~182 B fits
So the fix is NOT simply dropping the `// 2`. Doing that alone doubles the
chunk size to ~509 B and breaks the transfer on every link below 512 -- the
halving was, by accident, the only thing keeping chunks anywhere near a real
MTU. Both parts have to move together:
- assume 185, the conservative floor (what iOS uses, at or below every MTU
seen in practice), so a chunk fits whatever was actually negotiated;
- drop the `// 2`, which is redundant now the budget is honest. Its stated
job -- worst-case JSON double-escaping -- is already done by the loop that
measures the ENCODED chunk and shrinks until it fits.
The cost is more chunks per reply. A truncated chunk is a certain failure; a
longer transfer is not.
Verified by simulating the chunker over a realistic 4722-byte log payload
(quotes, backslashes, newlines, unicode, random printable): 42 chunks, max
chunk 182 bytes, every chunk within budget, fits ATT_MTU 247 and 185, and the
reassembled payload is byte-identical to the input.
Not verified on hardware: which MTU this Blox actually negotiates. That is the
one thing that would turn "explains the symptom" into "was the symptom", and
it needs a device.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJyqpFReP1wbz86nsqsoXq
|
Holding this as a draft. It does not survive a question from the field. The Blox's Bluetooth logs DID work from both the Android and the iOS native The asymmetry I leaned on does not exist either. I claimed the mobile app asks Worse, the change may push the wrong way. Sizing to 185 roughly doubles the Not deleting the branch: assuming 512 when nothing can read the negotiated MTU |
|
not needed because the BLE issue was not related to this fix |
fix(ble): size notification chunks for the MTU that was negotiated, not 512
The Bluetooth logs panel showed its two headers -- "=== Docker Logs ===" and
"=== System Logs ===" -- with nothing under them. The reply was arriving as
damaged JSON, failing to parse, and leaving an object with no
dockerand nosystemkey for the client to render.Every chunk goes out as a D-Bus PropertiesChanged "Value", and BlueZ silently
truncates that to the NEGOTIATED ATT MTU minus 3.
BLEResponseHandlerassumed512 -- the largest MTU anyone negotiates, not the smallest -- and nothing here
can see the real one: service.py implements none of the BlueZ APIs that report
it (no AcquireNotify, no MTU property), so there is no value to read and no
error when a chunk is cut short.
With base_overhead 47 and the old halving, chunks came out at ~278 bytes. A
phone that settles on ATT_MTU 247 -- completely ordinary on Android -- cuts
those at 244. Every chunk lands damaged, and the reply can never be
reassembled.
assumed 512, //2 chunk ~278 B truncated at 244
assumed 512, no //2 chunk ~509 B truncated at 244 (worse)
assumed 185, no //2 chunk ~182 B fits
So the fix is NOT simply dropping the
// 2. Doing that alone doubles thechunk size to ~509 B and breaks the transfer on every link below 512 -- the
halving was, by accident, the only thing keeping chunks anywhere near a real
MTU. Both parts have to move together:
seen in practice), so a chunk fits whatever was actually negotiated;
// 2, which is redundant now the budget is honest. Its statedjob -- worst-case JSON double-escaping -- is already done by the loop that
measures the ENCODED chunk and shrinks until it fits.
The cost is more chunks per reply. A truncated chunk is a certain failure; a
longer transfer is not.
Verified by simulating the chunker over a realistic 4722-byte log payload
(quotes, backslashes, newlines, unicode, random printable): 42 chunks, max
chunk 182 bytes, every chunk within budget, fits ATT_MTU 247 and 185, and the
reassembled payload is byte-identical to the input.
Not verified on hardware: which MTU this Blox actually negotiates. That is the
one thing that would turn "explains the symptom" into "was the symptom", and
it needs a device.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01YJyqpFReP1wbz86nsqsoXq