Document BLE v3 ACK framing and bounded remote credit
This commit is contained in:
1 parent
6338a2aae1
commit
c83b30aa0b
2 files changed
+60
-18
No files matched your search
+12
-7
@@ -1,12 +1,13 @@
|
||||
BLE GATT → ETCP (chatgui desktop + Android)
|
||||
|
||||
Текущее состояние диагностики задержек (29.10.2026):
|
||||
Write Without Response/Notify, HIGH и PHY2M включены. Исправлены готовность
|
||||
BlueZ по ServicesResolved, использование старого ManufacturerData, входящий
|
||||
HIGH Android и потолок DATA RTO=1s. Версия объявления повышена до 2, чтобы
|
||||
не подключаться к старому DATA Write/Indicate сервису. Полная нагрузка обеих
|
||||
аппаратных ролей пока не подтверждена: остаётся очередь между локальным
|
||||
completion и HCI TX. Успех короткой пробы не означает устранения этой очереди.
|
||||
Текущее состояние (29.10.2026):
|
||||
Write Without Response/Notify, HIGH и PHY2M включены. BLE version3 добавляет
|
||||
16-bit ID каждого DATA GATT-value, накопительный ACK по отдельной характеристике
|
||||
не чаще15ms и окно12500байт на направление (фиксированные2Мбит/с ×50ms).
|
||||
Локальный completion не возвращает кредит. Формат и таймауты — doc/ble_transport.txt.
|
||||
Предыдущие исправления готовности BlueZ, старого ManufacturerData, incoming HIGH
|
||||
и потолка DATA RTO сохранены. Ограничение очереди само по себе не считается
|
||||
подтверждённым исправлением всей ETCP-нагрузки; аппаратные результаты ниже.
|
||||
|
||||
Общий код: tools/cgathui_common/ble_etcp.c/h.
|
||||
Платформенные backend остаются в lib/ble и android_ble.c; Qt не участвует в протоколе.
|
||||
@@ -52,10 +53,14 @@ max_packet=1500 — размер непрозрачного пакета BLE, в
|
||||
TX limit=1 включает пакет в процессе фрагментации. У адаптера нет второй TX-очереди:
|
||||
send сразу передаёт ll_entry в ble_tx_put. Готовность читается через ble_tx_ready;
|
||||
после завершения целого пакета writable будит ETCP. При отказе владение не меняется.
|
||||
Дополнительно ble_tx_ready учитывает ACK-кредит: после локальной отправки целого
|
||||
пакета writable выдаётся, только если есть место в удалённом окне. При полном
|
||||
окне ACK освобождает кредит и будит отправителя; INIT/keepalive проходят тот же лимит.
|
||||
RX использует ll_queue общего транспорта и включается packet_link потребителем.
|
||||
DATA передаётся через Write Without Response (client → server) и Notification
|
||||
(server → client). BLE send завершает только локальную отправку; доставку и повторы
|
||||
обеспечивает ETCP. INVITE остаётся отдельным Write Request/Indication каналом.
|
||||
BLE ACK подтверждает приём GATT-value драйвером, не доставку целого ETCP-пакета.
|
||||
|
||||
После BLE HELLO/ACCEPT первым целым кадром каждой стороны идёт 40 байт:
|
||||
'V' 'C' 'E' 'T' 01 00 00 00 | 32 байта X25519 public key
|
||||
|
||||
+48
-11
@@ -5,7 +5,7 @@ BLE транспорт VibeChat (lib/ble)
|
||||
Независимая от Qt библиотека предоставляет manufacturer-броадкасты и адресные
|
||||
двусторонние GATT-сессии. Один локальный GATT-сервис обслуживает до 16 соединений.
|
||||
Броадкаст — повторяемое ОС объявление, а не гарантированно доставляемая датаграмма.
|
||||
Общий codec ble_identity.h: VC (2 байта), версия 2 (1), node_id BE64 (8),
|
||||
Общий codec ble_identity.h: VC (2 байта), версия 3 (1), node_id BE64 (8),
|
||||
имя UTF-8 (0..13). Имя сокращается по границе символа. Всего 11..24 байта.
|
||||
Приглашения и автоматический выбор соседей реализованы в tools/cgathui_common (doc/ble_etcp.txt); транспорт не запускает JOIN/ETCP.
|
||||
Node ID передаётся в HELLO для сопоставления сессий; подлинность он не доказывает.
|
||||
@@ -61,6 +61,13 @@ RX при заполнении отбрасывает целый новый ка
|
||||
если он присутствует. Это не Ethernet payload MTU 1500 (которому нужно >=1514).
|
||||
На link не больше одной незавершённой GATT-передачи. Она хранит свой fragment
|
||||
до завершения ОС. Очередь ядра/Qt не используется как неограниченный TX-буфер.
|
||||
DATA дополнительно ограничен неподтверждёнными GATT-values: фиксированные2Мбит/с
|
||||
×50ms/8 =12500байт на направление, включая flow/framing headers. Локальный sent
|
||||
не возвращает этот кредит: его освобождает накопительный ACK удалённого драйвера.
|
||||
При заполнении ble_tx_ready=false; ACK будит writable после появления места.
|
||||
Текущий кадр может остановиться посреди фрагментации, INVITE и ACK продолжают работать.
|
||||
Предел12500 — расчётный размер по фиксированной скорости, не гарантия фактических50ms
|
||||
при медленном радио. Сверх окна остаётся только ограниченная собственная TX-очередь.
|
||||
Переподключение не переносит старые кадры и не повторяет неоднозначные отправки.
|
||||
|
||||
Wire protocol
|
||||
@@ -69,31 +76,58 @@ UUID service: f61a9020-4abc-45ac-98e1-761df4c13811
|
||||
UUID RX: f61a9021-4abc-45ac-98e1-761df4c13811 (Write Without Response)
|
||||
UUID TX: f61a9022-4abc-45ac-98e1-761df4c13811 (Notify)
|
||||
UUID INVITE: f61a9023-4abc-45ac-98e1-761df4c13811 (Write + Indicate)
|
||||
UUID ACK: f61a9024-4abc-45ac-98e1-761df4c13811 (Write + Indicate)
|
||||
RX/TX названы относительно сервера; API одинаков для обеих ролей.
|
||||
Версия объявления 2 означает DATA Write Without Response/Notify; объявления
|
||||
версии 1 отклоняются до GATT-подключения. Desktop и Android должны использовать
|
||||
Версия объявления3 означает DATA Write Without Response/Notify с16-bit flow ID
|
||||
и обязательной ACK-характеристикой; версии1/2 отклоняются до GATT-подключения.
|
||||
Desktop и Android должны использовать
|
||||
новую версию вместе. Scan prefix Android приходит из C codec через JNI start,
|
||||
номер версии в Kotlin не дублируется. Это отдельный протокол от BLE_EXAMPLE
|
||||
и tools/ble-test-android; автоматическая совместимость с ними не заявляется.
|
||||
|
||||
Все многобайтные целые big-endian, без выравнивания C-структур.
|
||||
Каждое DATA GATT-value начинается с flow_id(2), затем одно из тел ниже.
|
||||
INVITE содержит только тело FRAME без flow_id; ACK содержит только last_flow_id(2).
|
||||
HELLO: [type=1:1][source_node:8][target_node:8], Central -> Peripheral.
|
||||
ACCEPT: [type=2:1][source_node:8][target_node:8], Peripheral -> Central.
|
||||
DATA: [type=3:1][frame_id:2][total_len:2][offset:2][payload:1..].
|
||||
HELLO/ACCEPT идут только по DATA, сборка DATA и INVITE независима.
|
||||
frame_id увеличивается modulo 65536 отдельно для каждого направления и канала.
|
||||
Планировщик чередует фрагменты DATA/INVITE; HELLO имеет приоритет.
|
||||
Планировщик чередует фрагменты DATA/INVITE; готовый ACK имеет высший приоритет,
|
||||
затем HELLO/ACCEPT. До HELLO обязательны подписки DATA/INVITE/ACK с обеих сторон.
|
||||
Размер значения <= min(ATT_MTU-3,512), минимальный ATT MTU=23.
|
||||
Полезный DATA-фрагмент:11байт при MTU23,235 при247,503 при517; HELLO/ACCEPT занимают19.
|
||||
Нулевой offset начинает новый кадр и заменяет незавершённую предыдущую сборку.
|
||||
Далее обязательны тот же frame_id/total_len и offset==число принятых байтов.
|
||||
Пропуск, неверная длина, новое начало или 15-секундный таймаут сбрасывают сборку;
|
||||
в RX помещается только целый кадр. Собственных ACK/ретрансляций/окна протокола нет.
|
||||
в RX помещается только целый кадр. Повторы целых кадров выполняет потребитель.
|
||||
DATA, включая HELLO/ACCEPT и начальный обмен ключами, использует Write Command
|
||||
и Notification без ATT-подтверждений. Подтверждения/повторы данных выполняет ETCP;
|
||||
потеря начального обмена завершает попытку по существующему дедлайну подключения.
|
||||
INVITE сохраняет Write Request и Indication. Завершение локального send не означает
|
||||
чтения кадра приложением и не защищает от заполнения RX.
|
||||
|
||||
ACK и управление окном
|
||||
----------------------
|
||||
flow_id начинается с1, увеличивается modulo65536 для каждого DATA GATT-value,
|
||||
включая HELLO/ACCEPT, bootstrap, ETCP control и DATA. ACK — последний полученный
|
||||
flow_id; при пропуске последующий ID освобождает кредит предыдущих отправок,
|
||||
но НЕ утверждает их доставку приложению. Сборщик/ETCP обнаруживают потери отдельно.
|
||||
Дубликат/старый DATA не выдаётся повторно, текущий ACK может быть повторён.
|
||||
Кольцо хранит только размеры неподтверждённых GATT-values, без копии payload.
|
||||
В окне не более1250 минимальных DATA-values по10байт; это меньше половины16-bit
|
||||
пространства ID. Modulo-разность ACK однозначно проверяется по активному окну.
|
||||
Старый/повторный ACK не освобождает кредит, ACK вперёд за отправленное окно закрывает
|
||||
соединение с WARN. При reconnect все flow ID/кредит/таймеры создаются заново.
|
||||
|
||||
ACK отправляется отдельным uasync-таймером, не polling-таймером100ms. Поступившие
|
||||
ID объединяются до последнего; между принятыми backend ACK не менее15000us.
|
||||
Один ACK в процессе отправки и один объединённый ожидающий; очереди ACK нет.
|
||||
ACK не расходует DATA-кредит и не требует встречного DATA. Его Write Request/
|
||||
Indication подтверждается ATT; ошибка/зависание завершают соединение с диагностикой.
|
||||
15s без продвижения ACK закрывают link даже при успешных локальных completions.
|
||||
Дубликаты ACK этот срок не продлевают. Close/stop отменяют ACK-таймер.
|
||||
|
||||
Linux
|
||||
-----
|
||||
BlueZ D-Bus, клиент sd-bus из libsystemd; зависимость от Qt отсутствует.
|
||||
@@ -106,11 +140,11 @@ instance может иметь другой random address). Нехватка р
|
||||
Входящий WriteValue содержит device; серверный AcquireNotify(device,mtu) получает
|
||||
отдельный SOCK_SEQPACKET socketpair для каждого клиента и канала. DATA fd принимает
|
||||
notifications: успешная запись завершает локальный send без ожидания ATT ACK.
|
||||
INVITE fd передаёт indications; байт 1 в обратную сторону подтверждает ATT completion.
|
||||
INVITE/ACK fd передают indications; байт1 в обратную сторону подтверждает ATT completion.
|
||||
EAGAIN сохраняет фрагмент и ждёт writable; синхронных callbacks send нет.
|
||||
Value/Confirm без адреса клиента для серверной передачи не используются.
|
||||
Выходящий клиент: DATA WriteValue(type=command), INVITE WriteValue(type=request),
|
||||
StartNotify для TX и INVITE; открытие после обеих подписок.
|
||||
Выходящий клиент: DATA WriteValue(type=command), INVITE/ACK WriteValue(type=request),
|
||||
StartNotify для TX, INVITE и ACK; открытие после всех трёх подписок.
|
||||
Публичный Device1 D-Bus API не задаёт per-link PHY/connection interval: согласованием
|
||||
управляют ОС и peer; Android запрашивает 2M также в серверной роли.
|
||||
Не вызывается глобальный Device1.Disconnect: ACL может обслуживать другие сервисы.
|
||||
@@ -120,8 +154,8 @@ Windows
|
||||
-------
|
||||
WinRT BluetoothLEAdvertisementWatcher/Publisher и GattServiceProvider.
|
||||
GATT-клиент использует WriteValueWithResultAsync, сервер — адресный
|
||||
NotifyValueAsync(value, subscribedClient): DATA — notify-only, INVITE — indicate-only.
|
||||
DATA пишет с WriteWithoutResponse и подписывается на Notify; INVITE сохраняет
|
||||
NotifyValueAsync(value, subscribedClient): DATA — notify-only, INVITE/ACK — indicate-only.
|
||||
DATA пишет с WriteWithoutResponse и подписывается на Notify; INVITE/ACK сохраняют
|
||||
WriteWithResponse/Indicate. Сервер отвечает Respond только на WriteWithResponse.
|
||||
После открытия исходящего GATT при наличии IBluetoothLEDevice6 (Windows 11)
|
||||
запрашивается ThroughputOptimized. Request хранится до close линка и закрывается
|
||||
@@ -179,6 +213,9 @@ PHY_2M_supported — возможность адаптера, НЕ измере
|
||||
TX/RX_Bps полезных байтов BLE-кадров (оба канала вместе, до вычета заголовков ETCP),
|
||||
число кадров/фрагментов/отбросов, среднее/максимальное локальное ожидание GATT,
|
||||
размеры DATA/INVITE очередей и возраст pending. Локальный TX не равен доставке.
|
||||
Отдельная сводка flow показывает flight bytes/packets, peak_bytes, ID/ACK и число
|
||||
ACK TX/RX. INFO печатает фиксированную скорость/окно/интервал/разрядность ID.
|
||||
Wire TRACE категории debug содержит каждый flow TX, ACK TX/RX и освобождённый кредит.
|
||||
Повторные радиообъявления не засоряют DEBUG.
|
||||
|
||||
Desktop GUI
|
||||
@@ -206,7 +243,7 @@ eventfd немедленно будит uasync при новом событии;
|
||||
Переполнение mailbox останавливает radio с ошибкой. На соединение не более одной
|
||||
ожидающей отправки; сервер notifications/indications обслуживает адресно, по очереди.
|
||||
DATA использует WRITE_TYPE_NO_RESPONSE, CCC ENABLE_NOTIFICATION_VALUE и confirm=false;
|
||||
INVITE — WRITE_TYPE_DEFAULT, ENABLE_INDICATION_VALUE и confirm=true. Оба направления
|
||||
INVITE/ACK — WRITE_TYPE_DEFAULT, ENABLE_INDICATION_VALUE и confirm=true. Оба направления
|
||||
ждут локальный callback готовности стека, даже когда ATT ACK отсутствует.
|
||||
Политика входящих ble_set_incoming закрывает только входящие сессии.
|
||||
Настройки используют DataStore; Bluetooth off останавливает только радио приложения.
|
||||
|
||||
Reference in new issue
Block a user