From 25dda68ecf6d83fbb4f3bcc1500d4f4dcbca51c8 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 28 Oct 2026 12:22:17 +0100 Subject: [PATCH] Document LAN invitation framing and server link preview contracts --- .../java/com/utun/chat/data/InviteLink.kt | 5 +-- tools/chatgui-android/doc/ARCHITECTURE.md | 32 +++++++++++++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InviteLink.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InviteLink.kt index 8312cef9..d281cb8a 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InviteLink.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/InviteLink.kt @@ -55,8 +55,9 @@ data class InviteAddr( override fun hashCode(): Int = family * 31 + address.contentHashCode() + port * 31 + socketId * 31 + proto } -/** Полный снимок приглашения; равенство учитывает все поля, включая реквизиты входа и исходную ссылку. - * channelId — 1..Long.MAX_VALUE; joinKey/nodeId хранят все биты uint64 в Long (для unsigned-вывода использовать toULong()). +/** Предпросмотр приглашения; равенство учитывает все поля и исходную ссылку. + * CHANNEL: channelId=1..Long.MAX_VALUE и ненулевой joinKey; SERVER: channelId/joinKey=0, password пуст, add_key остаётся в link. + * joinKey/nodeId хранят все биты uint64 в Long (для unsigned-вывода использовать toULong()). * pubkey — 32 байта X25519 узла подключения, addrs — 1..32 его адресов. password — UTF-8 preview, link — исходная wire-ссылка. */ data class InviteData( val channelId: Long, diff --git a/tools/chatgui-android/doc/ARCHITECTURE.md b/tools/chatgui-android/doc/ARCHITECTURE.md index 1eeaf013..e48b1724 100644 --- a/tools/chatgui-android/doc/ARCHITECTURE.md +++ b/tools/chatgui-android/doc/ARCHITECTURE.md @@ -141,8 +141,10 @@ UDP-лог — в `[log_udp] ip/port`; полный набор задаёт Conf Для подключения канала/участника используются общие chat_core/topo_group API. `InviteLink.kt` получает предпросмотр через `NativeLib.decodeInvite()` — JNI вызывает -общий `src/chat/invite_link.c`. Ссылка `utun://...` содержит версию 0x03, пароль, -channel_id, join_key, REALITY-параметры и блоки публичного ключа/адресов. +общий `src/chat/invite_link.c`. Ссылка `utun://...` содержит версию 0x04 и kind: +CHANNEL несёт пароль/channel_id/join_key, SERVER — 32-байтовый add_key без полей канала. +Далее идут REALITY-параметры и блоки публичного ключа/адресов. В Kotlin-preview +SERVER имеет channelId/joinKey=0 и пустой password; add_key остаётся в исходной ссылке. JOIN передаёт исходную ссылку и request ID через потокобезопасный chat_join_port; принятый запрос завершается CONNECT_RESULT с тем же request. Отрицательный возврат submit означает отказ без события. Детали: `src/chat/chat_join.h`, `chat_join_port.h`. @@ -151,6 +153,32 @@ submit означает отказ без события. Детали: `src/cha Merkle распространяет запись; доступ к группе определяется локальной таблицей участников. JOIN_READY добавления и READY групповой сессии — разные состояния. +LAN-приглашения хранит app-scoped `LocalDiscovery`; AppEventHandler передаёт ему +события напрямую на main, без replay ViewModel. Список устройств 61 заменяется +целиком; входящая канальная ссылка 62 только ставится в очередь показа, без JOIN. +Кадры 61..63 имеют BE-поля ID/длин, 64 содержит байт активности. Принимаются +не более 128 устройств, имена 1..127 UTF-8 байтов без ASCII control, ссылка до +1100 байтов; входящая UI-очередь ограничена 8 приглашениями. Повреждённые кадры, +невалидный UTF-8, нулевые/повторные ID устройств не заменяют прежнее состояние. + +Одна исходящая LAN-отправка на приложение хранит request и снимок адресата. +Событие 63 применяется только к pending с совпавшими request и peer; статусы +0/1/2 означают failed/received/forbidden. ACK подтверждает приём ссылки, не JOIN. +Отказ/исключение JNI завершает pending ошибкой. Stop очищает устройства и pending, +сохраняет уже полученные приглашения; поздний ACK не возобновляет завершённый запрос. + +Android MulticastLock следует эффективной активности native discovery, а не +видимости Activity. Владелец сохраняется до configure/acquire и при ошибке +query/release: следующий ACTIVE/STOP повторяет операцию с тем же lock. +В логе указаны точный этап и сохранён ли владелец; на повторном ACTIVE удерживаемый +lock не захватывается ещё раз. Постоянный таймер повторов здесь не запускается. + +`InviteLinkDialog` получает candidates/link/pending от владельца генерации. +Он показывает QR или текст, запрашивает LAN-снимок и отображает результат +последней отправки вместе с её адресатом. Закрытие не отменяет pending-доставку. +Ошибка QR логируется без ссылки, текст остаётся доступен; ошибка clipboard +показывается в диалоге. Смена ссылки сбрасывает подтверждение копирования. + DM-команды используют conv_id и поколение беседы. История приходит событием CHAT_EVT_DM_MESSAGES, список — CHAT_EVT_DM_CONVERSATIONS. Frontend отбрасывает ответы старого поколения/другой беседы. Voice/video принимают target `dm:`;