Device Discovery & Peer Lifecycle
Система обнаружения устройств в локальной сети работает на трёх уровнях, каждый из которых надстраивается над предыдущим:
| Уровень | Механизм | Файл | Что делает |
|---|---|---|---|
| 1. Транспортный | mDNS (libp2p) | p2p/network.rs | Обнаружение IP-адресов в LAN |
| 2. Идентификационный | Announce (sharing) | sharing/libp2p_sharing.rs | Обмен identity + device info |
| 3. Liveness | Ping/Pong heartbeat | sharing/manager.rs | Online/offline статус (30s/90s) |
Уровень 1: mDNS Discovery
backend/src/p2p/network.rs — базовый P2P слой на libp2p.
При старте приложение слушает на /ip4/0.0.0.0/tcp/0 (все интерфейсы, случайный порт) и запускает mDNS behaviour. Когда новый пир появляется в сети, libp2p генерирует mdns::Event::Discovered, а при исчезновении (таймаут ~120с) — mdns::Event::Expired.
На этом уровне эмитятся низкоуровневые события peer_added / peer_removed и обновляется счётчик пиров в SharedState.peers.
Уровень 2: Announce — идентификация пира
backend/src/sharing/libp2p_sharing.rs + backend/src/sharing/manager.rs
После установки TCP-соединения (libp2p dial), инициатор отправляет сообщение SharingMessage::Announce с полной информацией о себе: identity_id, display_name, public_key, device_id, device_name, storage info, IP. Получатель сохраняет информацию в known_peers HashMap и генерирует SharingServiceEvent::PeerDiscovered.
Менеджер обрабатывает PeerDiscovered:
- Сохраняет
KnownPeerInfoвknown_peers - Обновляет storage и IP в БД через
DeviceManager - Эмитит
peer_onlineдля фронтенда - Если пир с той же identity — запускает автосинхронизацию (StateDigest + DeviceSync + auto-publish spaces)
Уровень 3: Heartbeat — online/offline статус
backend/src/sharing/manager.rs — каждые 30 секунд отправляет Ping всем known peers, ожидает Pong. Если пир не отвечает 90 секунд — считается offline.
Pairing Discovery — обнаружение для спаривания
Помимо sharing-уровня, существует отдельный Discovery Service для процесса паринга — backend/src/pairing/manager.rs + backend/src/pairing/libp2p_pairing.rs.
Он использует свой собственный libp2p swarm с request-response протоколом. Три типа сообщений:
| Сообщение | Назначение |
|---|---|
| Discovery | Объявление identity + device name |
| Initiate | Запрос на паринг (PIN + полные данные identity/device) |
| Confirm | Подтверждение паринга с данными подтверждающей стороны |
Полный жизненный цикл пира
Таймауты
| Параметр | Значение | Где |
|---|---|---|
| mDNS announce interval | ~20s (libp2p default) | libp2p mdns config |
| mDNS expiration | ~120s | libp2p mdns |
| Ping interval | 30s | sharing/manager.rs |
| Stale peer threshold | 90s | sharing/manager.rs |
| Idle connection timeout (sharing) | 120s | sharing/libp2p_sharing.rs |
| Idle connection timeout (pairing) | 60-300s | pairing/libp2p_pairing.rs |
| Pairing session timeout | 5 мин | pairing/manager.rs |
События для фронтенда
| Событие | Когда | Данные |
|---|---|---|
peer_added | mDNS обнаружил пир | {peer_id, count} |
peer_removed | mDNS таймаут пира | {peer_id} |
peer_online | Announce/Pong получен | {device_id, device_name, identity_id, storage} |
peer_offline | 90s без Pong | {device_id, device_name} |
sharing_peer_discovered | Первый Announce | Полная информация о пире |
devices_updated | DeviceSync получен | Список устройств обновлён |
Структуры данных
KnownPeerInfo (in-memory)
pub struct KnownPeerInfo {
peer_id: String, // libp2p PeerId
identity_id: String,
identity_display_name: String,
identity_public_key: Vec<u8>,
device_name: String,
device_id: String, // Hash of public key
}DiscoveredDevice (pairing)
pub struct DiscoveredDevice {
pub peer_id: String,
pub device_name: String,
pub identity_id: String,
pub identity_display_name: String,
pub address: String, // IP from mDNS
pub port: u16,
pub discovered_at: u64, // Unix timestamp
}devices table (SQLite)
CREATE TABLE devices (
id TEXT PRIMARY KEY, -- device_id (hash of public key)
name TEXT,
device_type TEXT,
identity_id TEXT,
public_key BLOB,
peer_id TEXT,
last_seen INTEGER, -- updated on peer_online
ip TEXT, -- from Announce message
storage_used INTEGER,
storage_total INTEGER,
storage_unit TEXT,
os TEXT,
is_current INTEGER
);