mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-27 02:17:33 +00:00
fix(tci): жизненный цикл, синхронизация слайсов и WebSocket по RFC
Разбор ревью ветки. Критичное — четыре отказа жизненного цикла и один пробел синхронизации. Use-after-free стора спотов: FTCIAdapter освобождается ДО FDXStore. Команда SPOT/SPOT_DELETE, пришедшая между их гибелью, обращалась к освобождённой памяти. Bind-адрес: TCIParseIPv4 стал строгим (out + Boolean, ровно четыре октета 0..255). Кривой адрес — отказ поднимать сокет, а не молчаливый INADDR_ANY: авторизации в TCI нет. В UI порт и адрес применяются по уходу фокуса и по Close, а не на каждую букву — набор «127.0.0.1» по дороге проходил через «127.0.0.» и открывал порт наружу. Остановка при висящем Synchronize: флаг Stopping (адаптер не начинает новых Invoke), прокачка CheckSynchronize в цикле ожидания Stop и запрет освобождать клиента, чей поток не вышел. Владение переделано: клиента освобождает только тик-поток (ReapClients), клиентский лишь помечает себя закрытым. Отправка больше не блокирует вызывающего: Send/Broadcast кладут строку в очередь клиента, в сокет пишет тик-поток вне общего лока, склеивая очередь в общие кадры. Медленный клиент морозил UI на таймаут отправки за каждое движение ручки VFO; теперь он просто вылетает. Слайсы: в контроллере появилось rfSliceState (нагрузка — FSliceFreqId), его шлют сами сеттеры слайса; SyncSetVfo зовёт SliceFreqChanged, как CAT. Адаптер разворачивает Id в пару (приёмник, канал) и рассылает состояние именно этого канала, а не канала 0 каждого пана. WebSocket по RFC 6455: маска обязательна, FIN/continuation собираются, RSV и незнакомые opcode рвут соединение, control-кадры ≤125 и только целиком, 64-битная длина не сворачивается в отрицательный Integer, пустой Sec-WebSocket-Key получает 400. Хвост пакета handshake больше не выбрасывается — первая команда не теряется. Клиент после исключения в разборе не остаётся висеть в массиве. Ещё: DSP и squelch доп. приёмников читаются и пишутся из TCtrlSlice (парные сеттеры сохраняли соседние поля значениями главного тракта); параметры потоков — в TTCIClient, они клиентские по спецификации; эхо под своим локом; ApplySettings возвращает результат, отказ старта виден оператору; инициализация объявляет только существующие каналы; SET_IN_FOCUS реализован через OnFocusRequest. Осознанно не сделано и записано в doc/TCI.md §3.1: AGC_GAIN для приёмников >0 (AGC-T один на тракт), цвет спота, KEYER, TX_FOOTSWITCH, арбитраж нескольких клиентов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+28
-3
@@ -475,6 +475,11 @@ type
|
||||
FChkTCIEnabled: TFlatCheckBox;
|
||||
FEdTCIPort: TFlatSpinEdit;
|
||||
FEdTCIBind: TFlatEdit;
|
||||
// Последнее применённое: обработчик висит на уходе фокуса и на Close,
|
||||
// а перезапускать сервер без правки нельзя (порвёт клиентов).
|
||||
FTCILastEnabled: Boolean;
|
||||
FTCILastPort: Integer;
|
||||
FTCILastBind: string;
|
||||
|
||||
// ---- Close button ----
|
||||
FBtnClose: TFlatButton;
|
||||
@@ -3478,7 +3483,9 @@ begin
|
||||
Spin.MinValue := 1;
|
||||
Spin.MaxValue := 65535;
|
||||
Spin.Value := 40001;
|
||||
Spin.OnChange := OnTCIAnyChange;
|
||||
// Применяем по уходу фокуса, а не на каждое нажатие: набирая «40001», через
|
||||
// OnChange мы бы подряд перезапустили сервер на портах 4, 40, 400, 4000.
|
||||
Spin.OnExit := OnTCIAnyChange;
|
||||
FEdTCIPort := Spin;
|
||||
|
||||
Y := Y + ROW_H;
|
||||
@@ -3490,7 +3497,9 @@ begin
|
||||
Ed.Font.Color := CLR_INPUT_TEXT;
|
||||
Ed.Font.Size := 9;
|
||||
Ed.Text := '127.0.0.1';
|
||||
Ed.OnChange := OnTCIAnyChange;
|
||||
// Тем более адрес: промежуточное «127.0.0.» — не адрес, и раньше это молча
|
||||
// означало «слушать на всех интерфейсах». Применяем по уходу фокуса.
|
||||
Ed.OnExit := OnTCIAnyChange;
|
||||
FEdTCIBind := Ed;
|
||||
|
||||
// Авторизации в протоколе нет: открытый наружу порт = полный доступ к трансиверу.
|
||||
@@ -3749,7 +3758,16 @@ procedure TSettingsForm.OnTCIAnyChange(Sender: TObject);
|
||||
begin
|
||||
if FLoading then Exit;
|
||||
if not Assigned(FOnTCISettingsChange) then Exit;
|
||||
FOnTCISettingsChange(FChkTCIEnabled.Checked, FEdTCIPort.Value, FEdTCIBind.Text);
|
||||
// Применение перезапускает сервер и рвёт соединения клиентов, поэтому зовём
|
||||
// только на РЕАЛЬНОЕ изменение: обработчик висит и на уходе фокуса, и на
|
||||
// кнопке Close, а они срабатывают и без правок.
|
||||
if (FChkTCIEnabled.Checked = FTCILastEnabled) and
|
||||
(FEdTCIPort.Value = FTCILastPort) and
|
||||
(FEdTCIBind.Text = FTCILastBind) then Exit;
|
||||
FTCILastEnabled := FChkTCIEnabled.Checked;
|
||||
FTCILastPort := FEdTCIPort.Value;
|
||||
FTCILastBind := FEdTCIBind.Text;
|
||||
FOnTCISettingsChange(FTCILastEnabled, FTCILastPort, FTCILastBind);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.LoadTCISettings(Enabled: Boolean; Port: Integer;
|
||||
@@ -3760,6 +3778,9 @@ begin
|
||||
FChkTCIEnabled.Checked := Enabled;
|
||||
FEdTCIPort.Value := EnsureRange(Port, 1, 65535);
|
||||
FEdTCIBind.Text := BindAddr;
|
||||
FTCILastEnabled := FChkTCIEnabled.Checked;
|
||||
FTCILastPort := FEdTCIPort.Value;
|
||||
FTCILastBind := FEdTCIBind.Text;
|
||||
finally
|
||||
FLoading := False;
|
||||
end;
|
||||
@@ -4594,6 +4615,10 @@ end;
|
||||
|
||||
procedure TSettingsForm.BtnCloseClick(Sender: TObject);
|
||||
begin
|
||||
// Порт и адрес TCI применяются по уходу фокуса (см. BuildAdvancedTab), а из
|
||||
// поля, в котором стоит курсор, при закрытии окна фокус может и не уйти —
|
||||
// дожимаем правку здесь, иначе она молча пропадёт.
|
||||
OnTCIAnyChange(nil);
|
||||
Close;
|
||||
end;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user