iosignal 1.4.2 → 1.6.0
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.md +247 -240
- package/auth_file.json +13 -13
- package/auth_file.mjs +23 -23
- package/dist/iosignal.cjs +1 -1
- package/dist/iosignal.esm.js +2 -2
- package/dist/iosignal.esm.js.map +1 -1
- package/dist/iosignal.min.js +2 -2
- package/dist/iosignal.min.js.map +1 -1
- package/index.js +19 -19
- package/indexWebBrowser.js +13 -13
- package/package.json +59 -60
- package/rollup.config.js +69 -69
- package/src/api/RedisAPI.js +68 -68
- package/src/api/api_constant.js +7 -7
- package/src/api/api_reply.js +34 -34
- package/src/api/api_sudo.js +96 -96
- package/src/auth/Auth_Env.js +78 -78
- package/src/auth/Auth_File.js +75 -75
- package/src/auth/Auth_Redis.js +50 -50
- package/src/auth/BohoAuth.js +168 -168
- package/src/client/CongPacket.js +150 -150
- package/src/client/IOCongSocket.js +88 -88
- package/src/client/IOCore.js +851 -851
- package/src/client/IOWS.js +81 -81
- package/src/client/IOWebSocket.js +107 -95
- package/src/common/constants.js +159 -159
- package/src/common/payload.js +82 -82
- package/src/common/quotaTable.js +68 -68
- package/src/common/util.js +61 -61
- package/src/server/FileLogger.js +30 -30
- package/src/server/Manager.js +449 -449
- package/src/server/Metric.js +154 -154
- package/src/server/Server.js +166 -148
- package/src/server/ServerRemote.js +227 -227
- package/src/server/ServerRemoteCore.js +467 -467
- package/src/server/serverOption.js +56 -55
- package/test-browser/favicon.ico +0 -0
- package/test-browser/index.html +92 -92
- package/test-browser/server.js +11 -11
- package/test-client-nodejs/client.cjs +17 -17
- package/test-client-nodejs/client.mjs +17 -17
- package/test-client-nodejs/client_api_reply.mjs +15 -15
- package/test-client-nodejs/stateChange.mjs +19 -19
- package/test-server/server-Auth_Env.js +19 -19
- package/test-server/server-Auth_File.js +12 -12
- package/test-server/server-api_reply.js +6 -6
- package/test-server/server-api_sudo.js +7 -7
- package/test-server/server-commonjs.cjs +8 -8
- package/test-server/server-esm.mjs +19 -19
- package/test-server/simple-server.mjs +11 -11
- package/test-signal-payloadType/signalPack.js +34 -34
- package/test-signal-payloadType/signaling.js +60 -60
|
@@ -1,467 +1,467 @@
|
|
|
1
|
-
import { webcrypto } from 'crypto';
|
|
2
|
-
import { MBP } from 'meta-buffer-pack'
|
|
3
|
-
import { Boho, BohoMsg, MetaSize } from 'boho'
|
|
4
|
-
import { serverOption } from './serverOption.js';
|
|
5
|
-
import { IOMsg, ENC_MODE, CLIENT_STATE } from '../common/constants.js'
|
|
6
|
-
import { quotaTable } from '../common/quotaTable.js'
|
|
7
|
-
|
|
8
|
-
const decoder = new TextDecoder()
|
|
9
|
-
|
|
10
|
-
const NB = MBP.NB
|
|
11
|
-
const MB = MBP.MB
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export class ServerRemoteCore {
|
|
15
|
-
constructor(socket, manager) {
|
|
16
|
-
|
|
17
|
-
this.socket = socket;
|
|
18
|
-
socket.isAlive = true;
|
|
19
|
-
socket.txCounter = 0;
|
|
20
|
-
socket.rxCounter = 0;
|
|
21
|
-
socket.openTime = Date.now()
|
|
22
|
-
|
|
23
|
-
this.boho = new Boho()
|
|
24
|
-
this.encMode = ENC_MODE.AUTO;
|
|
25
|
-
this.channels = new Set(); // subscribed tags
|
|
26
|
-
this.retain_signal = new Map();
|
|
27
|
-
|
|
28
|
-
this.ssid = ServerRemoteCore.ssid++; // ordered index number
|
|
29
|
-
this.manager = manager;
|
|
30
|
-
|
|
31
|
-
this.cid; // Communication Id
|
|
32
|
-
this.did; // Device Id
|
|
33
|
-
this.uid;
|
|
34
|
-
this.nick = ""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.lastEchoMessage = "N"
|
|
38
|
-
this.privateNode = false
|
|
39
|
-
this.HOME_CHANNEL = ""
|
|
40
|
-
this.level = serverOption.defaultQuotaIndex;
|
|
41
|
-
this.quota = quotaTable[this.level];
|
|
42
|
-
this.isAdmin = false;
|
|
43
|
-
|
|
44
|
-
this.state;
|
|
45
|
-
this.stateLog = [];
|
|
46
|
-
this.setState(CLIENT_STATE.INIT)
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
static ssid = 1;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
setState(state) {
|
|
55
|
-
this.state = state
|
|
56
|
-
if (serverOption.debug.showAuthInfo) {
|
|
57
|
-
this.stateLog.push(state)
|
|
58
|
-
console.log(this.stateLog.join('>'))
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
getState(){
|
|
63
|
-
return this.state
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
getStateName(){
|
|
67
|
-
//state <number>
|
|
68
|
-
//value of constant STATES.NAME < number >
|
|
69
|
-
//type of constant STATES.NAME name < string uppercase >
|
|
70
|
-
//stateName,eventName <string lowercase>
|
|
71
|
-
return ( CLIENT_STATE[ this.state ]).toLowerCase()
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
showMessageLog(message, isBinary) {
|
|
76
|
-
let from = this.boho.isAuthorized ? `did: ${this.did} ${this.cid}@` : `${this.cid}@`
|
|
77
|
-
if (isBinary) {
|
|
78
|
-
let msgTypeName = IOMsg[message[0]]
|
|
79
|
-
if (!msgTypeName) msgTypeName = BohoMsg[message[0]]
|
|
80
|
-
msgTypeName =' [' + msgTypeName + ']';
|
|
81
|
-
if (message.byteLength > 40) {
|
|
82
|
-
console.log(from + msgTypeName + ' LEN:', message.length);
|
|
83
|
-
} else {
|
|
84
|
-
console.log(from + msgTypeName , message);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
} else {
|
|
88
|
-
console.log(from + ' [TEXT] %s', message);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
rxQuotaChecker(message) {
|
|
93
|
-
let rxBytes = message.byteLength
|
|
94
|
-
this.manager.rxBytes += rxBytes;
|
|
95
|
-
|
|
96
|
-
if (serverOption.useQuota.signalSize && (rxBytes > this.quota.signalSize)) {
|
|
97
|
-
console.log('## quota: size over')
|
|
98
|
-
this.send(Buffer.from([IOMsg.OVER_SIZE]))
|
|
99
|
-
if (serverOption.useQuota.disconnect) this.close();
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// todo:
|
|
104
|
-
// traffic rate :
|
|
105
|
-
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
//
|
|
110
|
-
onTimeDelayMessage(message, isBinary = true) {
|
|
111
|
-
setTimeout(() => {
|
|
112
|
-
this.onSocketMessage(message, isBinary)
|
|
113
|
-
}
|
|
114
|
-
, serverOption.debug.delay)
|
|
115
|
-
}
|
|
116
|
-
// CongSocket or WebSocket
|
|
117
|
-
onSocketMessage(message, isBinary = true) {
|
|
118
|
-
|
|
119
|
-
this.receiveMonitor() // rx data and ping pong : timeout check
|
|
120
|
-
if (!this.rxQuotaChecker(message)) return
|
|
121
|
-
|
|
122
|
-
if (serverOption.showMessage === 'message') this.showMessageLog(message, isBinary)
|
|
123
|
-
|
|
124
|
-
let msgType, decoded
|
|
125
|
-
|
|
126
|
-
if (isBinary) {
|
|
127
|
-
msgType = message[0]
|
|
128
|
-
|
|
129
|
-
if (msgType === BohoMsg.ENC_488) {
|
|
130
|
-
// let from = this.boho.isAuthorized ? this.cid : "anonymous"
|
|
131
|
-
// console.log(`>> [E488] from: ${from} LEN ${message.byteLength}`)
|
|
132
|
-
// console.log( "ENC buffer", message)
|
|
133
|
-
try {
|
|
134
|
-
decoded = this.boho.decrypt_488(message)
|
|
135
|
-
} catch (err) {
|
|
136
|
-
console.log('-- E488 DEC_FAIL', err)
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (decoded) {
|
|
141
|
-
msgType = decoded[0]
|
|
142
|
-
message = decoded // signal message
|
|
143
|
-
// console.log('[D]', IOMsg[ msgType] )
|
|
144
|
-
} else {
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
} else if (msgType === BohoMsg.ENC_E2E) { // End to End Signal
|
|
149
|
-
try {
|
|
150
|
-
decoded = this.boho.decrypt_488(message)
|
|
151
|
-
} catch (err) {
|
|
152
|
-
console.log('-- E2E DEC_FAIL', err)
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
// console.log('e2e unpack:', decoded )
|
|
156
|
-
if (decoded) {
|
|
157
|
-
msgType = decoded[0]
|
|
158
|
-
message.set(decoded, MetaSize.ENC_488) // set decoded signal_e2e headaer.
|
|
159
|
-
message = message.subarray(MetaSize.ENC_488) // reset offset.
|
|
160
|
-
// console.log('[D]', IOMsg[ msgType] )
|
|
161
|
-
} else {
|
|
162
|
-
return
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
} else {
|
|
166
|
-
// console.log( `[P] ${ IOMsg[ msgType] || BohoMsg[cmd]} LEN: ${message.byteLength}` )
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
switch (msgType) {
|
|
170
|
-
case IOMsg.PING:
|
|
171
|
-
// console.log('-- ping from:' , this.cid )
|
|
172
|
-
this.pong();
|
|
173
|
-
break;
|
|
174
|
-
|
|
175
|
-
case IOMsg.PONG:
|
|
176
|
-
// console.log('<-pong')
|
|
177
|
-
break;
|
|
178
|
-
|
|
179
|
-
case IOMsg.CID_REQ:
|
|
180
|
-
if (this.state < CLIENT_STATE.SENT_SERVER_READY) {
|
|
181
|
-
// console.log('protocol error. cid_req before server_ready')
|
|
182
|
-
this.close()
|
|
183
|
-
}
|
|
184
|
-
if (!this.cid) {
|
|
185
|
-
this.cid = '?' + webcrypto.getRandomValues(Buffer.alloc(3)).toString('base64url')
|
|
186
|
-
this.manager.cid2remote.set(this.cid, this)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// console.log('<< SENDING CID_RES:', this.cid)
|
|
190
|
-
this.send_enc_mode(MBP.pack(
|
|
191
|
-
MB('#cid_ack', '8', IOMsg.CID_RES),
|
|
192
|
-
MB('#cid', this.cid)
|
|
193
|
-
))
|
|
194
|
-
|
|
195
|
-
this.setState(CLIENT_STATE.CID_READY)
|
|
196
|
-
break;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
case IOMsg.ECHO: // TEXT only
|
|
200
|
-
try {
|
|
201
|
-
let msg = decoder.decode(message.subarray(1))
|
|
202
|
-
this.lastEchoMessage = msg
|
|
203
|
-
} catch (error) {
|
|
204
|
-
// console.log('ECHO message is not a text')
|
|
205
|
-
}
|
|
206
|
-
this.send(message, isBinary)
|
|
207
|
-
break;
|
|
208
|
-
|
|
209
|
-
case IOMsg.IAM: // iam
|
|
210
|
-
if(message.byteLength > 1){
|
|
211
|
-
let iamInfo = message.subarray(1)
|
|
212
|
-
this.nick = decoder.decode(iamInfo)
|
|
213
|
-
console.log('iam nick reset', this.nick)
|
|
214
|
-
}
|
|
215
|
-
this.iamResponse()
|
|
216
|
-
break;
|
|
217
|
-
|
|
218
|
-
case IOMsg.SIGNAL_E2E:
|
|
219
|
-
case IOMsg.SIGNAL:
|
|
220
|
-
if (message.byteLength >= 3) {
|
|
221
|
-
let tagLen = message.readUInt8(1)
|
|
222
|
-
if (message.byteLength >= tagLen + 2) {
|
|
223
|
-
let tag = message.subarray(2, 2 + tagLen)
|
|
224
|
-
tag = decoder.decode(tag)
|
|
225
|
-
this.manager.sender(tag, this, message )
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
case IOMsg.UNSUBSCRIBE:
|
|
233
|
-
if (message.byteLength == 2) {
|
|
234
|
-
this.manager.unsubscribe([""], this)
|
|
235
|
-
} else if (message.byteLength >= 3) {
|
|
236
|
-
let tagLen = message.readUInt8(1)
|
|
237
|
-
|
|
238
|
-
if (message.byteLength == tagLen + 2) {
|
|
239
|
-
let tag = message.subarray(2, 2 + tagLen)
|
|
240
|
-
tag = decoder.decode(tag)
|
|
241
|
-
// console.log('unsub: tag', tag)
|
|
242
|
-
let tagList = tag.split(',')
|
|
243
|
-
this.manager.unsubscribe(tagList, this)
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
break;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
case IOMsg.SUBSCRIBE: // 1byte tagLen
|
|
250
|
-
if (message.byteLength >= 3) {
|
|
251
|
-
let tagLen = message.readUInt8(1)
|
|
252
|
-
if (message.byteLength == tagLen + 2) {
|
|
253
|
-
let tag = message.subarray(2, 2 + tagLen)
|
|
254
|
-
tag = decoder.decode(tag)
|
|
255
|
-
let tagList = tag.split(',')
|
|
256
|
-
// console.log('-- SUBSCRIBE req splittagList', tagList)
|
|
257
|
-
this.manager.subscribe(tagList, this)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
break;
|
|
261
|
-
|
|
262
|
-
case IOMsg.SUBSCRIBE_REQ: // 2bytes tagLen
|
|
263
|
-
// console.log('#####recv sub_req ', message, message.byteLength )
|
|
264
|
-
if (message.byteLength >= 6) {
|
|
265
|
-
let msgID = message.readUInt16BE(1)
|
|
266
|
-
let tagLen = message.readUInt16BE(3)
|
|
267
|
-
if (message.byteLength == tagLen + 5) {
|
|
268
|
-
let tag = message.subarray(5, 5 + tagLen)
|
|
269
|
-
tag = decoder.decode(tag)
|
|
270
|
-
let tagList = tag.split(',')
|
|
271
|
-
// console.log('>> SUBSCRIBE_REQ from:', this.cid, tagList)
|
|
272
|
-
let size = this.manager.subscribe(tagList, this)
|
|
273
|
-
|
|
274
|
-
// this.response(msgID, 0, { size: size })
|
|
275
|
-
this.response(msgID, 0)
|
|
276
|
-
} else {
|
|
277
|
-
this.response(msgID, 255 )
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
break;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
case IOMsg.REQUEST: //api request
|
|
284
|
-
try {
|
|
285
|
-
let req = MBP.unpack( message )
|
|
286
|
-
// console.log('request unpack req', req)
|
|
287
|
-
if(req){
|
|
288
|
-
// console.log('request target:', req.target, 'topic:', req.topic)
|
|
289
|
-
if( !req.target || !req.topic ) return
|
|
290
|
-
if(this.manager.server.apiNames.has(req.target)){
|
|
291
|
-
this.manager.server.emit( req.target, this , req )
|
|
292
|
-
}else{
|
|
293
|
-
console.log('UnKnown API req: ', req.target)
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
} catch (e) {
|
|
297
|
-
console.log('request catch error', e)
|
|
298
|
-
}
|
|
299
|
-
break;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
case IOMsg.CLOSE:
|
|
303
|
-
if (message.byteLength > 1) {
|
|
304
|
-
let reason = decoder.decode(message.subarray(1))
|
|
305
|
-
console.log('>> CLOSE reason:', reason)
|
|
306
|
-
this.close();
|
|
307
|
-
}
|
|
308
|
-
break;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// Auth
|
|
312
|
-
case BohoMsg.AUTH_REQ:
|
|
313
|
-
if (!this.manager.authManager) return
|
|
314
|
-
if (this.state < CLIENT_STATE.SENT_SERVER_READY) {
|
|
315
|
-
// console.log('protocol error. auth_req before server_ready')
|
|
316
|
-
this.close();
|
|
317
|
-
}
|
|
318
|
-
this.setState(CLIENT_STATE.RECV_AUTH_REQ)
|
|
319
|
-
let auth_nonce_pack = this.boho.auth_nonce()
|
|
320
|
-
// console.log('## auth_nonce_pack', auth_nonce_pack )
|
|
321
|
-
this.send(auth_nonce_pack)
|
|
322
|
-
this.setState(CLIENT_STATE.SENT_SERVER_NONCE)
|
|
323
|
-
break;
|
|
324
|
-
|
|
325
|
-
case BohoMsg.AUTH_HMAC:
|
|
326
|
-
if (!this.manager.authManager) return
|
|
327
|
-
if (this.state < CLIENT_STATE.SENT_SERVER_NONCE) {
|
|
328
|
-
// console.log('protocol error. auth_hmac before server_nonce')
|
|
329
|
-
this.close();
|
|
330
|
-
}
|
|
331
|
-
this.setState(CLIENT_STATE.RECV_AUTH_HMAC)
|
|
332
|
-
//async
|
|
333
|
-
this.manager.authManager.verify_auth_hmac(message, this)
|
|
334
|
-
.then(authInfo=>{
|
|
335
|
-
if(authInfo){
|
|
336
|
-
this.manager.deligateSignal(remote, '@$name', authInfo.name)
|
|
337
|
-
// console.log('device login success authInfo', authInfo)
|
|
338
|
-
// this.manager.sender('@$name', authInfo.name )
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
})
|
|
342
|
-
.catch(e=>{
|
|
343
|
-
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
return;
|
|
347
|
-
|
|
348
|
-
default:
|
|
349
|
-
// console.log('unknown MsgType:',msgType,' from:', this.ip, this.ssid, this.cid )
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
} else {
|
|
354
|
-
// TEXT FRAME
|
|
355
|
-
try {
|
|
356
|
-
let textMessage = decoder.decode(message)
|
|
357
|
-
// console.log('text buffer:',textMessage )
|
|
358
|
-
// console.log('text buffer:',message.toString('hex') )
|
|
359
|
-
this.manager.server.emit('text_message', textMessage, this)
|
|
360
|
-
} catch (error) {
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
response(msgId, statusCode, body) {
|
|
369
|
-
// console.log('response body', body)
|
|
370
|
-
if( body ){
|
|
371
|
-
|
|
372
|
-
this.send_enc_mode( MBP.pack(
|
|
373
|
-
MB('#type', '8', IOMsg.RESPONSE_MBP),
|
|
374
|
-
MB('status','8', statusCode),
|
|
375
|
-
MB('mid','16', msgId),
|
|
376
|
-
MB('body',body)
|
|
377
|
-
))
|
|
378
|
-
|
|
379
|
-
}else{
|
|
380
|
-
this.send_enc_mode( MBP.pack(
|
|
381
|
-
MB('#type','8', IOMsg.RESPONSE_MBP),
|
|
382
|
-
MB('status','8', statusCode),
|
|
383
|
-
MB('mid','16', msgId)
|
|
384
|
-
))
|
|
385
|
-
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
send_enc_mode(data, useEncryption = false) {
|
|
392
|
-
|
|
393
|
-
if (this.encMode === ENC_MODE.YES ||
|
|
394
|
-
this.encMode === ENC_MODE.AUTO &&
|
|
395
|
-
!this.TLS && this.boho.isAuthorized
|
|
396
|
-
) useEncryption = true;
|
|
397
|
-
|
|
398
|
-
// console.log('svr useEnc', useEncryption, data )
|
|
399
|
-
|
|
400
|
-
if (useEncryption && data[0] == IOMsg.SIGNAL_E2E) {
|
|
401
|
-
// E2E는,
|
|
402
|
-
// 서버는 수신자와 암호통신하는경우, 헤더부분만 암호화 하고, 데이타 부분은 그대로 전달해야한다.
|
|
403
|
-
let tagLen = data[1];
|
|
404
|
-
|
|
405
|
-
// let tagInfo = decoder.decode( data.subarray( 2, 2 + tagLen) )
|
|
406
|
-
// console.log('server bypass E2E signal taginfo:', tagInfo )
|
|
407
|
-
|
|
408
|
-
let encHeader = this.boho.encrypt_488(data.subarray(0, 3 + tagLen))
|
|
409
|
-
// console.log('size check:', encHeader.byteLength == (tagLen + 3 + 21 ) )
|
|
410
|
-
encHeader[0] = BohoMsg.ENC_E2E
|
|
411
|
-
// encHeader 크기가 21(enc_488)크기 만큼 커짐. tagLen+3 + 21
|
|
412
|
-
let newEncBuffer = Buffer.concat([encHeader, data.subarray(3 + tagLen)])
|
|
413
|
-
|
|
414
|
-
// console.log('ENC_E2E', newEncBuffer)
|
|
415
|
-
this.send(newEncBuffer)
|
|
416
|
-
|
|
417
|
-
} else if (useEncryption) {
|
|
418
|
-
let encPack = this.boho.encrypt_488(data)
|
|
419
|
-
if (encPack) {
|
|
420
|
-
// console.log('send *S* LEN:', encPack.byteLength)
|
|
421
|
-
this.send(encPack)
|
|
422
|
-
} else {
|
|
423
|
-
console.log('encryption FAIL: NO DATA TRANSIT')
|
|
424
|
-
}
|
|
425
|
-
} else {
|
|
426
|
-
// console.log('send -N-')
|
|
427
|
-
this.send(data)
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
iamResponse(info = "") {
|
|
436
|
-
|
|
437
|
-
if (info == "") {
|
|
438
|
-
//send general info
|
|
439
|
-
let channels = []
|
|
440
|
-
for (let tag of this.channels.keys()) {
|
|
441
|
-
channels.push(tag)
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
info = {
|
|
445
|
-
"ssid": this.ssid,
|
|
446
|
-
"uid": this.uid,
|
|
447
|
-
"cid": this.cid,
|
|
448
|
-
"did": this.did,
|
|
449
|
-
"nick": this.nick,
|
|
450
|
-
"ip": this.ip
|
|
451
|
-
, 'tag': channels
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
let pack = MBP.pack(
|
|
456
|
-
MB('#MsgType', '8', IOMsg.IAM_RES),
|
|
457
|
-
MB('#info', info)
|
|
458
|
-
)
|
|
459
|
-
this.send_enc_mode(pack)
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// storeSet(topic, ...args){
|
|
463
|
-
// this.manager.set( this, topic, ...args )
|
|
464
|
-
// }
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
1
|
+
import { webcrypto } from 'crypto';
|
|
2
|
+
import { MBP } from 'meta-buffer-pack'
|
|
3
|
+
import { Boho, BohoMsg, MetaSize } from 'boho'
|
|
4
|
+
import { serverOption } from './serverOption.js';
|
|
5
|
+
import { IOMsg, ENC_MODE, CLIENT_STATE } from '../common/constants.js'
|
|
6
|
+
import { quotaTable } from '../common/quotaTable.js'
|
|
7
|
+
|
|
8
|
+
const decoder = new TextDecoder()
|
|
9
|
+
|
|
10
|
+
const NB = MBP.NB
|
|
11
|
+
const MB = MBP.MB
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export class ServerRemoteCore {
|
|
15
|
+
constructor(socket, manager) {
|
|
16
|
+
|
|
17
|
+
this.socket = socket;
|
|
18
|
+
socket.isAlive = true;
|
|
19
|
+
socket.txCounter = 0;
|
|
20
|
+
socket.rxCounter = 0;
|
|
21
|
+
socket.openTime = Date.now()
|
|
22
|
+
|
|
23
|
+
this.boho = new Boho()
|
|
24
|
+
this.encMode = ENC_MODE.AUTO;
|
|
25
|
+
this.channels = new Set(); // subscribed tags
|
|
26
|
+
this.retain_signal = new Map();
|
|
27
|
+
|
|
28
|
+
this.ssid = ServerRemoteCore.ssid++; // ordered index number
|
|
29
|
+
this.manager = manager;
|
|
30
|
+
|
|
31
|
+
this.cid; // Communication Id
|
|
32
|
+
this.did; // Device Id
|
|
33
|
+
this.uid;
|
|
34
|
+
this.nick = ""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
this.lastEchoMessage = "N"
|
|
38
|
+
this.privateNode = false
|
|
39
|
+
this.HOME_CHANNEL = ""
|
|
40
|
+
this.level = serverOption.defaultQuotaIndex;
|
|
41
|
+
this.quota = quotaTable[this.level];
|
|
42
|
+
this.isAdmin = false;
|
|
43
|
+
|
|
44
|
+
this.state;
|
|
45
|
+
this.stateLog = [];
|
|
46
|
+
this.setState(CLIENT_STATE.INIT)
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static ssid = 1;
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
setState(state) {
|
|
55
|
+
this.state = state
|
|
56
|
+
if (serverOption.debug.showAuthInfo) {
|
|
57
|
+
this.stateLog.push(state)
|
|
58
|
+
console.log(this.stateLog.join('>'))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getState(){
|
|
63
|
+
return this.state
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getStateName(){
|
|
67
|
+
//state <number>
|
|
68
|
+
//value of constant STATES.NAME < number >
|
|
69
|
+
//type of constant STATES.NAME name < string uppercase >
|
|
70
|
+
//stateName,eventName <string lowercase>
|
|
71
|
+
return ( CLIENT_STATE[ this.state ]).toLowerCase()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
showMessageLog(message, isBinary) {
|
|
76
|
+
let from = this.boho.isAuthorized ? `did: ${this.did} ${this.cid}@` : `${this.cid}@`
|
|
77
|
+
if (isBinary) {
|
|
78
|
+
let msgTypeName = IOMsg[message[0]]
|
|
79
|
+
if (!msgTypeName) msgTypeName = BohoMsg[message[0]]
|
|
80
|
+
msgTypeName =' [' + msgTypeName + ']';
|
|
81
|
+
if (message.byteLength > 40) {
|
|
82
|
+
console.log(from + msgTypeName + ' LEN:', message.length);
|
|
83
|
+
} else {
|
|
84
|
+
console.log(from + msgTypeName , message);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} else {
|
|
88
|
+
console.log(from + ' [TEXT] %s', message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
rxQuotaChecker(message) {
|
|
93
|
+
let rxBytes = message.byteLength
|
|
94
|
+
this.manager.rxBytes += rxBytes;
|
|
95
|
+
|
|
96
|
+
if (serverOption.useQuota.signalSize && (rxBytes > this.quota.signalSize)) {
|
|
97
|
+
console.log('## quota: size over')
|
|
98
|
+
this.send(Buffer.from([IOMsg.OVER_SIZE]))
|
|
99
|
+
if (serverOption.useQuota.disconnect) this.close();
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// todo:
|
|
104
|
+
// traffic rate :
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//
|
|
110
|
+
onTimeDelayMessage(message, isBinary = true) {
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
this.onSocketMessage(message, isBinary)
|
|
113
|
+
}
|
|
114
|
+
, serverOption.debug.delay)
|
|
115
|
+
}
|
|
116
|
+
// CongSocket or WebSocket
|
|
117
|
+
onSocketMessage(message, isBinary = true) {
|
|
118
|
+
|
|
119
|
+
this.receiveMonitor() // rx data and ping pong : timeout check
|
|
120
|
+
if (!this.rxQuotaChecker(message)) return
|
|
121
|
+
|
|
122
|
+
if (serverOption.showMessage === 'message') this.showMessageLog(message, isBinary)
|
|
123
|
+
|
|
124
|
+
let msgType, decoded
|
|
125
|
+
|
|
126
|
+
if (isBinary) {
|
|
127
|
+
msgType = message[0]
|
|
128
|
+
|
|
129
|
+
if (msgType === BohoMsg.ENC_488) {
|
|
130
|
+
// let from = this.boho.isAuthorized ? this.cid : "anonymous"
|
|
131
|
+
// console.log(`>> [E488] from: ${from} LEN ${message.byteLength}`)
|
|
132
|
+
// console.log( "ENC buffer", message)
|
|
133
|
+
try {
|
|
134
|
+
decoded = this.boho.decrypt_488(message)
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.log('-- E488 DEC_FAIL', err)
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (decoded) {
|
|
141
|
+
msgType = decoded[0]
|
|
142
|
+
message = decoded // signal message
|
|
143
|
+
// console.log('[D]', IOMsg[ msgType] )
|
|
144
|
+
} else {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
} else if (msgType === BohoMsg.ENC_E2E) { // End to End Signal
|
|
149
|
+
try {
|
|
150
|
+
decoded = this.boho.decrypt_488(message)
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.log('-- E2E DEC_FAIL', err)
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
// console.log('e2e unpack:', decoded )
|
|
156
|
+
if (decoded) {
|
|
157
|
+
msgType = decoded[0]
|
|
158
|
+
message.set(decoded, MetaSize.ENC_488) // set decoded signal_e2e headaer.
|
|
159
|
+
message = message.subarray(MetaSize.ENC_488) // reset offset.
|
|
160
|
+
// console.log('[D]', IOMsg[ msgType] )
|
|
161
|
+
} else {
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
} else {
|
|
166
|
+
// console.log( `[P] ${ IOMsg[ msgType] || BohoMsg[cmd]} LEN: ${message.byteLength}` )
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
switch (msgType) {
|
|
170
|
+
case IOMsg.PING:
|
|
171
|
+
// console.log('-- ping from:' , this.cid )
|
|
172
|
+
this.pong();
|
|
173
|
+
break;
|
|
174
|
+
|
|
175
|
+
case IOMsg.PONG:
|
|
176
|
+
// console.log('<-pong')
|
|
177
|
+
break;
|
|
178
|
+
|
|
179
|
+
case IOMsg.CID_REQ:
|
|
180
|
+
if (this.state < CLIENT_STATE.SENT_SERVER_READY) {
|
|
181
|
+
// console.log('protocol error. cid_req before server_ready')
|
|
182
|
+
this.close()
|
|
183
|
+
}
|
|
184
|
+
if (!this.cid) {
|
|
185
|
+
this.cid = '?' + webcrypto.getRandomValues(Buffer.alloc(3)).toString('base64url')
|
|
186
|
+
this.manager.cid2remote.set(this.cid, this)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// console.log('<< SENDING CID_RES:', this.cid)
|
|
190
|
+
this.send_enc_mode(MBP.pack(
|
|
191
|
+
MB('#cid_ack', '8', IOMsg.CID_RES),
|
|
192
|
+
MB('#cid', this.cid)
|
|
193
|
+
))
|
|
194
|
+
|
|
195
|
+
this.setState(CLIENT_STATE.CID_READY)
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
case IOMsg.ECHO: // TEXT only
|
|
200
|
+
try {
|
|
201
|
+
let msg = decoder.decode(message.subarray(1))
|
|
202
|
+
this.lastEchoMessage = msg
|
|
203
|
+
} catch (error) {
|
|
204
|
+
// console.log('ECHO message is not a text')
|
|
205
|
+
}
|
|
206
|
+
this.send(message, isBinary)
|
|
207
|
+
break;
|
|
208
|
+
|
|
209
|
+
case IOMsg.IAM: // iam
|
|
210
|
+
if(message.byteLength > 1){
|
|
211
|
+
let iamInfo = message.subarray(1)
|
|
212
|
+
this.nick = decoder.decode(iamInfo)
|
|
213
|
+
console.log('iam nick reset', this.nick)
|
|
214
|
+
}
|
|
215
|
+
this.iamResponse()
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
case IOMsg.SIGNAL_E2E:
|
|
219
|
+
case IOMsg.SIGNAL:
|
|
220
|
+
if (message.byteLength >= 3) {
|
|
221
|
+
let tagLen = message.readUInt8(1)
|
|
222
|
+
if (message.byteLength >= tagLen + 2) {
|
|
223
|
+
let tag = message.subarray(2, 2 + tagLen)
|
|
224
|
+
tag = decoder.decode(tag)
|
|
225
|
+
this.manager.sender(tag, this, message )
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
case IOMsg.UNSUBSCRIBE:
|
|
233
|
+
if (message.byteLength == 2) {
|
|
234
|
+
this.manager.unsubscribe([""], this)
|
|
235
|
+
} else if (message.byteLength >= 3) {
|
|
236
|
+
let tagLen = message.readUInt8(1)
|
|
237
|
+
|
|
238
|
+
if (message.byteLength == tagLen + 2) {
|
|
239
|
+
let tag = message.subarray(2, 2 + tagLen)
|
|
240
|
+
tag = decoder.decode(tag)
|
|
241
|
+
// console.log('unsub: tag', tag)
|
|
242
|
+
let tagList = tag.split(',')
|
|
243
|
+
this.manager.unsubscribe(tagList, this)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
case IOMsg.SUBSCRIBE: // 1byte tagLen
|
|
250
|
+
if (message.byteLength >= 3) {
|
|
251
|
+
let tagLen = message.readUInt8(1)
|
|
252
|
+
if (message.byteLength == tagLen + 2) {
|
|
253
|
+
let tag = message.subarray(2, 2 + tagLen)
|
|
254
|
+
tag = decoder.decode(tag)
|
|
255
|
+
let tagList = tag.split(',')
|
|
256
|
+
// console.log('-- SUBSCRIBE req splittagList', tagList)
|
|
257
|
+
this.manager.subscribe(tagList, this)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
case IOMsg.SUBSCRIBE_REQ: // 2bytes tagLen
|
|
263
|
+
// console.log('#####recv sub_req ', message, message.byteLength )
|
|
264
|
+
if (message.byteLength >= 6) {
|
|
265
|
+
let msgID = message.readUInt16BE(1)
|
|
266
|
+
let tagLen = message.readUInt16BE(3)
|
|
267
|
+
if (message.byteLength == tagLen + 5) {
|
|
268
|
+
let tag = message.subarray(5, 5 + tagLen)
|
|
269
|
+
tag = decoder.decode(tag)
|
|
270
|
+
let tagList = tag.split(',')
|
|
271
|
+
// console.log('>> SUBSCRIBE_REQ from:', this.cid, tagList)
|
|
272
|
+
let size = this.manager.subscribe(tagList, this)
|
|
273
|
+
|
|
274
|
+
// this.response(msgID, 0, { size: size })
|
|
275
|
+
this.response(msgID, 0)
|
|
276
|
+
} else {
|
|
277
|
+
this.response(msgID, 255 )
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
case IOMsg.REQUEST: //api request
|
|
284
|
+
try {
|
|
285
|
+
let req = MBP.unpack( message )
|
|
286
|
+
// console.log('request unpack req', req)
|
|
287
|
+
if(req){
|
|
288
|
+
// console.log('request target:', req.target, 'topic:', req.topic)
|
|
289
|
+
if( !req.target || !req.topic ) return
|
|
290
|
+
if(this.manager.server.apiNames.has(req.target)){
|
|
291
|
+
this.manager.server.emit( req.target, this , req )
|
|
292
|
+
}else{
|
|
293
|
+
console.log('UnKnown API req: ', req.target)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} catch (e) {
|
|
297
|
+
console.log('request catch error', e)
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
case IOMsg.CLOSE:
|
|
303
|
+
if (message.byteLength > 1) {
|
|
304
|
+
let reason = decoder.decode(message.subarray(1))
|
|
305
|
+
console.log('>> CLOSE reason:', reason)
|
|
306
|
+
this.close();
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
// Auth
|
|
312
|
+
case BohoMsg.AUTH_REQ:
|
|
313
|
+
if (!this.manager.authManager) return
|
|
314
|
+
if (this.state < CLIENT_STATE.SENT_SERVER_READY) {
|
|
315
|
+
// console.log('protocol error. auth_req before server_ready')
|
|
316
|
+
this.close();
|
|
317
|
+
}
|
|
318
|
+
this.setState(CLIENT_STATE.RECV_AUTH_REQ)
|
|
319
|
+
let auth_nonce_pack = this.boho.auth_nonce()
|
|
320
|
+
// console.log('## auth_nonce_pack', auth_nonce_pack )
|
|
321
|
+
this.send(auth_nonce_pack)
|
|
322
|
+
this.setState(CLIENT_STATE.SENT_SERVER_NONCE)
|
|
323
|
+
break;
|
|
324
|
+
|
|
325
|
+
case BohoMsg.AUTH_HMAC:
|
|
326
|
+
if (!this.manager.authManager) return
|
|
327
|
+
if (this.state < CLIENT_STATE.SENT_SERVER_NONCE) {
|
|
328
|
+
// console.log('protocol error. auth_hmac before server_nonce')
|
|
329
|
+
this.close();
|
|
330
|
+
}
|
|
331
|
+
this.setState(CLIENT_STATE.RECV_AUTH_HMAC)
|
|
332
|
+
//async
|
|
333
|
+
this.manager.authManager.verify_auth_hmac(message, this)
|
|
334
|
+
.then(authInfo=>{
|
|
335
|
+
if(authInfo){
|
|
336
|
+
this.manager.deligateSignal(remote, '@$name', authInfo.name)
|
|
337
|
+
// console.log('device login success authInfo', authInfo)
|
|
338
|
+
// this.manager.sender('@$name', authInfo.name )
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
})
|
|
342
|
+
.catch(e=>{
|
|
343
|
+
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
return;
|
|
347
|
+
|
|
348
|
+
default:
|
|
349
|
+
// console.log('unknown MsgType:',msgType,' from:', this.ip, this.ssid, this.cid )
|
|
350
|
+
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
} else {
|
|
354
|
+
// TEXT FRAME
|
|
355
|
+
try {
|
|
356
|
+
let textMessage = decoder.decode(message)
|
|
357
|
+
// console.log('text buffer:',textMessage )
|
|
358
|
+
// console.log('text buffer:',message.toString('hex') )
|
|
359
|
+
this.manager.server.emit('text_message', textMessage, this)
|
|
360
|
+
} catch (error) {
|
|
361
|
+
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
response(msgId, statusCode, body) {
|
|
369
|
+
// console.log('response body', body)
|
|
370
|
+
if( body ){
|
|
371
|
+
|
|
372
|
+
this.send_enc_mode( MBP.pack(
|
|
373
|
+
MB('#type', '8', IOMsg.RESPONSE_MBP),
|
|
374
|
+
MB('status','8', statusCode),
|
|
375
|
+
MB('mid','16', msgId),
|
|
376
|
+
MB('body',body)
|
|
377
|
+
))
|
|
378
|
+
|
|
379
|
+
}else{
|
|
380
|
+
this.send_enc_mode( MBP.pack(
|
|
381
|
+
MB('#type','8', IOMsg.RESPONSE_MBP),
|
|
382
|
+
MB('status','8', statusCode),
|
|
383
|
+
MB('mid','16', msgId)
|
|
384
|
+
))
|
|
385
|
+
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
send_enc_mode(data, useEncryption = false) {
|
|
392
|
+
|
|
393
|
+
if (this.encMode === ENC_MODE.YES ||
|
|
394
|
+
this.encMode === ENC_MODE.AUTO &&
|
|
395
|
+
!this.TLS && this.boho.isAuthorized
|
|
396
|
+
) useEncryption = true;
|
|
397
|
+
|
|
398
|
+
// console.log('svr useEnc', useEncryption, data )
|
|
399
|
+
|
|
400
|
+
if (useEncryption && data[0] == IOMsg.SIGNAL_E2E) {
|
|
401
|
+
// E2E는,
|
|
402
|
+
// 서버는 수신자와 암호통신하는경우, 헤더부분만 암호화 하고, 데이타 부분은 그대로 전달해야한다.
|
|
403
|
+
let tagLen = data[1];
|
|
404
|
+
|
|
405
|
+
// let tagInfo = decoder.decode( data.subarray( 2, 2 + tagLen) )
|
|
406
|
+
// console.log('server bypass E2E signal taginfo:', tagInfo )
|
|
407
|
+
|
|
408
|
+
let encHeader = this.boho.encrypt_488(data.subarray(0, 3 + tagLen))
|
|
409
|
+
// console.log('size check:', encHeader.byteLength == (tagLen + 3 + 21 ) )
|
|
410
|
+
encHeader[0] = BohoMsg.ENC_E2E
|
|
411
|
+
// encHeader 크기가 21(enc_488)크기 만큼 커짐. tagLen+3 + 21
|
|
412
|
+
let newEncBuffer = Buffer.concat([encHeader, data.subarray(3 + tagLen)])
|
|
413
|
+
|
|
414
|
+
// console.log('ENC_E2E', newEncBuffer)
|
|
415
|
+
this.send(newEncBuffer)
|
|
416
|
+
|
|
417
|
+
} else if (useEncryption) {
|
|
418
|
+
let encPack = this.boho.encrypt_488(data)
|
|
419
|
+
if (encPack) {
|
|
420
|
+
// console.log('send *S* LEN:', encPack.byteLength)
|
|
421
|
+
this.send(encPack)
|
|
422
|
+
} else {
|
|
423
|
+
console.log('encryption FAIL: NO DATA TRANSIT')
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
// console.log('send -N-')
|
|
427
|
+
this.send(data)
|
|
428
|
+
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
iamResponse(info = "") {
|
|
436
|
+
|
|
437
|
+
if (info == "") {
|
|
438
|
+
//send general info
|
|
439
|
+
let channels = []
|
|
440
|
+
for (let tag of this.channels.keys()) {
|
|
441
|
+
channels.push(tag)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
info = {
|
|
445
|
+
"ssid": this.ssid,
|
|
446
|
+
"uid": this.uid,
|
|
447
|
+
"cid": this.cid,
|
|
448
|
+
"did": this.did,
|
|
449
|
+
"nick": this.nick,
|
|
450
|
+
"ip": this.ip
|
|
451
|
+
, 'tag': channels
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
let pack = MBP.pack(
|
|
456
|
+
MB('#MsgType', '8', IOMsg.IAM_RES),
|
|
457
|
+
MB('#info', info)
|
|
458
|
+
)
|
|
459
|
+
this.send_enc_mode(pack)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// storeSet(topic, ...args){
|
|
463
|
+
// this.manager.set( this, topic, ...args )
|
|
464
|
+
// }
|
|
465
|
+
|
|
466
|
+
}
|
|
467
|
+
|