You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

18 KiB

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 или 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, для отладки сетевого стека)

Каталог headless/ содержит отдельный каркас управления: его main не запускает instance_lite и полноценный чат. Для сетевых сценариев использовать основной src/utun с [chatserver] headless_control_bind и tools/chatcli (см. корневой AGENTS.md). Команды сборки каркаса; требуется заранее собранный lib/libopus/libopus_internal.a:

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-сокет (если его запуск включён в конфигурации приложения):
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():

[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):

tools/logreceiver/logreceiver_restart.sh

Скрипт ротирует логи (история 10 запусков: .log → .log.1 → … → .log.10), убивает старый демон и запускает новый на 0.0.0.0:9999, вывод в logreceiver_output.log. При каждом запуске логи фиксируются в истории и начинается свежий лог.

В INI-конфиге Android (генерируется ConfigProvider.buildConfigText):

[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