fix(tci): callsign_send слал размноженный позывной, tci_error — неэкранированное имя

Два расхождения со спекой, оба в том, что уходит клиенту.

callsign_send (§3.2.2). Команда должна нести «финальный вариант позывного,
переданного в эфир», а несла его вместе с повторами: для «cw_msg:0,_,RA6LH$2,
599 004;» уезжало «callsign_send:RA6LH RA6LH;», потому что переменная Call к
этому моменту уже была затёрта развёрнутым текстом сообщения. Теперь позывной
запоминается ДО развёртки. Заодно: подтверждение уходит АВТОРУ сообщения, а не
broadcast'ом (у остальных клиентов своих сообщений нет), и через TCIEscape —
позывной пришёл от клиента, и символы ^ ~ * после снятия экранирования
превращались в сырые : , ; прямо посреди кадра.

Момент отправки остаётся расхождением, теперь честно описанным в doc/TCI.md:
документ шлёт команду по факту окончания передачи позывного, а наш передатчик
текста моментов внутри очереди не отмечает. Доотправка позывного (cw_msg:arg1;)
всё равно не поддержана, так что финальный вариант известен уже в момент
постановки в очередь и позже не изменится.

tci_error. Имя команды возвращается клиенту как ПЕРВЫЙ АРГУМЕНТ, то есть обязано
экранироваться, — и общий обработчик (HandleCommand) это делает, а пятнадцать
точечных отказов пропускали его сырым. Имя приходит от клиента, TCIParse режет
его только по ':', так что «foo,bar:1;» возвращался как «tci_error:foo,bar,bad
receiver;» — три аргумента вместо двух.

Заодно в doc/TCI.md: правило 3 §1.1 теперь говорит про ВСЕ потоки сервера
(accept и тик тоже, общий JoinPumped), а не только про клиентские, и счётчик
проверок стенда приведён к нынешним 244.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 11:21:58 +03:00
co-authored by Claude Opus 5
parent 4081ea0e57
commit b62369ec6b
2 changed files with 54 additions and 27 deletions
+15 -4
View File
@@ -85,10 +85,15 @@ TCI-клиенты ──WebSocket──► TTCIServer ──► TTCIAdapter ─
том и в другом случае наверх уходит `OnDisconnect` (единая точка
`Disconnected`): иначе после остановки у адаптера оставались висеть захваты
параметров ушедших клиентов.
3. **`Stop` ждёт выхода клиентских потоков без таймаута** и прокачивает при
3. **`Stop` ждёт выхода ВСЕХ своих потоков без таймаута** и прокачивает при
этом очередь `Synchronize`. Останавливает сервер поток контроллера (UI), а
клиентский поток в этот момент может висеть как раз на `Invoke` в него же:
без прокачки это взаимный клин. Выйти по таймауту нельзя — следом
без прокачки это взаимный клин. ★Это относится и к accept- с тик-потоком
(общий `JoinPumped`), а не только к клиентским: тик-поток ходит в контроллер
через `ReapClients``Disconnected``Invoke`, и проверка `CanInvoke` от
клина не спасает — она читает `Stopping` ДО входа в `Synchronize`, так что
клиент, отвалившийся ровно в момент остановки, успевает проскочить в это
окно. Выйти по таймауту нельзя — следом
освобождаются и клиенты, и сам сервер с адаптером, а не вышедший поток
вернулся бы в эту память. Поэтому: `Stopping` (адаптер новых `Invoke` не
начинает) + закрытые сокеты + повторный `shutdown` раз в полсекунды, и
@@ -703,7 +708,13 @@ ExpertSDR3 давно бы не было.
Текст приводится к тому, что понимает передатчик текста ewsdr (`CWXSend`):
экранирование `^ ~ *` снимается, `CALL$N` разворачивается в N повторов
позывного, префикс/суффикс `_` считаются пустыми. **Не поддержано:** шаг
позывного, префикс/суффикс `_` считаются пустыми. Подтверждение
`callsign_send` уходит **автору сообщения** и содержит позывной ДО развёртки
повторов (`RA6LH`, а не «RA6LH RA6LH»). ★Расхождение со спекой, осознанное:
документ шлёт эту команду по факту окончания передачи позывного, а наш
передатчик текста моментов внутри очереди не отмечает — подтверждаем сразу.
Доотправка всё равно не поддержана, так что финальный вариант известен уже
здесь и позже не изменится. **Не поддержано:** шаг
скорости внутри текста (`<` / `>`) и слитная передача аббревиатур (`|SK|`) —
эти символы просто снимаются, потому что `TCWSender` работает на одной
скорости и не знает прос-знаков. Доотправка позывного (`cw_msg:arg1;`)
@@ -999,7 +1010,7 @@ running`), а сам телеграф уходит **текущему TX-ист
движков и сети валится с AV — клиент получает `tci_error`, соединение живо) и
неразрывность пачки инициализации под крутящейся ручкой.
### Стенд этапа 2 (бинарные потоки) — 238 проверок, все зелёные
### Стенд этапа 2 (бинарные потоки) — 244 проверки, все зелёные
Отдельная программа (`test/tci/tcitest.pas`, прогон — `test/tci/run.sh`,
внешних библиотек не требует) проверяет потоки на четырёх уровнях: