iosignal 6.1.0 → 6.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ko.md ADDED
@@ -0,0 +1,269 @@
1
+ # iosignal
2
+
3
+ [English](README.md) | [한국어](README.ko.md)
4
+
5
+ Node.js와 브라우저를 위한 실시간 메시징 라이브러리입니다. WebSocket 기반
6
+ 클라이언트·서버 통신, 태그 기반 발행/구독, RPC 서비스를 제공합니다.
7
+ Node.js에서는 TCP 기반 `IOCongSocket`도 사용할 수 있습니다.
8
+
9
+ ## 설치
10
+
11
+ ```bash
12
+ npm install iosignal
13
+ ```
14
+
15
+ 아래 Node.js 예제는 ESM 형식입니다. `.mjs` 파일로 저장하거나 프로젝트의
16
+ `package.json`에 `"type": "module"`을 설정하세요.
17
+
18
+ ## 빠른 시작
19
+
20
+ ### 서버
21
+
22
+ 다음을 `server.mjs`로 저장하고 `node server.mjs`로 실행합니다.
23
+
24
+ ```js
25
+ import { Server, replyService } from 'iosignal';
26
+
27
+ const server = new Server({ port: 8080 });
28
+ server.attach('reply', replyService);
29
+
30
+ server.on('ready', () => {
31
+ console.log(`Listening on port ${server.port}`);
32
+ });
33
+
34
+ process.once('SIGINT', () => server.close());
35
+ ```
36
+
37
+ 이 예제는 인증 없는 로컬 개발용 서버입니다.
38
+
39
+ ### Node.js 클라이언트
40
+
41
+ 서버 실행 후 다음을 `client.mjs`로 저장하고 `node client.mjs`로 실행합니다.
42
+
43
+ ```js
44
+ import { IO } from 'iosignal';
45
+
46
+ const io = new IO('ws://localhost:8080');
47
+ io.on('error', console.error);
48
+
49
+ io.on('ready', async () => {
50
+ try {
51
+ const response = await io.call('reply', 'echo', 'Hello, iosignal!');
52
+ console.log(response.body);
53
+ } catch (error) {
54
+ console.error(error);
55
+ } finally {
56
+ io.stop();
57
+ }
58
+ });
59
+ ```
60
+
61
+ CommonJS에서도 공개 API를 사용할 수 있습니다.
62
+
63
+ ```js
64
+ const { IO, Server, replyService } = require('iosignal');
65
+ ```
66
+
67
+ ## 발행과 구독
68
+
69
+ 연결이 준비되면 `subscribe()`로 태그를 구독하고 `signal()`로 메시지를 보냅니다.
70
+ 다음 클라이언트는 메시지를 계속 수신합니다.
71
+
72
+ ```js
73
+ import { IO } from 'iosignal';
74
+
75
+ const io = new IO('ws://localhost:8080');
76
+ io.on('error', console.error);
77
+ io.on('message', (tag, message) => {
78
+ console.log(tag, message);
79
+ });
80
+ io.on('ready', () => {
81
+ io.subscribe('room');
82
+ io.signal('room', 'Hello, subscribers!');
83
+ });
84
+
85
+ process.once('SIGINT', () => io.stop());
86
+ ```
87
+
88
+ `signal(tag, ...args)`는 문자열, 바이너리, 객체 및 여러 인자를 지원합니다.
89
+ `unsubscribe(tag)`로 구독을 해제할 수 있습니다.
90
+
91
+ ## 브라우저
92
+
93
+ 번들러를 사용하는 브라우저 프로젝트에서는 브라우저 전용 진입점을 가져옵니다.
94
+
95
+ ```js
96
+ import IO from 'iosignal/io';
97
+
98
+ const io = new IO('ws://localhost:8080');
99
+ io.on('error', console.error);
100
+ io.on('message', (tag, message) => console.log(tag, message));
101
+ io.on('ready', () => {
102
+ io.subscribe('room');
103
+ io.signal('room', 'Hello from the browser!');
104
+ });
105
+ ```
106
+
107
+ 브라우저 예제도 위 서버에 연결합니다. HTTPS 페이지에서는 TLS가 구성된
108
+ `wss://` 서버 주소를 사용하세요. 사용이 끝나면 `io.stop()`으로 연결과 자동 재연결을 중단합니다.
109
+
110
+ ## 주요 API
111
+
112
+ | API | 용도 |
113
+ | --- | --- |
114
+ | `Server` | WebSocket/TCP 서버 생성 |
115
+ | `IO` | Node.js WebSocket 클라이언트; `iosignal/io`에서는 브라우저 클라이언트 |
116
+ | `IOCongSocket` | Node.js TCP 클라이언트 |
117
+ | `server.attach(name, service)` | RPC 서비스 등록 |
118
+ | `io.call(service, command, ...args)` | RPC 요청 |
119
+ | `io.subscribe(tag)` / `io.unsubscribe(tag)` | 태그 구독 / 해제 |
120
+ | `io.signal(tag, ...args)` | 메시지 발행 |
121
+ | `io.stop()` | 자동 재연결 중단 및 연결 정리 |
122
+ | `server.close(callback)` | 서버 종료 |
123
+ | `BohoAuth` | 서버 인증 관리자 |
124
+ | `StringKeyProvider`, `FileKeyProvider`, `RedisKeyProvider` | 인증 키 공급자 |
125
+
126
+ ## boho를 이용한 인증과 암호통신
127
+
128
+ IOSignal은 [boho](https://github.com/remocons/boho)를 사용하여 공유 키 기반
129
+ 인증과 메시지 암호화를 제공합니다. boho에는
130
+ [Arduino 구현](https://github.com/remocons/boho-arduino)과 Node.js·브라우저용
131
+ JavaScript 구현이 있어, 직접 만든 장치와 웹앱을 같은 방식으로 연결할 수 있습니다.
132
+
133
+ ### 왜 공유 키 기반의 boho인가요?
134
+
135
+ Arduino 장치와 직접 개발한 웹앱, 관리 주체가 같은 서버에서는 양쪽 코드와
136
+ 최초 키 설정을 직접 관리할 수 있습니다. 제작 시 USB·직렬 연결로 장치별 키를
137
+ 기록하거나, 웹앱 사용자가 별도 경로로 받은 키를 입력하거나, 서버 사이에서
138
+ 기존 SSH·TLS 연결로 키를 배포할 수 있습니다.
139
+
140
+ 이처럼 안전한 키 설정 경로가 이미 있다면 사전 공유 대칭키로도 인증과 암호통신을
141
+ 설계할 수 있습니다. 인증기관 없이 통신할 상대를 정할 수 있으며, 이때 신뢰의
142
+ 기반은 키 설정 경로와 양쪽 프로그램입니다. 장치별 키, 안전한 보관과 교체·폐기
143
+ 방법은 여전히 필요합니다. 웹앱의 공개 JavaScript 번들에 공통 비밀 키를 넣는
144
+ 것은 안전한 키 배포가 아닙니다.
145
+
146
+ boho는 이 환경을 위해 SHA-256 기반 키스트림, XOR 암호화, 공유 키 인증과
147
+ 바이너리 패킷을 묶습니다. 차별점은 암호 연산 자체의 새로움보다 **Arduino와
148
+ JavaScript의 호환 구현을 IOSignal의 연결·메시지 전달에 활용할 수 있다는 점**입니다.
149
+
150
+ ### XOR와 해시 기반 ‘가상 OTP’
151
+
152
+ XOR는 같은 값으로 두 번 연산하면 원래 값으로 돌아오는 단순한 연산입니다.
153
+
154
+ ```text
155
+ 암호화: 암호문 = 평문 XOR 키스트림
156
+ 복호화: 평문 = 암호문 XOR 키스트림
157
+ ```
158
+
159
+ 진짜 일회용 패드(One-Time Pad, OTP)는 평문과 독립적인 완전한 무작위 패드를
160
+ 데이터 길이만큼 준비하고, 비밀로 유지하며 한 번만 사용합니다. 이 조건에서는
161
+ 완전한 비밀성을 얻지만, 1MB 데이터마다 새로운 1MB 비밀 패드를 양쪽에 공급해야
162
+ 합니다. 같은 패드를 재사용하면 `C1 XOR C2 = P1 XOR P2`로 평문 사이의 관계가
163
+ 노출됩니다.
164
+
165
+ boho는 데이터 길이만큼 패드를 저장하는 대신, 비밀 키와 메시지별 값에서
166
+ SHA-256으로 키스트림을 생성합니다. 일반적인 `set_key` 경로는 다음과 같습니다.
167
+
168
+ ```text
169
+ K = SHA256(입력 키)
170
+ B = SHA256(K || salt12)
171
+ S_i = SHA256(B || LE32(i)) // i = 1, 2, 3, …
172
+ 키스트림 = S_1 || S_2 || … // 평문 길이만큼 사용
173
+ ```
174
+
175
+ `||`는 바이트열 연결이고, `LE32`는 4바이트 little-endian 정수입니다.
176
+ 각 해시 출력은 32바이트입니다. `salt12`는 시각·카운터·nonce로 구성되며,
177
+ 수신자도 같은 값과 키를 사용해 키스트림을 재현합니다.
178
+
179
+ ‘가상 OTP’는 이 의사난수 키스트림을 설명하는 표현이며 진짜 OTP의 완전한
180
+ 비밀성을 뜻하지 않습니다. **공개된 랜덤 값만 해시하면 누구나 같은 출력을
181
+ 계산할 수 있으므로 공유 비밀 키가 필요합니다.** 같은 키와 `salt12`의 중복도
182
+ 피해야 합니다. XOR만으로 변조를 검출할 수 없어 boho는 데이터 패킷에
183
+ `SHA256(K || salt12 || 평문)`의 앞 8바이트로 계산한 태그도 포함합니다.
184
+ 이 태그는 기존 코드의 명칭과 달리 표준 HMAC이 아닙니다.
185
+
186
+ ### TLS와 함께 이해하기
187
+
188
+ 일반적인 인증서 기반 TLS는 사전에 비밀을 공유하지 않은 상대와 인증·키 합의를
189
+ 수행하고 실제 데이터에는 대칭키를 사용합니다. 로그인하지 않은 웹 사용자도
190
+ 서버를 인증하고 암호화 연결을 만들 수 있지만, 네트워크 익명성을 제공하는 것은
191
+ 아닙니다. 임베디드 환경에서는 인증서·신뢰 루트·시각·갱신 관리와 핸드셰이크
192
+ 연산 및 버퍼가 부담이 될 수 있습니다. 키 설정 경로가 이미 있는 작은 시스템은
193
+ 이런 구성 중 일부를 줄일 수 있습니다.
194
+
195
+ 다만 TLS도 **PSK-only와 PSK+(EC)DHE**를 지원하므로 항상 제3자 인증서나 공개키
196
+ 교환이 필요한 것은 아닙니다.
197
+ [TLS 1.3의 PSK 방식](https://www.rfc-editor.org/rfc/rfc8446.html#section-2.2)
198
+ boho가 모든 환경에서 TLS나 표준 대칭키 구현보다 빠르거나 메모리를 덜 사용한다는
199
+ 뜻은 아니며 실제 비교에는 측정이 필요합니다.
200
+
201
+ ### 연결 암호화와 E2E 데이터 키
202
+
203
+ ```text
204
+ Arduino 장치 ←→ IOSignal 서버 ←→ 브라우저 웹앱
205
+ └──────── 별도 E2E 데이터 키 ────────┘
206
+ ```
207
+
208
+ 연결 인증용 키는 각 클라이언트의 서버 접속 자격을 확인하는 데 사용합니다.
209
+ 중계 서버가 메시지 본문을 읽지 못하게 하려면 최종 송수신자만 공유하는 별도
210
+ 데이터 키로 `signal_e2e`를 사용하고, 수신 측에서 `decrypt_e2e`로 검증·복호화합니다.
211
+ 일반 연결 암호화만으로 종단간 암호화가 되는 것은 아닙니다. 서버는 E2E 메시지의
212
+ 전달에 필요한 라우팅 정보를 처리하며 트래픽 크기 등의 메타데이터도 남습니다.
213
+
214
+ 현재 JavaScript 구현의 `AUTO` 모드는 TLS를 사용하지 않고 boho 인증이 완료된
215
+ 경우 boho 연결 암호화를 사용합니다. E2E 본문 암호화는 연결 보호 여부와 별도입니다.
216
+ 위 빠른 시작의 인증 없는 로컬 예제는 boho 인증·암호화를 활성화한 예제가 아닙니다.
217
+
218
+ 일반 웹 배포에서는 HTTPS로 웹앱을 제공하고 WSS로 연결하면서 본문에 E2E를
219
+ 적용할 수 있습니다. 앱 코드가 변조되면 키와 평문도 노출될 수 있어 E2E에서도
220
+ 웹앱 코드 제공자에 대한 신뢰가 필요합니다. boho는 브라우저의 mixed content
221
+ 정책을 우회하지 않습니다.
222
+
223
+ ### 현재 구현의 적용 조건
224
+
225
+ - 충분히 무작위인 키를 사용하세요. `set_key`의 SHA-256 한 번은 비밀번호용 느린 KDF가 아닙니다.
226
+ - JavaScript는 `crypto.getRandomValues()`를 사용하지만 현재 Arduino 구현의 독립 패킷과 인증 요청의 클라이언트 nonce는 `micros()`에 기반합니다. 이는 암호학적 난수가 아니므로 재시작·장치·통신 방향을 포함해 같은 키와 `salt12`가 반복되지 않는지 검토해야 합니다.
227
+ - boho 자체는 재전송 거부나 challenge 만료를 강제하지 않습니다. 유효한 태그만으로 메시지의 신선도나 명령 실행 권한이 보장되지 않으므로 호출 측과 응용 계층의 수신 정책을 확인해야 합니다.
228
+ - boho는 SHA-256을 조합한 자체 프로토콜입니다. 표준 AEAD/TLS와 동일한 보안 보장이나 장기 키 유출 후 과거 통신을 보호하는 순방향 비밀성을 제공한다고 취급해서는 안 됩니다.
229
+
230
+ ## 소스 빌드 및 검증
231
+
232
+ 이 저장소의 개발·검증 환경은 Node.js 22와 npm 10을 사용합니다.
233
+
234
+ ```bash
235
+ npm ci
236
+ npm run verify
237
+ ```
238
+
239
+ `verify`는 공개 파일 정책을 검사하고, `dist`를 삭제한 뒤 번들과 타입 선언을
240
+ 재생성합니다. 이어서 자동 테스트와 npm 패키지 포함 파일 검사를 수행합니다.
241
+
242
+ 빌드만 실행하려면 `npm run build`, 이미 빌드된 결과의 자동 테스트만 실행하려면
243
+ `npm test`를 사용합니다.
244
+
245
+ ### 통신 자동 테스트
246
+
247
+ ```bash
248
+ npm run build
249
+ npm run test:integration
250
+ ```
251
+
252
+ `test/integration/`은 공개 ESM·CommonJS 빌드 각각에 대해 실제 WebSocket 통신을 검사합니다.
253
+ 로컬 루프백(`127.0.0.1`)의 임시 포트를 사용하므로 외부 서버, Redis, 실제 인증키가 필요하지 않습니다.
254
+
255
+ - 연결과 RPC 응답, 없는 명령 및 권한 거부
256
+ - 두 클라이언트 간 문자열·객체·바이너리 발행/구독과 구독 해제
257
+ - 인증 성공 및 암호화된 RPC 응답, 잘못된 인증 거부
258
+ - 연결 종료 후 클라이언트 재사용과 서버 자원 정리
259
+
260
+ `npm test`와 `npm run verify`에도 통신 검사가 포함됩니다. 테스트별 시간 제한과
261
+ 전체 테스트 파일 시간 제한을 두며, 종료 시 서버와 클라이언트를 정리합니다.
262
+ 실제 브라우저, TLS, TCP 전송 및 Redis 연동 검증은 이 통신 테스트 범위에 포함되지 않습니다.
263
+
264
+ 기존 `test/attach-services/`, `test/auth_server_client/`, `test/pubsub-counter/`,
265
+ `test/subscribe-signal/`은 수동 예제입니다. 자동 실행하지 않으며, 일부는 별도 서버나 Redis가 필요합니다.
266
+
267
+ ## 라이선스
268
+
269
+ 패키지 라이선스: MIT.
package/README.md CHANGED
@@ -1,23 +1,25 @@
1
1
  # iosignal
2
2
 
3
- Node.js와 브라우저를 위한 실시간 메시징 라이브러리입니다. WebSocket 기반
4
- 클라이언트·서버 통신, 태그 기반 발행/구독, RPC 서비스를 제공합니다.
5
- Node.js에서는 TCP 기반 `IOCongSocket`도 사용할 수 있습니다.
3
+ [English](README.md) | [한국어](README.ko.md)
6
4
 
7
- ## 설치
5
+ A real-time messaging library for Node.js and browsers, providing WebSocket
6
+ client–server communication, tag-based publish/subscribe and RPC services.
7
+ Node.js also supports TCP through `IOCongSocket`.
8
+
9
+ ## Installation
8
10
 
9
11
  ```bash
10
12
  npm install iosignal
11
13
  ```
12
14
 
13
- 아래 Node.js 예제는 ESM 형식입니다. `.mjs` 파일로 저장하거나 프로젝트의
14
- `package.json`에 `"type": "module"`을 설정하세요.
15
+ The Node.js examples below use ESM. Save them as `.mjs` files or set
16
+ `"type": "module"` in your project's `package.json`.
15
17
 
16
- ## 빠른 시작
18
+ ## Quick start
17
19
 
18
- ### 서버
20
+ ### Server
19
21
 
20
- 다음을 `server.mjs`로 저장하고 `node server.mjs`로 실행합니다.
22
+ Save the following as `server.mjs` and run `node server.mjs`.
21
23
 
22
24
  ```js
23
25
  import { Server, replyService } from 'iosignal';
@@ -32,11 +34,12 @@ server.on('ready', () => {
32
34
  process.once('SIGINT', () => server.close());
33
35
  ```
34
36
 
35
- 이 예제는 인증 없는 로컬 개발용 서버입니다.
37
+ This is a local development server without authentication.
36
38
 
37
- ### Node.js 클라이언트
39
+ ### Node.js client
38
40
 
39
- 서버 실행 후 다음을 `client.mjs`로 저장하고 `node client.mjs`로 실행합니다.
41
+ After starting the server, save the following as `client.mjs` and run
42
+ `node client.mjs`.
40
43
 
41
44
  ```js
42
45
  import { IO } from 'iosignal';
@@ -56,16 +59,16 @@ io.on('ready', async () => {
56
59
  });
57
60
  ```
58
61
 
59
- CommonJS에서도 공개 API를 사용할 수 있습니다.
62
+ The public API is also available from CommonJS.
60
63
 
61
64
  ```js
62
65
  const { IO, Server, replyService } = require('iosignal');
63
66
  ```
64
67
 
65
- ## 발행과 구독
68
+ ## Publish and subscribe
66
69
 
67
- 연결이 준비되면 `subscribe()`로 태그를 구독하고 `signal()`로 메시지를 보냅니다.
68
- 다음 클라이언트는 메시지를 계속 수신합니다.
70
+ Once connected, use `subscribe()` to subscribe to a tag and `signal()` to send
71
+ messages. The following client keeps receiving messages.
69
72
 
70
73
  ```js
71
74
  import { IO } from 'iosignal';
@@ -83,12 +86,12 @@ io.on('ready', () => {
83
86
  process.once('SIGINT', () => io.stop());
84
87
  ```
85
88
 
86
- `signal(tag, ...args)`는 문자열, 바이너리, 객체 및 여러 인자를 지원합니다.
87
- `unsubscribe(tag)`로 구독을 해제할 수 있습니다.
89
+ `signal(tag, ...args)` supports strings, binary data, objects and multiple
90
+ arguments. Use `unsubscribe(tag)` to unsubscribe.
88
91
 
89
- ## 브라우저
92
+ ## Browsers
90
93
 
91
- 번들러를 사용하는 브라우저 프로젝트에서는 브라우저 전용 진입점을 가져옵니다.
94
+ For browser projects using a bundler, import the browser-specific entry point.
92
95
 
93
96
  ```js
94
97
  import IO from 'iosignal/io';
@@ -102,62 +105,178 @@ io.on('ready', () => {
102
105
  });
103
106
  ```
104
107
 
105
- 브라우저 예제도 위 서버에 연결합니다. HTTPS 페이지에서는 TLS가 구성된
106
- `wss://` 서버 주소를 사용하세요. 사용이 끝나면 `io.stop()`으로 연결과 자동 재연결을 중단합니다.
108
+ This browser example connects to the same server above. From an HTTPS page,
109
+ use a `wss://` server URL with TLS configured. When finished, call `io.stop()`
110
+ to close the connection and stop automatic reconnection.
107
111
 
108
- ## 주요 API
112
+ ## Main APIs
109
113
 
110
- | API | 용도 |
114
+ | API | Purpose |
111
115
  | --- | --- |
112
- | `Server` | WebSocket/TCP 서버 생성 |
113
- | `IO` | Node.js WebSocket 클라이언트; `iosignal/io`에서는 브라우저 클라이언트 |
114
- | `IOCongSocket` | Node.js TCP 클라이언트 |
115
- | `server.attach(name, service)` | RPC 서비스 등록 |
116
- | `io.call(service, command, ...args)` | RPC 요청 |
117
- | `io.subscribe(tag)` / `io.unsubscribe(tag)` | 태그 구독 / 해제 |
118
- | `io.signal(tag, ...args)` | 메시지 발행 |
119
- | `io.stop()` | 자동 재연결 중단 및 연결 정리 |
120
- | `server.close(callback)` | 서버 종료 |
121
- | `BohoAuth` | 서버 인증 관리자 |
122
- | `StringKeyProvider`, `FileKeyProvider`, `RedisKeyProvider` | 인증 키 공급자 |
123
-
124
- ## 소스 빌드 및 검증
125
-
126
- 이 저장소의 개발·검증 환경은 Node.js 22와 npm 10을 사용합니다.
116
+ | `Server` | Create a WebSocket/TCP server |
117
+ | `IO` | Node.js WebSocket client; the browser client when imported from `iosignal/io` |
118
+ | `IOCongSocket` | Node.js TCP client |
119
+ | `server.attach(name, service)` | Register an RPC service |
120
+ | `io.call(service, command, ...args)` | Make an RPC request |
121
+ | `io.subscribe(tag)` / `io.unsubscribe(tag)` | Subscribe / unsubscribe |
122
+ | `io.signal(tag, ...args)` | Publish a message |
123
+ | `io.stop()` | Stop automatic reconnection and clean up the connection |
124
+ | `server.close(callback)` | Shut down the server |
125
+ | `BohoAuth` | Server authentication manager |
126
+ | `StringKeyProvider`, `FileKeyProvider`, `RedisKeyProvider` | Authentication key providers |
127
+
128
+ ## Authentication and encrypted communication with Boho
129
+
130
+ IOSignal uses [Boho](https://github.com/remocons/boho) for shared-key authentication
131
+ and message encryption. Boho has an
132
+ [Arduino implementation](https://github.com/remocons/boho-arduino) and a JavaScript
133
+ implementation for Node.js and browsers, allowing DIY devices and web apps to use
134
+ the same approach.
135
+
136
+ ### Why shared-key Boho?
137
+
138
+ With Arduino devices, your own web apps and servers under common administration,
139
+ you can control both endpoint code and initial key provisioning. Device-specific
140
+ keys can be installed over USB/serial during setup, web app users can enter keys
141
+ obtained through another channel, and servers can distribute keys through
142
+ existing SSH/TLS connections.
143
+
144
+ When a secure provisioning path already exists, pre-shared symmetric keys can
145
+ support authentication and encrypted communication. You can establish peers
146
+ without a certificate authority; trust rests on the provisioning path and
147
+ endpoint software. Device-specific keys, secure storage, rotation and revocation
148
+ are still necessary. Embedding a common secret in a public JavaScript bundle is
149
+ not secure key distribution.
150
+
151
+ Boho combines a SHA-256-based keystream, XOR encryption, shared-key authentication
152
+ and binary packets for this environment. Its distinction is **compatible Arduino
153
+ and JavaScript implementations integrated with IOSignal connections and message
154
+ delivery**, rather than novelty in the cryptographic operations themselves.
155
+
156
+ ### XOR and a hash-based “virtual OTP”
157
+
158
+ XOR is a simple operation that restores the original value when applied twice
159
+ with the same value.
160
+
161
+ ```text
162
+ Encryption: ciphertext = plaintext XOR keystream
163
+ Decryption: plaintext = ciphertext XOR keystream
164
+ ```
165
+
166
+ A true one-time pad (OTP) uses a uniformly random pad independent of the
167
+ plaintext, as long as the data, kept secret and used only once. This provides
168
+ perfect secrecy, but requires supplying both endpoints with a new 1 MB secret
169
+ pad for every 1 MB of data. Reusing a pad exposes `C1 XOR C2 = P1 XOR P2`, revealing
170
+ a relationship between plaintexts.
171
+
172
+ Instead of storing a pad as large as the data, Boho generates a SHA-256 keystream
173
+ from a secret key and message-specific values. The usual `set_key` path is:
174
+
175
+ ```text
176
+ K = SHA256(input key)
177
+ B = SHA256(K || salt12)
178
+ S_i = SHA256(B || LE32(i)) // i = 1, 2, 3, …
179
+ keystream = S_1 || S_2 || … // use only the plaintext length
180
+ ```
181
+
182
+ `||` means byte concatenation; `LE32` is a four-byte little-endian integer. Each
183
+ hash output is 32 bytes. `salt12` contains time, counter and nonce values, and
184
+ the receiver reproduces the stream using the same values and key.
185
+
186
+ “Virtual OTP” describes this pseudorandom stream, not the perfect secrecy of a
187
+ true OTP. **Public random values alone can be hashed by anyone, so a shared secret
188
+ key is required.** Repeating the same key and `salt12` must also be avoided. XOR
189
+ alone does not detect tampering, so Boho data packets include the first eight
190
+ bytes of `SHA256(K || salt12 || plaintext)` as a tag. Despite legacy names in the
191
+ code, this tag is not standard HMAC.
192
+
193
+ ### Understanding the relationship with TLS
194
+
195
+ Typical certificate-based TLS authenticates peers and establishes keys without a
196
+ previously shared secret, then uses symmetric encryption for application data.
197
+ Even web users who have not logged in can authenticate the server and establish
198
+ an encrypted connection, but this does not provide network anonymity. On embedded
199
+ devices, certificate, trust-root, clock and renewal management, handshake work
200
+ and buffers can be burdensome. Small systems with an existing provisioning path
201
+ may be able to omit some of this machinery.
202
+
203
+ However, TLS supports **PSK-only and PSK with (EC)DHE**, so it does not always
204
+ require third-party certificates or public-key exchange.
205
+ [TLS 1.3 pre-shared keys](https://www.rfc-editor.org/rfc/rfc8446.html#section-2.2)
206
+ This does not mean Boho is faster or uses less memory than every TLS or standard
207
+ symmetric implementation; actual comparisons require measurement.
208
+
209
+ ### Connection encryption and E2E data keys
210
+
211
+ ```text
212
+ Arduino device ←→ IOSignal server ←→ Browser app
213
+ └──────── Separate E2E data key ────────┘
214
+ ```
215
+
216
+ Connection credentials establish each client's right to connect to the server.
217
+ To keep a message body confidential from a relay, use `signal_e2e` with a separate
218
+ data key held only by the final endpoints, then verify and decrypt it with
219
+ `decrypt_e2e` at the receiver. Ordinary connection encryption is not automatically
220
+ end-to-end encryption. The server processes routing information for E2E delivery,
221
+ and metadata such as traffic sizes remains visible.
222
+
223
+ The current JavaScript implementation's `AUTO` mode uses Boho connection
224
+ encryption when TLS is not used and Boho authentication is complete. E2E body
225
+ encryption is separate from connection protection. The unauthenticated local
226
+ quick-start examples above do not enable Boho authentication/encryption.
227
+
228
+ Normal web deployments can serve app code over HTTPS and connect over WSS while
229
+ also applying E2E to message bodies. Modified app code can expose keys and
230
+ plaintext, so E2E still requires trust in the app code provider. Boho does not
231
+ bypass browser mixed content rules.
232
+
233
+ ### Conditions in the current implementation
234
+
235
+ - Use sufficiently random keys. A single SHA-256 in `set_key` is not a slow password KDF.
236
+ - JavaScript uses `crypto.getRandomValues()`, but current Arduino standalone-packet nonces and the authentication client nonce use `micros()`. This is not cryptographic randomness; consider repeated key/`salt12` combinations across restarts, devices and communication directions.
237
+ - Boho itself does not enforce replay rejection or challenge expiry. A valid tag alone does not establish freshness or permission to execute a command; check receive policies in the caller and application layer.
238
+ - Boho is a custom SHA-256-based protocol. Do not assume standard AEAD/TLS guarantees or forward secrecy protecting past traffic after a long-term key leak.
239
+
240
+ ## Building and verification
241
+
242
+ This repository uses Node.js 22 and npm 10 for development and verification.
127
243
 
128
244
  ```bash
129
245
  npm ci
130
246
  npm run verify
131
247
  ```
132
248
 
133
- `verify`는 공개 파일 정책을 검사하고, `dist`를 삭제한 뒤 번들과 타입 선언을
134
- 재생성합니다. 이어서 자동 테스트와 npm 패키지 포함 파일 검사를 수행합니다.
249
+ `verify` checks the public-file policy, removes `dist` and rebuilds bundles and
250
+ type declarations. It then runs automated tests and checks the npm package file
251
+ list.
135
252
 
136
- 빌드만 실행하려면 `npm run build`, 이미 빌드된 결과의 자동 테스트만 실행하려면
137
- `npm test`를 사용합니다.
253
+ Use `npm run build` to build only, or `npm test` to test an existing build.
138
254
 
139
- ### 통신 자동 테스트
255
+ ### Automated communication tests
140
256
 
141
257
  ```bash
142
258
  npm run build
143
259
  npm run test:integration
144
260
  ```
145
261
 
146
- `test/integration/`은 공개 ESM·CommonJS 빌드 각각에 대해 실제 WebSocket 통신을 검사합니다.
147
- 로컬 루프백(`127.0.0.1`)의 임시 포트를 사용하므로 외부 서버, Redis, 실제 인증키가 필요하지 않습니다.
262
+ `test/integration/` tests actual WebSocket communication for both public ESM and
263
+ CommonJS builds. It uses temporary ports on local loopback (`127.0.0.1`), so it
264
+ requires no external server, Redis instance or real authentication credentials.
148
265
 
149
- - 연결과 RPC 응답, 없는 명령 및 권한 거부
150
- - 두 클라이언트 간 문자열·객체·바이너리 발행/구독과 구독 해제
151
- - 인증 성공 및 암호화된 RPC 응답, 잘못된 인증 거부
152
- - 연결 종료 후 클라이언트 재사용과 서버 자원 정리
266
+ - Connections, RPC responses, unknown commands and permission denial
267
+ - String, object and binary publish/subscribe between two clients, including unsubscribe
268
+ - Successful authentication and encrypted RPC responses, plus rejection of invalid authentication
269
+ - Client reuse and server resource cleanup after disconnection
153
270
 
154
- `npm test`와 `npm run verify`에도 통신 검사가 포함됩니다. 테스트별 시간 제한과
155
- 전체 테스트 파일 시간 제한을 두며, 종료 시 서버와 클라이언트를 정리합니다.
156
- 실제 브라우저, TLS, TCP 전송 및 Redis 연동 검증은 이 통신 테스트 범위에 포함되지 않습니다.
271
+ These communication checks are included in `npm test` and `npm run verify`.
272
+ Individual tests and test files have time limits, and connections and servers
273
+ are cleaned up on completion. Actual browsers, TLS, TCP transport and Redis
274
+ integration are outside their scope.
157
275
 
158
- 기존 `test/attach-services/`, `test/auth_server_client/`, `test/pubsub-counter/`,
159
- `test/subscribe-signal/`은 수동 예제입니다. 자동 실행하지 않으며, 일부는 별도 서버나 Redis가 필요합니다.
276
+ Existing `test/attach-services/`, `test/auth_server_client/`,
277
+ `test/pubsub-counter/` and `test/subscribe-signal/` are manual examples. They are
278
+ not run automatically, and some require a separate server or Redis.
160
279
 
161
- ## 라이선스
280
+ ## License
162
281
 
163
- 패키지 라이선스: MIT.
282
+ Package license: MIT.
@@ -41,6 +41,7 @@ declare class IOCore {
41
41
  * @type {string}
42
42
  */
43
43
  stateName: string;
44
+ _stateRevision: number;
44
45
  /**
45
46
  * Transmitted message counter.
46
47
  * @type {number}
@@ -408,8 +409,11 @@ declare class IOCore {
408
409
  * 1. 상태가 변경 될 때만 'change' 이벤트 호출된다.
409
410
  * 2. emitEventAndMessage 옵션 값이 지정되야 해당 이벤트 이름이 호출된다.
410
411
  * 보통 이벤트 이름과 동일하게 적거나 이벤트 상황 안내문을 넣는다.
412
+ * 3. 두 상태값 갱신 후 change, 개별 이벤트 순서로 호출한다.
413
+ * 콜백에서 다른 상태로 전이하면 이전 상태의 개별 이벤트는 생략한다.
414
+ * @returns {boolean} Whether this transition is still current after callbacks.
411
415
  */
412
- stateChange(state: string, emitEventAndMessage?: string): void;
416
+ stateChange(state: string, emitEventAndMessage?: string): boolean;
413
417
  }
414
418
  type Buffer$1 = boho.Buffer;
415
419