12 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 файлов
│ ├── utun_config_api.h/c # конфиг-провайдер (Kotlin → C)
│ └── invite_link_c.h/c # invite-ссылки (encode/decode, совместимы с десктопом)
│
├── 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 # план реализации по этапам
Технологии
| Слой | Язык | Фреймворк |
|---|---|---|
| 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"
Сборка
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
[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=<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/invite_link_c.h/c— invite-ссылки (encode/decode, бинарный формат идентичен десктопуtools/chatgui/src/invite_link.cpp)jni_bridge/android_jni_bridge.h/c— bridge API:utun_bridge_join_channel(),utun_bridge_send_message(), JNI-функцииlibutun_lite/utun_config_api.h/c— конфиг-провайдер (Kotlin → C через коллбэки)libutun_lite/utun_sources.cmake— список всех .c файлов, компилируемых в libutun_lite
Kotlin-слой
data/InviteLink.kt— парсингutun://base64blob,deriveNodeId()(SHA256 от pubkey),serializeAddrs()data/NativeLib.kt— JNI-обёртка, всеexternal fun native*ui/screens/JoinChannelDialog.kt— диалог: ввод/вставка ссылки, debounce-декодинг, preview, Connectui/screens/QrScanScreen.kt— CameraX + ML Kit сканер, фильтруетutun://ссылкиui/screens/ChannelListScreen.kt— список каналов, кнопка Join (QrCode), кнопка CreateMainActivity.kt— навигация экранов, QrScan → JoinDialog flow
Headless
headless/headless_control.c— управляющий TCP-сокет, команды:join,connect,subscribe,debug_levelheadless/headless_main.c— точка входа, парсинг аргументов, uasync event loop
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