1 """ TCP server that communicates with terminals """
3 from getopt import getopt
4 from logging import getLogger
5 from logging.handlers import SysLogHandler
6 from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
8 from struct import pack
12 from .gps303proto import HIBERNATION, LOGIN, parse_message, proto_of_message
14 log = getLogger("gps303/collector")
18 """Zmq message to broadcast what was received from the terminal"""
20 def __init__(self, imei, msg):
22 pack("B", proto_of_message(msg))
23 + ("0000000000000000" if imei is None else imei).encode()
29 """Zmq message received from a third party to send to the terminal"""
31 def __init__(self, *args, **kwargs):
32 if not kwargs and len(args) == 1 and isinstance(args[0], bytes):
33 self.imei = msg[:16].decode()
34 self.payload = msg[16:]
36 self.imei = kwargs["imei"]
37 self.payload = kwargs["payload"]
41 """Connected socket to the terminal plus buffer and metadata"""
43 def __init__(self, sock, addr):
50 log.debug("Closing fd %d (IMEI %s)", self.sock.fileno(), self.imei)
56 """Read from the socket and parse complete messages"""
58 segment = self.sock.recv(4096)
61 "Reading from fd %d (IMEI %s): %s",
67 if not segment: # Terminal has closed connection
69 "EOF reading from fd %d (IMEI %s)",
75 self.buffer += segment
78 framestart = self.buffer.find(b"xx")
79 if framestart == -1: # No frames, return whatever we have
81 if framestart > 0: # Should not happen, report
83 'Undecodable data "%s" from fd %d (IMEI %s)',
84 self.buffer[:framestart].hex(),
88 self.buffer = self.buffer[framestart:]
89 # At this point, buffer starts with a packet
90 frameend = self.buffer.find(b"\r\n", 4)
91 if frameend == -1: # Incomplete frame, return what we have
93 packet = self.buffer[2:frameend]
94 self.buffer = self.buffer[frameend + 2 :]
95 if proto_of_message(packet) == LOGIN.PROTO:
96 self.imei = parse_message(packet).imei
98 "LOGIN from fd %d (IMEI %s)", self.sock.fileno(), self.imei
103 def send(self, buffer):
105 self.sock.send(b"xx" + buffer + b"\r\n")
108 "Sending to fd %d (IMEI %s): %s",
120 def add(self, clntsock, clntaddr):
121 fd = clntsock.fileno()
122 self.by_fd[fd] = Client(clntsock, clntaddr)
126 clnt = self.by_fd[fd]
127 log.info("Stop serving fd %d (IMEI %s)", clnt.sock.fileno(), clnt.imei)
130 del self.by_imei[clnt.imei]
134 clnt = self.by_fd[fd]
140 if proto_of_message(msg) == LOGIN.PROTO: # Could do blindly...
141 self.by_imei[clnt.imei] = clnt
142 result.append((clnt.imei, msg))
145 def response(self, resp):
146 if resp.imei in self.by_imei:
147 self.by_imei[resp.imei].send(resp.payload)
152 zpub = zctx.socket(zmq.PUB)
153 zpub.bind(conf.get("collector", "publishurl"))
154 zsub = zctx.socket(zmq.SUB)
155 zsub.connect(conf.get("collector", "listenurl"))
156 tcpl = socket(AF_INET, SOCK_STREAM)
157 tcpl.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
158 tcpl.bind(("", conf.getint("collector", "port")))
160 tcpfd = tcpl.fileno()
161 poller = zmq.Poller()
162 poller.register(zsub, flags=zmq.POLLIN)
163 poller.register(tcpfd, flags=zmq.POLLIN)
170 events = poller.poll(10)
171 for sk, fl in events:
175 msg = zsub.recv(zmq.NOBLOCK)
176 tosend.append(Resp(msg))
180 clntsock, clntaddr = tcpl.accept()
181 topoll.append((clntsock, clntaddr))
183 for imei, msg in clients.recv(sk):
184 zpub.send(Bcast(imei, msg).as_bytes)
187 or proto_of_message(msg) == HIBERNATION.PROTO
190 "HIBERNATION from fd %d (IMEI %s)", sk, imei
193 elif proto_of_message(msg) == LOGIN.PROTO:
194 clients.response(Resp(imei=imei, payload=LOGIN.response()))
195 # poll queue consumed, make changes now
197 poller.unregister(fd)
200 clients.response(zmsg)
201 for clntsock, clntaddr in topoll:
202 fd = clients.add(clntsock, clntaddr)
204 except KeyboardInterrupt:
208 if __name__.endswith("__main__"):
209 runserver(common.init(log))