Files
utun2/tools/chatgui-android/AGENTS.md
T

311 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md — chatgui-android (Android P2P Chat)
Android-версия чатгуи: P2P чат на общем ядре ETCP/STCP, UI на Jetpack Compose (Kotlin).
`libutun_lite/utun_sources.cmake` собирает исходники `lib/` и `src/`, включая UDP/TCP,
NCD, группы/BGP и маршрутизатор. Исключения перечислены в CMake (например, `utun.c`);
голосовой стек собирается отдельно через `src/call/voice_sources.cmake`.
`instance_lite` создаёт ядро и вызывает `utun_core_start()` + `chat_service_start()` в отдельном uasync-потоке.
UTUN-сервис не запускается. При stop ядро освобождает чат и общие ресурсы; SQLite хранится в `db_path/chats.db`.
Владение ресурсами и порядок остановки: `../../doc/service_lifecycle.md`.
## Состав проекта (5 частей)
```
tools/chatgui-android/
├── libutun_lite/ # C-ядро (статическая библиотека)
│ ├── CMakeLists.txt
│ ├── utun_sources.cmake # GLOB исходников, исключения и файлы обёртки
│ ├── instance_lite.h/c # жизненный цикл (старт/стоп/рестарт, ключи)
│ ├── utun_config_api.h/c # конфиг-провайдер (Kotlin → C)
│ ├── invite_link_c.h/c # старая копия парсера; .c не входит в UTUN_SOURCES
│ ├── voice_recorder.h/c # запись голосовых (PCM→Opus→канал)
│ └── attachment_sender.h/c # отправка файлов в канал
│
├── headless/ # CLI для Linux (тестирование без Android)
│ ├── CMakeLists.txt
│ ├── headless_main.c # отдельный каркас control-loop; сам ядро не запускает
│ └── headless_control.c/h # управляющий TCP-сокет (JSON/text протокол)
│
├── jni_bridge/ # JNI прослойка C ↔ Kotlin
│ └── android_jni_bridge.c/h # C API + JNI-функции (компилятся только для Android)
│
├── app/ # Android приложение (Gradle + NDK + Compose)
│ ├── build.gradle.kts
│ └── src/main/
│ ├── AndroidManifest.xml
│ ├── cpp/ # NDK: jni_bridge + libutun_lite → libutun.so
│ │ └── CMakeLists.txt
│ ├── java/com/utun/chat/
│ │ ├── ChatApplication.kt # Application, инициализация
│ │ ├── MainActivity.kt # Compose UI, навигация экранов
│ │ ├── data/
│ │ │ ├── NativeLib.kt # JNI-обёртка
│ │ │ ├── ChatRepository.kt # репозиторий (JNI + JSON)
│ │ │ ├── InviteLink.kt # парсер utun://base64blob
│ │ │ ├── ConfigProvider.kt # конфиг из DataStore
│ │ │ ├── LogManager.kt # лог-менеджер
│ │ │ └── ServerEntry.kt # модели данных
│ │ ├── viewmodel/
│ │ │ └── ChatViewModel.kt # StateFlow для UI
│ │ ├── ui/screens/
│ │ │ ├── ChannelListScreen.kt # список каналов + join/create
│ │ │ ├── ChatScreen.kt # сообщения + ввод
│ │ │ ├── JoinChannelDialog.kt # диалог подключения по invite
│ │ │ ├── QrScanScreen.kt # CameraX + ML Kit QR-сканер
│ │ │ ├── SettingsScreen.kt # настройки
│ │ │ └── LogScreen.kt # лог-просмотр
│ │ ├── ui/components/
│ │ │ └── MessageBubble.kt # бабблы сообщений + InputBar
│ │ └── headless/
│ │ └── HeadlessService.kt # фоновый сервис
│ └── res/
│
└── doc/ # документация
├── ARCHITECTURE.md # подробная архитектура
└── IMPL_PLAN.md # план реализации по этапам
```
Этот `AGENTS.md` лежит в корне `tools/chatgui-android/`. Старые планы в `doc/` сверять с текущим кодом.
## Chat-подсистема (src/chat/)
Все модули из `src/chat/` компилируются в `libutun_lite` через `file(GLOB_RECURSE)`.
Описания каждого модуля — см. корневой `AGENTS.md`, секция «Chat (src/chat/)».
## Технологии
| Слой | Язык | Фреймворк |
|------|------|-----------|
| C-ядро | C99 | OpenSSL, SQLite3 |
| Bridge | C + JNI | Android NDK 29 |
| Android UI | Kotlin | Jetpack Compose, Material3, CameraX, ML Kit |
| Сборка C | CMake 3.16+ | GCC (Linux) / Clang (Android NDK) |
| Сборка APK | Gradle 8.x | AGP 8.x, Kotlin 1.9 |
## Зависимости
### Для сборки APK (Android)
| Компонент | Версия | Установка |
|-----------|--------|-----------|
| JDK | 17+ | `apt install openjdk-17-jdk` |
| CMake | 3.22+ | `apt install cmake` |
| Android SDK | API 36 | через [sdkmanager](https://developer.android.com/studio#command-line-tools-only) или Android Studio |
| Android NDK | 29.0.14206865 | `sdkmanager "ndk;29.0.14206865"` |
| Gradle | 8.5 (wrapper) | `./gradlew` скачает сам |
| Kotlin | 1.9.20 | встроен в Gradle плагин |
| adb | platform-tools | `apt install adb` (для прошивки) |
OpenSSL для APK **предсобран** под `arm64-v8a` и `x86_64`, лежит в `app/src/main/cpp/openssl/`.
Собирать отдельно не нужно.
### Для Headless (Linux, отладка без телефона)
| Компонент | Установка |
|-----------|-----------|
| CMake 3.16+ | `apt install cmake` |
| GCC / Clang | `apt install build-essential` |
| OpenSSL dev | `apt install libssl-dev` |
| pthread | встроен в libc |
### Настройка окружения
Создать `local.properties` в корне `tools/chatgui-android/`:
```properties
sdk.dir=/home/user/Android/Sdk
```
Путь `ndk.dir` можно не указывать — Gradle скачает NDK сам (версия из `ndkVersion` в `app/build.gradle.kts`).
Если NDK уже установлен отдельно:
```properties
ndk.dir=/home/user/Android/Sdk/ndk/29.0.14206865
```
### Установка Android SDK (без Android Studio)
```bash
cd /tmp && wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
unzip commandlinetools-linux-*.zip -d ~/Android
mkdir -p ~/Android/Sdk/cmdline-tools
mv ~/Android/cmdline-tools ~/Android/Sdk/cmdline-tools/latest
export ANDROID_HOME=~/Android/Sdk
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
yes | sdkmanager --licenses
sdkmanager "platforms;android-36" "build-tools;36.0.0" "ndk;29.0.14206865"
```
## Сборка
```
./build.sh # clean + сборка + установка на все подключённые устройства
./build.sh noinstall # только сборка, без прошивки
./build.sh <SN> # clean + сборка + прошивка на конкретный девайс
```
### APK (Android)
```bash
cd tools/chatgui-android
export ANDROID_HOME=/home/user/Android/Sdk
./gradlew assembleDebug
# APK: app/build/outputs/apk/debug/app-debug.apk
```
### Headless (Linux, для отладки сетевого стека)
Каталог `headless/` содержит отдельный каркас управления: его `main` не запускает
`instance_lite` и полноценный чат. Для сетевых сценариев использовать основной
`src/utun` с `[chatserver] headless_control_bind` и `tools/chatcli` (см. корневой `AGENTS.md`).
Команды сборки каркаса; требуется заранее собранный `lib/libopus/libopus_internal.a`:
```bash
cd tools/chatgui-android && mkdir -p build && cd build
cmake .. && make -j4
./headless/headless
```
## Прошивка на телефон
```bash
# Проверить доступные устройства
adb devices
# Установить APK
adb -s <serial> install -r app/build/outputs/apk/debug/app-debug.apk
# Смотреть логи
adb logcat -s utun:*
# Control-сокет (если его запуск включён в конфигурации приложения):
adb forward tcp:9999 tcp:9999
nc localhost 9999
> {"id":1,"cmd":"status"}
> {"id":2,"cmd":"connect","link":"utun://..."}
```
## Конфиг
Конфиг хранится в Android DataStore (`ConfigProvider.kt`) и передаётся в C-ядро
при старте как INI-текст через `nativeStart(configText)`.
Сокращённый пример INI; текущие поля и значения генератора — в `ConfigProvider.buildConfigText()`:
```ini
[global]
my_node_name=MyDevice
my_public_key=<64 hex chars X25519>
my_private_key=<64 hex chars X25519>
db_path=/data/data/com.utun.chat/files
db_sync_enabled=1
chatserver_enabled=1
auto_sockets=android
# Секции [server:<имя>] генерируются из настроенных интерфейсов.
[allowed_keys]
allow_all=yes
[ntp]
enabled=no
[chat]
storage_autoload=1
storage_autoload_maxsize_mb=10
storage_maxsize_gb=1
opus_codec_preset=1
compressor_enabled=0
compressor_max_gain_db=25
compressor_rise_rate=10
media_download_max_peers=3
[debug]
chat=info
chat_sync=info
# Опционально — UDP-лог
[log_udp]
ip=<ip хоста>
port=9999
```
Ключи (`my_public_key`, `my_private_key`) генерируются автоматически при первом запуске
и сохраняются в `keys.conf` в `db_path`. Имя узла (`my_node_name`) берётся из
настроек DataStore или `Build.MODEL`.
## Логирование через logreceiver (только Android)
Debug-сообщения с телефона дублируются через UDP на приёмник `tools/logreceiver/`.
Запуск (всегда через скрипт, до старта utun):
```bash
tools/logreceiver/logreceiver_restart.sh
```
Скрипт ротирует логи (история 10 запусков: `.log` → `.log.1` → … → `.log.10`),
убивает старый демон и запускает новый на `0.0.0.0:9999`, вывод в `logreceiver_output.log`.
При каждом запуске логи фиксируются в истории и начинается свежий лог.
В INI-конфиге Android (генерируется `ConfigProvider.buildConfigText`):
```ini
[log_udp]
ip=<ip хоста>
port=9999
```
## Ключевые файлы для доработок
### C-слой
- `libutun_lite/instance_lite.h/c` — Жизненный цикл uTun для Android: запуск/остановка C-ядра в отдельном потоке, перезапуск при смене конфига, генерация и обновление X25519-ключей, health-check
- `jni_bridge/android_jni_bridge.h/c` — JNI-прослойка Kotlin↔C: все операции из UI (отправка сообщений, вход в каналы, голосовые, статус, настройки) и обратные вызовы (логи, события). Здесь же — JNI-функции, компилируемые только для Android
- `../../src/chat/invite_link.c/h` — Общий C-код формата invite-ссылок; Android разбирает ссылки в `data/InviteLink.kt`.
Старый `libutun_lite/invite_link_c.c` не компилируется текущим списком источников
- `libutun_lite/utun_config_api.h/c` — Поставщик конфигурации из Kotlin в C-ядро через callback-интерфейс (get_string, get_int64, get_int)
- `libutun_lite/voice_recorder.h/c` — Запись голосовых сообщений: накопление PCM-сэмплов с компрессором, кодирование в Opus-файл, отправка в канал через chat_core
- `src/call/call_audio.h/c` (+ `voice_jitter.cpp`, `call_tones.c`, SoundTouch) — единый голосовой стек звонка (`libutun_voice`), собирается через `src/call/voice_sources.cmake`; Opus encode (PCM→peer) и decode + адаптивный джиттер-буфер + time-stretch (PCM отдаётся через `nativeCallAudioPull`). Аудио I/O в Kotlin (AudioRecord/AudioTrack)
- `libutun_lite/attachment_sender.h/c` — Отправка файлов в канал: копирование в media-директорию и регистрация через chat_core (media_index → db_sync)
- `libutun_lite/utun_sources.cmake` — Общий список источников: `lib/*.c` и рекурсивный `src/*.c` подхватываются автоматически;
файлы самой обёртки перечислены в `_cfg_src`, исключения — через `list(FILTER/REMOVE_ITEM)`
### Kotlin-слой
- `data/NativeLib.kt` — Kotlin-обёртка над C-библиотекой: все вызовы из Kotlin транслируются в JNI-функции
- `data/CallAudioEngine.kt` — Аудио-движок звонка: AudioRecord/AudioTrack, audio-focus, потоки захвата (feed) / воспроизведения (pull из C-стека)
- `data/ChatRepository.kt` — Хранилище данных: буферизация сообщений и каналов между C-ядром и UI через StateFlow
- `data/InviteLink.kt` — Разбор invite-ссылок: извлекает ID канала, публичный ключ, адреса для подключения
- `data/ConfigProvider.kt` — Настройки DataStore и генерация INI-текста для запуска C-ядра
- `data/LogManager.kt` — Сбор и хранение логов из C-ядра через log-callback
- `viewmodel/ChatViewModel.kt` — ViewModel: StateFlow-состояние для UI (список каналов, сообщения, статус)
- `ui/screens/ChannelListScreen.kt` — Главный экран: список каналов, кнопки Join (по invite-ссылке) и Create
- `ui/screens/ChatScreen.kt` — Экран чата: список сообщений + поле ввода
- `ui/screens/JoinChannelDialog.kt` — Диалог подключения к каналу: ввод/вставка invite-ссылки, предпросмотр, кнопка Connect
- `ui/screens/QrScanScreen.kt` — QR-сканер для invite-ссылок (CameraX + ML Kit)
- `MainActivity.kt` — Точка входа Android-приложения: навигация между экранами, QrScan → JoinDialog flow
### Headless
- `headless/headless_main.c` — Отдельный select/poll-цикл управления; запуск ядра в этом main не реализован
- `headless/headless_control.c/h` — Каркас TCP-управления. Рабочий headless API общего чата находится в `../../src/chat/chat_headless_control.c/h`
## Invite-ссылки (механика подключения к каналу)
Создаваемые ссылки имеют версию `0x03`: `utun://` + base64(версия, длина/байты пароля,
channel_id, join_key, Reality-параметры, блоки ключа и адресов). У адреса есть socketId,
proto, IP и port. Точный формат: `../../src/chat/invite_link.c` и `data/InviteLink.kt`.
Node ID получает Kotlin через `NativeLib.deriveNodeId`, вызывающий `sc_derive_node_id_from_pubkey()`.
Ссылка готова после подтверждённой регистрации join_key. NCD удерживается до результата join;
доступ к группе появляется после сохранения мембера в локальной таблице принимающего узла.
JOIN_READY протокола добавления и READY групповой сессии — разные состояния.
Полный контракт: `../../src/chat/chat_join.h` и `../../src/routing_layer/topo_group.h`.
## Правила разработки
- Все C-файлы: C99, стиль как в корневом `AGENTS.md` (4 пробела, snake_case, DEBUG_* макросы)
- Для новых C-файлов проверить включение через `libutun_lite/utun_sources.cmake`; файлы обёртки добавить в `_cfg_src`
- Все новые Kotlin-файлы — Compose, Material3, coroutines/StateFlow
- ViewModel не держит ссылки на Context
- Логи в C — через `debug_set_log_hook` → bridge → Kotlin `LogManager`
- Основной запуск: Kotlin передаёт INI-текст через `nativeStart(configText)`; парсинг выполняет общий `config_parser`
- Перед коммитом: головная C-сборка + `./gradlew assembleDebug`