15 KiB
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 или 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/:
sdk.dir=/home/user/Android/Sdk
Путь ndk.dir можно не указывать — Gradle скачает NDK сам (версия из ndkVersion в app/build.gradle.kts).
Если NDK уже установлен отдельно:
ndk.dir=/home/user/Android/Sdk/ndk/29.0.14206865
Установка Android SDK (без Android Studio)
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)
cd tools/chatgui-android
export ANDROID_HOME=/home/user/Android/Sdk
./gradlew assembleDebug
# APK: app/build/outputs/apk/debug/app-debug.apk
Headless (Linux, для отладки сетевого стека)
cd tools/chatgui-android && mkdir -p build && cd build
cmake .. && make -j4
./headless/headless
Прошивка на телефон
# Проверить доступные устройства
adb devices
# Установить APK
adb -s <serial> 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).
Формат (генерируется автоматически, пользователь не редактирует):
[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:<listen_port>
[allowed_keys]
allow_all=yes
[ntp]
enabled=no
[chatserver]
storage_autoload=1
storage_unit_size=10M
storage_total_size=1G
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=<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):
tools/logreceiver/logreceiver_restart.sh
Скрипт ротирует логи (история 10 запусков: .log → .log.1 → … → .log.10),
убивает старый демон и запускает новый на 0.0.0.0:9999, вывод в logreceiver_output.log.
При каждом запуске логи фиксируются в истории и начинается свежий лог.
В конфиге Android:
log_udp_ip = <ip хоста>
log_udp_port = 9999
Ключевые файлы для доработок
C-слой
libutun_lite/instance_lite.h/c— Жизненный цикл uTun для Android: запуск/остановка C-ядра в отдельном потоке, перезапуск при смене конфига, генерация и обновление X25519-ключей, health-checkjni_bridge/android_jni_bridge.h/c— JNI-прослойка Kotlin↔C: все операции из UI (отправка сообщений, вход в каналы, голосовые, статус, настройки) и обратные вызовы (логи, события). Здесь же — JNI-функции, компилируемые только для Androidlibutun_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_corelibutun_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 через StateFlowdata/InviteLink.kt— Разбор invite-ссылок: извлекает ID канала, публичный ключ, адреса для подключенияdata/ConfigProvider.kt— Поставщик конфигурации из Android DataStore в C-ядро через callback-интерфейсdata/LogManager.kt— Сбор и хранение логов из C-ядра через log-callbackviewmodel/ChatViewModel.kt— ViewModel: StateFlow-состояние для UI (список каналов, сообщения, статус)ui/screens/ChannelListScreen.kt— Главный экран: список каналов, кнопки Join (по invite-ссылке) и Createui/screens/ChatScreen.kt— Экран чата: список сообщений + поле вводаui/screens/JoinChannelDialog.kt— Диалог подключения к каналу: ввод/вставка invite-ссылки, предпросмотр, кнопка Connectui/screens/QrScanScreen.kt— QR-сканер для invite-ссылок (CameraX + ML Kit)MainActivity.kt— Точка входа Android-приложения: навигация между экранами, QrScan → JoinDialog flow
Headless (тестирование без телефона)
headless/headless_main.c— Точка входа headless-режима: парсинг аргументов, запуск C-ядра в uasync event loopheadless/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 → KotlinLogManager - Конфиг в C — через
utun_config_provider_tколлбэки в Kotlin - Перед коммитом: головная C-сборка +
./gradlew assembleDebug