feat: add safe Tornado K3D playout adapter
This commit is contained in:
@@ -25,6 +25,9 @@
|
||||
| 동기 Oracle/MySQL 연결 | 비동기 Oracle/MariaDB 공급자, 취소·timeout·선별 재시도 |
|
||||
| DB 실패 시 `Application.Exit()` | 앱 유지 + WebView 소스별 health/오류/재조회 |
|
||||
| 종목·지수 선택 데이터 | WebView의 KRX/NXT 종목 및 5개 지수 실데이터 표 |
|
||||
| 직접 K3D COM 호출 | COM 중립 `IPlayoutEngine` + x64 late-bound 어댑터 |
|
||||
| UI thread의 Tornado 호출 | bounded STA FIFO, timeout 격리, 재연결·프로세스 감시 |
|
||||
| 버튼 즉시 상태 변경 | requestId 기반 WebView bridge와 실제 결과 후 상태 갱신 |
|
||||
| AnyCPU/x86 혼재 | 솔루션 및 게시 프로필 x64 단일화 |
|
||||
| 상대 경로 Web 파일 | MSIX Content 및 안전한 가상 호스트 매핑 |
|
||||
|
||||
@@ -43,11 +46,14 @@ WebView는 `https://app.mbn.local` 가상 호스트로 패키지 내부 파일
|
||||
|
||||
### Tornado/K3D
|
||||
|
||||
- 설치된 x64 COM 타입 라이브러리에서 재현 가능한 Interop 생성
|
||||
- `IPlayoutEngine` 경계로 COM 형식 격리
|
||||
- STA 호출 직렬화와 프로세스 재연결
|
||||
- MSIX 환경의 COM 활성화 및 벤더 재배포 조건 확인
|
||||
- 기존 `StartsWith("Tornado2")` 프로세스 탐지 변경 반영
|
||||
- 완료: Registry64의 K3D TypeLib/CLSID/ProgID/AMD64/Apartment 등록 검사
|
||||
- 완료: 빌드 입력에 Interop DLL을 두지 않는 late binding과 선택적 진단용 `TlbImp` 스크립트
|
||||
- 완료: `IPlayoutEngine` 경계, bounded STA FIFO/message pump, timeout 후 `OutcomeUnknown` 격리
|
||||
- 완료: 연결/해제, 명시적 재연결(no replay), `Tornado2` 접두사 프로세스 감시
|
||||
- 완료: `PREPARE`, `TAKE IN`, `NEXT`, `TAKE OUT` WebView 메시지 및 상태/오류 UI 연결
|
||||
- 완료: 기본 DryRun, Test의 단일 loopback 테스트 인스턴스·채널·씬 allowlist, Live 이중 승인
|
||||
- 확인 대기: 현재 `PGM`과 분리된 테스트 Tornado 인스턴스·출력 채널·테스트 씬의 실제 호출
|
||||
- 확인 대기: 승인된 Test 환경에서 MSIX 컨텍스트의 실제 COM 활성화와 출력 관찰
|
||||
|
||||
### 화면 기능
|
||||
|
||||
|
||||
187
docs/PLAYOUT.md
Normal file
187
docs/PLAYOUT.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Tornado/K3D x64 송출 운영 가이드
|
||||
|
||||
## 안전 기준
|
||||
|
||||
기본 모드는 `DryRun`입니다. `DryRun`은 COM 객체를 만들거나 Tornado 출력에 명령을 보내지 않고 WebView 메시지, 큐, 상태 및 오류 표시 흐름만 검증합니다. 현재 방송 PROGRAM(PGM)에 연결된 Tornado 프로세스는 항상 **안전하지 않은 대상**으로 취급합니다. 프로세스 이름이나 실행 여부만으로 테스트 대상이라고 판단하지 않습니다.
|
||||
|
||||
이 저장소의 작업과 자동 검증은 실제 PGM 출력 또는 라이브 `TAKE IN`을 허가하지 않습니다. `Test` 검증은 PGM과 분리된 전용 테스트 인스턴스, 테스트 출력 채널 및 허용 목록에 든 테스트 씬을 모두 확인한 뒤에만 수행합니다. `Live`는 운영 책임자의 명시적인 회차별 허가 없이는 사용하지 않습니다.
|
||||
|
||||
## 확인된 x64 COM 등록
|
||||
|
||||
현재 개발 장비에서 확인할 등록 기준은 다음과 같습니다.
|
||||
|
||||
| 항목 | 기대값 |
|
||||
|---|---|
|
||||
| 레지스트리 뷰 | `HKLM\SOFTWARE\Classes`의 `Registry64`; 관련 HKCU override 없음 |
|
||||
| TypeLib GUID | `{2B7F2D64-3A8D-401C-BE73-5C0747BA342C}` |
|
||||
| TypeLib 버전/대상 | `1.0` / `0\win64` |
|
||||
| KAEngine CLSID | `{D756CDBE-AA31-42B2-9CC7-018753CA61BF}` |
|
||||
| ProgID | `K3DAsyncEngine.KAEngine.1` |
|
||||
| KAEventHandler CLSID / ProgID | `{39828C77-EFF0-4E59-979B-8673C028C718}` / `K3DAsyncEngine.KAEventHandler.1` |
|
||||
| ThreadingModel | `Apartment` |
|
||||
| DLL PE 대상 | TypeLib, KAEngine, KAEventHandler 모두 `AMD64` |
|
||||
|
||||
[Inspect-K3DRegistration.ps1](../scripts/Inspect-K3DRegistration.ps1)은 64비트 HKLM 등록에서 GUID, 버전과 `win64` TypeLib을 확인합니다. KAEngine과 KAEventHandler 각각의 CLSID→ProgID 및 ProgID→CLSID 양방향 매핑, `Apartment` 모델, `InprocServer32` 존재와 AMD64 PE 헤더도 검사합니다. HKCU 64비트 `Software\Classes`에 같은 TypeLib GUID, CLSID 또는 ProgID override가 하나라도 있으면 fail-closed합니다. 레지스트리와 파일을 읽기만 하며 COM을 활성화하거나 등록을 변경하지 않습니다.
|
||||
|
||||
`MBN_STOCK_WEBVIEW.PlayoutSmoke --probe`는 런타임과 같은 검사기를 통해 KAEngine과 KAEventHandler 등록을 함께 확인합니다. 이 probe도 COM 객체를 생성하지 않습니다.
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass `
|
||||
-File .\scripts\Inspect-K3DRegistration.ps1
|
||||
```
|
||||
|
||||
검사가 실패하면 x86 등록으로 대체하거나 DLL을 앱 폴더에 복사하지 않습니다. 벤더가 제공한 x64 설치 프로그램과 라이선스 절차로 장비 상태를 복구한 뒤 다시 검사합니다.
|
||||
|
||||
## 런타임 참조 방식
|
||||
|
||||
앱은 빌드 시점의 `Interop.K3DAsyncEngineLib.dll`을 참조하지 않습니다. HKLM 64비트 등록과 HKCU override 부재를 검증한 뒤 고정된 KAEngine/KAEventHandler CLSID로만 런타임 late binding하고, COM 객체와 호출 세부 사항은 `IPlayoutEngine` 구현 안에 격리합니다. 따라서 `obj` 폴더에 남은 Interop DLL 또는 특정 개발 장비의 벤더 설치 경로가 빌드 입력이 되지 않습니다.
|
||||
|
||||
타입 정보를 조사해야 할 때만 [Generate-K3DInterop.ps1](../scripts/Generate-K3DInterop.ps1)을 선택적으로 사용합니다. 생성 전 Inspect와 같은 HKLM 양방향 매핑·두 COM 서버 AMD64·HKCU override 부재 검사를 통과해야 합니다. 그 뒤 알려진 Windows SDK의 x64 `TlbImp.exe` 위치를 선택하거나 명시적으로 받은 x64 경로를 검증하고 `/machine:X64`로 생성합니다. 입력 TypeLib과 도구 및 결과물 모두 AMD64인지 확인하며, 결과는 Git에서 제외된 다음 위치에만 씁니다.
|
||||
|
||||
```text
|
||||
artifacts\K3DInterop\Interop.K3DAsyncEngineLib.dll
|
||||
```
|
||||
|
||||
실행 전 계획만 확인:
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass `
|
||||
-File .\scripts\Generate-K3DInterop.ps1 -WhatIf
|
||||
```
|
||||
|
||||
진단용 Interop 생성:
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass `
|
||||
-File .\scripts\Generate-K3DInterop.ps1
|
||||
```
|
||||
|
||||
SDK가 기본 위치에 없다면 x64 SDK의 `TlbImp.exe` 절대 경로를 `-TlbImpPath`에 지정합니다. 생성물은 진단용일 뿐 프로젝트 참조나 MSIX 콘텐츠로 추가하지 않습니다. 이 스크립트도 COM 활성화와 등록 변경은 하지 않습니다.
|
||||
|
||||
## 로컬 설정
|
||||
|
||||
런타임 설정 기본 경로는 다음과 같습니다.
|
||||
|
||||
```text
|
||||
%LOCALAPPDATA%\MBN_STOCK_WEBVIEW\Config\playout.local.json
|
||||
```
|
||||
|
||||
[playout.example.json](../Config/playout.example.json)을 구조 참고용으로 사용합니다. 예시는 `DryRun`, 외부 씬 루트와 출력 채널 미지정, 빈 씬 허용 목록, 라이브 신뢰 플래그 해제 상태이므로 실제 출력에 사용할 수 없습니다. 테스트 장비의 호스트, 채널, 창 제목 패턴, 외부 씬 루트와 허용할 씬 이름은 로컬 파일에만 기록하고 Git, 로그 또는 지원 첨부파일에 넣지 않습니다.
|
||||
|
||||
| 속성 | 의미 |
|
||||
|---|---|
|
||||
| `mode` | `Disabled`, `DryRun`, `Test`, `Live` 중 하나 |
|
||||
| `host`, `port` | 테스트 또는 운영 승인을 받은 KTAP endpoint |
|
||||
| `tcpMode`, `clientPort` | `KTAPConnect`의 전송 모드와 로컬 client port 인자. Test/Live는 유실 방지를 위해 `tcpMode: 1`만 허용 |
|
||||
| `sceneDirectory` | Test/Live에서 사용하는 외부 `.t2s` 루트의 절대 경로. `null`은 안전한 미설정 상태 |
|
||||
| `outputChannel` | 확인된 전용 출력 채널. `null`은 안전한 미설정 상태 |
|
||||
| `layoutIndex` | 씬 플레이어 layout 위치 |
|
||||
| `testProcessWindowTitlePattern` | 전용 테스트 인스턴스만 식별하는 창 제목 패턴 |
|
||||
| `testSceneAllowlist` | `Test`에서 허용한 테스트 scene name(code) 목록. 경로나 확장자는 넣지 않음 |
|
||||
| `trustedLiveOutputEnabled` | 운영자가 로컬 파일에서만 설정하는 라이브 1차 게이트 |
|
||||
| `queueCapacity` | 직렬 STA 명령 큐의 최대 대기 항목 수 |
|
||||
| `*TimeoutMilliseconds` | 연결, 작업 및 해제 제한 시간 |
|
||||
| `processPollIntervalMilliseconds` | `Tornado2` 접두사 프로세스 감시 주기 |
|
||||
| `reconnect*` | 재연결 지연, 최대 횟수 및 활성화 여부 |
|
||||
|
||||
JSON보다 다음 환경 변수가 우선합니다.
|
||||
|
||||
```text
|
||||
MBN_STOCK_PLAYOUT_MODE
|
||||
MBN_STOCK_PLAYOUT_HOST
|
||||
MBN_STOCK_PLAYOUT_PORT
|
||||
MBN_STOCK_PLAYOUT_TCP_MODE
|
||||
MBN_STOCK_PLAYOUT_CLIENT_PORT
|
||||
MBN_STOCK_PLAYOUT_SCENE_DIRECTORY
|
||||
MBN_STOCK_PLAYOUT_OUTPUT_CHANNEL
|
||||
MBN_STOCK_PLAYOUT_LAYOUT_INDEX
|
||||
MBN_STOCK_PLAYOUT_TEST_WINDOW_TITLE_PATTERN
|
||||
MBN_STOCK_PLAYOUT_QUEUE_CAPACITY
|
||||
MBN_STOCK_PLAYOUT_CONNECT_TIMEOUT_MS
|
||||
MBN_STOCK_PLAYOUT_OPERATION_TIMEOUT_MS
|
||||
MBN_STOCK_PLAYOUT_DISCONNECT_TIMEOUT_MS
|
||||
MBN_STOCK_PLAYOUT_PROCESS_POLL_INTERVAL_MS
|
||||
MBN_STOCK_PLAYOUT_RECONNECT_DELAY_MS
|
||||
MBN_STOCK_PLAYOUT_MAXIMUM_RECONNECT_ATTEMPTS
|
||||
MBN_STOCK_PLAYOUT_RECONNECT_ENABLED
|
||||
```
|
||||
|
||||
`testSceneAllowlist`와 `trustedLiveOutputEnabled`는 환경 변수로 변경할 수 없으며 로컬 설정 파일에서만 관리합니다. `SceneDirectory`는 Test/Live에서 존재하는 비-reparse 외부 디렉터리여야 하며, 엔진은 상대 `.t2s` 파일을 정규화해 이 루트 밖으로 나가는 경로를 거부합니다. scene file의 basename과 scene name 및 Test allowlist 항목도 서로 일치해야 합니다. 설정 파일은 실행 계정만 읽을 수 있도록 ACL을 제한합니다. 라이선스 키나 인증정보를 이 파일에 기록하지 않습니다.
|
||||
|
||||
## 모드와 안전 게이트
|
||||
|
||||
`Disabled`는 모든 송출 명령을 거부하는 운영 롤백 모드입니다. `DryRun`은 COM 없이 WebView 동작을 성공 결과로 모의합니다. `Test`와 `Live`만 등록된 COM을 사용할 수 있습니다.
|
||||
|
||||
`Test` 전환 전 다음 조건을 모두 사람이 확인합니다.
|
||||
|
||||
1. Tornado 인스턴스가 현재 PGM과 물리적·논리적으로 분리되어 있고 Test의 `host`가 로컬 loopback IP literal(`127.0.0.1` 또는 `::1`)입니다. `localhost`를 포함한 호스트 이름은 허용하지 않습니다.
|
||||
2. `testProcessWindowTitlePattern`이 전용 테스트 인스턴스 하나만 식별합니다.
|
||||
3. `outputChannel`이 라우터/엔진에서 테스트 출력임을 확인했습니다.
|
||||
4. `sceneDirectory`가 승인된 테스트 자산 루트이고 사용할 scene name이 `testSceneAllowlist`에 있으며 운영 씬이 아닙니다.
|
||||
5. 같은 플레이리스트로 `DryRun`의 `PREPARE`, `TAKE IN`, `NEXT`, `TAKE OUT` 메시지와 화면 상태를 먼저 확인했습니다.
|
||||
|
||||
프로세스 감시는 이름이 `Tornado2`로 시작하는 모든 프로세스를 대소문자 구분 없이 찾습니다. 이는 가용성 신호일 뿐 송출 권한이 아닙니다. Test에서는 Tornado2 프로세스가 정확히 하나이고 그 창 제목이 테스트 규칙에만 일치해야 하며, `PGM`/`PROGRAM` 창이 하나라도 있거나 여러 인스턴스가 모호하면 COM을 활성화하지 않습니다. 즉 현재 방송 PGM이 실행 중인 장비에서는 Test 연결을 거부합니다.
|
||||
|
||||
Test에서 로컬 프로세스/창 제목 검사를 원격 KTAP endpoint의 신원 증명으로 사용하지 않습니다. 따라서 Test 모드는 loopback endpoint만 허용하며, 원격 host는 아래의 Live 이중 승인을 모두 통과한 경우에만 사용할 수 있습니다.
|
||||
|
||||
`Live`에는 별도의 이중 승인이 필요합니다.
|
||||
|
||||
1. 로컬 JSON의 `trustedLiveOutputEnabled`를 명시적으로 `true`로 설정합니다.
|
||||
2. 그 회차에만 프로세스 환경 변수 `MBN_STOCK_PLAYOUT_AUTHORIZE_LIVE_OUTPUT`을 정확히 `I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH`로 설정합니다.
|
||||
|
||||
둘 중 하나라도 없으면 라이브 출력을 거부해야 합니다. 이 값은 사용자/시스템 영구 환경 변수로 저장하지 않으며 앱 종료 후 제거합니다. 현재 작업에는 이 승인이 주어지지 않았으므로 `Live` 및 실제 PGM `TAKE IN` 검증을 수행하지 않습니다.
|
||||
|
||||
## 안전한 스모크 절차
|
||||
|
||||
먼저 `mode`가 `DryRun`이고 라이브 환경 변수가 없는지 확인합니다.
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:MBN_STOCK_PLAYOUT_AUTHORIZE_LIVE_OUTPUT -ErrorAction SilentlyContinue
|
||||
powershell -NoProfile -ExecutionPolicy Bypass `
|
||||
-File .\scripts\Inspect-K3DRegistration.ps1
|
||||
dotnet run --project .\tools\MBN_STOCK_WEBVIEW.PlayoutSmoke `
|
||||
-c Debug -p:Platform=x64 -- --probe
|
||||
dotnet run --project .\tools\MBN_STOCK_WEBVIEW.PlayoutSmoke `
|
||||
-c Debug -p:Platform=x64 -- --dry-run
|
||||
dotnet test .\MBN_STOCK_WEBVIEW.sln -c Debug -p:Platform=x64
|
||||
dotnet test .\MBN_STOCK_WEBVIEW.sln -c Release -p:Platform=x64
|
||||
```
|
||||
|
||||
`PlayoutSmoke --probe`의 기본 출력은 등록 준비 여부·issue 이름, Tornado2 프로세스 수와 PROGRAM 감지 여부만 제공합니다. 벤더 DLL 전체 경로, 프로세스 ID/이름 및 창 제목은 출력하거나 로그에 남기지 않습니다. 정확한 로컬 등록 경로가 필요한 관리자는 `Inspect-K3DRegistration.ps1` 결과를 해당 장비에서만 확인하고 지원 첨부파일에 포함하지 않습니다.
|
||||
|
||||
앱을 실행한 뒤 다음을 확인합니다.
|
||||
|
||||
1. 시작 상태가 `DRY RUN`으로 보이고 앱이 COM 또는 Tornado 없이 종료되지 않습니다.
|
||||
2. WebView의 `PREPARE`, `TAKE IN`, `NEXT`, `TAKE OUT`이 native bridge를 통과해 명확한 결과를 표시합니다.
|
||||
3. 잘못된 설정 또는 엔진 부재가 앱 종료가 아니라 연결 상태와 안전한 오류 메시지로 표시됩니다.
|
||||
4. x64 MSIX를 설치해도 같은 dry-run 흐름이 동작합니다.
|
||||
|
||||
실제 COM 스모크는 별도 테스트 인스턴스와 테스트 씬이 준비된 때에만 진행합니다. 위 안전 게이트를 독립적으로 재확인하고 `mode`를 `Test`로 바꾼 뒤 테스트 모니터에서 `PREPARE → TAKE IN → NEXT → TAKE OUT` 결과를 관찰합니다. PGM/운영 출력에 변화가 보이면 즉시 앱을 종료하고 롤백합니다. 명령 timeout 뒤 결과가 불명확하면 명령을 자동 또는 수동으로 반복하지 말고 테스트 출력 상태를 먼저 확인합니다.
|
||||
|
||||
패키지 스모크에서는 벤더 x64 COM이 장비에 정식 등록되어 있어야 합니다. MSIX에 벤더 DLL을 복사해 활성화 오류를 우회하지 않습니다. 패키지 컨텍스트에서 COM 활성화가 막히면 `DryRun` 또는 `Disabled`를 유지하고 HRESULT와 등록 검사 결과만 보고합니다.
|
||||
|
||||
## 장애 및 롤백
|
||||
|
||||
가장 빠른 롤백은 로컬 설정을 다음처럼 바꾸고 앱을 재시작하는 것입니다.
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "Disabled"
|
||||
}
|
||||
```
|
||||
|
||||
그 다음 회차별 라이브 환경 변수를 제거하고 테스트/운영 라우팅 상태를 사람이 확인합니다. 필요하면 조직 배포 절차로 직전 서명 MSIX로 되돌립니다. COM 등록 해제, 벤더 설치 폴더 삭제 또는 DLL 교체는 앱 롤백 절차가 아니므로 수행하지 않습니다. 진단용 `artifacts\K3DInterop`을 삭제해도 런타임에는 영향이 없습니다.
|
||||
|
||||
연결 장애나 Tornado 재시작은 화면의 `Disconnected`, `Reconnecting`, `Faulted` 또는 `OutcomeUnknown` 상태로 판단합니다. 재연결 횟수를 모두 소진하거나 결과가 불명확한 송출 명령이 있으면 자동 재송출하지 않고 `Disabled`로 전환한 뒤 운영자가 테스트 출력 상태를 확인합니다.
|
||||
|
||||
## 저장소 반입 금지
|
||||
|
||||
다음 항목은 Git과 MSIX에 넣지 않습니다.
|
||||
|
||||
- 벤더 COM DLL 및 생성한 Interop DLL
|
||||
- Tornado/K3D 라이선스 파일, 키 또는 인증서
|
||||
- 실제 `.t2s` 씬, 이미지, 영상 및 방송 자산
|
||||
- 운영 호스트, 채널, 창 제목 및 로컬 허용 목록이 든 설정
|
||||
- 실제 출력 캡처나 비밀정보가 포함된 진단 로그
|
||||
|
||||
벤더 바이너리와 자산은 승인된 설치·배포 위치에서 관리하고, 앱 저장소에는 COM 중립 인터페이스와 안전한 설정 예시만 유지합니다.
|
||||
Reference in New Issue
Block a user