diff --git a/doc/chat_call_arch.md b/doc/chat_call_arch.md index 6962fda7..fc0ae347 100644 --- a/doc/chat_call_arch.md +++ b/doc/chat_call_arch.md @@ -17,7 +17,7 @@ DM-группа, маршрутизация через `etcp_router` идент | Файл | Язык | Сборки | Назначение | |---|---|---|---| -| `src/call/call_proto.h` | C | везде | wire-формат, subcmd, svc_id `ETCP_RT_ID_CALL 0x34` | +| `src/call/call_proto.h` | C | везде | wire-формат, subcmd, svc_id `ETCP_RT_ID_CALL 0x36` | | `src/call/call.h` / `call.c` | C | везде (autotools + GUI) | сигналинг, реестр сессий, таймеры, watchdog (20с по приёму), релей медиа с backpressure, счётчики | | `src/call/call_audio.h/c` | C | только GUI-сборки | Opus encode/decode, фрейминг, pull-обвязка с тонами; регистрация через `call_audio_ops` | | `src/call/call_tones.h/c` | C | только GUI-сборки | генератор тонов (glitch/ended), синус | @@ -28,19 +28,20 @@ Autotools-сборка демона остаётся C-only (`.cpp` не ком Диагностика: категория `call` (`DEBUG_CATEGORY_CALL`); транспортная доставка — `etcp_route`. -## 3. Протокол (svc_id 0x34, первый байт payload = subcmd) +## 3. Протокол (сигналинг svc_id 0x36, первый байт payload = subcmd) -Формат приёма (как media_delivery, одноуровневый): `entry->dgram[0]=svc_id`, -`entry->dgram[1]=subcmd`, `entry->dgram[2..]=payload`. +Формат приёма: `entry->dgram[0]=svc_id`, служебный заголовок роутера до +`ROUTER_SVC_PAYLOAD_OFF=26`, затем subcmd и тело команды. ``` CALL_INVITE 0x01 {call_id:8, group_id:8, tracks:[{type:AUDIO, codec:OPUS, sr:4, ch:1, bitrate:4}]} CALL_RINGING 0x02 {call_id:8} -CALL_ACCEPT 0x03 {call_id:8, agreed_tracks} +CALL_ACCEPT 0x03 {call_id:8} CALL_DECLINE 0x04 {call_id:8, reason:1} CALL_BUSY 0x05 {call_id:8} CALL_CANCEL 0x06 {call_id:8} CALL_HANGUP 0x07 {call_id:8, reason:1} +CALL_END_ACK 0x08 {call_id:8} CALL_MEDIA 0x10 {call_id:8, track_id:1, seq:2, ts_ms:4, capture_pts_us:8, capture_epoch:8, codec_delay_samples:4, payload} ``` @@ -75,9 +76,18 @@ NO_TRAFFIC / RING_TIMEOUT / CONNECT_FAIL`. - `CONN_EVENT_DOWN/TIMEOUT` CM не завершают звонок: роутер использует доступные пути; - **NO_TRAFFIC 20с** — нет входящих медиа-кадров → авто HANGUP. -Завершающие кодограммы (HANGUP/CANCEL/DECLINE) шлются без ретрансмиссий — потеря -компенсируется watchdog'ом 2с у вызываемого. Запоздалые кодограммы вызова при -идущем разговоре (`ACTIVE`) игнорируются. +Завершающие кодограммы HANGUP/CANCEL/DECLINE повторяются каждые 300мс до END_ACK, +не дольше 20с. Это необходимо при смене эпохи роутера: старые очереди удаляются, +даже если отправитель ранее получил успешный результат постановки команды в очередь. +Медиа и их локальные очереди освобождаются сразу, ENDED публикуется один раз. +В реестре до ACK/таймаута остаётся только завершённая сессия с CM handle и таймером. +Получатель подтверждает также повторные команды после удаления сессии, без повторного ENDED. +Команда и ACK проверяются по подписи, шифрованию, размеру, call_id, пиру и группе существующей сессии. +ACK не подтверждается. Новый звонок имеет другой call_id и не затрагивается поздними командами старого. +При одновременном завершении стороны независимо подтверждают команды друг друга. +После ACK сессия удаляется на ближайшем tick завершения; при удалённом завершении CM удерживается +ещё 500мс для отправки ACK. Остановка ядра отменяет таймер без повторных отправок. +Запоздалые кодограммы вызова при идущем разговоре (`ACTIVE`) игнорируются. Онлайн-проверка: `topo_node_find_by_id(group, peer) == NULL` → «не в сети» (event). diff --git a/src/call/call.c b/src/call/call.c index 4023d375..a8f6db23 100644 --- a/src/call/call.c +++ b/src/call/call.c @@ -724,6 +724,12 @@ static void call_etcp_recv(struct ETCP_CONN* conn, struct ll_entry* entry) { int terminal = subcmd == CALL_SUBCMD_HANGUP || subcmd == CALL_SUBCMD_CANCEL || subcmd == CALL_SUBCMD_DECLINE; if (terminal || subcmd == CALL_SUBCMD_END_ACK) { + uint8_t required = ROUTER_FLAG_SIGNED | ROUTER_FLAG_ENCRYPTED; + if ((d[ROUTER_SVC_FLAGS_OFF] & required) != required) { + DEBUG_WARN(DEBUG_CATEGORY_CALL, "call end unauthenticated command=%s peer=%016llx flags=%02x", + call_subcmd_name(subcmd), (unsigned long long)from, d[ROUTER_SVC_FLAGS_OFF]); + queue_dgram_free(entry); queue_entry_free(entry); return; + } size_t expected = subcmd == CALL_SUBCMD_CANCEL || subcmd == CALL_SUBCMD_END_ACK ? 9 : 10; if (blen != expected) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "call end invalid packet command=%s bytes=%zu expected=%zu",