-
Notifications
You must be signed in to change notification settings - Fork 0
/
eti_server.py
executable file
·129 lines (104 loc) · 3.37 KB
/
eti_server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#!/usr/bin/env python3
# Dummy ETI demo server
#
#
# Requires at least Python 3.7.
#
# SPDX-FileCopyrightText: © 2021 Georg Sauthoff <mail@gms.tf>
# SPDX-License-Identifier: GPL-3.0-or-later
import argparse
import asyncio
import logging
import random
import struct
import sys
import eti.v9_1 as eti
from dressup import pformat
log = logging.getLogger(__name__)
len_st = struct.Struct('<I')
def set_seq_num(m, seq):
i = iter(m.__annotations__.items())
next(i)
zs = next(i)
header = m.__getattribute__(zs[0])
try:
header.__setattr__('MsgSeqNum', seq)
return seq + 1
except AttributeError:
# broadcast messages are out of sequence ...
return seq
def send_response(wstream, xs, seq, bs):
if not xs:
return seq
T, cond, zs = random.choice(xs)
if T is not None:
m = T()
seq = set_seq_num(m, seq)
n = m.pack_into(bs)
log.info(f'Sending Response: {T}')
wstream.write(memoryview(bs)[:n])
return send_response(wstream, zs, seq, bs)
def send_reject(wstream, text, seq, bs):
m = eti.Reject()
m.NRResponseHeaderME.MsgSeqNum = seq
m.SessionRejectReason = eti.SessionRejectReason.OTHER
m.VarText = text.encode()
m.VarTextLen = len(m.VarText)
m.update_length()
n = m.pack_into(bs)
log.info('Sending Reject')
wstream.write(memoryview(bs)[:n])
return seq + 1
async def serve_session(rstream, wstream):
seq = 1
buf = bytearray(1024)
global logon_count
lc = logon_count
logon_count += 1
try:
while True:
bs = await rstream.readexactly(4)
n = len_st.unpack(bs)[0]
rest = await rstream.readexactly(n - 4)
bs += rest
m = eti.unpack_from(bs)
m.rstrip()
log.info(f'Received: {pformat(m, width=45)}')
if lc == reject_nth_logon:
send_reject(wstream, 'You cannot logon until you have payed your bills!',
seq, buf)
await wstream.drain()
wstream.close()
await wstream.wait_closed()
log.info('rejected session, connection closed')
return
xs = eti.request2response[m.MessageHeaderIn.TemplateID]
seq = send_response(wstream, xs, seq, buf)
await wstream.drain()
except asyncio.IncompleteReadError:
log.info('Got EOF on read end')
async def server(host, port):
s = await asyncio.start_server(serve_session, host, port)
async with s:
await s.serve_forever()
def parse_args():
p = argparse.ArgumentParser(description='ETI example server')
p.add_argument('host', help='address to bind to')
p.add_argument('port', type=int, help='port to listen on')
p.add_argument('--reject-nth-logon', type=int, help='reject the nth logon (count start at 0)')
args = p.parse_args()
return args
def main():
args = parse_args()
global reject_nth_logon
reject_nth_logon = args.reject_nth_logon
global logon_count
logon_count = 0
logging.basicConfig(level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S',
format='%(asctime)s.%(msecs)03d [%(name)s] %(levelname).1s %(message)s')
asyncio.run(server(args.host, args.port))
if __name__ == '__main__':
try:
sys.exit(main())
except KeyboardInterrupt:
log.info('quitting due to SIGINT')