Skip to content

Протокол передачи

Протокол — это правила общения между устройствами. Как в разговоре: сначала здороваешься, потом задаёшь вопрос, потом получаешь ответ. Если нарушить порядок — собеседник тебя не поймёт.

В csend есть два протокола: один для обнаружения (Discovery), другой для передачи файлов (Transfer). Оба используют одну и ту же «трубу» — libp2p request-response поверх JSON.

Идентификатор протокола

/kontinuum/send/1.0.0

Все устройства с csend говорят на этом «языке». Если кто-то использует другой протокол — они просто не поймут друг друга (и проигнорируют).

Типы сообщений

Все сообщения описаны одним перечислением Message. Каждое сообщение может быть как запросом, так и ответом — libp2p обрабатывает их одинаково.

plantuml Diagram

Роли Discovery-сообщений

СообщениеКто отправляетЗачем
WhoWantsToSendПотенциальный получатель«Есть ли у кого файлы для меня?» — запрос раздач от всех пиров
AdvertiseSendОтправитель (ответ)«Да, у меня есть файлы» — ответ на WhoWantsToSend (используется при quiet = false)
WhoWantsToReceiveОтправитель«Я раздаю файлы, кто хочет принять?» — объявление раздачи всем пирам
AdvertiseReceiveПотенциальный получатель (ответ)«Я здесь, готов принимать» — подтверждение присутствия
NoLongerSendingОтправитель«Я больше не раздаю эти файлы» — отмена конкретной раздачи

Два направления Discovery: WhoWantsToReceive — отправитель объявляет свои файлы. WhoWantsToSend — получатель спрашивает, у кого есть файлы. Оба механизма работают одновременно, чтобы устройства находили друг друга независимо от порядка подключения к сети.

Поток Discovery — обнаружение устройств

Discovery работает постоянно в фоне, пока открыт TUI. Его задача — показывать, кто в сети хочет отправить файлы.

plantuml Diagram

Важно: в Discovery не передаётся ни кодовая фраза, ни её хеш. Только публичные метаданные: имя устройства, метка передачи, список файлов и размер. transfer_id — случайный идентификатор, не связанный с кодовой фразой.

Поток WhoWantsToSend — обратное обнаружение

Когда устройство подключается к сети и хочет узнать, кто раздаёт файлы, оно отправляет WhoWantsToSend:

plantuml Diagram

Этот механизм дополняет основной поток: если Боб подключился к сети после того, как Алиса начала раздачу, introduce_to_new_peer() автоматически отправит ему все активные раздачи. Но если Алиса не заметила Боба через mDNS (редкий случай), WhoWantsToSend позволяет Бобу запросить информацию самостоятельно.

Ограничение списка файлов

В Discovery-сообщениях список файлов обрезается до 100 записей (MAX_DISCOVERY_FILES). Это нужно, чтобы сообщение не стало слишком большим. Полный список файлов передаётся позже, в сообщении Offer при рукопожатии.

Поток Transfer — передача файлов

Transfer — это последовательность шагов, которая запускается после того, как получатель ввёл кодовую фразу и устройства нашли друг друга.

plantuml Diagram

Подробности о чанках (Chunk)

Файлы передаются не целиком, а кусочками (chunks) по 256 КиБ (262 144 байта). Это нужно по нескольким причинам:

ПричинаОбъяснение
ПрогрессМожно показывать процент выполнения
ПамятьНе нужно загружать весь файл в оперативную память
НадёжностьЕсли что-то сломалось, потеряется только один кусочек
ШифрованиеКаждый кусочек шифруется отдельно с уникальным nonce

Структура чанка:

Chunk {
  file_index: 0,     // Какой файл (индекс в списке)
  offset: 262144,    // Смещение в файле (в байтах)
  data: [...]        // Зашифрованные данные (до 256 КиБ + 16 байт тег)
}

Как работает подтверждение (Ack)

После каждого чанка получатель отправляет Ack — подтверждение, что данные получены. Отправитель ждёт Ack перед отправкой следующего чанка:

plantuml Diagram

Несколько файлов

Если отправляется несколько файлов, они передаются последовательно — один за другим. file_index указывает, к какому файлу относится чанк:

Chunk { file_index: 0, offset: 0, data: [...] }       # photo1.jpg, начало
Chunk { file_index: 0, offset: 262144, data: [...] }   # photo1.jpg, продолжение
Chunk { file_index: 1, offset: 0, data: [...] }       # photo2.jpg, начало
...
Done

Размеры сообщений

JSON-кодек libp2p имеет ограничения на размер сообщений:

ПараметрЛимитЗачем
Макс. размер запроса16 MiBChunk (256 КиБ данных → ~1 MiB в JSON)
Макс. размер ответа16 MiBOffer с большим списком файлов
Таймаут запроса600 сек (10 мин)Медленные соединения

Почему JSON? Vec<u8> в JSON сериализуется как массив чисел: [72, 101, 108, 108, 111]. Это неэффективно (каждый байт занимает ~3.5 символа), но просто для отладки. В будущем можно перейти на бинарный формат для скорости.

Сообщение Error

Error { message } используется для сигнализации ошибок во время передачи — например, если отправитель не может прочитать файл с диска или получатель не может записать данные. При получении Error передача прерывается, а сообщение показывается пользователю.

FileInfo — метаданные файла

Каждый файл описывается структурой FileInfo:

rust
pub struct FileInfo {
    pub name: String,   // "photos/cat.jpg" (путь относительно корня)
    pub size: u64,      // 1048576 (размер в байтах)
}

Для директорий имя включает путь: если отправляешь папку photos/, файл внутри будет называться photos/cat.jpg, а не просто cat.jpg.