311 lines
18 KiB
Markdown
311 lines
18 KiB
Markdown
# 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`
|