# AGENTS.md — chatgui-android (Android P2P Chat) Android-версия чатгуи: P2P чат на STCP (TCP), UI на Jetpack Compose (Kotlin). C-ядро: `libutun_lite` (выборочная компиляция нужных .c из `lib/` и `src/`). Весь UDP-стек (ETCP, BBR, loadbalancer, NAT, routing, TUN) исключён. Транспорт: только STCP (X25519 + AES-CCM поверх TCP). ## Состав проекта (5 частей) ``` tools/chatgui-android/ ├── libutun_lite/ # C-ядро (статическая библиотека) │ ├── CMakeLists.txt │ ├── utun_sources.cmake # список компилируемых .c файлов │ ├── instance_lite.h/c # жизненный цикл (старт/стоп/рестарт, ключи) │ ├── utun_config_api.h/c # конфиг-провайдер (Kotlin → C) │ ├── invite_link_c.h/c # invite-ссылки (encode/decode, совместимы с десктопом) │ ├── voice_recorder.h/c # запись голосовых (PCM→Opus→канал) │ └── attachment_sender.h/c # отправка файлов в канал │ ├── headless/ # CLI для Linux (тестирование без Android) │ ├── CMakeLists.txt │ ├── headless_main.c # точка входа, uasync event 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/ # документация ├── AGENTS.md # этот файл ├── ARCHITECTURE.md # подробная архитектура └── IMPL_PLAN.md # план реализации по этапам ``` ## 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 # 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, для отладки сетевого стека) ```bash cd tools/chatgui-android && mkdir -p build && cd build cmake .. && make -j4 ./headless/headless ``` ## Прошивка на телефон ```bash # Проверить доступные устройства adb devices # Установить APK adb -s install -r app/build/outputs/apk/debug/app-debug.apk # Смотреть логи adb logcat -s utun:* # Control-сокет (при запущенном HeadlessService): adb forward tcp:9999 tcp:9999 nc localhost 9999 > {"id":1,"cmd":"status"} > {"id":2,"cmd":"join","link":"utun://..."} ``` ## Конфиг Конфиг хранится в Android DataStore (`ConfigProvider.kt`) и передаётся в C-ядро при старте как INI-текст через `nativeStart(configText)`. Формат (генерируется автоматически, пользователь не редактирует): ```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 [server:main] addr=0.0.0.0: [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] console_level=info # Опционально — UDP-лог [log_udp] 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`. При каждом запуске логи фиксируются в истории и начинается свежий лог. В конфиге Android: ```ini log_udp_ip = log_udp_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 - `libutun_lite/invite_link_c.h/c` — Кодирование и декодирование invite-ссылок `utun://` в бинарный формат. Совместим с десктопной версией - `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 - `libutun_lite/attachment_sender.h/c` — Отправка файлов в канал: копирование в media-директорию и регистрация через chat_core (media_index → db_sync) - `libutun_lite/utun_sources.cmake` — Список всех .c файлов, компилируемых в libutun_lite. Новые файлы добавлять сюда ### Kotlin-слой - `data/NativeLib.kt` — Kotlin-обёртка над C-библиотекой: все вызовы из Kotlin транслируются в JNI-функции - `data/ChatRepository.kt` — Хранилище данных: буферизация сообщений и каналов между C-ядром и UI через StateFlow - `data/InviteLink.kt` — Разбор invite-ссылок: извлекает ID канала, публичный ключ, адреса для подключения - `data/ConfigProvider.kt` — Поставщик конфигурации из Android DataStore в C-ядро через callback-интерфейс - `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` — Точка входа headless-режима: парсинг аргументов, запуск C-ядра в uasync event loop - `headless/headless_control.c/h` — Управляющий TCP-сокет: JSON-команды (`status`, `send`, `join`, `subscribe`) и асинхронные события ## Invite-ссылки (механика подключения к каналу) Формат идентичен десктопной версии: ``` utun:// + base64( version(1B, 0x01) | channel_id(8B BE) | [header(1B: bits0-1=count-1, bits2-5=family_flags) | pubkey(32B) | addrs*(socketId(1B) | address(4B v4/16B v6) | port(2B BE))]+ ) ``` Node ID вычисляется как `SHA256(pubkey)[0:8] & 0x7FFFFFFFFFFFFFFF` (совместимо с `sc_derive_node_id_from_pubkey()`). ## Правила разработки - Все C-файлы: C99, стиль как в корневом `AGENTS.md` (4 пробела, snake_case, DEBUG_* макросы) - Все новые C-файлы добавлять в `libutun_lite/utun_sources.cmake` - Все новые Kotlin-файлы — Compose, Material3, coroutines/StateFlow - ViewModel не держит ссылки на Context - Логи в C — через `debug_set_log_hook` → bridge → Kotlin `LogManager` - Конфиг в C — через `utun_config_provider_t` коллбэки в Kotlin - Перед коммитом: головная C-сборка + `./gradlew assembleDebug`