pymud 0.21.0a3__py3-none-any.whl → 0.21.0a5__py3-none-any.whl
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.
- pymud/__init__.py +16 -16
- pymud/__main__.py +3 -3
- pymud/decorators.py +234 -0
- pymud/dialogs.py +166 -160
- pymud/extras.py +918 -942
- pymud/i18n.py +63 -42
- pymud/lang/i18n_chs.py +223 -204
- pymud/lang/i18n_eng.py +846 -42
- pymud/logger.py +167 -162
- pymud/main.py +220 -206
- pymud/modules.py +285 -431
- pymud/objects.py +1032 -1030
- pymud/pkuxkx.py +280 -262
- pymud/protocol.py +1010 -1008
- pymud/pymud.py +1286 -1292
- pymud/session.py +3448 -3392
- pymud/settings.py +196 -193
- {pymud-0.21.0a3.dist-info → pymud-0.21.0a5.dist-info}/METADATA +433 -369
- pymud-0.21.0a5.dist-info/RECORD +23 -0
- {pymud-0.21.0a3.dist-info → pymud-0.21.0a5.dist-info}/licenses/LICENSE.txt +674 -674
- pymud-0.21.0a3.dist-info/RECORD +0 -22
- {pymud-0.21.0a3.dist-info → pymud-0.21.0a5.dist-info}/WHEEL +0 -0
- {pymud-0.21.0a3.dist-info → pymud-0.21.0a5.dist-info}/entry_points.txt +0 -0
- {pymud-0.21.0a3.dist-info → pymud-0.21.0a5.dist-info}/top_level.txt +0 -0
pymud/protocol.py
CHANGED
@@ -1,1008 +1,1010 @@
|
|
1
|
-
import logging, asyncio, datetime, traceback
|
2
|
-
from asyncio import BaseTransport, Protocol
|
3
|
-
from .settings import Settings
|
4
|
-
|
5
|
-
IAC = b"\xff" # TELNET 命令字 IAC
|
6
|
-
DONT = b"\xfe"
|
7
|
-
DO = b"\xfd"
|
8
|
-
WONT = b"\xfc"
|
9
|
-
WILL = b"\xfb"
|
10
|
-
SE = b"\xf0"
|
11
|
-
NOP = b"\xf1"
|
12
|
-
DM = b"\xf2"
|
13
|
-
BRK = b"\xf3"
|
14
|
-
IP = b"\xf4"
|
15
|
-
AO = b"\xf5"
|
16
|
-
AYT = b"\xf6"
|
17
|
-
EC = b"\xf7"
|
18
|
-
EL = b"\xf8"
|
19
|
-
GA = b"\xf9"
|
20
|
-
SB = b"\xfa"
|
21
|
-
|
22
|
-
LOGOUT = b"\x12"
|
23
|
-
ECHO = b"\x01"
|
24
|
-
|
25
|
-
CHARSET = b"*"
|
26
|
-
REQUEST = 1
|
27
|
-
ACCEPTED = 2
|
28
|
-
REJECTED = 3
|
29
|
-
TTABLE_IS = 4
|
30
|
-
TTABLE_REJECTED = 5
|
31
|
-
TTABLE_ACK = 6
|
32
|
-
TTABLE_NAK = 7
|
33
|
-
|
34
|
-
# TELNET & MUD PROTOCOLS OPTIONS
|
35
|
-
#IAC DO TTYPE x18 https://tintin.mudhalla.net/protocols/mtts/
|
36
|
-
#IAC DO NAWS x1F Negotiate About Window Size https://www.rfc-editor.org/rfc/rfc1073.html
|
37
|
-
#IAC DO NEW_ENVIRON b"'" https://tintin.mudhalla.net/protocols/mnes/
|
38
|
-
#IAC WILL b'\xc9' GMCP 201 https://tintin.mudhalla.net/protocols/gmcp/
|
39
|
-
#IAC WILL b'F' MSSP 70 https://tintin.mudhalla.net/protocols/mssp/
|
40
|
-
#IAC WILL CHARSET b"*" https://www.rfc-editor.org/rfc/rfc2066.html
|
41
|
-
#IAC WILL b'Z' 90, MUD Sound Protocol, MSP http://www.zuggsoft.com/zmud/msp.htm
|
42
|
-
|
43
|
-
LINEMODE = b'"' # LINEMODE, rfc1184 https://www.rfc-editor.org/rfc/rfc1184.html
|
44
|
-
SGA = b"\x03" # SUPPRESS GO AHEAD, rfc858 https://www.rfc-editor.org/rfc/rfc858.html
|
45
|
-
|
46
|
-
TTYPE = b"\x18" # MUD终端类型标准,Mud Terminal Type Standard, https://tintin.mudhalla.net/protocols/mtts/
|
47
|
-
NAWS = b"\x1f" # 协商窗口尺寸, RFC1073, Negotiate About Window Size, https://www.rfc-editor.org/rfc/rfc1073.html
|
48
|
-
MNES = b"'" # MUD环境标准,MUD NEW-ENVIRON Standard, https://tintin.mudhalla.net/protocols/mnes/
|
49
|
-
|
50
|
-
# 通用MUD通信协议, Generic Mud Communication Protocol, https://tintin.mudhalla.net/protocols/gmcp/
|
51
|
-
GMCP = b"\xc9"
|
52
|
-
|
53
|
-
# MUD服务器数据协议, Mud Server Data Protocol, https://tintin.mudhalla.net/protocols/msdp/
|
54
|
-
MSDP = b"E"
|
55
|
-
MSDP_VAR = 1
|
56
|
-
MSDP_VAL = 2
|
57
|
-
MSDP_TABLE_OPEN = 3
|
58
|
-
MSDP_TABLE_CLOSE = 4
|
59
|
-
MSDP_ARRAY_OPEN = 5
|
60
|
-
MSDP_ARRAY_CLOSE = 6
|
61
|
-
|
62
|
-
# MUD服务器状态协议, Mud Server Status Protocol, https://tintin.mudhalla.net/protocols/mssp/
|
63
|
-
MSSP = b"F"
|
64
|
-
MSSP_VAR = 1
|
65
|
-
MSSP_VAL = 2
|
66
|
-
|
67
|
-
# MUD客户端压缩协议,V1V2V3版, 参见 http://www.zuggsoft.com/zmud/mcp.htm, https://tintin.mudhalla.net/protocols/mccp/
|
68
|
-
# MCCP V1 使用选项85(U)作为协商选项,于1998年定义;
|
69
|
-
# 2000年, 规定了MCCP V2,使用选项86(V)作为协商选项。
|
70
|
-
# 此后,MCCP1由于子协商内容非法, 2004年被弃用。当前所有MUD服务器端均切换到支持V2版协议。
|
71
|
-
# 2019年,V3协议被定义,但作为一个新协议使用,当前尚未支持。
|
72
|
-
MCCP1 = b"U" # MUD客户端压缩协议V1,已被弃用
|
73
|
-
MCCP2 = b"V" # MUD客户端压缩协议V2, Mud Client Compression Protocol, https://tintin.mudhalla.net/protocols/mccp/
|
74
|
-
MCCP3 = b"W" # MUD客户端压缩协议V3, V2版同样可参见 http://www.zuggsoft.com/zmud/mcp.htm
|
75
|
-
|
76
|
-
# MUD声音协议, MUD Sound Protocol, http://www.zuggsoft.com/zmud/msp.htm
|
77
|
-
MSP = b"Z"
|
78
|
-
|
79
|
-
# MUD扩展协议, MUD eXtension Protocol, http://www.zuggsoft.com/zmud/mxp.htm
|
80
|
-
MXP = b"["
|
81
|
-
|
82
|
-
_cmd_name_str = {
|
83
|
-
IAC : "IAC",
|
84
|
-
WILL : "WILL",
|
85
|
-
WONT : "WONT",
|
86
|
-
DO : "DO",
|
87
|
-
DONT : "DONT",
|
88
|
-
SB : "SB",
|
89
|
-
SE : "SE",
|
90
|
-
}
|
91
|
-
|
92
|
-
_option_name_str = {
|
93
|
-
LINEMODE: "LINEMODE",
|
94
|
-
SGA : "SGA",
|
95
|
-
|
96
|
-
ECHO : "ECHO",
|
97
|
-
CHARSET : "CHARSET",
|
98
|
-
TTYPE : "TTYPE",
|
99
|
-
NAWS : "NAWS",
|
100
|
-
MNES : "MNES",
|
101
|
-
GMCP : "GMCP",
|
102
|
-
MSDP : "MSDP",
|
103
|
-
MSSP : "MSSP",
|
104
|
-
MCCP2 : "MCCP2",
|
105
|
-
MCCP3 : "MCCP3",
|
106
|
-
MSP : "MSP",
|
107
|
-
MXP : "MXP"
|
108
|
-
}
|
109
|
-
|
110
|
-
def name_command(cmd):
|
111
|
-
if cmd in _cmd_name_str.keys():
|
112
|
-
return _cmd_name_str[cmd]
|
113
|
-
else:
|
114
|
-
return cmd
|
115
|
-
|
116
|
-
def name_option(opt):
|
117
|
-
if opt in _option_name_str.keys():
|
118
|
-
return _option_name_str[opt]
|
119
|
-
return opt
|
120
|
-
|
121
|
-
class MudClientProtocol(Protocol):
|
122
|
-
"""
|
123
|
-
适用于MUD客户端的asyncio的协议实现
|
124
|
-
基本协议: TELNET
|
125
|
-
扩展协议: GMCP, MCDP, MSP, MXP等等
|
126
|
-
"""
|
127
|
-
|
128
|
-
def __init__(self, session, *args, **kwargs) -> None:
|
129
|
-
"""
|
130
|
-
MUD客户端协议实现, 参数包括:
|
131
|
-
+ session: 管理protocol的会话
|
132
|
-
除此之外,还可以接受的命名参数包括:
|
133
|
-
+ onConnected: 当连接建立时的回调,包含2个参数: MudClientProtocol本身,以及生成的传输Transport对象
|
134
|
-
+ onDisconnected: 当连接断开时的回调,包含1个参数: MudClientProtocol本身
|
135
|
-
"""
|
136
|
-
|
137
|
-
self.log = logging.getLogger("pymud.MudClientProtocol")
|
138
|
-
self.session = session # 数据处理的会话
|
139
|
-
self.connected = False # 连接状态标识
|
140
|
-
self._iac_handlers = dict() # 支持选项协商处理函数
|
141
|
-
self._iac_subneg_handlers = dict() # 处理选项子协商处理函数
|
142
|
-
|
143
|
-
for k, v in _option_name_str.items():
|
144
|
-
func = getattr(self, f"handle_{v.lower()}", None) # 选项协商处理函数,处理函数中使用小写字母
|
145
|
-
self._iac_handlers[k] = func
|
146
|
-
subfunc = getattr(self, f"handle_{v.lower()}_sb", None) # 子协商处理函数
|
147
|
-
self._iac_subneg_handlers[k] = subfunc
|
148
|
-
|
149
|
-
self.encoding = Settings.server["default_encoding"] # 字节串基本编码
|
150
|
-
self.encoding_errors = Settings.server["encoding_errors"] # 编码解码错误时的处理
|
151
|
-
self.mnes = Settings.mnes
|
152
|
-
|
153
|
-
self._extra = dict() # 存储有关的额外信息的词典
|
154
|
-
|
155
|
-
self.mssp = dict() # 存储MSSP协议传来的服务器有关参数
|
156
|
-
self.msdp = dict() # 存储MSDP协议传来的服务器所有数据
|
157
|
-
self.gmcp = dict() # 存储GMCP协议传来的服务器所有数据
|
158
|
-
|
159
|
-
self._extra.update(kwargs=kwargs)
|
160
|
-
|
161
|
-
self.on_connection_made = kwargs.get("onConnected", None) # 连接时的回调
|
162
|
-
self.on_connection_lost = kwargs.get("onDisconnected", None) # 断开时的回调
|
163
|
-
|
164
|
-
def get_extra_info(self, name, default=None):
|
165
|
-
"""获取传输信息或者额外的协议信息."""
|
166
|
-
if self._transport:
|
167
|
-
default = self._transport.
|
168
|
-
return self._extra.get(name, default)
|
169
|
-
|
170
|
-
def connection_made(self, transport: BaseTransport) -> None:
|
171
|
-
self._transport = transport # 保存传输
|
172
|
-
self._when_connected = datetime.datetime.now() # 连接建立时间
|
173
|
-
self._last_received = datetime.datetime.now() # 最后收到数据时间
|
174
|
-
|
175
|
-
#self.session.set_transport(self._transport) # 将传输赋值给session
|
176
|
-
|
177
|
-
# self.reader = self._reader_factory(loop = self.loop, encoding = self.encoding, encoding_errors = self.encoding_errors)
|
178
|
-
# self.writer = self._writer_factory(self._transport, self, self.reader, self.loop)
|
179
|
-
|
180
|
-
self._state_machine = "normal" # 状态机标识, normal,
|
181
|
-
self._bytes_received_count = 0 # 收到的所有字节数(含命令)
|
182
|
-
self._bytes_count = 0 # 收到的字节数(不含协商),即写入streamreader的字节数
|
183
|
-
self.connected = True
|
184
|
-
|
185
|
-
self.log.info(f'已建立连接到: {self}.')
|
186
|
-
|
187
|
-
# 若设置了onConnected回调函数,则调用
|
188
|
-
if self.on_connection_made and callable(self.on_connection_made):
|
189
|
-
self.on_connection_made(self._transport)
|
190
|
-
|
191
|
-
# 设置future
|
192
|
-
#self._waiter_connected.set_result(True)
|
193
|
-
|
194
|
-
def connection_lost(self, exc) -> None:
|
195
|
-
if not self.connected:
|
196
|
-
return
|
197
|
-
|
198
|
-
self.connected = False
|
199
|
-
|
200
|
-
if exc is None:
|
201
|
-
self.log.info(f'连接已经断开: {self}.')
|
202
|
-
self.session.feed_eof()
|
203
|
-
else:
|
204
|
-
self.log.warning(f'由于异常连接已经断开: {self}, {exc}.')
|
205
|
-
self.session.set_exception(exc)
|
206
|
-
|
207
|
-
self._transport
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
self.
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
self.
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
self.
|
241
|
-
|
242
|
-
|
243
|
-
self.
|
244
|
-
self.
|
245
|
-
|
246
|
-
|
247
|
-
self.
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
self.
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
self.
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
self.
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
self.
|
272
|
-
self.
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
self.
|
279
|
-
self.
|
280
|
-
|
281
|
-
|
282
|
-
self.
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
#
|
289
|
-
#
|
290
|
-
#
|
291
|
-
#
|
292
|
-
#
|
293
|
-
#
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
self.
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
self.
|
358
|
-
|
359
|
-
|
360
|
-
self.
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
self.
|
374
|
-
|
375
|
-
|
376
|
-
self.
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
self.
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
#
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
sbneg
|
414
|
-
sbneg.
|
415
|
-
sbneg.
|
416
|
-
sbneg.extend(
|
417
|
-
|
418
|
-
self.
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
self.
|
440
|
-
self.
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
""
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
#
|
463
|
-
#
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
self.
|
469
|
-
self.
|
470
|
-
|
471
|
-
|
472
|
-
#
|
473
|
-
#
|
474
|
-
|
475
|
-
self.
|
476
|
-
self.
|
477
|
-
|
478
|
-
|
479
|
-
#
|
480
|
-
#
|
481
|
-
#
|
482
|
-
#
|
483
|
-
#
|
484
|
-
#
|
485
|
-
#
|
486
|
-
#
|
487
|
-
#
|
488
|
-
#
|
489
|
-
#
|
490
|
-
#
|
491
|
-
#
|
492
|
-
|
493
|
-
self.
|
494
|
-
self.
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
(
|
506
|
-
(client sends) IAC
|
507
|
-
|
508
|
-
|
509
|
-
"""
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
self.
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
self.
|
525
|
-
|
526
|
-
(
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
self.
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
#
|
565
|
-
# client - IAC SB MNES IS VAR "
|
566
|
-
#
|
567
|
-
#
|
568
|
-
|
569
|
-
|
570
|
-
sbneg
|
571
|
-
sbneg.
|
572
|
-
sbneg.
|
573
|
-
sbneg.
|
574
|
-
sbneg.
|
575
|
-
sbneg.extend(
|
576
|
-
|
577
|
-
self.
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
client - IAC
|
615
|
-
|
616
|
-
"""
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
self.
|
623
|
-
|
624
|
-
|
625
|
-
self.
|
626
|
-
|
627
|
-
|
628
|
-
#
|
629
|
-
#
|
630
|
-
# self.
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
#
|
649
|
-
#
|
650
|
-
# b'\xff\xfa\
|
651
|
-
# b'\xff\xfa\
|
652
|
-
# b'\xff\xfa\xc9GMCP.
|
653
|
-
#
|
654
|
-
#
|
655
|
-
#
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
#
|
664
|
-
#
|
665
|
-
#
|
666
|
-
|
667
|
-
|
668
|
-
self.
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
#
|
676
|
-
# client - IAC
|
677
|
-
#
|
678
|
-
#
|
679
|
-
#
|
680
|
-
#
|
681
|
-
#
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
self.
|
689
|
-
self.
|
690
|
-
self.send_msdp_sb(b"LIST", b"
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
self.
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
sbneg
|
714
|
-
sbneg.
|
715
|
-
sbneg.
|
716
|
-
sbneg.
|
717
|
-
sbneg.
|
718
|
-
sbneg.extend(
|
719
|
-
|
720
|
-
self.
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
#
|
731
|
-
#
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
val_in_text.
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
val_in_text.
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
table_var_value.
|
800
|
-
|
801
|
-
|
802
|
-
state_machine_table = "
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
server - IAC
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
self.
|
830
|
-
|
831
|
-
self.
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
elif cmd ==
|
836
|
-
nohandle = True
|
837
|
-
elif cmd ==
|
838
|
-
nohandle = True
|
839
|
-
|
840
|
-
nohandle = True
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
self.
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
#
|
866
|
-
|
867
|
-
#
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
#
|
876
|
-
var
|
877
|
-
val
|
878
|
-
|
879
|
-
|
880
|
-
next_bytes = "
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
#
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
self.
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
elif cmd ==
|
915
|
-
nohandle = True
|
916
|
-
elif cmd ==
|
917
|
-
nohandle = True
|
918
|
-
|
919
|
-
nohandle = True
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
self.
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
elif cmd ==
|
944
|
-
nohandle = True
|
945
|
-
elif cmd ==
|
946
|
-
nohandle = True
|
947
|
-
|
948
|
-
nohandle = True
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
self.
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
elif cmd ==
|
973
|
-
nohandle = True
|
974
|
-
elif cmd ==
|
975
|
-
nohandle = True
|
976
|
-
|
977
|
-
nohandle = True
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
self.
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
elif cmd ==
|
1001
|
-
nohandle = True
|
1002
|
-
elif cmd ==
|
1003
|
-
nohandle = True
|
1004
|
-
|
1005
|
-
nohandle = True
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1
|
+
import logging, asyncio, datetime, traceback
|
2
|
+
from asyncio import BaseTransport, Protocol
|
3
|
+
from .settings import Settings
|
4
|
+
|
5
|
+
IAC = b"\xff" # TELNET 命令字 IAC
|
6
|
+
DONT = b"\xfe"
|
7
|
+
DO = b"\xfd"
|
8
|
+
WONT = b"\xfc"
|
9
|
+
WILL = b"\xfb"
|
10
|
+
SE = b"\xf0"
|
11
|
+
NOP = b"\xf1"
|
12
|
+
DM = b"\xf2"
|
13
|
+
BRK = b"\xf3"
|
14
|
+
IP = b"\xf4"
|
15
|
+
AO = b"\xf5"
|
16
|
+
AYT = b"\xf6"
|
17
|
+
EC = b"\xf7"
|
18
|
+
EL = b"\xf8"
|
19
|
+
GA = b"\xf9"
|
20
|
+
SB = b"\xfa"
|
21
|
+
|
22
|
+
LOGOUT = b"\x12"
|
23
|
+
ECHO = b"\x01"
|
24
|
+
|
25
|
+
CHARSET = b"*"
|
26
|
+
REQUEST = 1
|
27
|
+
ACCEPTED = 2
|
28
|
+
REJECTED = 3
|
29
|
+
TTABLE_IS = 4
|
30
|
+
TTABLE_REJECTED = 5
|
31
|
+
TTABLE_ACK = 6
|
32
|
+
TTABLE_NAK = 7
|
33
|
+
|
34
|
+
# TELNET & MUD PROTOCOLS OPTIONS
|
35
|
+
#IAC DO TTYPE x18 https://tintin.mudhalla.net/protocols/mtts/
|
36
|
+
#IAC DO NAWS x1F Negotiate About Window Size https://www.rfc-editor.org/rfc/rfc1073.html
|
37
|
+
#IAC DO NEW_ENVIRON b"'" https://tintin.mudhalla.net/protocols/mnes/
|
38
|
+
#IAC WILL b'\xc9' GMCP 201 https://tintin.mudhalla.net/protocols/gmcp/
|
39
|
+
#IAC WILL b'F' MSSP 70 https://tintin.mudhalla.net/protocols/mssp/
|
40
|
+
#IAC WILL CHARSET b"*" https://www.rfc-editor.org/rfc/rfc2066.html
|
41
|
+
#IAC WILL b'Z' 90, MUD Sound Protocol, MSP http://www.zuggsoft.com/zmud/msp.htm
|
42
|
+
|
43
|
+
LINEMODE = b'"' # LINEMODE, rfc1184 https://www.rfc-editor.org/rfc/rfc1184.html
|
44
|
+
SGA = b"\x03" # SUPPRESS GO AHEAD, rfc858 https://www.rfc-editor.org/rfc/rfc858.html
|
45
|
+
|
46
|
+
TTYPE = b"\x18" # MUD终端类型标准,Mud Terminal Type Standard, https://tintin.mudhalla.net/protocols/mtts/
|
47
|
+
NAWS = b"\x1f" # 协商窗口尺寸, RFC1073, Negotiate About Window Size, https://www.rfc-editor.org/rfc/rfc1073.html
|
48
|
+
MNES = b"'" # MUD环境标准,MUD NEW-ENVIRON Standard, https://tintin.mudhalla.net/protocols/mnes/
|
49
|
+
|
50
|
+
# 通用MUD通信协议, Generic Mud Communication Protocol, https://tintin.mudhalla.net/protocols/gmcp/
|
51
|
+
GMCP = b"\xc9"
|
52
|
+
|
53
|
+
# MUD服务器数据协议, Mud Server Data Protocol, https://tintin.mudhalla.net/protocols/msdp/
|
54
|
+
MSDP = b"E"
|
55
|
+
MSDP_VAR = 1
|
56
|
+
MSDP_VAL = 2
|
57
|
+
MSDP_TABLE_OPEN = 3
|
58
|
+
MSDP_TABLE_CLOSE = 4
|
59
|
+
MSDP_ARRAY_OPEN = 5
|
60
|
+
MSDP_ARRAY_CLOSE = 6
|
61
|
+
|
62
|
+
# MUD服务器状态协议, Mud Server Status Protocol, https://tintin.mudhalla.net/protocols/mssp/
|
63
|
+
MSSP = b"F"
|
64
|
+
MSSP_VAR = 1
|
65
|
+
MSSP_VAL = 2
|
66
|
+
|
67
|
+
# MUD客户端压缩协议,V1V2V3版, 参见 http://www.zuggsoft.com/zmud/mcp.htm, https://tintin.mudhalla.net/protocols/mccp/
|
68
|
+
# MCCP V1 使用选项85(U)作为协商选项,于1998年定义;
|
69
|
+
# 2000年, 规定了MCCP V2,使用选项86(V)作为协商选项。
|
70
|
+
# 此后,MCCP1由于子协商内容非法, 2004年被弃用。当前所有MUD服务器端均切换到支持V2版协议。
|
71
|
+
# 2019年,V3协议被定义,但作为一个新协议使用,当前尚未支持。
|
72
|
+
MCCP1 = b"U" # MUD客户端压缩协议V1,已被弃用
|
73
|
+
MCCP2 = b"V" # MUD客户端压缩协议V2, Mud Client Compression Protocol, https://tintin.mudhalla.net/protocols/mccp/
|
74
|
+
MCCP3 = b"W" # MUD客户端压缩协议V3, V2版同样可参见 http://www.zuggsoft.com/zmud/mcp.htm
|
75
|
+
|
76
|
+
# MUD声音协议, MUD Sound Protocol, http://www.zuggsoft.com/zmud/msp.htm
|
77
|
+
MSP = b"Z"
|
78
|
+
|
79
|
+
# MUD扩展协议, MUD eXtension Protocol, http://www.zuggsoft.com/zmud/mxp.htm
|
80
|
+
MXP = b"["
|
81
|
+
|
82
|
+
_cmd_name_str = {
|
83
|
+
IAC : "IAC",
|
84
|
+
WILL : "WILL",
|
85
|
+
WONT : "WONT",
|
86
|
+
DO : "DO",
|
87
|
+
DONT : "DONT",
|
88
|
+
SB : "SB",
|
89
|
+
SE : "SE",
|
90
|
+
}
|
91
|
+
|
92
|
+
_option_name_str = {
|
93
|
+
LINEMODE: "LINEMODE",
|
94
|
+
SGA : "SGA",
|
95
|
+
|
96
|
+
ECHO : "ECHO",
|
97
|
+
CHARSET : "CHARSET",
|
98
|
+
TTYPE : "TTYPE",
|
99
|
+
NAWS : "NAWS",
|
100
|
+
MNES : "MNES",
|
101
|
+
GMCP : "GMCP",
|
102
|
+
MSDP : "MSDP",
|
103
|
+
MSSP : "MSSP",
|
104
|
+
MCCP2 : "MCCP2",
|
105
|
+
MCCP3 : "MCCP3",
|
106
|
+
MSP : "MSP",
|
107
|
+
MXP : "MXP"
|
108
|
+
}
|
109
|
+
|
110
|
+
def name_command(cmd):
|
111
|
+
if cmd in _cmd_name_str.keys():
|
112
|
+
return _cmd_name_str[cmd]
|
113
|
+
else:
|
114
|
+
return cmd
|
115
|
+
|
116
|
+
def name_option(opt):
|
117
|
+
if opt in _option_name_str.keys():
|
118
|
+
return _option_name_str[opt]
|
119
|
+
return opt
|
120
|
+
|
121
|
+
class MudClientProtocol(Protocol):
|
122
|
+
"""
|
123
|
+
适用于MUD客户端的asyncio的协议实现
|
124
|
+
基本协议: TELNET
|
125
|
+
扩展协议: GMCP, MCDP, MSP, MXP等等
|
126
|
+
"""
|
127
|
+
|
128
|
+
def __init__(self, session, *args, **kwargs) -> None:
|
129
|
+
"""
|
130
|
+
MUD客户端协议实现, 参数包括:
|
131
|
+
+ session: 管理protocol的会话
|
132
|
+
除此之外,还可以接受的命名参数包括:
|
133
|
+
+ onConnected: 当连接建立时的回调,包含2个参数: MudClientProtocol本身,以及生成的传输Transport对象
|
134
|
+
+ onDisconnected: 当连接断开时的回调,包含1个参数: MudClientProtocol本身
|
135
|
+
"""
|
136
|
+
|
137
|
+
self.log = logging.getLogger("pymud.MudClientProtocol")
|
138
|
+
self.session = session # 数据处理的会话
|
139
|
+
self.connected = False # 连接状态标识
|
140
|
+
self._iac_handlers = dict() # 支持选项协商处理函数
|
141
|
+
self._iac_subneg_handlers = dict() # 处理选项子协商处理函数
|
142
|
+
|
143
|
+
for k, v in _option_name_str.items():
|
144
|
+
func = getattr(self, f"handle_{v.lower()}", None) # 选项协商处理函数,处理函数中使用小写字母
|
145
|
+
self._iac_handlers[k] = func
|
146
|
+
subfunc = getattr(self, f"handle_{v.lower()}_sb", None) # 子协商处理函数
|
147
|
+
self._iac_subneg_handlers[k] = subfunc
|
148
|
+
|
149
|
+
self.encoding = kwargs.get("encoding", Settings.server["default_encoding"]) # 字节串基本编码
|
150
|
+
self.encoding_errors = Settings.server["encoding_errors"] # 编码解码错误时的处理
|
151
|
+
self.mnes = Settings.mnes
|
152
|
+
|
153
|
+
self._extra = dict() # 存储有关的额外信息的词典
|
154
|
+
|
155
|
+
self.mssp = dict() # 存储MSSP协议传来的服务器有关参数
|
156
|
+
self.msdp = dict() # 存储MSDP协议传来的服务器所有数据
|
157
|
+
self.gmcp = dict() # 存储GMCP协议传来的服务器所有数据
|
158
|
+
|
159
|
+
self._extra.update(kwargs=kwargs)
|
160
|
+
|
161
|
+
self.on_connection_made = kwargs.get("onConnected", None) # 连接时的回调
|
162
|
+
self.on_connection_lost = kwargs.get("onDisconnected", None) # 断开时的回调
|
163
|
+
|
164
|
+
def get_extra_info(self, name, default=None):
|
165
|
+
"""获取传输信息或者额外的协议信息."""
|
166
|
+
if self._transport:
|
167
|
+
default = self._transport.get_extra_info(name, default)
|
168
|
+
return self._extra.get(name, default)
|
169
|
+
|
170
|
+
def connection_made(self, transport: BaseTransport) -> None:
|
171
|
+
self._transport = transport # 保存传输
|
172
|
+
self._when_connected = datetime.datetime.now() # 连接建立时间
|
173
|
+
self._last_received = datetime.datetime.now() # 最后收到数据时间
|
174
|
+
|
175
|
+
#self.session.set_transport(self._transport) # 将传输赋值给session
|
176
|
+
|
177
|
+
# self.reader = self._reader_factory(loop = self.loop, encoding = self.encoding, encoding_errors = self.encoding_errors)
|
178
|
+
# self.writer = self._writer_factory(self._transport, self, self.reader, self.loop)
|
179
|
+
|
180
|
+
self._state_machine = "normal" # 状态机标识, normal,
|
181
|
+
self._bytes_received_count = 0 # 收到的所有字节数(含命令)
|
182
|
+
self._bytes_count = 0 # 收到的字节数(不含协商),即写入streamreader的字节数
|
183
|
+
self.connected = True
|
184
|
+
|
185
|
+
self.log.info(f'已建立连接到: {self}.')
|
186
|
+
|
187
|
+
# 若设置了onConnected回调函数,则调用
|
188
|
+
if self.on_connection_made and callable(self.on_connection_made):
|
189
|
+
self.on_connection_made(self._transport)
|
190
|
+
|
191
|
+
# 设置future
|
192
|
+
#self._waiter_connected.set_result(True)
|
193
|
+
|
194
|
+
def connection_lost(self, exc) -> None:
|
195
|
+
if not self.connected:
|
196
|
+
return
|
197
|
+
|
198
|
+
self.connected = False
|
199
|
+
|
200
|
+
if exc is None:
|
201
|
+
self.log.info(f'连接已经断开: {self}.')
|
202
|
+
self.session.feed_eof()
|
203
|
+
else:
|
204
|
+
self.log.warning(f'由于异常连接已经断开: {self}, {exc}.')
|
205
|
+
self.session.set_exception(exc)
|
206
|
+
|
207
|
+
if self._transport:
|
208
|
+
self._transport.close()
|
209
|
+
self._transport = None
|
210
|
+
#self.session.set_transport(None)
|
211
|
+
|
212
|
+
# 若设置了onConnected回调函数,则调用
|
213
|
+
if self.on_connection_lost and callable(self.on_connection_lost):
|
214
|
+
self.on_connection_lost(self)
|
215
|
+
|
216
|
+
self._state_machine = "normal" # 状态机标识恢复到 normal,
|
217
|
+
|
218
|
+
def eof_received(self):
|
219
|
+
self.log.debug("收到服务器发来的EOF, 连接关闭.")
|
220
|
+
self.connection_lost(None)
|
221
|
+
|
222
|
+
def data_received(self, data: bytes) -> None:
|
223
|
+
self._last_received = datetime.datetime.now()
|
224
|
+
|
225
|
+
for byte in data:
|
226
|
+
byte = bytes([byte,])
|
227
|
+
self._bytes_received_count += 1
|
228
|
+
|
229
|
+
# 状态机为 正常,接下来可能接收到命令包括 IAC 或 正常字符
|
230
|
+
if self._state_machine == "normal":
|
231
|
+
if byte == IAC: # 若接收到IAC,状态机切换到等待命令
|
232
|
+
self._state_machine = "waitcommand"
|
233
|
+
self.session.go_ahead()
|
234
|
+
else: # 否则收到为正常数据,传递给reader
|
235
|
+
self.session.feed_data(byte)
|
236
|
+
|
237
|
+
# 状态机为 等待命令,接下来应该收到的字节仅可能包括: WILL/WONT/DO/DONT/SB
|
238
|
+
elif self._state_machine == "waitcommand":
|
239
|
+
if byte in (WILL, WONT, DO, DONT): # 此时,后续选项仅1个字节
|
240
|
+
self._iac_command = byte
|
241
|
+
self._state_machine = "waitoption" # 后续为选项
|
242
|
+
elif byte == SB:
|
243
|
+
self._iac_command = byte
|
244
|
+
self._iac_sub_neg_data = IAC+SB # 保存完整子协商命令
|
245
|
+
self._state_machine = "waitsubnegotiation" # 后续为子协商,以 IAC SE终止
|
246
|
+
elif byte == NOP: # 空操作 TODO 确认空操作是否为IAC NOP,没有其他
|
247
|
+
self.log.debug(f"收到服务器NOP指令: IAC NOP")
|
248
|
+
self._state_machine = "normal"
|
249
|
+
# 对NOP信号和GA信号处理相同
|
250
|
+
self.session.go_ahead()
|
251
|
+
elif byte == GA:
|
252
|
+
self.log.debug(f"收到服务器GA指令: IAC GA")
|
253
|
+
self._state_machine = "normal"
|
254
|
+
# 对NOP信号和GA信号处理相同,让缓冲区全部发送出去
|
255
|
+
self.session.go_ahead()
|
256
|
+
else: # 错误数据,无法处置,记录错误,并恢复状态机到normal
|
257
|
+
self.log.error(f"与服务器协商过程中,收到未处理的非法命令: {byte}")
|
258
|
+
self._state_machine = "normal"
|
259
|
+
|
260
|
+
elif self._state_machine == "waitoption": # 后续可以接受选项
|
261
|
+
if byte in _option_name_str.keys():
|
262
|
+
iac_handler = self._iac_handlers[byte] # 根据选项选择对应的处理函数
|
263
|
+
if iac_handler and callable(iac_handler):
|
264
|
+
self.log.debug(f"收到IAC选项协商: IAC {name_command(self._iac_command)} {name_option(byte)}, 并传递至处理函数 {iac_handler.__name__}")
|
265
|
+
iac_handler(self._iac_command) # 执行IAC协商
|
266
|
+
else:
|
267
|
+
self.log.debug(f"收到不支持(尚未定义处理函数)的IAC协商: IAC {name_command(self._iac_command)} {name_option(byte)}, 将使用默认处理(不接受)")
|
268
|
+
self._iac_default_handler(self._iac_command, byte)
|
269
|
+
self._state_machine = "normal" # 状态机恢复到正常状态
|
270
|
+
else:
|
271
|
+
self.log.warning(f"收到不识别(不在定义范围内)的IAC协商: IAC {name_command(self._iac_command)} {name_option(byte)}, 将使用默认处理(不接受)")
|
272
|
+
self._iac_default_handler(self._iac_command, byte)
|
273
|
+
self._state_machine = "normal" # 状态机恢复到正常状态
|
274
|
+
|
275
|
+
elif self._state_machine == "waitsubnegotiation": # 当收到了IAC SB
|
276
|
+
# 此时,下一个字节应为可选选项,至少不应为IAC
|
277
|
+
if byte != IAC:
|
278
|
+
self._iac_sub_neg_option = byte # 保存子协商选项
|
279
|
+
self._iac_sub_neg_data += byte # 保存子协商全部内容
|
280
|
+
self._state_machine = "waitsbdata" # 下一状态,等待子协商数据
|
281
|
+
else:
|
282
|
+
self.log.error('子协商中在等待选项码的字节中错误收到了IAC')
|
283
|
+
self._state_machine = "normal" # 此时丢弃所有前面的状态
|
284
|
+
|
285
|
+
elif self._state_machine == "waitsbdata":
|
286
|
+
self._iac_sub_neg_data += byte # 保存子协商全部内容
|
287
|
+
if byte == IAC:
|
288
|
+
# 在子协商过程中,如果收到IAC,则下一个字节可能是其他,或者SE。
|
289
|
+
# 当下一个字节为其他时,IAC是子协商中的一个字符
|
290
|
+
# 当下一个字节为SE时,表示子协商命令结束
|
291
|
+
# 基于上述理由,在子协商状态下,状态机的转换规则为:
|
292
|
+
# 1. 子协商中收到IAC后,状态切换为 waitse
|
293
|
+
# 2. 在waitse之后,如果收到SE,则子协商结束,回复到normal
|
294
|
+
# 3. 在waitse之后,如果收到的不是SE,则回复到waitsubnegotiation状态
|
295
|
+
self._state_machine = "waitse"
|
296
|
+
else:
|
297
|
+
# 子协商过程中,收到的所有非IAC字节,都是子协商的具体内容
|
298
|
+
pass
|
299
|
+
|
300
|
+
elif self._state_machine == "waitse":
|
301
|
+
self._iac_sub_neg_data += byte # 保存子协商全部内容
|
302
|
+
if byte == SE: # IAC SE 表示子协商已接收完毕
|
303
|
+
self._state_machine = "normal"
|
304
|
+
if self._iac_sub_neg_option in _option_name_str.keys():
|
305
|
+
iac_subneg_handler = self._iac_subneg_handlers[self._iac_sub_neg_option] # 根据选项子协商选择对应的处理函数
|
306
|
+
if iac_subneg_handler and callable(iac_subneg_handler):
|
307
|
+
self.log.debug(f"收到{name_option(self._iac_sub_neg_option)}选项子协商: {self._iac_sub_neg_data}, 并传递至处理函数 {iac_subneg_handler.__name__}")
|
308
|
+
iac_subneg_handler(self._iac_sub_neg_data)
|
309
|
+
else:
|
310
|
+
self.log.debug(f"收到不支持(尚未定义处理函数)的{name_option(self._iac_sub_neg_option)}选项子协商: {self._iac_sub_neg_data}, 将丢弃数据不处理.")
|
311
|
+
else:
|
312
|
+
self._state_machine = "waitsbdata"
|
313
|
+
|
314
|
+
# public properties
|
315
|
+
@property
|
316
|
+
def duration(self):
|
317
|
+
"""自客户端连接以来的总时间,以秒为单位,浮点数表示"""
|
318
|
+
return (datetime.datetime.now() - self._when_connected).total_seconds()
|
319
|
+
|
320
|
+
@property
|
321
|
+
def idle(self):
|
322
|
+
"""自收到上一个服务器发送数据以来的总时间,以秒为单位,浮点数表示"""
|
323
|
+
return (datetime.datetime.now() - self._last_received).total_seconds()
|
324
|
+
|
325
|
+
# public protocol methods
|
326
|
+
def __repr__(self):
|
327
|
+
"%r下的表述"
|
328
|
+
hostport = self.get_extra_info("peername", ["-", "closing"])[:2] # type: ignore
|
329
|
+
return "<Peer {0} {1}>".format(*hostport)
|
330
|
+
|
331
|
+
def _iac_default_handler(self, cmd, option):
|
332
|
+
"""
|
333
|
+
默认IAC协商处理, 对于不识别的选项,直接回复不接受
|
334
|
+
对于WILL,WONT回复DONT; 对于DO,DONT回复WONT
|
335
|
+
"""
|
336
|
+
if cmd in (WILL, WONT):
|
337
|
+
ack = DONT
|
338
|
+
|
339
|
+
elif cmd in (DO, DONT):
|
340
|
+
ack = WONT
|
341
|
+
|
342
|
+
else:
|
343
|
+
# 非正常情况,此由前述函数调用和处理确保为为不可能分支。为保留代码完整性而保留
|
344
|
+
self.log.error(f'选项协商进入非正常分支,参考数据: , _iac_default_handler, {cmd}, {option}')
|
345
|
+
return
|
346
|
+
|
347
|
+
self.session.write(IAC + ack + option)
|
348
|
+
self.log.debug(f'使用默认协商处理拒绝了服务器的请求的 IAC {name_command(cmd)} {name_option(option)}, 回复为 IAC {name_command(ack)} {name_option(option)}')
|
349
|
+
|
350
|
+
# SGA done.
|
351
|
+
def handle_sga(self, cmd):
|
352
|
+
"""
|
353
|
+
SGA, supress go ahead, 抑制 GA 信号。在全双工环境中,不需要GA信号,因此默认同意抑制
|
354
|
+
"""
|
355
|
+
if cmd == WILL:
|
356
|
+
if Settings.server["SGA"]:
|
357
|
+
self.session.write(IAC + DO + SGA)
|
358
|
+
self.log.debug(f'发送选项协商, 同意抑制GA信号 IAC DO SGA.')
|
359
|
+
else:
|
360
|
+
self.session.write(IAC + DONT + SGA)
|
361
|
+
self.log.debug(f'发送选项协商, 不同意抑制GA信号 IAC DONT SGA.')
|
362
|
+
|
363
|
+
else:
|
364
|
+
self.log.warning(f"收到服务器的未处理的SGA协商: IAC {name_command(cmd)} SGA")
|
365
|
+
|
366
|
+
# ECHO done.
|
367
|
+
def handle_echo(self, cmd):
|
368
|
+
"""
|
369
|
+
ECHO, 回响。默认不同意
|
370
|
+
"""
|
371
|
+
if cmd == WILL:
|
372
|
+
if Settings.server["ECHO"]:
|
373
|
+
self.session.write(IAC + DO + ECHO)
|
374
|
+
self.log.debug(f'发送选项协商, 同意ECHO选项协商 IAC DO ECHO.')
|
375
|
+
else:
|
376
|
+
self.session.write(IAC + DONT + ECHO)
|
377
|
+
self.log.debug(f'发送选项协商, 不同意ECHO选项协商 IAC DONT ECHO.')
|
378
|
+
|
379
|
+
else:
|
380
|
+
self.log.warning(f"收到服务器的未处理的ECHO协商: IAC {name_command(cmd)} ECHO")
|
381
|
+
|
382
|
+
def handle_charset(self, cmd):
|
383
|
+
"""
|
384
|
+
CHARSET, 字符集协商 https://www.rfc-editor.org/rfc/rfc2066.html
|
385
|
+
"""
|
386
|
+
nohandle = False
|
387
|
+
if cmd == WILL:
|
388
|
+
# 1. 回复同意CHARSET协商
|
389
|
+
self.session.write(IAC + DO + CHARSET)
|
390
|
+
self.log.debug(f'发送选项协商, 同意CHARSET协商 IAC DO CHARSET.等待子协商')
|
391
|
+
elif cmd == WONT:
|
392
|
+
nohandle = True
|
393
|
+
elif cmd == DO:
|
394
|
+
nohandle = True
|
395
|
+
elif cmd == DONT:
|
396
|
+
nohandle = True
|
397
|
+
else:
|
398
|
+
nohandle = True
|
399
|
+
|
400
|
+
if nohandle:
|
401
|
+
self.log.warning(f"收到服务器的未处理的CHARSET协商: IAC {name_command(cmd)} TTYPE")
|
402
|
+
|
403
|
+
def handle_charset_sb(self, data: bytes):
|
404
|
+
'字符集子协商'
|
405
|
+
# b'\xff\xfa*\x01;UTF-8\xff\xf0'
|
406
|
+
# IAC SB CHARSET \x01 ; UTF-8 IAC SE
|
407
|
+
unhandle = True
|
408
|
+
self.log.debug('charset 子协商')
|
409
|
+
if data[3] == REQUEST:
|
410
|
+
charset_list = data[4:-2].decode(self.encoding).lower().split(';')
|
411
|
+
# 若服务器存在UTF-8选项,默认选择UTF-8
|
412
|
+
if 'utf-8' in charset_list:
|
413
|
+
sbneg = bytearray()
|
414
|
+
sbneg.extend(IAC + SB + CHARSET)
|
415
|
+
sbneg.append(ACCEPTED)
|
416
|
+
sbneg.extend(b"UTF-8")
|
417
|
+
sbneg.extend(IAC + SE)
|
418
|
+
self.session.write(sbneg)
|
419
|
+
self.log.debug(f'发送CHARSET子协商,同意UTF-8编码 IAC SB ACCEPTED "UTF-8" IAC SE')
|
420
|
+
unhandle = False
|
421
|
+
|
422
|
+
if unhandle:
|
423
|
+
self.log.warning(f'未处理CHARSET子协商: {data}')
|
424
|
+
|
425
|
+
def handle_ttype(self, cmd):
|
426
|
+
"""
|
427
|
+
处理MUD终端类型标准协议的协商 https://tintin.mudhalla.net/protocols/mtts/
|
428
|
+
server - IAC DO TTYPE
|
429
|
+
client - IAC WILL TTYPE
|
430
|
+
等待子协商,子协商见下面的 handle_ttype_sb
|
431
|
+
"""
|
432
|
+
nohandle = False
|
433
|
+
if cmd == WILL:
|
434
|
+
nohandle = True
|
435
|
+
elif cmd == WONT:
|
436
|
+
nohandle = True
|
437
|
+
elif cmd == DO:
|
438
|
+
# 1. 回复同意MTTS协商
|
439
|
+
self.session.write(IAC + WILL + TTYPE)
|
440
|
+
self._mtts_index = 0
|
441
|
+
self.log.debug(f'发送选项协商, 同意MTTS(TTYPE)协商 IAC WILL TTYPE.等待子协商')
|
442
|
+
elif cmd == DONT:
|
443
|
+
nohandle = True
|
444
|
+
else:
|
445
|
+
nohandle = True
|
446
|
+
|
447
|
+
if nohandle:
|
448
|
+
self.log.warning(f"收到服务器的未处理的TTYPE/MTTS协商: IAC {name_command(cmd)} TTYPE")
|
449
|
+
|
450
|
+
def handle_ttype_sb(self, data):
|
451
|
+
"""
|
452
|
+
处理TTYPE/MTTS的子协商, 详细信息参见 handle_ttype
|
453
|
+
server - IAC SB TTYPE SEND IAC SE
|
454
|
+
client - IAC SB TTYPE IS "TINTIN++" IAC SE
|
455
|
+
server - IAC SB TTYPE SEND IAC SE
|
456
|
+
client - IAC SB TTYPE IS "XTERM" IAC SE
|
457
|
+
server - IAC SB TTYPE SEND IAC SE
|
458
|
+
client - IAC SB TTYPE IS "MTTS 137" IAC SE
|
459
|
+
"""
|
460
|
+
IS, SEND = b"\x00", 1
|
461
|
+
|
462
|
+
# 服务器子协商一定为6字节,内容为 IAC SB TTYPE SEND IAC SE
|
463
|
+
# 由于子协商data的IAC SB TTYPE和后面的IAC SE都是固定写进去的,在之前判断过,因此此处不判断
|
464
|
+
# 因此判断服务器子协商命令,仅判断长度为6和第4字节为SEND
|
465
|
+
if (len(data) == 6) and (data[3] == SEND):
|
466
|
+
if self._mtts_index == 0:
|
467
|
+
# 第一次收到,回复客户端全名,全大写
|
468
|
+
self.session.write(IAC + SB + TTYPE + IS + Settings.__appname__.encode(self.encoding, self.encoding_errors) + IAC + SE)
|
469
|
+
self._mtts_index += 1
|
470
|
+
self.log.debug(f'回复第一次MTTS子协商: IAC SB TTYPE IS "{Settings.__appname__}" IAC SE')
|
471
|
+
elif self._mtts_index == 1:
|
472
|
+
# 第二次收到,回复客户端终端类型,此处默认设置为XTERM(使用系统控制台), ANSI(代码已支持),后续功能完善后再更改
|
473
|
+
# VT100 https://tintin.mudhalla.net/info/vt100/
|
474
|
+
# XTERM https://tintin.mudhalla.net/info/xterm/
|
475
|
+
self.session.write(IAC + SB + TTYPE + IS + b"XTERM" + IAC + SE)
|
476
|
+
self._mtts_index += 1
|
477
|
+
self.log.debug('回复第二次MTTS子协商: IAC SB TTYPE IS "XTERM" IAC SE')
|
478
|
+
elif self._mtts_index == 2:
|
479
|
+
# 第三次收到,回复客户端终端支持的标准功能,此处默认设置783(支持ANSI, VT100, UTF-8, 256 COLORS, TRUECOLOR, MNES),后续功能完善后再更改
|
480
|
+
# 根据完善的终端模拟功能,修改终端标准
|
481
|
+
# 1 "ANSI" Client supports all common ANSI color codes.
|
482
|
+
# 2 "VT100" Client supports all common VT100 codes.
|
483
|
+
# 4 "UTF-8" Client is using UTF-8 character encoding.
|
484
|
+
# 8 "256 COLORS" Client supports all 256 color codes.
|
485
|
+
# 16 "MOUSE TRACKING" Client supports xterm mouse tracking.
|
486
|
+
# 32 "OSC COLOR PALETTE" Client supports OSC and the OSC color palette.
|
487
|
+
# 64 "SCREEN READER" Client is using a screen reader.
|
488
|
+
# 128 "PROXY" Client is a proxy allowing different users to connect from the same IP address.
|
489
|
+
# 256 "TRUECOLOR" Client supports truecolor codes using semicolon notation.
|
490
|
+
# 512 "MNES" Client supports the Mud New Environment Standard for information exchange.
|
491
|
+
# 1024 "MSLP" Client supports the Mud Server Link Protocol for clickable link handling.
|
492
|
+
# 2048 "SSL" Client supports SSL for data encryption, preferably TLS 1.3 or higher.
|
493
|
+
self.session.write(IAC + SB + TTYPE + IS + b"MTTS 783" + IAC + SE)
|
494
|
+
self._mtts_index += 1
|
495
|
+
self.log.debug('回复第三次MTTS子协商: IAC SB TTYPE IS "MTTS 783" IAC SE')
|
496
|
+
else:
|
497
|
+
self.log.warning(f'收到第{self._mtts_index + 1}次(正常为3次)的MTTS子协商, 将不予应答')
|
498
|
+
else:
|
499
|
+
self.log.warning(f'收到不正确的MTTS子协商: {data},将不予应答')
|
500
|
+
|
501
|
+
def handle_naws(self, cmd):
|
502
|
+
"""
|
503
|
+
处理屏幕尺寸的协商 https://www.rfc-editor.org/rfc/rfc1073.html
|
504
|
+
服务器发送请求协商尺寸时,逻辑为:
|
505
|
+
(server sends) IAC DO NAWS
|
506
|
+
(client sends) IAC WILL NAWS
|
507
|
+
(client sends) IAC SB NAWS 0(WIDTH1) 80(WIDTH0) 0(HEIGHT1) 24(HEIGHT0) IAC SE
|
508
|
+
本客户端不主动协商需要NAWS,只有当服务器协商需要NAWS时,才进行协商。
|
509
|
+
协商给定的默认尺寸为: self._extra["naws_width"]和self._extra["naws_height"]
|
510
|
+
"""
|
511
|
+
nohandle = False
|
512
|
+
if cmd == WILL:
|
513
|
+
nohandle = True
|
514
|
+
elif cmd == WONT:
|
515
|
+
nohandle = True
|
516
|
+
elif cmd == DO: # 正常情况下,仅处理服务器的 IAC DO NAWS
|
517
|
+
# 1. 回复同意NAWS
|
518
|
+
self.session.write(IAC + WILL + NAWS)
|
519
|
+
self.log.debug(f'发送选项协商, 同意NAWS协商 IAC WILL NAWS.')
|
520
|
+
# 2. 发送子协商确认尺寸
|
521
|
+
width_bytes = Settings.client["naws_width"].to_bytes(2, "big")
|
522
|
+
height_bytes = Settings.client["naws_height"].to_bytes(2, "big")
|
523
|
+
sb_cmd = IAC + SB + NAWS + width_bytes + height_bytes + IAC + SE
|
524
|
+
self.session.write(sb_cmd)
|
525
|
+
self.log.debug(
|
526
|
+
'发送NAWS选项子协商, 明确窗体尺寸。IAC SB NAWS (width = %d, height = %d) IAC SE' %
|
527
|
+
(Settings.client["naws_width"], Settings.client["naws_height"])
|
528
|
+
)
|
529
|
+
elif cmd == DONT:
|
530
|
+
nohandle = True
|
531
|
+
else:
|
532
|
+
nohandle = True
|
533
|
+
|
534
|
+
if nohandle:
|
535
|
+
self.log.warning(f"收到服务器的未处理的NAWS协商: IAC {name_command(cmd)} NAWS")
|
536
|
+
|
537
|
+
def handle_mnes(self, cmd):
|
538
|
+
"""
|
539
|
+
处理MUD新环境标准, Mud New-Env Standard的协商 https://tintin.mudhalla.net/protocols/mnes/
|
540
|
+
MNES作为MTTS的扩展。MTTS设计为只能响应特定的客户端特性,MNES可以提供更多的扩展
|
541
|
+
"""
|
542
|
+
nohandle = False
|
543
|
+
if cmd == WILL:
|
544
|
+
nohandle = True
|
545
|
+
elif cmd == WONT:
|
546
|
+
nohandle = True
|
547
|
+
elif cmd == DO:
|
548
|
+
# 1. 回复同意MNES
|
549
|
+
self.session.write(IAC + WILL + MNES)
|
550
|
+
self.log.debug(f'发送选项协商, 同意MNES协商 IAC WILL MNES. 等待服务器子协商')
|
551
|
+
elif cmd == DONT:
|
552
|
+
nohandle = True
|
553
|
+
else:
|
554
|
+
nohandle = True
|
555
|
+
|
556
|
+
if nohandle:
|
557
|
+
self.log.warning(f"收到服务器的未处理的MNES协商: IAC {name_command(cmd)} MNES")
|
558
|
+
|
559
|
+
def handle_mnes_sb(self, data: bytes):
|
560
|
+
"""
|
561
|
+
处理MNES的子协商 https://tintin.mudhalla.net/protocols/mnes/
|
562
|
+
|
563
|
+
"""
|
564
|
+
# server - IAC SB MNES SEND VAR "CLIENT_NAME" SEND VAR "CLIENT_VERSION" IAC SE
|
565
|
+
# client - IAC SB MNES IS VAR "CLIENT_NAME" VAL "TINTIN++" IAC SE
|
566
|
+
# client - IAC SB MNES IS VAR "CLIENT_VERSION" VAL "2.01.7" IAC SE
|
567
|
+
# server - IAC SB MNES SEND VAR "CHARSET" IAC SE
|
568
|
+
# client - IAC SB MNES INFO VAR "CHARSET" VAL "ASCII" IAC SE
|
569
|
+
def send_mnes_value(var: str, val: str):
|
570
|
+
sbneg = bytearray()
|
571
|
+
sbneg.extend(IAC + SB + MNES)
|
572
|
+
sbneg.append(IS)
|
573
|
+
sbneg.extend(var.encode(self.encoding))
|
574
|
+
sbneg.append(VAL)
|
575
|
+
sbneg.extend(val.encode(self.encoding))
|
576
|
+
sbneg.extend(IAC + SE)
|
577
|
+
self.session.write(sbneg)
|
578
|
+
self.log.debug(f"回复MNES请求: {var} = {val}")
|
579
|
+
|
580
|
+
IS, SEND, INFO = 0, 1, 2
|
581
|
+
VAR, VAL = 0, 1
|
582
|
+
|
583
|
+
request_var = list()
|
584
|
+
var_name = bytearray()
|
585
|
+
state_machine = "wait_cmd"
|
586
|
+
for idx in range (3, len(data) - 1):
|
587
|
+
byte = data[idx]
|
588
|
+
if state_machine == "wait_cmd": # 下一个字节为命令, 应该是 SEND
|
589
|
+
if byte == SEND:
|
590
|
+
state_machine = "wait_var"
|
591
|
+
|
592
|
+
elif state_machine == "wait_var":
|
593
|
+
if byte == VAR:
|
594
|
+
state_machine = "wait_var_content"
|
595
|
+
var_name.clear()
|
596
|
+
|
597
|
+
elif state_machine == "wait_var_content":
|
598
|
+
if byte not in (SEND, IAC):
|
599
|
+
var_name.append(byte)
|
600
|
+
else:
|
601
|
+
if len(var_name) > 0:
|
602
|
+
request_var.append(var_name.decode(self.encoding))
|
603
|
+
state_machine = "wait_cmd"
|
604
|
+
|
605
|
+
self.log.debug(f"收到{len(request_var)}个MNES子协商请求变量: {request_var}")
|
606
|
+
for var_name in request_var:
|
607
|
+
if var_name in self.mnes.keys():
|
608
|
+
send_mnes_value(var_name, self.mnes[var_name])
|
609
|
+
|
610
|
+
def handle_gmcp(self, cmd):
|
611
|
+
"""
|
612
|
+
处理 通用MUD通信协议, GMCP的协商 https://tintin.mudhalla.net/protocols/gmcp/
|
613
|
+
server - IAC WILL GMCP
|
614
|
+
client - IAC DO GMCP
|
615
|
+
client - IAC SB GMCP 'MSDP {"LIST" : "COMMANDS"}' IAC SE
|
616
|
+
server - IAC SB GMCP 'MSDP {"COMMANDS":["LIST","REPORT","RESET","SEND","UNREPORT"]}' IAC SE
|
617
|
+
"""
|
618
|
+
nohandle = False
|
619
|
+
if cmd == WILL:
|
620
|
+
# 1. 回复同意GMCP与否
|
621
|
+
if Settings.server["GMCP"]:
|
622
|
+
self.session.write(IAC + DO + GMCP)
|
623
|
+
self.log.debug(f'发送选项协商, 同意GMPC协商 IAC DO GMCP.')
|
624
|
+
else:
|
625
|
+
self.session.write(IAC + DONT + GMCP)
|
626
|
+
self.log.debug(f'发送选项协商, 不同意GMPC协商 IAC DONT GMCP.')
|
627
|
+
|
628
|
+
# 2. 发送GMCP子协商,获取MSDP的相关命令?待定后续处理
|
629
|
+
# 支持了MSDP协议, 不使用GMCP获取MSDP的有关命令,即不使用 MDSP over GMCP
|
630
|
+
# self.session.write(IAC + SB + GMCP + b'MSDP {"LIST" : "COMMANDS"}' + IAC + SE)
|
631
|
+
# self.log.debug(f'发送GMPC子协商 IAC SB GMCP ''MSDP {"LIST" : "COMMANDS"}'' IAC SE.')
|
632
|
+
elif cmd == WONT:
|
633
|
+
nohandle = True
|
634
|
+
elif cmd == DO:
|
635
|
+
nohandle = True
|
636
|
+
elif cmd == DONT:
|
637
|
+
nohandle = True
|
638
|
+
else:
|
639
|
+
nohandle = True
|
640
|
+
|
641
|
+
if nohandle:
|
642
|
+
self.log.warning(f"收到服务器的未处理的GMCP协商: IAC {name_command(cmd)} GMCP")
|
643
|
+
|
644
|
+
def handle_gmcp_sb(self, data: bytes):
|
645
|
+
"""
|
646
|
+
处理GMCP的子协商 https://tintin.mudhalla.net/protocols/gmcp/
|
647
|
+
"""
|
648
|
+
# Evennia GMCP 数据实例:
|
649
|
+
# 在发送IAC DO GMCP之后,当发送MSDP子协商请求时,会同时收到MSDP数据和GMCP数据
|
650
|
+
# b'\xff\xfa\xc9Core.Lists ["commands", "lists", "configurable_variables", "reportable_variables", "reported_variables", "sendable_variables"]\xff\xf0'
|
651
|
+
# b'\xff\xfa\xc9Reportable.Variables ["name", "location", "desc"]\xff\xf0'
|
652
|
+
# b'\xff\xfa\xc9GMCP.Move [{"result":"true","dir":["southeast","southwest","northup"],"short":"\xb9\xcf\xb2\xbd"}]\xff\xf0'
|
653
|
+
# b'\xff\xfa\xc9GMCP.Status {"qi":1045,"name":"\xc4\xbd\xc8\xdd\xca\xc0\xbc\xd2\xbc\xd2\xd4\xf4","id":"xqtraveler\'s murong jiazei#7300006"}\xff\xf0',
|
654
|
+
# 收到GMCP选项子协商:
|
655
|
+
# 服务器子协商长度不确定,中间使用字符串表示一系列内容,内容长度为总长度-5
|
656
|
+
# 子协商总长度-5为子协商的状态内容,子协商前3个字节为IAC SB GMCP,后2个字节为IAC SE
|
657
|
+
gmcp_data = data[3:-2].decode(self.encoding)
|
658
|
+
|
659
|
+
space_split = gmcp_data.find(" ")
|
660
|
+
name = gmcp_data[:space_split]
|
661
|
+
value = gmcp_data[space_split+1:]
|
662
|
+
|
663
|
+
# try:
|
664
|
+
# value = eval(value_str)
|
665
|
+
# except:
|
666
|
+
# value = value_str
|
667
|
+
|
668
|
+
self.log.debug(f'收到GMCP子协商数据: {name} = {value}')
|
669
|
+
self.session.feed_gmcp(name, value)
|
670
|
+
|
671
|
+
def handle_msdp(self, cmd):
|
672
|
+
"""
|
673
|
+
处理 通用MUD服务器数据协议, MSDP的协商 https://tintin.mudhalla.net/protocols/msdp/
|
674
|
+
"""
|
675
|
+
# server - IAC WILL MSDP
|
676
|
+
# client - IAC DO MSDP
|
677
|
+
# client - IAC SB MSDP MSDP_VAR "LIST" MSDP_VAL "COMMANDS" IAC SE
|
678
|
+
# server - IAC SB MSDP MSDP_VAR "COMMANDS" MSDP_VAL MSDP_ARRAY_OPEN MSDP_VAL "LIST" MSDP_VAL "REPORT" MSDP_VAL "SEND" MSDP_ARRAY_CLOSE IAC SE
|
679
|
+
# client - IAC SB MSDP MSDP_VAR "LIST" MSDP_VAL "REPORTABLE_VARIABLES" IAC SE
|
680
|
+
# server - IAC SB MSDP MSDP_VAR "REPORTABLE_VARIABLES" MSDP_VAL "HINT" IAC SE
|
681
|
+
# client - IAC SB MSDP MSDP_VAR "SEND" MSDP_VAL "HINT" IAC SE
|
682
|
+
# server - IAC SB MSDP MSDP_VAR "HINT" MSDP_VAL "THE GAME" IAC SE
|
683
|
+
|
684
|
+
nohandle = False
|
685
|
+
if cmd == WILL:
|
686
|
+
# 1. 回复同意MSDP与否
|
687
|
+
if Settings.server["MSDP"]:
|
688
|
+
self.session.write(IAC + DO + MSDP)
|
689
|
+
self.log.debug(f'发送选项协商, 同意MSDP协商 IAC DO MSDP.')
|
690
|
+
self.send_msdp_sb(b"LIST", b"LISTS")
|
691
|
+
self.send_msdp_sb(b"LIST", b"REPORTABLE_VARIABLES")
|
692
|
+
|
693
|
+
else:
|
694
|
+
self.session.write(IAC + DONT + MSDP)
|
695
|
+
self.log.debug(f'发送选项协商, 不同意MSDP协商 IAC DONT MSDP.')
|
696
|
+
|
697
|
+
elif cmd == WONT:
|
698
|
+
nohandle = True
|
699
|
+
elif cmd == DO:
|
700
|
+
nohandle = True
|
701
|
+
elif cmd == DONT:
|
702
|
+
nohandle = True
|
703
|
+
else:
|
704
|
+
nohandle = True
|
705
|
+
|
706
|
+
if nohandle:
|
707
|
+
self.log.warning(f"收到服务器的未处理的MSDP协商: IAC {name_command(cmd)} MSDP")
|
708
|
+
|
709
|
+
def send_msdp_sb(self, cmd: bytes, param: bytes):
|
710
|
+
'''
|
711
|
+
发送MSDP子协商,获取MSDP的相关数据
|
712
|
+
'''
|
713
|
+
sbneg = bytearray()
|
714
|
+
sbneg.extend(IAC + SB + MSDP)
|
715
|
+
sbneg.append(MSDP_VAR)
|
716
|
+
sbneg.extend(cmd)
|
717
|
+
sbneg.append(MSDP_VAL)
|
718
|
+
sbneg.extend(param)
|
719
|
+
sbneg.extend(IAC + SE)
|
720
|
+
self.session.write(sbneg)
|
721
|
+
self.log.debug(f'发送MSDP子协商查询支持的MSDP命令 IAC SB MSDP MSDP_VAR "{cmd.decode(self.encoding)}" MSDP_VAL "{param.decode(self.encoding)}" IAC SE.')
|
722
|
+
|
723
|
+
def handle_msdp_sb(self, data):
|
724
|
+
"""
|
725
|
+
处理MSDP的子协商 https://tintin.mudhalla.net/protocols/msdp/
|
726
|
+
"""
|
727
|
+
# b'\xff\xfaE\x01commands\x02\x05\x02bot_data_in\x02client_gui\x02client_options\x02default\x02echo\x02external_discord_hello\x02get_client_options\x02get_inputfuncs\x02get_value\x02hello\x02login\x02monitor\x02monitored\x02list\x02report\x02send\x02unreport\x02repeat\x02supports_set\x02text\x02unmonitor\x02unrepeat\x02webclient_options\x06\xff\xf0'
|
728
|
+
msdp_data = dict() # 保存服务器可用msdp命令列表
|
729
|
+
|
730
|
+
# 服务器子协商长度不确定,中间包含若干个VAR和表示变量值的对应若干个VAL(每个VAL可能以数组、表形式包含若干个)
|
731
|
+
# 变量名和变量值都使用字符串表示
|
732
|
+
# 子协商总长度-5为子协商的状态内容,子协商前3个字节为IAC SB MSDP,后2个字节为IAC SE
|
733
|
+
var_name = bytearray()
|
734
|
+
val_in_array = list()
|
735
|
+
val_in_table = dict()
|
736
|
+
val_in_text = bytearray()
|
737
|
+
|
738
|
+
table_var_name, table_var_value = bytearray(), bytearray()
|
739
|
+
|
740
|
+
state_machine = "wait_var"
|
741
|
+
for idx in range (3, len(data) - 2):
|
742
|
+
byte = data[idx]
|
743
|
+
if state_machine == "wait_var": # 下一个字节为类型 MSDP_VAR
|
744
|
+
if byte == MSDP_VAR:
|
745
|
+
# 接收变量名称,
|
746
|
+
state_machine = "wait_var_name"
|
747
|
+
var_name.clear() # var_name等待接收变量名称
|
748
|
+
else:
|
749
|
+
self.log.warning(f"MSDP状态机错误: 在状态wait_var时收到数据不是MSDP_VAR, 而是{str(byte)}")
|
750
|
+
elif state_machine == "wait_var_name":
|
751
|
+
if byte == MSDP_VAL: # MSDP_VAL表示变量名结束,下一个是value了
|
752
|
+
val_in_array.clear()
|
753
|
+
val_in_table.clear()
|
754
|
+
val_in_text.clear()
|
755
|
+
current_var = var_name.decode(self.encoding)
|
756
|
+
msdp_data[current_var] = None
|
757
|
+
state_machine = "wait_var_value"
|
758
|
+
elif byte in (MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE): # 理论上不会是这个
|
759
|
+
self.log.warning(f"MSDP状态机错误: 在状态wait_var_name时收到数据不是MSDP_VAL, 而是{byte}")
|
760
|
+
# 应该丢弃数据,直接返回 return
|
761
|
+
else:
|
762
|
+
var_name.append(byte)
|
763
|
+
elif state_machine == "wait_var_value":
|
764
|
+
if byte == MSDP_ARRAY_OPEN: # value是一个数组
|
765
|
+
state_machine = "wait_val_in_array"
|
766
|
+
elif byte == MSDP_TABLE_OPEN: # value是一个表,使用字典保存
|
767
|
+
state_machine = "wait_val_in_table"
|
768
|
+
elif byte in (IAC, MSDP_VAR, MSDP_VAL): # 正常数据 value 结束
|
769
|
+
current_val = val_in_text.decode(self.encoding)
|
770
|
+
msdp_data[current_var] = current_val # type: ignore
|
771
|
+
state_machine = "wait_end"
|
772
|
+
self.log.debug(f"收到文本形式的MSDP子协商数据: {current_var} = '{current_val}'") # type: ignore
|
773
|
+
else: # value是正常数据
|
774
|
+
val_in_text.append(byte)
|
775
|
+
elif state_machine == "wait_val_in_array":
|
776
|
+
if byte == MSDP_ARRAY_CLOSE:
|
777
|
+
# 最后一个val 已结束
|
778
|
+
val_in_array.append(val_in_text.decode(self.encoding))
|
779
|
+
val_in_text.clear()
|
780
|
+
msdp_data[current_var] = val_in_array # type: ignore
|
781
|
+
state_machine = "wait_end"
|
782
|
+
self.log.debug(f"收到数组形式的MSDP子协商数据: {current_var} = '{val_in_array}'") # type: ignore
|
783
|
+
elif byte == MSDP_VAL:
|
784
|
+
if len(val_in_text) > 0: # 一个VAL已完成,保存到array,后面还有val
|
785
|
+
val_in_array.append(val_in_text.decode(self.encoding))
|
786
|
+
val_in_text.clear()
|
787
|
+
else:
|
788
|
+
val_in_text.append(byte)
|
789
|
+
elif state_machine == "wait_val_in_table":
|
790
|
+
state_machine_table = "not_initialized"
|
791
|
+
if byte == MSDP_TABLE_CLOSE:
|
792
|
+
# 最后一组已结束
|
793
|
+
val_in_table[table_var_name.decode(self.encoding)] = table_var_value.decode(self.encoding)
|
794
|
+
msdp_data[current_var] = val_in_table # type: ignore
|
795
|
+
state_machine = "wait_end"
|
796
|
+
self.log.debug(f"收到表格形式的MSDP子协商数据: {current_var} = '{val_in_table}'") # type: ignore
|
797
|
+
elif byte == MSDP_VAR:
|
798
|
+
if len(table_var_name) > 0: # 上一个VAL已完成,保存到table,后面继续为VAR
|
799
|
+
val_in_table[table_var_name.decode(self.encoding)] = table_var_value.decode(self.encoding)
|
800
|
+
table_var_name.clear()
|
801
|
+
table_var_value.clear()
|
802
|
+
state_machine_table = "wait_table_var"
|
803
|
+
elif byte == MSDP_VAL:
|
804
|
+
state_machine_table = "wait_table_val"
|
805
|
+
else:
|
806
|
+
if state_machine_table == "wait_table_var":
|
807
|
+
table_var_name.append(byte)
|
808
|
+
elif state_machine_table == "wait_table_val":
|
809
|
+
table_var_value.append(byte)
|
810
|
+
else:
|
811
|
+
self.log.warning(f"MSDP状态机错误: 在状态wait_val_in_table时状态不正确,为{state_machine_table},收到数据是{byte}")
|
812
|
+
|
813
|
+
|
814
|
+
# 更新MSDP_支持的数据到服务器
|
815
|
+
self.msdp.update(msdp_data)
|
816
|
+
self.log.debug(f"MSDP服务器状态子协商处理完毕,共收到{len(msdp_data.keys())}组VAR/VAL数据.")
|
817
|
+
|
818
|
+
def handle_mssp(self, cmd):
|
819
|
+
"""
|
820
|
+
处理MUD服务器状态协议, MSSP的协商 https://tintin.mudhalla.net/protocols/mssp/
|
821
|
+
server - IAC WILL MSSP
|
822
|
+
client - IAC DO MSSP
|
823
|
+
server - IAC SB MSSP MSSP_VAR "PLAYERS" MSSP_VAL "52" MSSP_VAR "UPTIME" MSSP_VAL "1234567890" IAC SE
|
824
|
+
"""
|
825
|
+
nohandle = False
|
826
|
+
if cmd == WILL:
|
827
|
+
# 1. 回复同意MSSP协商
|
828
|
+
if Settings.server["MSSP"]:
|
829
|
+
self.session.write(IAC + DO + MSSP)
|
830
|
+
self._mtts_index = 0
|
831
|
+
self.log.debug(f'发送选项协商, 同意MSSP协商 IAC DO MSSP.等待子协商')
|
832
|
+
else:
|
833
|
+
self.session.write(IAC + DONT + MSSP)
|
834
|
+
self.log.debug(f'发送选项协商, 不同意MSSP协商 IAC DONT MSSP.等待子协商')
|
835
|
+
elif cmd == WONT:
|
836
|
+
nohandle = True
|
837
|
+
elif cmd == DO:
|
838
|
+
nohandle = True
|
839
|
+
elif cmd == DONT:
|
840
|
+
nohandle = True
|
841
|
+
else:
|
842
|
+
nohandle = True
|
843
|
+
|
844
|
+
if nohandle:
|
845
|
+
self.log.warning(f"收到服务器的未处理的MSSP协商: IAC {name_command(cmd)} MSSP")
|
846
|
+
|
847
|
+
def handle_mssp_sb(self, data):
|
848
|
+
"""
|
849
|
+
处理MSSP的子协商
|
850
|
+
server - IAC SB MSSP MSSP_VAR "PLAYERS" MSSP_VAL "52" MSSP_VAR "UPTIME" MSSP_VAL "1234567890" IAC SE
|
851
|
+
# 以下为必须的状态信息
|
852
|
+
NAME Name of the MUD.
|
853
|
+
PLAYERS Current number of logged in players.
|
854
|
+
UPTIME Unix time value of the startup time of the MUD.
|
855
|
+
详细状态信息的定义,参见 https://tintin.mudhalla.net/protocols/mssp/
|
856
|
+
"""
|
857
|
+
def add_svr_status(var: bytearray, val: bytearray):
|
858
|
+
if len(var) > 0:
|
859
|
+
var_str = var.decode(self.encoding)
|
860
|
+
val_str = val.decode(self.encoding)
|
861
|
+
svrStatus[var_str] = val_str
|
862
|
+
self.session.feed_mssp(var_str, val_str)
|
863
|
+
self.log.debug(f"收到服务器状态(来自MSSP子协商): {var_str} = {val_str}")
|
864
|
+
|
865
|
+
svrStatus = dict() # 使用字典保存服务器发来的状态信息
|
866
|
+
|
867
|
+
# 服务器子协商长度不确定,中间包含若干个VAR(VARIABLE,变量名)以及对应数目的VAL(VALUE,变量值)
|
868
|
+
# 变量名和变量值都使用字符串表示
|
869
|
+
# 子协商总长度-5为子协商的状态内容,子协商前3个字节为IAC SB MSSP,后2个字节为IAC SE
|
870
|
+
var, val = bytearray(), bytearray()
|
871
|
+
next_bytes = "type"
|
872
|
+
for idx in range (3, len(data) - 2):
|
873
|
+
byte = data[idx]
|
874
|
+
if (byte == MSSP_VAR):
|
875
|
+
# 表示前一组VAR, VAL已收到
|
876
|
+
add_svr_status(var, val)
|
877
|
+
# 重置var,val变量,以便收取后一个
|
878
|
+
var.clear()
|
879
|
+
val.clear()
|
880
|
+
next_bytes = "var"
|
881
|
+
elif (byte == MSSP_VAL):
|
882
|
+
next_bytes = "val"
|
883
|
+
else:
|
884
|
+
if next_bytes == "var":
|
885
|
+
var.append(byte)
|
886
|
+
elif next_bytes == "val":
|
887
|
+
val.append(byte)
|
888
|
+
else:
|
889
|
+
self.log.warning("在MSSP子协商中收到非正常序列!!")
|
890
|
+
|
891
|
+
# 数据处理完之后,结束的IAC SE之前,最后一组VAR/VAL还需添加进取
|
892
|
+
add_svr_status(var, val)
|
893
|
+
# 更新状态到服务器
|
894
|
+
self.mssp.update(svrStatus)
|
895
|
+
self.log.debug(f"MSSP服务器状态子协商处理完毕,共收到{len(svrStatus.keys())}组VAR/VAL数据.")
|
896
|
+
|
897
|
+
def handle_mccp2(self, cmd):
|
898
|
+
"""
|
899
|
+
处理MUD客户端压缩协议的协商V2 https://mudhalla.net/tintin/protocols/mccp/
|
900
|
+
server - IAC WILL MCCP2
|
901
|
+
client - IAC DONT MCCP2
|
902
|
+
当前不接收客户端压缩(未实现)
|
903
|
+
"""
|
904
|
+
nohandle = False
|
905
|
+
if cmd == WILL:
|
906
|
+
# 1. 回复不同意MCCP2协商
|
907
|
+
if Settings.server["MCCP2"]:
|
908
|
+
self.session.write(IAC + DO + MCCP2)
|
909
|
+
self.log.debug(f'发送选项协商, 同意MCCP V2协商 IAC DO MCCP2')
|
910
|
+
else:
|
911
|
+
self.session.write(IAC + DONT + MCCP2)
|
912
|
+
self.log.debug(f'发送选项协商, 不同意MCCP V2协商 IAC DONT MCCP2')
|
913
|
+
|
914
|
+
elif cmd == WONT:
|
915
|
+
nohandle = True
|
916
|
+
elif cmd == DO:
|
917
|
+
nohandle = True
|
918
|
+
elif cmd == DONT:
|
919
|
+
nohandle = True
|
920
|
+
else:
|
921
|
+
nohandle = True
|
922
|
+
|
923
|
+
if nohandle:
|
924
|
+
self.log.warning(f"收到服务器的未处理的MCCP V2协商: IAC {name_command(cmd)} MCCP2")
|
925
|
+
|
926
|
+
def handle_mccp3(self, cmd):
|
927
|
+
"""
|
928
|
+
处理MUD客户端压缩协议的协商V3 https://mudhalla.net/tintin/protocols/mccp/
|
929
|
+
server - IAC WILL MCCP3
|
930
|
+
client - IAC DONT MCCP3
|
931
|
+
当前不接收客户端压缩(未实现)
|
932
|
+
"""
|
933
|
+
nohandle = False
|
934
|
+
if cmd == WILL:
|
935
|
+
# 1. 回复不同意MCCP2协商
|
936
|
+
if Settings.server["MCCP3"]:
|
937
|
+
self.session.write(IAC + DO + MCCP3)
|
938
|
+
self.log.debug(f'发送选项协商, 同意MCCP V3协商 IAC DO MCCP3')
|
939
|
+
else:
|
940
|
+
self.session.write(IAC + DONT + MCCP3)
|
941
|
+
self.log.debug(f'发送选项协商, 不同意MCCP V3协商 IAC DONT MCCP3')
|
942
|
+
|
943
|
+
elif cmd == WONT:
|
944
|
+
nohandle = True
|
945
|
+
elif cmd == DO:
|
946
|
+
nohandle = True
|
947
|
+
elif cmd == DONT:
|
948
|
+
nohandle = True
|
949
|
+
else:
|
950
|
+
nohandle = True
|
951
|
+
|
952
|
+
if nohandle:
|
953
|
+
self.log.warning(f"收到服务器的未处理的MCCP V3协商: IAC {name_command(cmd)} MCCP3")
|
954
|
+
|
955
|
+
def handle_msp(self, cmd):
|
956
|
+
"""
|
957
|
+
处理MUD音频协议的协商 http://www.zuggsoft.com/zmud/msp.htm
|
958
|
+
server - IAC WILL MSP
|
959
|
+
client - IAC DONT MSP
|
960
|
+
当前不接收MUD音频协议(未实现)
|
961
|
+
"""
|
962
|
+
nohandle = False
|
963
|
+
if cmd == WILL:
|
964
|
+
# 1. 回复不同意MSP协商
|
965
|
+
if Settings.server["MSP"]:
|
966
|
+
self.session.write(IAC + DO + MSP)
|
967
|
+
self.log.debug(f'发送选项协商, 同意MSP协商 IAC DO MSP')
|
968
|
+
else:
|
969
|
+
self.session.write(IAC + DONT + MSP)
|
970
|
+
self.log.debug(f'发送选项协商, 不同意MSP协商 IAC DONT MSP')
|
971
|
+
|
972
|
+
elif cmd == WONT:
|
973
|
+
nohandle = True
|
974
|
+
elif cmd == DO:
|
975
|
+
nohandle = True
|
976
|
+
elif cmd == DONT:
|
977
|
+
nohandle = True
|
978
|
+
else:
|
979
|
+
nohandle = True
|
980
|
+
|
981
|
+
if nohandle:
|
982
|
+
self.log.warning(f"收到服务器的未处理的MSP协商: IAC {name_command(cmd)} MSP")
|
983
|
+
|
984
|
+
def handle_mxp(self, cmd):
|
985
|
+
"""
|
986
|
+
处理MUD扩展协议的协商 http://www.zuggsoft.com/zmud/mxp.htm
|
987
|
+
server - IAC WILL MXP
|
988
|
+
client - IAC DONT MXP
|
989
|
+
当前不接收MUD扩展协议(未实现)
|
990
|
+
"""
|
991
|
+
nohandle = False
|
992
|
+
if cmd == WILL:
|
993
|
+
if Settings.server["MXP"]:
|
994
|
+
self.session.write(IAC + DO + MXP)
|
995
|
+
self.log.debug(f'发送选项协商, 同意MXP协商 IAC DO MXP')
|
996
|
+
else:
|
997
|
+
self.session.write(IAC + DONT + MXP)
|
998
|
+
self.log.debug(f'发送选项协商, 不同意MXP协商 IAC DONT MXP')
|
999
|
+
|
1000
|
+
elif cmd == WONT:
|
1001
|
+
nohandle = True
|
1002
|
+
elif cmd == DO:
|
1003
|
+
nohandle = True
|
1004
|
+
elif cmd == DONT:
|
1005
|
+
nohandle = True
|
1006
|
+
else:
|
1007
|
+
nohandle = True
|
1008
|
+
|
1009
|
+
if nohandle:
|
1010
|
+
self.log.warning(f"收到服务器的未处理的MXP协商: IAC {name_command(cmd)} MXP")
|