<!-- task=kotlin_wss_tls plan=0 tag=KOTLIN_WSS_TLS -->

# Kotlin WsServer TLS(WSS) 지원 추가

## 이 파일을 읽는 구현 에이전트에게

각 항목의 체크리스트를 완료 표시하고, 중간 검증 명령을 실행한 뒤 출력을 CODE_REVIEW.md `검증 결과` 섹션에 붙여 넣는다. 최종 검증까지 완료 후 CODE_REVIEW.md 각 항목을 `[x]`로 체크한다.

## 배경

Kotlin `WsServer`는 `org.java_websocket:Java-WebSocket:1.5.6` 라이브러리의 `WebSocketServer`를 상속한다. 동일 라이브러리에는 `DefaultSSLWebSocketServerFactory`가 포함되어 있으며 `WebSocketServer.setWebSocketFactory()` 호출만으로 TLS를 활성화할 수 있다. 이번 작업으로 WsServer에 `sslContext` 파라미터를 추가하고 WSS 단위 테스트를 추가한다. 또한 이 작업이 완료되면 `tls_crosstest` PLAN.md의 TLS_CROSSTEST-8 항목을 WSS phase 포함 4 phase로 확장한다.

**기준 파일:**
- 구현: `kotlin/src/main/kotlin/com/tokilabs/toki_socket/WsServer.kt`
- 테스트 참조: `kotlin/src/test/kotlin/com/tokilabs/toki_socket/TlsTcpTest.kt`, `WsTest.kt`
- 인증서: `kotlin/src/test/resources/server.crt` + `server.key`

---

## [KOTLIN_WSS_TLS-1] WsServer.kt — sslContext 파라미터 추가 및 TLS 활성화

### 문제

`kotlin/src/main/kotlin/com/tokilabs/toki_socket/WsServer.kt` line 14-20 의 `WsServer` 생성자에 `sslContext` 파라미터가 없어 WSS 서버를 생성할 방법이 없다. `TcpServer`는 line 20에 `sslContext: SSLContext? = null`을 이미 지원하지만 `WsServer`는 누락됐다.

### 해결 방법

1. `import org.java_websocket.server.DefaultSSLWebSocketServerFactory` 추가
2. `import javax.net.ssl.SSLContext` 추가
3. 생성자에 `private val sslContext: SSLContext? = null` 파라미터를 **`newClient` 앞**에 추가 (trailing lambda 호환 유지)
4. `start()` 에서 `super.start()` 호출 전 TLS factory 등록 추가

**Before** (`WsServer.kt` line 6-8 imports 이후, line 14-20):
```kotlin
class WsServer(
    host: String,
    port: Int,
    private val path: String = "/",
    private val newClient: (WebSocket) -> WsClient,
) : WebSocketServer(InetSocketAddress(host, port)) {
```

**After:**
```kotlin
class WsServer(
    host: String,
    port: Int,
    private val path: String = "/",
    private val sslContext: SSLContext? = null,
    private val newClient: (WebSocket) -> WsClient,
) : WebSocketServer(InetSocketAddress(host, port)) {
```

**Before** (`WsServer.kt` line 30-33):
```kotlin
    override fun start() {
        startedFlag.set(true)
        super.start()
    }
```

**After:**
```kotlin
    override fun start() {
        startedFlag.set(true)
        if (sslContext != null) {
            setWebSocketFactory(DefaultSSLWebSocketServerFactory(sslContext))
        }
        super.start()
    }
```

기존 호출부 (`WsTest.kt`, crosstest 파일)는 모두 trailing lambda 형식 `WsServer(host, port, path) { conn -> ... }` 을 사용하므로 파라미터 추가 후에도 재컴파일 없이 호환된다.

### 수정 파일 및 체크리스트

- `kotlin/src/main/kotlin/com/tokilabs/toki_socket/WsServer.kt`
  - [x] `import org.java_websocket.server.DefaultSSLWebSocketServerFactory` 추가
  - [x] `import javax.net.ssl.SSLContext` 추가
  - [x] `private val sslContext: SSLContext? = null` 파라미터를 `newClient` 앞에 추가
  - [x] `start()` 에 `if (sslContext != null) { setWebSocketFactory(...) }` 블록 추가

### 테스트 작성

[KOTLIN_WSS_TLS-2] 항목에서 별도 신규 파일로 작성.

### 중간 검증

```
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle \
  ./gradlew compileKotlin
BUILD SUCCESSFUL
```

---

## [KOTLIN_WSS_TLS-2] TlsWsTest.kt — WSS 단위 테스트 추가

### 문제

`WsServer` TLS path에 대한 테스트가 없다. `TlsTcpTest.kt`에는 TCP TLS 2개 테스트가 있지만 WS에 해당하는 파일이 없다.

### 해결 방법

`kotlin/src/test/kotlin/com/tokilabs/toki_socket/TlsWsTest.kt` 신규 파일 작성. `TlsTcpTest.kt`의 인증서 로딩 헬퍼(`createTlsContexts()`)를 동일하게 사용한다.

**구현할 테스트:**

```kotlin
package com.tokilabs.toki_socket

import com.tokilabs.toki_socket.packets.TestData
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.runBlocking
import java.nio.file.Paths
import kotlin.test.Test
import kotlin.test.assertEquals

class TlsWsTest {
    @Test
    fun testWssSendReceive() = runBlocking {
        val (serverContext, clientContext) = createTlsContexts()
        val port = freePort()
        val received = CompletableDeferred<TestData>()
        val listenerReady = CompletableDeferred<Unit>()
        val server = WsServer("127.0.0.1", port, "/", sslContext = serverContext) { conn ->
            WsClient.forServer(conn, 0, 0, testParserMap())
        }
        server.onClientConnected = { client ->
            addListenerTyped<TestData>(client.communicator) { received.complete(it) }
            listenerReady.complete(Unit)
        }
        server.start()
        val client = dialWss("127.0.0.1", port, "/", clientContext, 0, 0, testParserMap())

        listenerReady.await()
        client.send(TestData.newBuilder().setIndex(55).setMessage("hello wss").build())

        assertEquals(55, received.await().index)
        client.close()
        server.stop()
    }

    @Test
    fun testWssRequestResponse() = runBlocking {
        val (serverContext, clientContext) = createTlsContexts()
        val port = freePort()
        val server = WsServer("127.0.0.1", port, "/", sslContext = serverContext) { conn ->
            WsClient.forServer(conn, 0, 0, testParserMap())
        }
        server.onClientConnected = { client ->
            addRequestListenerTyped<TestData, TestData>(client.communicator) { req ->
                TestData.newBuilder()
                    .setIndex(req.index * 2)
                    .setMessage("wss echo: ${req.message}")
                    .build()
            }
        }
        server.start()
        val client = dialWss("127.0.0.1", port, "/", clientContext, 0, 0, testParserMap())

        val res = sendRequestTyped<TestData, TestData>(
            client.communicator,
            TestData.newBuilder().setIndex(11).setMessage("wss req").build(),
            timeoutMs = 2_000,
        )

        assertEquals(22, res.index)
        assertEquals("wss echo: wss req", res.message)
        client.close()
        server.stop()
    }

    private fun createTlsContexts(): Pair<javax.net.ssl.SSLContext, javax.net.ssl.SSLContext> {
        val loader = Thread.currentThread().contextClassLoader
        val certPath = Paths.get(loader.getResource("server.crt")!!.toURI()).toString()
        val keyPath = Paths.get(loader.getResource("server.key")!!.toURI()).toString()
        return createTestSslContexts(certPath, keyPath)
    }
}
```

`freePort()`, `testParserMap()`, `addListenerTyped`, `addRequestListenerTyped`, `sendRequestTyped`, `createTestSslContexts` 는 기존 `TestHelpers.kt` 및 `Communicator.kt` 에서 제공하므로 별도 import만 추가한다.

### 수정 파일 및 체크리스트

- `kotlin/src/test/kotlin/com/tokilabs/toki_socket/TlsWsTest.kt` (신규)
  - [x] `testWssSendReceive()` — WsServer(sslContext) → dialWss(clientContext) send/receive 검증
  - [x] `testWssRequestResponse()` — request/response index 2배, 메시지 검증
  - [x] `createTlsContexts()` 헬퍼 (TlsTcpTest.kt 와 동일 구조)

### 테스트 작성

본 항목 자체가 테스트 추가.

### 중간 검증

```
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle \
  ./gradlew test --tests "*.TlsWsTest"
BUILD SUCCESSFUL
2 tests completed
```

---

## [KOTLIN_WSS_TLS-3] tls_crosstest PLAN.md — Kotlin WSS phase 추가

### 문제

`agent-task/tls_crosstest/PLAN.md` 의 배경 섹션이 "Kotlin WsServer는 TLS를 지원하지 않으므로 Kotlin 서버 조합은 TLS TCP 1개 phase만 추가한다"로 제한되어 있고, 포트 배정표의 kotlin_* 행에 WSS 포트가 없으며, TLS_CROSSTEST-8 항목이 2 phase(send-push, requests)만 기술하고 있다.

이번 작업으로 WSS가 가능해졌으므로 4 phase(TLS TCP send-push, TLS TCP requests, WSS send-push, WSS requests)로 확장한다.

### 해결 방법

`agent-task/tls_crosstest/PLAN.md` 수정:

1. **배경 섹션**: "Kotlin WsServer는 TLS를 지원하지 않으므로 Kotlin 서버 조합은 TLS TCP 1개 phase만 추가한다" 문장 삭제
2. **포트 배정표** — kotlin_* 행 WSS 포트 추가:

   | orchestrator | TCP | WS | TLS TCP | WSS |
   |---|---|---|---|---|
   | kotlin_dart | 29490 | 29492 | **29494** | **29496** |
   | kotlin_go | 29390 | 29392 | **29394** | **29396** |
   | kotlin_python | 29394 | 29396 | **29398** | **29400** |
   | kotlin_typescript | 29794 | 29796 | **29798** | **29800** |

   (각 파일 내에서 기존 TCP/WS 포트와 겹치지 않음. 다른 orchestrator 파일과의 포트 중복은 허용.)

3. **TLS_CROSSTEST-8 항목**: "TLS TCP phase 1개만" 제약 삭제 → WSS phase 2개(WsServer 생성 시 `sslContext = serverCtx` 전달, 클라이언트 `--mode=wss` 호출) 추가

4. **CODE_REVIEW.md TLS_CROSSTEST-8 설명**: "TLS TCP phase 추가" → "TLS TCP + WSS phase 추가"로 갱신

5. **CODE_REVIEW.md 리뷰어 체크포인트**: "Kotlin WsServer TLS 미지원" 항목 삭제

### 수정 파일 및 체크리스트

- `agent-task/tls_crosstest/PLAN.md`
  - [x] 배경 섹션 Kotlin WsServer TLS 미지원 제약 문구 삭제
  - [x] 포트 배정표 kotlin_dart WSS 29496, kotlin_go WSS 29396, kotlin_python WSS 29400, kotlin_typescript WSS 29800 추가
  - [x] TLS_CROSSTEST-8 항목 포트 상수, WsServer WSS server 생성, `runWssSendPush` / `runWssRequests` 함수, main 호출 추가 기술
- `agent-task/tls_crosstest/CODE_REVIEW.md`
  - [x] TLS_CROSSTEST-8 행 설명 "TLS TCP + WSS phase 추가"로 갱신
  - [x] 리뷰어 체크포인트 "Kotlin WsServer TLS 미지원" 항목 삭제

### 테스트 작성

스킵 — 문서 수정.

### 중간 검증

없음.

---

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `kotlin/src/main/kotlin/com/tokilabs/toki_socket/WsServer.kt` | KOTLIN_WSS_TLS-1 |
| `kotlin/src/test/kotlin/com/tokilabs/toki_socket/TlsWsTest.kt` (신규) | KOTLIN_WSS_TLS-2 |
| `agent-task/tls_crosstest/PLAN.md` | KOTLIN_WSS_TLS-3 |
| `agent-task/tls_crosstest/CODE_REVIEW.md` | KOTLIN_WSS_TLS-3 |

## 의존 관계 및 구현 순서

**1단계**: KOTLIN_WSS_TLS-1 (WsServer.kt)
**2단계**: KOTLIN_WSS_TLS-2 (TlsWsTest.kt) — WsServer.kt 변경 후
**3단계**: KOTLIN_WSS_TLS-3 (tls_crosstest 문서) — 병렬 가능

## 최종 검증

```
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle \
  ./gradlew test
BUILD SUCCESSFUL
X tests completed
(기존 테스트 포함 전체 PASS, TlsWsTest 2개 포함)
```
