Showing
65 changed files
with
3960 additions
and
0 deletions
.gitignore
0 → 100644
LdapService.py
0 → 100755
1 | +#!/usr/bin/python | |
2 | + | |
3 | +import time | |
4 | +import random | |
5 | +import mmap | |
6 | +import sys, getopt | |
7 | +from struct import pack | |
8 | +from collections import deque | |
9 | + | |
10 | +from os.path import dirname, realpath | |
11 | +from sys import argv, path | |
12 | +path.append(dirname(realpath(__file__)) + '/lib') | |
13 | + | |
14 | +from Server import Server | |
15 | + | |
16 | +from Event.EventHandler import EventHandler | |
17 | +from Event.EventDispatcher import EventDispatcher | |
18 | +from Communication.EndPoint import CommunicationEndPoint | |
19 | + | |
20 | +from Protocol.Http.Http import Http | |
21 | +from Protocol.Websocket.Websocket import Websocket | |
22 | + | |
23 | +from LdapTree import LdapTree | |
24 | + | |
25 | +class Application(EventHandler): | |
26 | + def __init__(self, hosturi, binddn, basedn, password): | |
27 | + super(Application, self).__init__() | |
28 | + | |
29 | + self._event_methods = { | |
30 | + EventDispatcher.eventId('heartbeat') : self._heartbeat, | |
31 | + CommunicationEndPoint.eventId('new_msg') : self._handle_data, | |
32 | + CommunicationEndPoint.eventId('close') : self._handle_close, | |
33 | + CommunicationEndPoint.eventId('upgrade') : self._upgrade | |
34 | + } | |
35 | + | |
36 | + self._websockets = [] | |
37 | + | |
38 | + self._wstest = open('websocket.html', 'r+b') | |
39 | + self._wstestmm = mmap.mmap(self._wstest.fileno(), 0) | |
40 | + | |
41 | + random.seed() | |
42 | + | |
43 | + self.ldaptree = LdapTree(hosturi, binddn, basedn, password, False) | |
44 | + | |
45 | + def __del__(self): | |
46 | + self._wstestmm.close() | |
47 | + self._wstest.close() | |
48 | + | |
49 | + def _upgrade(self, event): | |
50 | + self._websockets.append(event.subject) | |
51 | + # let other also handle the upgrade .. no return True | |
52 | + | |
53 | + def _heartbeat(self, event): | |
54 | + now = pack('!d', time.time()) | |
55 | + for event.subject in self._websockets: | |
56 | + self.issueEvent(event.subject, 'send_msg', now) | |
57 | + | |
58 | + return True | |
59 | + | |
60 | + def _handle_data(self, event): | |
61 | + protocol = event.subject.getProtocol() | |
62 | + | |
63 | + if event.subject.hasProtocol(Http): | |
64 | + if event.data.isRequest(): | |
65 | + if event.data.getUri() == '/': | |
66 | + resp = protocol.createResponse(event.data, 200, 'OK') | |
67 | + resp.setBody(self._wstestmm[0:]) | |
68 | + elif event.data.getUri() == '/ldap': | |
69 | + resp = protocol.createResponse(event.data, 200, 'OK') | |
70 | + resp.setHeader('Content-Type', 'image/svg+xml') | |
71 | + resp.setBody(self.ldaptree.graph()) | |
72 | + else: | |
73 | + resp = protocol.createResponse(event.data, 404, 'Not Found') | |
74 | + resp.setBody('<h1>404 - Not Found</h1>') | |
75 | + | |
76 | + self.issueEvent(event.subject, 'send_msg', resp) | |
77 | + | |
78 | + return True | |
79 | + | |
80 | + def _handle_close(self, event): | |
81 | + if event.subject in self._websockets: | |
82 | + print 'websocket closed...' | |
83 | + self._websockets = [w for w in self._websockets if w!=event.subject] | |
84 | + | |
85 | + return True | |
86 | + | |
87 | +def usage(): | |
88 | + print "Usage: " + sys.argv[0] + " -[HDbhpk] bindip bindport\n" | |
89 | + print "Create a tree representation of all DNs starting with a given base DN." | |
90 | + print "Only simple binds to the directory with DN and password are supported." | |
91 | + print "If no password OPTION is given the password will be asked interactive." | |
92 | + print "If no outfile the given the result will be written to stdout.\n" | |
93 | + print "Required OPTIONS are:\n" | |
94 | + print " {:30s} : {:s}".format('-H, --hosturi=URI', 'The URI to the ldap server to query in the form:') | |
95 | + print " {:30s} {:s}".format('', 'ldap[s]://host.uri[:port]') | |
96 | + print " {:30s} : {:s}".format('-D, --binddn=DN', 'The DN to use for the LDAP bind.') | |
97 | + print " {:30s} : {:s}".format('-p, --password=PASSWORD', 'The password to use for the LDAP bind.') | |
98 | + print " {:30s} : {:s}\n".format('-b, --basedn=DN', 'The DN to start the tree with.') | |
99 | + print "Optional OPTIONS are:\n" | |
100 | + print " {:30s} : {:s}".format('-h, --help', 'Show this help page') | |
101 | + | |
102 | +def main(): | |
103 | + try: | |
104 | + opts, args = getopt.getopt( | |
105 | + sys.argv[1:], | |
106 | + 'hH:D:b:p:', | |
107 | + ['help', 'hosturi=', 'binddn=', 'basedn=', 'password=']) | |
108 | + except getopt.GetoptError as err: | |
109 | + print str(err) | |
110 | + usage() | |
111 | + sys.exit(2) | |
112 | + | |
113 | + hosturi = binddn = basedn = password = None | |
114 | + | |
115 | + for o, a in opts: | |
116 | + if o in ["-h", "--help"]: | |
117 | + usage() | |
118 | + sys.exit(0) | |
119 | + elif o in ["-H", "--hosturi"]: | |
120 | + hosturi = a | |
121 | + elif o in ["-D", "--binddn"]: | |
122 | + binddn = a | |
123 | + elif o in ["-b", "--basedn"]: | |
124 | + basedn = a | |
125 | + elif o in ["-p", "--password"]: | |
126 | + password = a | |
127 | + else: | |
128 | + print "unknown parameter: " + a | |
129 | + usage() | |
130 | + sys.exit(2) | |
131 | + | |
132 | + if not hosturi or not binddn or not basedn or not password: | |
133 | + usage() | |
134 | + sys.exit(2) | |
135 | + | |
136 | + server = Server(Application(hosturi, binddn, basedn, password)) | |
137 | + server.bindTcp(args[0], int(args[1]), Http()) | |
138 | + server.start(1.0) | |
139 | + | |
140 | +if __name__ == '__main__': | |
141 | + main() | |
142 | + | |
143 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
README.md
0 → 100644
ldaptree.py
0 → 100755
1 | +#!/usr/bin/python | |
2 | +from os.path import dirname, realpath | |
3 | +import getopt, sys | |
4 | +sys.path.append(dirname(realpath(__file__)) + '/lib') | |
5 | + | |
6 | +import getpass | |
7 | +from LdapTree import LdapTree | |
8 | + | |
9 | +def usage(): | |
10 | + print "Usage: " + sys.argv[0] + " OPTION...\n" | |
11 | + print "Create a tree representation of all DNs starting with a given base DN." | |
12 | + print "Only simple binds to the directory with DN and password are supported." | |
13 | + print "If no password OPTION is given the password will be asked interactive." | |
14 | + print "If no outfile the given the result will be written to stdout.\n" | |
15 | + print "Required OPTIONS are:\n" | |
16 | + print " {:30s} : {:s}".format('-H, --hosturi=URI', 'The URI to the ldap server to query in the form:') | |
17 | + print " {:30s} {:s}".format('', 'ldap[s]://host.uri[:port]') | |
18 | + print " {:30s} : {:s}".format('-D, --binddn=DN', 'The DN to use for the LDAP bind.') | |
19 | + print " {:30s} : {:s}\n".format('-b, --basedn=DN', 'The DN to start the tree with.') | |
20 | + print "Optional OPTIONS are:\n" | |
21 | + print " {:30s} : {:s}".format('-h, --help', 'Show this help page') | |
22 | + print " {:30s} : {:s}".format('-p, --password=PASSWORD', 'The password to use for the LDAP bind.') | |
23 | + print " {:30s} : {:s}".format('-o, --outfile=FILENAME', 'File to write the result to.') | |
24 | + print " {:30s} : {:s}".format('-k, --kerberos', 'Use gssapi auth.') | |
25 | + | |
26 | +def main(): | |
27 | + try: | |
28 | + opts, args = getopt.getopt( | |
29 | + sys.argv[1:], | |
30 | + 'hkgH:D:b:p:o:', | |
31 | + ['help', 'kerberos', 'hosturi=', 'binddn=', 'basedn=', 'password=', 'outfile=']) | |
32 | + except getopt.GetoptError as err: | |
33 | + print str(err) | |
34 | + usage() | |
35 | + sys.exit(2) | |
36 | + | |
37 | + hosturi = binddn = basedn = password = outfile = None | |
38 | + creategraph = False | |
39 | + use_gssapi = False | |
40 | + | |
41 | + for o, a in opts: | |
42 | + if o in ["-h", "--help"]: | |
43 | + usage() | |
44 | + sys.exit(0) | |
45 | + elif o in ["-H", "--hosturi"]: | |
46 | + hosturi = a | |
47 | + elif o in ["-D", "--binddn"]: | |
48 | + binddn = a | |
49 | + elif o in ["-b", "--basedn"]: | |
50 | + basedn = a | |
51 | + elif o in ["-p", "--password"]: | |
52 | + password = a | |
53 | + elif o in ["-o", "--outfile"]: | |
54 | + outfile = a | |
55 | + elif o == "-g": | |
56 | + creategraph = True | |
57 | + elif o in ["-k", "--kerberos"]: | |
58 | + use_gssapi = True; | |
59 | + else: | |
60 | + print "unknown parameter: " + a | |
61 | + usage() | |
62 | + sys.exit(2) | |
63 | + | |
64 | + if not hosturi or (not binddn and not use_gssapi) or not basedn: | |
65 | + usage() | |
66 | + sys.exit(2) | |
67 | + | |
68 | + if not password and not use_gssapi: | |
69 | + password = getpass.getpass() | |
70 | + | |
71 | + info = LdapTree(hosturi, binddn, basedn, password, use_gssapi) | |
72 | + | |
73 | + if not creategraph: | |
74 | + if outfile: | |
75 | + info.text(outfile) | |
76 | + else: | |
77 | + print info.text() | |
78 | + else: | |
79 | + if outfile: | |
80 | + info.graph(outfile) | |
81 | + else: | |
82 | + print info.graph() | |
83 | + | |
84 | +if __name__ == "__main__": | |
85 | + main() | ... | ... |
lib/Communication/ConnectEntryPoint.py
0 → 100644
1 | +""" | |
2 | +Associate a physical transport layer with a protocol. | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | + | |
7 | +from EndPoint import CommunicationEndPoint | |
8 | + | |
9 | +from Transport import Transport | |
10 | + | |
11 | +class ConnectEntryPoint(CommunicationEndPoint): | |
12 | + _EVENTS = {'acc_ready': 0x01} | |
13 | + | |
14 | + def __init__(self, transport, protocol): | |
15 | + super(ConnectEntryPoint, self).__init__(transport, protocol) | |
16 | + self._accepted = [] | |
17 | + | |
18 | + self._transport.bind() | |
19 | + | |
20 | + def accept(self): | |
21 | + con = self._transport.accept() | |
22 | + | |
23 | + if not con: | |
24 | + return False | |
25 | + | |
26 | + while con: | |
27 | + self._accepted.append(con) | |
28 | + try: | |
29 | + con = self._transport.accept() | |
30 | + except Transport.Error as error: | |
31 | + con = None | |
32 | + | |
33 | + return True | |
34 | + | |
35 | + def pop(self): | |
36 | + try: | |
37 | + return self._accepted.pop() | |
38 | + except IndexError: | |
39 | + return None | |
40 | + | |
41 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Communication/Connection.py
0 → 100644
1 | +""" | |
2 | +Associate a physical transport layer with a protocol. | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | + | |
7 | +from EndPoint import CommunicationEndPoint | |
8 | + | |
9 | +from Transport import Transport | |
10 | + | |
11 | +class Connection(CommunicationEndPoint): | |
12 | + _EVENTS = { 'new_con' : 0x01 } | |
13 | + | |
14 | + def __init__(self, transport, protocol, read_chunk_size=8192): | |
15 | + super(Connection, self).__init__(transport, protocol, read_chunk_size) | |
16 | + self._current_msg = None | |
17 | + self._read_buffer = '' | |
18 | + self._write_buffer = '' | |
19 | + | |
20 | + def hasPendingData(self): | |
21 | + return '' != self._write_buffer | |
22 | + | |
23 | + def __iter__(self): | |
24 | + return self | |
25 | + | |
26 | + def next(self): | |
27 | + """ | |
28 | + iterate through all available data and return all messages that can | |
29 | + be created from it. This is destructive for data. | |
30 | + """ | |
31 | + if not self._current_msg or self._current_msg.ready(): | |
32 | + self._current_msg = self._protocol.createMessage( | |
33 | + self.getTransport().remote) | |
34 | + | |
35 | + end = self._protocol.getParser().parse( | |
36 | + self._current_msg, self._read_buffer) | |
37 | + | |
38 | + if 0 == end: | |
39 | + raise StopIteration | |
40 | + | |
41 | + self._read_buffer = self._read_buffer[end:] | |
42 | + if not self._current_msg.ready(): | |
43 | + raise StopIteration | |
44 | + | |
45 | + return self._current_msg | |
46 | + | |
47 | + def compose(self, message): | |
48 | + try: | |
49 | + self._write_buffer += self._protocol.getComposer().compose(message) | |
50 | + except Exception: | |
51 | + return False | |
52 | + | |
53 | + return True | |
54 | + | |
55 | + def appendReadData(self, data_remote): | |
56 | + self._read_buffer += data_remote[0] | |
57 | + | |
58 | + def nextWriteData(self): | |
59 | + buf = self._write_buffer | |
60 | + self._write_buffer = '' | |
61 | + return (buf, None) | |
62 | + | |
63 | + def appendWriteData(self, data_remote): | |
64 | + self._write_buffer += data_remote[0] | |
65 | + | |
66 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Communication/Connector.py
0 → 100644
1 | +""" | |
2 | +Handles the acc_ready event. Accept as long as possible on subject. | |
3 | +For each successfull accept assign protocol and emit a new_con event | |
4 | +holding the new connection. | |
5 | + | |
6 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
7 | +""" | |
8 | + | |
9 | +from Connection import Connection | |
10 | +from ConnectEntryPoint import ConnectEntryPoint | |
11 | + | |
12 | +from Event.EventHandler import EventHandler | |
13 | +from Transport import Transport | |
14 | + | |
15 | +class Connector(EventHandler): | |
16 | + def __init__(self): | |
17 | + super(Connector, self).__init__() | |
18 | + | |
19 | + self._event_methods = { | |
20 | + ConnectEntryPoint.eventId('acc_ready') : self._accept | |
21 | + } | |
22 | + | |
23 | + def _accept(self, event): | |
24 | + try: | |
25 | + protocol = event.subject.getProtocol() | |
26 | + if event.subject.accept(): | |
27 | + con = event.subject.pop() | |
28 | + while con: | |
29 | + new_con = Connection(con, protocol) | |
30 | + self.issueEvent(new_con, 'new_con') | |
31 | + con = event.subject.pop() | |
32 | + except Transport.Error as error: | |
33 | + self.issueEvent(event.subject, 'close') | |
34 | + | |
35 | + return True | |
36 | + | |
37 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Communication/DatagramEntryPoint.py
0 → 100644
1 | +""" | |
2 | +Associate a physical transport layer with a protocol. | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | +from DatagramService import DatagramService | |
7 | + | |
8 | +class DatagramEntryPoint(DatagramService): | |
9 | + def __init__(self, transport, protocol, read_chunk_size=8192): | |
10 | + super(DatagramEntryPoint, self).__init__( | |
11 | + transport, protocol, read_chunk_size) | |
12 | + self._transport.bind() | |
13 | + | |
14 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Communication/DatagramService.py
0 → 100644
1 | +""" | |
2 | +Associate a physical transport layer with a protocol. | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | +from collections import deque | |
7 | + | |
8 | +from EndPoint import CommunicationEndPoint | |
9 | +from Transport import Transport | |
10 | + | |
11 | +class DatagramService(CommunicationEndPoint): | |
12 | + _EVENTS = {} | |
13 | + | |
14 | + def __init__(self, transport, protocol, read_chunk_size=8192): | |
15 | + super(DatagramService, self).__init__( | |
16 | + transport, protocol, read_chunk_size) | |
17 | + self._read_buffer = deque([]) | |
18 | + self._write_buffer = deque([]) | |
19 | + self._transport.open() | |
20 | + | |
21 | + def hasPendingData(self): | |
22 | + return self._write_buffer | |
23 | + | |
24 | + def __iter__(self): | |
25 | + return self | |
26 | + | |
27 | + def next(self): | |
28 | + """ | |
29 | + here a message has to be fit into a single packet, so no multiple | |
30 | + reads are done.. if a message was not complete after a read the | |
31 | + data will be dropped silently because it can't be guaranteed | |
32 | + that we got the rest somehow in the correct order. | |
33 | + """ | |
34 | + if not self._read_buffer: | |
35 | + raise StopIteration | |
36 | + | |
37 | + msginfo = self._read_buffer.popleft() | |
38 | + message = self._protocol.createMessage(msginfo[1]) | |
39 | + if not message: | |
40 | + raise StopIteration | |
41 | + | |
42 | + end = self._protocol.getParser().parse(message, msginfo[0]) | |
43 | + if 0 == end: raise StopIteration | |
44 | + | |
45 | + return message | |
46 | + | |
47 | + def compose(self, message): | |
48 | + try: | |
49 | + data = self._protocol.getComposer().compose(message) | |
50 | + self.appendWriteData((data, message.getRemote())) | |
51 | + except Exception: | |
52 | + return False | |
53 | + | |
54 | + return True | |
55 | + | |
56 | + def appendReadData(self, data_remote): | |
57 | + self._read_buffer.append(data_remote) | |
58 | + | |
59 | + def nextWriteData(self): | |
60 | + if not self._write_buffer: | |
61 | + return ('', None) | |
62 | + | |
63 | + return self._write_buffer.popleft() | |
64 | + | |
65 | + def appendWriteData(self, data_remote): | |
66 | + self._write_buffer.append(data_remote) | |
67 | + | |
68 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Communication/EndPoint.py
0 → 100644
1 | +""" | |
2 | +Associate a physical transport layer with a protocol. | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | +import errno | |
7 | + | |
8 | +from Event.EventSubject import EventSubject | |
9 | + | |
10 | +class CommunicationEndPoint(EventSubject): | |
11 | + _EVENTS = { | |
12 | + 'read_ready' : 0x01, | |
13 | + 'write_ready' : 0x02, | |
14 | + 'upgrade' : 0x03, | |
15 | + 'new_data' : 0x04, | |
16 | + 'pending_data' : 0x05, | |
17 | + 'end_data' : 0x06, | |
18 | + 'new_msg' : 0x07, | |
19 | + 'send_msg' : 0x08, | |
20 | + 'shutdown_read' : 0x09, | |
21 | + 'shutdown_write' : 0x10, | |
22 | + 'close' : 0x11 | |
23 | + } | |
24 | + | |
25 | + def __init__(self, transport, protocol, read_chunk_size=8192): | |
26 | + super(CommunicationEndPoint, self).__init__() | |
27 | + self.setProtocol(protocol) | |
28 | + self._transport = transport | |
29 | + self._read_chunk_size = read_chunk_size | |
30 | + self._do_close = False | |
31 | + | |
32 | + def setClose(self): | |
33 | + self._do_close = True | |
34 | + | |
35 | + def hasProtocol(self, protocol): | |
36 | + return isinstance(self.getProtocol(), protocol) | |
37 | + | |
38 | + def hasPendingData(self): | |
39 | + return False | |
40 | + | |
41 | + def shouldClose(self): | |
42 | + return self._do_close | |
43 | + | |
44 | + def getTransport(self): | |
45 | + return self._transport | |
46 | + | |
47 | + def setProtocol(self, protocol): | |
48 | + self._protocol = protocol | |
49 | + | |
50 | + def getProtocol(self): | |
51 | + return self._protocol | |
52 | + | |
53 | + def getHandle(self): | |
54 | + return self.getTransport().getHandle() | |
55 | + | |
56 | + def appendReadData(self, data_remote): | |
57 | + pass | |
58 | + | |
59 | + def nextWriteData(self): | |
60 | + return None | |
61 | + | |
62 | + def appendWriteData(self, data_remote): | |
63 | + pass | |
64 | + | |
65 | + def bufferRead(self): | |
66 | + data_remote = self._transport.recv(self._read_chunk_size) | |
67 | + | |
68 | + if not data_remote: | |
69 | + return False | |
70 | + | |
71 | + while data_remote: | |
72 | + self.appendReadData(data_remote) | |
73 | + data_remote = self._transport.recv(self._read_chunk_size) | |
74 | + | |
75 | + return True | |
76 | + | |
77 | + def writeBuffered(self): | |
78 | + data, remote = self.nextWriteData() | |
79 | + send = 0 | |
80 | + | |
81 | + while data: | |
82 | + current_send = self._transport.send(data, remote) | |
83 | + if 0 == current_send: | |
84 | + if data: | |
85 | + self.appendWriteData((data, remote)) | |
86 | + break | |
87 | + | |
88 | + send += current_send | |
89 | + data = data[send:] | |
90 | + if not data: | |
91 | + data, remote = self.nextWriteData() | |
92 | + | |
93 | + if 0 == send: | |
94 | + return False | |
95 | + | |
96 | + return True | |
97 | + | |
98 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Communication/Manager.py
0 → 100644
1 | +""" | |
2 | +Manage Communication Events. | |
3 | + | |
4 | +The events handled here are: | |
5 | + new_con: | |
6 | + | |
7 | +@author Georg Hopp <ghopp@spamtitan.com> | |
8 | +""" | |
9 | +import threading | |
10 | + | |
11 | +from EndPoint import CommunicationEndPoint as EndPoint | |
12 | +from Connection import Connection | |
13 | + | |
14 | +from Event.EventHandler import EventHandler | |
15 | +from Event.EventDispatcher import EventDispatcher as Dispatcher | |
16 | + | |
17 | +class CommunicationManager(EventHandler): | |
18 | + def __init__(self): | |
19 | + super(CommunicationManager, self).__init__() | |
20 | + | |
21 | + self._cons = {} | |
22 | + self._listen = {} | |
23 | + self._wcons = [] | |
24 | + self._rcons = [] | |
25 | + self._ready = ([],[],[]) | |
26 | + self._cons_lock = threading.Lock() | |
27 | + | |
28 | + self._event_methods = { | |
29 | + Dispatcher.eventId('data_wait') : self._select, | |
30 | + Dispatcher.eventId('shutdown') : self._shutdown, | |
31 | + Connection.eventId('new_con') : self._addCon, | |
32 | + Connection.eventId('pending_data') : self._enableWrite, | |
33 | + Connection.eventId('end_data') : self._disableWrite, | |
34 | + EndPoint.eventId('close') : self._close, | |
35 | + EndPoint.eventId('shutdown_read') : self._shutdownRead, | |
36 | + EndPoint.eventId('shutdown_write') : self._shutdownWrite | |
37 | + } | |
38 | + | |
39 | + def addEndPoint(self, end_point): | |
40 | + handle = end_point.getHandle() | |
41 | + self._cons_lock.acquire() | |
42 | + if handle not in self._listen and handle not in self._cons: | |
43 | + if end_point.getTransport().isListen(): | |
44 | + self._listen[handle] = end_point | |
45 | + else: | |
46 | + self._cons[handle] = end_point | |
47 | + self._rcons.append(handle) | |
48 | + self._cons_lock.release() | |
49 | + | |
50 | + def _addCon(self, event): | |
51 | + self.addEndPoint(event.subject) | |
52 | + return True | |
53 | + | |
54 | + def _enableWrite(self, event): | |
55 | + handle = event.subject.getHandle() | |
56 | + fin_state = event.subject.getTransport()._fin_state | |
57 | + | |
58 | + if handle not in self._wcons and 0 == fin_state & 2: | |
59 | + self._wcons.append(handle) | |
60 | + return True | |
61 | + | |
62 | + def _disableWrite(self, event): | |
63 | + handle = event.subject.getHandle() | |
64 | + fin_state = event.subject.getTransport()._fin_state | |
65 | + | |
66 | + if handle in self._wcons: | |
67 | + self._wcons.remove(handle) | |
68 | + | |
69 | + if 1 == fin_state & 1: | |
70 | + self.issueEvent(event.subject, 'shutdown_write') | |
71 | + return True | |
72 | + | |
73 | + def _select(self, event): | |
74 | + import select | |
75 | + | |
76 | + try: | |
77 | + timeout = event.data | |
78 | + if timeout is None: | |
79 | + timeout = event.subject.getDataWaitTime() | |
80 | + | |
81 | + self._cons_lock.acquire() | |
82 | + if timeout < 0.0: | |
83 | + self._ready = select.select(self._rcons, self._wcons, []) | |
84 | + else: | |
85 | + self._ready = select.select(self._rcons, self._wcons, [], timeout) | |
86 | + self._cons_lock.release() | |
87 | + except select.error: | |
88 | + self._cons_lock.release() | |
89 | + pass | |
90 | + | |
91 | + | |
92 | + for handle in self._ready[0]: | |
93 | + if handle in self._listen: | |
94 | + self.issueEvent(self._listen[handle], 'acc_ready') | |
95 | + if handle in self._cons: | |
96 | + self.issueEvent(self._cons[handle], 'read_ready') | |
97 | + | |
98 | + for handle in self._ready[1]: | |
99 | + if handle in self._cons: | |
100 | + self.issueEvent(self._cons[handle], 'write_ready') | |
101 | + | |
102 | + return True | |
103 | + | |
104 | + def _shutdown(self, event): | |
105 | + for handle in self._listen: | |
106 | + self.issueEvent(self._listen[handle], 'close') | |
107 | + | |
108 | + for handle in self._cons: | |
109 | + self.issueEvent(self._cons[handle], 'close') | |
110 | + | |
111 | + self._rcons = self._wcons = [] | |
112 | + | |
113 | + return False | |
114 | + | |
115 | + """ | |
116 | + shutdown and close events...these are handled here because the communication | |
117 | + end points need to be remove for the according lists here. So this is the | |
118 | + highest abstraction level that needs to react on this event. | |
119 | + """ | |
120 | + def _shutdownRead(self, event): | |
121 | + handle = event.subject.getHandle() | |
122 | + if handle in self._rcons: | |
123 | + self._rcons.remove(handle) | |
124 | + | |
125 | + if 3 == event.subject.getTransport().shutdownRead(): | |
126 | + """ | |
127 | + close in any case | |
128 | + """ | |
129 | + self.issueEvent(event.subject, 'close') | |
130 | + elif not event.subject.hasPendingData(): | |
131 | + """ | |
132 | + If there is pending data we will handle a disable_write later on. | |
133 | + There this event will be fired. In that case. | |
134 | + """ | |
135 | + self.issueEvent(event.subject, 'shutdown_write') | |
136 | + else: | |
137 | + """ | |
138 | + Flag this endpoint as subject to close when there is nothing more | |
139 | + to do with it. After this is set all pending IO may finish and then | |
140 | + a close event should be issued | |
141 | + """ | |
142 | + event.subject.setClose() | |
143 | + return False | |
144 | + | |
145 | + def _shutdownWrite(self, event): | |
146 | + handle = event.subject.getHandle() | |
147 | + if handle in self._wcons: | |
148 | + self._wcons.remove(handle) | |
149 | + | |
150 | + if 3 == event.subject.getTransport().shutdownWrite(): | |
151 | + self.issueEvent(event.subject, 'close') | |
152 | + # a read will be done anyway so no special handling here. | |
153 | + # As long as the socket is ready for reading we will read from it. | |
154 | + return False | |
155 | + | |
156 | + def _close(self, event): | |
157 | + self._cons_lock.acquire() | |
158 | + event.subject.getTransport().shutdown() | |
159 | + | |
160 | + handle = event.subject.getHandle() | |
161 | + if handle in self._rcons: | |
162 | + self._rcons.remove(handle) | |
163 | + if handle in self._wcons: | |
164 | + self._wcons.remove(handle) | |
165 | + | |
166 | + if handle in self._listen: | |
167 | + del(self._listen[handle]) | |
168 | + else: | |
169 | + del(self._cons[handle]) | |
170 | + | |
171 | + event.subject.getTransport().close() | |
172 | + self._cons_lock.release() | |
173 | + return False | |
174 | + | |
175 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Communication/ProtocolHandler.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | +""" | |
4 | +from contextlib import contextmanager | |
5 | + | |
6 | +from Connection import Connection | |
7 | +from Event.EventHandler import EventHandler | |
8 | + | |
9 | +class ProtocolHandler(EventHandler): | |
10 | + def __init__(self): | |
11 | + super(ProtocolHandler, self).__init__() | |
12 | + | |
13 | + self._event_methods = { | |
14 | + Connection.eventId('new_data') : self._parse, | |
15 | + Connection.eventId('send_msg') : self._compose, | |
16 | + Connection.eventId('upgrade') : self._upgrade | |
17 | + } | |
18 | + | |
19 | + def _parse(self, event): | |
20 | + for message in event.subject: | |
21 | + try: | |
22 | + """ | |
23 | + only because websockets currently have no message | |
24 | + class which would handle this correctly...so we | |
25 | + just ignore this problem here. | |
26 | + """ | |
27 | + if message.isCloseMessage(): | |
28 | + self.issueEvent(event.subject, 'new_msg', message) | |
29 | + if message.isResponse(): | |
30 | + event.subject.setClose() # setting this results in | |
31 | + # closing the endpoint as | |
32 | + # soon as everything was tried | |
33 | + elif message.isUpgradeMessage(): | |
34 | + if message.isRequest(): | |
35 | + protocol = event.subject.getProtocol() | |
36 | + response = protocol.createUpgradeResponse(message) | |
37 | + self.issueEvent(event.subject, 'send_msg', response) | |
38 | + else: | |
39 | + protocol = event.subject.getProtocol() | |
40 | + self.issueEvent(event.subject, 'upgrade', message) | |
41 | + else: | |
42 | + self.issueEvent(event.subject, 'new_msg', message) | |
43 | + except Exception: | |
44 | + pass | |
45 | + | |
46 | + def _compose(self, event): | |
47 | + endpoint = event.subject | |
48 | + message = event.data | |
49 | + | |
50 | + if endpoint.compose(message): | |
51 | + self.issueEvent(endpoint, 'write_ready') | |
52 | + | |
53 | + try: | |
54 | + """ | |
55 | + only because websockets currently have no message | |
56 | + class which would handle this correctly...so we | |
57 | + just ignore this problem here. | |
58 | + """ | |
59 | + if message.isResponse(): | |
60 | + if message.isCloseMessage(): | |
61 | + endpoint.setClose() | |
62 | + if message.isUpgradeMessage(): | |
63 | + self.issueEvent(endpoint, 'upgrade', message) | |
64 | + except Exception: | |
65 | + pass | |
66 | + | |
67 | + def _upgrade(self, event): | |
68 | + protocol = event.subject.getProtocol() | |
69 | + new_proto = protocol.upgrade(event.data) | |
70 | + event.subject.setProtocol(new_proto) | |
71 | + | |
72 | +# vim: set ft=python et ts=4 sw=4 sts=4: | ... | ... |
lib/Communication/__init__.py
0 → 100644
lib/DnsClient.py
0 → 100644
1 | +""" | |
2 | +get our current external IP via HTTP | |
3 | + | |
4 | +@author Georg Hopp | |
5 | + | |
6 | +@copyright (C) 2014 Copperfasten Technologies | |
7 | +""" | |
8 | +import struct | |
9 | + | |
10 | +from SimpleClient import SimpleClient | |
11 | +from Protocol.Dns.Dns import Dns | |
12 | +from Communication.DatagramService import DatagramService | |
13 | +from Transport.UdpSocket import UdpSocket | |
14 | + | |
15 | +class DnsClient(object): | |
16 | + def __init__(self, host, port): | |
17 | + self._proto = Dns() | |
18 | + self._client = SimpleClient( | |
19 | + DatagramService(UdpSocket(host, port), self._proto) | |
20 | + ) | |
21 | + | |
22 | + def getIp(self, name, timeout=3.0): | |
23 | + request = self._proto.createRequest(self._client.getRemoteAddr()) | |
24 | + request.addQuery(name) | |
25 | + response = self._client.issue(request, timeout) | |
26 | + | |
27 | + if not response or not response._answers: | |
28 | + raise Exception('no valid response') | |
29 | + | |
30 | + return '.'.join('%d'%i | |
31 | + for i in struct.unpack( | |
32 | + '4B', response._answers[0][4])) | ... | ... |
lib/Event/Event.py
0 → 100644
1 | +""" | |
2 | +This holds a generated Event. | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | + | |
7 | +class Event(object): | |
8 | + _SERIAL = 0 | |
9 | + | |
10 | + def __init__(self, name, type, subject): | |
11 | + self.name = name | |
12 | + self.type = type | |
13 | + self.subject = subject | |
14 | + self.data = None | |
15 | + self.sno = Event._SERIAL | |
16 | + Event._SERIAL += 1 | |
17 | + | |
18 | + def setData(self, data): | |
19 | + self.data = data | |
20 | + | |
21 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Event/EventDispatcher.py
0 → 100644
1 | +""" | |
2 | +Dispatch Events to registered handlers. | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | +import sys | |
7 | +import time | |
8 | +import threading | |
9 | +from collections import deque | |
10 | + | |
11 | +from Event import Event | |
12 | +from EventHandler import EventHandler | |
13 | +from EventSubject import EventSubject | |
14 | + | |
15 | +class DefaultHandler(EventHandler): | |
16 | + def handleEvent(self, event): | |
17 | + return True | |
18 | + | |
19 | +SERVER = 0x00 | |
20 | +CLIENT = 0x01 | |
21 | + | |
22 | +class EventDispatcher(EventSubject): | |
23 | + _EVENTS = { | |
24 | + 'heartbeat' : 0x01, | |
25 | + 'user_wait' : 0x02, | |
26 | + 'data_wait' : 0x03, | |
27 | + 'shutdown' : 0x04 | |
28 | + } | |
29 | + | |
30 | + def __init__(self, mode = SERVER, default_handler = DefaultHandler()): | |
31 | + super(EventDispatcher, self).__init__() | |
32 | + | |
33 | + self._events = deque([]) | |
34 | + self._handler = {} | |
35 | + self._default_handler = default_handler | |
36 | + self._running = False | |
37 | + self._heartbeat = 0.0 | |
38 | + self._nextbeat = 0.0 | |
39 | + self._mode = mode | |
40 | + self._queue_lock = threading.Lock() | |
41 | + self._event_wait = threading.Condition() | |
42 | + self._data_wait_id = EventDispatcher.eventId('data_wait') | |
43 | + self._user_wait_id = EventDispatcher.eventId('user_wait') | |
44 | + | |
45 | + def registerHandler(self, handler): | |
46 | + for eid in handler.getHandledIds(): | |
47 | + if eid in self._handler: | |
48 | + self._handler[eid].append(handler) | |
49 | + else: | |
50 | + self._handler[eid] = [handler] | |
51 | + | |
52 | + handler.setDispatcher(self) | |
53 | + | |
54 | + def setHeartbeat(self, heartbeat): | |
55 | + self._heartbeat = heartbeat | |
56 | + if self._heartbeat: | |
57 | + self._nextbeat = time.time() + self._heartbeat | |
58 | + else: | |
59 | + self._nextbeat = 0.0 | |
60 | + | |
61 | + def getBeattime(self): | |
62 | + return self._nextbeat - time.time() | |
63 | + | |
64 | + def getDataWaitTime(self): | |
65 | + if self._mode == SERVER: | |
66 | + return self.getBeattime() | |
67 | + | |
68 | + # here comes a timeout into play.... currently I expect | |
69 | + # the stuff to work... | |
70 | + # TODO add timeout | |
71 | + return 0.0 | |
72 | + | |
73 | + def queueEvent(self, event): | |
74 | + self._queue_lock.acquire() | |
75 | + self._events.append(event) | |
76 | + self._queue_lock.release() | |
77 | + self._event_wait.acquire() | |
78 | + self._event_wait.notify_all() | |
79 | + self._event_wait.release() | |
80 | + | |
81 | + def start(self, name): | |
82 | + self._running = True | |
83 | + | |
84 | + while self._running or self._events: | |
85 | + now = time.time() | |
86 | + if self._nextbeat and self._nextbeat <= now: | |
87 | + self._nextbeat += self._heartbeat | |
88 | + self.queueEvent(self.emit('heartbeat')) | |
89 | + | |
90 | + current = None | |
91 | + if not self._events: | |
92 | + if not name: | |
93 | + if self._mode == CLIENT: | |
94 | + current = self.emit('user_wait') | |
95 | + else: | |
96 | + current = self.emit('data_wait') | |
97 | + else: | |
98 | + self._event_wait.acquire() | |
99 | + self._event_wait.wait() | |
100 | + self._event_wait.release() | |
101 | + | |
102 | + self._queue_lock.acquire() | |
103 | + if (not current) and self._events: | |
104 | + current = self._events.popleft() | |
105 | + self._queue_lock.release() | |
106 | + | |
107 | + if current: | |
108 | + if current.type not in self._handler: | |
109 | + #print '[%s] handle: %s(%d) on %s: %s' % ( | |
110 | + # name, current.name, current.sno, hex(id(current.subject)), 'default') | |
111 | + self._default_handler.handleEvent(current) | |
112 | + else: | |
113 | + for handler in self._handler[current.type]: | |
114 | + #print '[%s] handle: %s(%d) on %s: %s' % ( | |
115 | + # name, current.name, current.sno, hex(id(current.subject)), | |
116 | + # handler.__class__.__name__) | |
117 | + if handler.handleEvent(current): | |
118 | + break | |
119 | + | |
120 | + # if we leave the loop eventually inform all other threads | |
121 | + # so they can quit too. | |
122 | + self._event_wait.acquire() | |
123 | + self._event_wait.notify_all() | |
124 | + self._event_wait.release() | |
125 | + | |
126 | + def stop(self): | |
127 | + self._running = False | |
128 | + | |
129 | + def shutdown(self): | |
130 | + self.queueEvent(self.emit('shutdown')) | |
131 | + self.stop() | |
132 | + | |
133 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Event/EventHandler.py
0 → 100644
1 | +""" | |
2 | +Base event handler | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | +class EventHandler(object): | |
7 | + def __init__(self): | |
8 | + self._dispatcher = [] | |
9 | + self._event_methods = {} | |
10 | + | |
11 | + def setDispatcher(self, dispatcher): | |
12 | + self._dispatcher.append(dispatcher) | |
13 | + | |
14 | + def getHandledIds(self): | |
15 | + return self._event_methods.keys() | |
16 | + | |
17 | + def issueEvent(self, eventSource, ident, data = None): | |
18 | + event = eventSource.emit(ident, data) | |
19 | + #print 'issue %s(%d) on %s: %s' % ( | |
20 | + # ident, event.sno, hex(id(event.subject)), self.__class__.__name__) | |
21 | + for dispatcher in self._dispatcher: | |
22 | + dispatcher.queueEvent(event) | |
23 | + | |
24 | + def handleEvent(self, event): | |
25 | + if event.type not in self._event_methods: | |
26 | + return False | |
27 | + | |
28 | + return self._event_methods[event.type](event) | ... | ... |
lib/Event/EventSubject.py
0 → 100644
1 | +""" | |
2 | +Methodology to craete Events, that can be uniquely identified. | |
3 | + | |
4 | +@Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | +from Event import Event | |
7 | + | |
8 | +class EventSubject(object): | |
9 | + _EVENTS = {} | |
10 | + | |
11 | + @classmethod | |
12 | + def eventId(cls, ident): | |
13 | + """ | |
14 | + Get a unique event identifier based on the class of the event source | |
15 | + and the found map value of ident. If there is no mapping in the | |
16 | + current class its EventSource bases super classes are queried until | |
17 | + an event id can be found... if you derive one event source from | |
18 | + multiple others that provide the same event identifier this means that | |
19 | + you can't predict which one will be created. | |
20 | + I guess that there might be a more pythonic way to do this with | |
21 | + something like a generator expression. | |
22 | + """ | |
23 | + if ident in cls._EVENTS: | |
24 | + return (id(cls) << 8) | cls._EVENTS[ident] | |
25 | + else: | |
26 | + for base in [b for b in cls.__bases__ if issubclass(b, EventSubject)]: | |
27 | + event_id = base.eventId(ident) | |
28 | + if event_id: return event_id | |
29 | + | |
30 | + def emit(self, ident, data = None): | |
31 | + event = Event(ident, type(self).eventId(ident), self) | |
32 | + if data: event.setData(data) | |
33 | + return event | |
34 | + | |
35 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Event/EventThread.py
0 → 100644
1 | +""" | |
2 | +Dispatch Events to registered handlers. | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | +import threading | |
7 | + | |
8 | +class EventThread(threading.Thread): | |
9 | + def __init__(self, dispatcher, name): | |
10 | + super(EventThread, self).__init__() | |
11 | + self._dispatcher = dispatcher | |
12 | + self._name = name | |
13 | + | |
14 | + def run(self): | |
15 | + print 'start thread' | |
16 | + self._dispatcher.start(self._name) | |
17 | + print 'stop thread' | |
18 | + | |
19 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Event/Signal.py
0 → 100644
1 | +import signal | |
2 | + | |
3 | +def initSignals(dispatcher): | |
4 | + def signalHandler(num, frame): | |
5 | + #signal.signal(num, signal.SIG_IGN) | |
6 | + dispatcher.shutdown() | |
7 | + | |
8 | + signal.signal(signal.SIGTERM, signalHandler) | |
9 | + signal.signal(signal.SIGINT, signalHandler) | |
10 | + signal.signal(signal.SIGQUIT, signalHandler) | |
11 | + signal.signal(signal.SIGABRT, signalHandler) | |
12 | + | |
13 | + signal.signal(signal.SIGHUP, signal.SIG_IGN) | |
14 | + signal.signal(signal.SIGALRM, signal.SIG_IGN) | |
15 | + signal.signal(signal.SIGURG, signal.SIG_IGN) | |
16 | + | |
17 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Event/__init__.py
0 → 100644
lib/LdapTree.py
0 → 100644
1 | +import ldap | |
2 | +import pygraphviz as pgv | |
3 | + | |
4 | +class LdapTree(object): | |
5 | + def __init__(self, hosturi, binddn, basedn, password, use_gssapi): | |
6 | + #ldap.set_option(ldap.OPT_DEBUG_LEVEL, 1) | |
7 | + self._ldap = ldap.initialize(hosturi) | |
8 | + """ | |
9 | + Setting ldap.OPT_REFERRALS to 0 was neccessary to query a samba4 | |
10 | + active directory... Currently I don't know if it is a good idea | |
11 | + to keep it generally here. | |
12 | + """ | |
13 | + self._ldap.set_option(ldap.OPT_REFERRALS, 0) | |
14 | + if use_gssapi: | |
15 | + sasl_auth = ldap.sasl.sasl({},'GSSAPI') | |
16 | + self._ldap.sasl_interactive_bind_s("", sasl_auth) | |
17 | + else: | |
18 | + self._ldap.bind(binddn, password, ldap.AUTH_SIMPLE) | |
19 | + self._basedn = basedn | |
20 | + self._ldap_result = [] | |
21 | + | |
22 | + def text(self, filename = None): | |
23 | + """ | |
24 | + Returns a text representing the directory. | |
25 | + If filename is given it will be written in that file. | |
26 | + """ | |
27 | + if filename: | |
28 | + with open(filename, "w") as text_file: | |
29 | + text_file.write(self._text(self._basedn, 0)) | |
30 | + else: | |
31 | + return self._text(self._basedn, 0) | |
32 | + | |
33 | + def graph(self, filename = None): | |
34 | + """ | |
35 | + Returns an svg representing the directory. | |
36 | + If filename is given it will be written in that file. | |
37 | + """ | |
38 | + graph = pgv.AGraph( | |
39 | + directed=True, charset='utf-8', fixedsize='true', ranksep=0.1) | |
40 | + | |
41 | + graph.node_attr.update( | |
42 | + style='rounded,filled', width='0', height='0', shape='box', | |
43 | + fillcolor='#E5E5E5', concentrate='true', fontsize='8.0', | |
44 | + fontname='Arial', margin='0.03') | |
45 | + | |
46 | + graph.edge_attr.update(arrowsize='0.55') | |
47 | + | |
48 | + self._graph(graph, self._basedn) | |
49 | + | |
50 | + graph.layout(prog='dot') | |
51 | + if filename: | |
52 | + graph.draw(path=filename, format='svg') | |
53 | + return None | |
54 | + else: | |
55 | + return graph.draw(format='svg') | |
56 | + | |
57 | + def _text(self, dn, level): | |
58 | + """ | |
59 | + Recursive function that returns a string representation of the | |
60 | + directory where each depth is indicated by a dash. | |
61 | + """ | |
62 | + result = self._ldap.search_s(dn, ldap.SCOPE_ONELEVEL) | |
63 | + indent = '-' * level | |
64 | + text = indent + dn + "\n" | |
65 | + | |
66 | + for entry in (entry[0] for entry in result): | |
67 | + if entry: | |
68 | + text += self._text(entry, level + 1) | |
69 | + | |
70 | + return text | |
71 | + | |
72 | + def _graph(self, graph, dn): | |
73 | + """ | |
74 | + Recursive function creating a graphviz graph from the directory. | |
75 | + """ | |
76 | + result = self._ldap.search_s(dn, ldap.SCOPE_ONELEVEL) | |
77 | + minlen = thislen = 1 | |
78 | + edge_start = dn | |
79 | + | |
80 | + for entry in (entry[0] for entry in result): | |
81 | + if entry: | |
82 | + point = entry + '_p' | |
83 | + sub = graph.add_subgraph() | |
84 | + sub.graph_attr['rank'] = 'same' | |
85 | + sub.add_node( | |
86 | + point, shape='circle', fixedsize='true', width='0.04', | |
87 | + label='', fillcolor='transparent') | |
88 | + sub.add_node(entry) | |
89 | + graph.add_edge(edge_start, point, arrowhead='none', | |
90 | + minlen=str(minlen)) | |
91 | + graph.add_edge(point, entry) | |
92 | + edge_start = point | |
93 | + minlen = self._graph(graph, entry) | |
94 | + thislen += minlen | |
95 | + | |
96 | + return thislen | ... | ... |
lib/MultiEndClient.py
0 → 100644
1 | +import time | |
2 | + | |
3 | +from Event.EventDispatcher import EventDispatcher, CLIENT | |
4 | +from Event.EventHandler import EventHandler | |
5 | +import Event.Signal as Signal | |
6 | + | |
7 | +from Communication.Manager import CommunicationManager | |
8 | +from Communication.EndPoint import CommunicationEndPoint | |
9 | +from Communication.ProtocolHandler import ProtocolHandler | |
10 | +from Transport.IoHandler import IoHandler | |
11 | + | |
12 | +class MultiEndClient(EventHandler): | |
13 | + def __init__(self): | |
14 | + self._event_methods = { | |
15 | + EventDispatcher.eventId('user_wait') : self._userInteraction, | |
16 | + CommunicationEndPoint.eventId('new_msg') : self._handleData | |
17 | + } | |
18 | + | |
19 | + self._con_mngr = CommunicationManager() | |
20 | + | |
21 | + self._dispatcher = EventDispatcher(CLIENT) | |
22 | + self._dispatcher.registerHandler(self._con_mngr) | |
23 | + self._dispatcher.registerHandler(IoHandler()) | |
24 | + self._dispatcher.registerHandler(ProtocolHandler()) | |
25 | + self._dispatcher.registerHandler(self) | |
26 | + Signal.initSignals(self._dispatcher) | |
27 | + | |
28 | + self._end_point = None | |
29 | + self._timeout = None | |
30 | + self._starttime = None | |
31 | + self._request = None | |
32 | + self._response = None | |
33 | + self._sendIssued = False | |
34 | + | |
35 | + | |
36 | + def issue(self, end_point, request, timeout): | |
37 | + self._starttime = time.time() | |
38 | + self._timeout = timeout | |
39 | + self._request = request | |
40 | + self._response = None | |
41 | + self._sendIssued = False | |
42 | + self._end_point = end_point | |
43 | + self._con_mngr.addEndPoint(end_point) | |
44 | + self._dispatcher.start() | |
45 | + | |
46 | + return self._response | |
47 | + | |
48 | + def _userInteraction(self, event): | |
49 | + if self._sendIssued: | |
50 | + now = time.time() | |
51 | + | |
52 | + if self._response or self._timeout <= (now - self._starttime): | |
53 | + event.subject.stop() | |
54 | + else: | |
55 | + self.issueEvent( | |
56 | + event.subject, | |
57 | + 'data_wait', | |
58 | + self._timeout - (now - self._starttime) | |
59 | + ) | |
60 | + else: | |
61 | + self.issueEvent(self._end_point, 'send_msg', self._request) | |
62 | + self._sendIssued = True | |
63 | + return True | |
64 | + | |
65 | + def _handleData(self, event): | |
66 | + if event.data.isResponse(): | |
67 | + self._response = event.data | |
68 | + return True | |
69 | + | |
70 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Protocol/Dns/Composer.py
0 → 100644
1 | +""" | |
2 | + @author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | +import struct | |
6 | + | |
7 | +class Composer(object): | |
8 | + def __init__(self): | |
9 | + self._name_ofs = {} | |
10 | + | |
11 | + def compose(self, message): | |
12 | + self._name_ofs = {} | |
13 | + | |
14 | + header = struct.pack( | |
15 | + '!HHHHHH', | |
16 | + message._msg_id, | |
17 | + message._flags, | |
18 | + len(message._queries), | |
19 | + len(message._answers), | |
20 | + len(message._authoritys), | |
21 | + len(message._additionals) | |
22 | + ) | |
23 | + | |
24 | + queries = answers = authoritys = additionals = '' | |
25 | + ofs = len(header) | |
26 | + if message._queries: | |
27 | + queries = self._composeQueries(message, ofs) | |
28 | + | |
29 | + ofs += len(queries) | |
30 | + if message._answers: | |
31 | + answers = self._composeAnswers(message, ofs) | |
32 | + | |
33 | + ofs += len(answers) | |
34 | + if message._authoritys: | |
35 | + authoritys = self._composeAuthoritys(message, ofs) | |
36 | + | |
37 | + ofs += len(authoritys) | |
38 | + if message._additionals: | |
39 | + additionals = self._composeAdditionals(message, ofs) | |
40 | + | |
41 | + return header + queries + answers + authoritys + additionals | |
42 | + | |
43 | + def _composeQueries(self, message, ofs): | |
44 | + encoded = '' | |
45 | + | |
46 | + for query in message._queries: | |
47 | + name, typ, cls = query | |
48 | + ename = self._encodeName(name, ofs) | |
49 | + | |
50 | + query = struct.pack('!%dsHH'%len(ename), ename, typ, cls) | |
51 | + ofs += len(query) | |
52 | + encoded += query | |
53 | + | |
54 | + return encoded | |
55 | + | |
56 | + def _composeAnswers(self, message, ofs): | |
57 | + encoded = '' | |
58 | + | |
59 | + for answer in message._answers: | |
60 | + record = self._composeResourceRecord(answer, ofs) | |
61 | + ofs += len(record) | |
62 | + encoded += record | |
63 | + | |
64 | + return encoded | |
65 | + | |
66 | + def _composeAuthoritys(self, message, ofs): | |
67 | + encoded = '' | |
68 | + | |
69 | + for authority in message._authoritys: | |
70 | + record = self._composeResourceRecord(authority, ofs) | |
71 | + ofs += len(record) | |
72 | + encoded += record | |
73 | + | |
74 | + return encoded | |
75 | + | |
76 | + def _composeAdditionals(self, message, ofs): | |
77 | + encoded = '' | |
78 | + | |
79 | + for additional in message._additionals: | |
80 | + record = self._composeResourceRecord(additional, ofs) | |
81 | + ofs += len(record) | |
82 | + encoded += record | |
83 | + | |
84 | + return encoded | |
85 | + | |
86 | + def _composeResourceRecord(self, record, ofs): | |
87 | + name, typ, cls, ttl, data = record | |
88 | + ename = self._encodeName(name, ofs) | |
89 | + return struct.pack('!%dsHHLH%ds'%(len(ename), len(data)), | |
90 | + ename, typ, cls, ttl, len(data), data) | |
91 | + | |
92 | + def _encodeName(self, name, ofs): | |
93 | + if name in self._name_ofs: | |
94 | + name = struct.pack('!H', | |
95 | + int('1100000000000000', 2) | self._name_ofs[name]) | |
96 | + else: | |
97 | + self._name_ofs[name] = ofs | |
98 | + name = ''.join([struct.pack('B%ds'%len(p), len(p), p) | |
99 | + for p in name.split('.')]) + '\x00' | |
100 | + | |
101 | + return name | |
102 | + | |
103 | +# vim: set ft=python et ts=4 sw=4 sts=4: | ... | ... |
lib/Protocol/Dns/Dns.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | +from ..Protocol import Protocol | |
6 | + | |
7 | +from Parser import Parser | |
8 | +from Composer import Composer | |
9 | +from Message import Message | |
10 | + | |
11 | +class Dns(Protocol): | |
12 | + def __init__(self): | |
13 | + self.parser = Parser() | |
14 | + self.composer = Composer() | |
15 | + | |
16 | + def getParser(self): | |
17 | + return self.parser | |
18 | + | |
19 | + def getComposer(self): | |
20 | + return self.composer | |
21 | + | |
22 | + def createMessage(self, remote = None): | |
23 | + return Message(remote) | |
24 | + | |
25 | + def createRequest(self, remote = None): | |
26 | + return Message(remote) | |
27 | + | |
28 | + def createResponse(self, req, remote = None): | |
29 | + return Message(remote, req) | |
30 | + | |
31 | + def upgrade(self, message): | |
32 | + ''' | |
33 | + there is no upgrade mechanism for DNS | |
34 | + ''' | |
35 | + pass | |
36 | + | |
37 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Protocol/Dns/Info.txt
0 → 100644
1 | + A simple DNS message and response implementation. | |
2 | + It only supports name queries. | |
3 | + | |
4 | + good informations about dns: | |
5 | + rfc1035 | |
6 | + http://technet.microsoft.com/en-us/library/dd197470(v=ws.10).aspx | |
7 | + serveral more could be found via google. | |
8 | + | |
9 | + What we need: | |
10 | + dns header 6 * 16bit | |
11 | + 16bit ID | |
12 | + 16bit Flags | |
13 | + 1bit request/response indicator (0 = request) | |
14 | + 4bit operation code / what operation to be done (0 = query) | |
15 | + 1bit authoritive answer / obviosly only used for responses | |
16 | + 1bit truncation / indicate that the message was to large for a UDP datagram | |
17 | + 1bit recursion desired / 1 to recurse the request (we normally want this) | |
18 | + 1bit recursion available / obvious | |
19 | + 3bit reserved / set to 000 | |
20 | + 4bit return code / 0 means successfull, currently all other are wrong for us | |
21 | + 16bit Question count | |
22 | + 16bit Answer count | |
23 | + 16bit Authority count | |
24 | + 16bit Additional count | |
25 | + | |
26 | + 1 question resource record (valriable len) our would look like this. | |
27 | + question name: 0x09localhost0x00 | |
28 | + 16bit question type: 0x0001 (for A record question) | |
29 | + 16bit question class: 0x0001 (represents the IN question class) | |
30 | + | |
31 | +TYPE value and meaning | |
32 | +======================================================== | |
33 | +(removed all obsolete and experimental codes) | |
34 | +A 1 a host address | |
35 | +NS 2 an authoritative name server | |
36 | +CNAME 5 the canonical name for an alias | |
37 | +SOA 6 marks the start of a zone of authority | |
38 | +WKS 11 a well known service description | |
39 | +PTR 12 a domain name pointer | |
40 | +HINFO 13 host information | |
41 | +MINFO 14 mailbox or mail list information | |
42 | +MX 15 mail exchange | |
43 | +TXT 16 text strings | |
44 | + | |
45 | +QTYPE values | |
46 | +======================================================== | |
47 | +QTYPE fields appear in the question part of a query. QTYPES are a | |
48 | +superset of TYPEs, hence all TYPEs are valid QTYPEs. In addition, the | |
49 | +following QTYPEs are defined: | |
50 | + | |
51 | +AXFR 252 A request for a transfer of an entire zone | |
52 | +* 255 A request for all records | |
53 | + | |
54 | +CLASS values | |
55 | +======================================================== | |
56 | +IN 1 the Internet | |
57 | +CH 3 the CHAOS class | |
58 | +HS 4 Hesiod [Dyer 87] | |
59 | + | |
60 | + | |
61 | + Our hardcoded request message: | |
62 | + 434301000001000000000000096C6F63616C686F73740000010001 | |
63 | + ^ ^ ^ ^ ^ ^ | |
64 | + ID | | | | | | |
65 | + flags | | | | | |
66 | + one query | | | | |
67 | + query name (localhost) | | | |
68 | + type | | |
69 | + class | |
70 | + | |
71 | + OK, as i analyse the response i realize that my request was repeated back along | |
72 | + with the answer. For now I assume this is the default behaviour of DNS. | |
73 | + At least I can be sure that our DNS will always respond that way. | |
74 | + | |
75 | + The last 4 bytes of the answer record represent the ip address. We can savely | |
76 | + assume this as currently we only query IPv4 A records. With these this should | |
77 | + be always true. | |
78 | + | |
79 | + out complete response was: | |
80 | + 434381800001000100000000096c6f63616c686f73740000010001c00c000100010000000f00040a0100dc | |
81 | + ^ ^ ^ | |
82 | + no error | | | |
83 | + one request | | |
84 | + one response | |
85 | + | |
86 | + We cut of the headers and the request (as it was our own...we do not care about | |
87 | + it), leaving us with: | |
88 | + c00c000100010000000f00040a0100dc | |
89 | + ^ ^ ^ ^ ^ ^ | |
90 | + nref | | | | | | |
91 | + type | | | | | |
92 | + class | | | | |
93 | + TTL | | | |
94 | + resource date len | | |
95 | + here starts our ip | |
96 | + | |
97 | + nref => is a reference of the name queried corresponding the | |
98 | + DNS Packet Compression Schema: | |
99 | + 2bits: compression indicator (11 when compression is active) | |
100 | + rest: offset to name | |
101 | + | |
102 | + In our case this means the offset is 0x0c (12). The offset is the offset from | |
103 | + the start of the message. | ... | ... |
lib/Protocol/Dns/Message.py
0 → 100644
1 | +""" | |
2 | + @author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | +import struct | |
6 | +import random | |
7 | + | |
8 | +from ..Message import Message as BaseMessage | |
9 | + | |
10 | +class Message(BaseMessage): | |
11 | + TYPE_A = 1 | |
12 | + TYPE_NS = 2 | |
13 | + TYPE_CNAME = 5 | |
14 | + TYPE_SOA = 6 | |
15 | + TYPE_WKS = 11 | |
16 | + TYPE_PTR = 12 | |
17 | + TYPE_HINFO = 13 | |
18 | + TYPE_MINFO = 14 | |
19 | + TYPE_MX = 15 | |
20 | + TYPE_TXT = 16 | |
21 | + | |
22 | + CLASS_IN = 1 | |
23 | + CLASS_CH = 3 | |
24 | + CLASS_HS = 4 | |
25 | + | |
26 | + OP_QUERY = 1 | |
27 | + | |
28 | + FLAG_QR = int('1000000000000000', 2) | |
29 | + | |
30 | + def __init__(self, remote, msg=None): | |
31 | + super(Message, self).__init__(remote) | |
32 | + """ | |
33 | + if we want to create a response we initialize the message with the request. | |
34 | + """ | |
35 | + if msg: | |
36 | + if not msg.isRequest(): | |
37 | + raise Exception('initialize with non request') | |
38 | + | |
39 | + self._msg_id = msg._msg_id | |
40 | + self._flags = msg._flags | Message.FLAG_QR | |
41 | + self._queries = list(msg._queries) | |
42 | + else: | |
43 | + random.seed | |
44 | + self._msg_id = random.randint(0, 0xffff) | |
45 | + self._flags = 0 | |
46 | + self._queries = [] | |
47 | + | |
48 | + self._answers = [] | |
49 | + self._authoritys = [] | |
50 | + self._additionals = [] | |
51 | + | |
52 | + def isRequest(self): | |
53 | + return 0 == self._flags & Message.FLAG_QR | |
54 | + | |
55 | + def isResponse(self): | |
56 | + return not self.isRequest() | |
57 | + | |
58 | + def isCloseMessage(self): | |
59 | + return False | |
60 | + | |
61 | + def isUpgradeMessage(self): | |
62 | + return False | |
63 | + | |
64 | + def setRepsonse(self): | |
65 | + self._flags |= Message.FLAG_QR | |
66 | + | |
67 | + def addQuery(self, name, typ=TYPE_A, cls=CLASS_IN): | |
68 | + self._queries.append((name, typ, cls)) | |
69 | + | |
70 | + def addAnswer(self, name, typ, cls, ttl, data): | |
71 | + self._answers.append((name, typ, cls, ttl, data)) | |
72 | + | |
73 | + def getResponseCode(self): | |
74 | + return 0 | |
75 | + | |
76 | + def getResponseMessage(self): | |
77 | + return None | |
78 | + | |
79 | +# vim: set ft=python et ts=4 sw=4 sts=4: | ... | ... |
lib/Protocol/Dns/Parser.py
0 → 100644
1 | +""" | |
2 | + @author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | +import struct | |
6 | + | |
7 | +class Parser(object): | |
8 | + def __init__(self): | |
9 | + self._ofs_names = {} | |
10 | + | |
11 | + def parse(self, message, data): | |
12 | + self._ofs_names = {} | |
13 | + | |
14 | + message._msg_id, \ | |
15 | + message._flags, \ | |
16 | + nqueries, \ | |
17 | + nanswers, \ | |
18 | + nauthorities, \ | |
19 | + nadditionals = struct.unpack('!HHHHHH', data[0:12]) | |
20 | + | |
21 | + ofs = 12 | |
22 | + ofs = self._parseQueries(message, data, ofs, nqueries) | |
23 | + ofs = self._parseAnswers(message, data, ofs, nanswers) | |
24 | + ofs = self._parseAuthorities(message, data, ofs, nauthorities) | |
25 | + self._parseAdditionals(message, data, ofs, nadditionals) | |
26 | + | |
27 | + def _parseQueries(self, message, data, ofs, count): | |
28 | + while 0 < count: | |
29 | + name, ofs = self._decodeName(data, ofs) | |
30 | + typ, cls = struct.unpack('!HH', data[ofs:ofs+4]) | |
31 | + ofs += 4 | |
32 | + count -= 1 | |
33 | + message._queries.append((name, typ, cls)) | |
34 | + | |
35 | + return ofs | |
36 | + | |
37 | + def _parseAnswers(self, message, data, ofs, count): | |
38 | + while 0 < count: | |
39 | + record, ofs = self._parseResourceRecord(message, data, ofs) | |
40 | + count -= 1 | |
41 | + message._answers.append(record) | |
42 | + | |
43 | + return ofs | |
44 | + | |
45 | + def _parseAuthorities(self, message, data, ofs, count): | |
46 | + while 0 < count: | |
47 | + record, ofs = self._parseResourceRecord(message, data, ofs) | |
48 | + count -= 1 | |
49 | + message._authorities.append(record) | |
50 | + | |
51 | + return ofs | |
52 | + | |
53 | + def _parseAdditionals(self, message, data, ofs, count): | |
54 | + while 0 < count: | |
55 | + record, ofs = self._parseResourceRecord(message, data, ofs) | |
56 | + count -= 1 | |
57 | + message._additionals.append(record) | |
58 | + | |
59 | + return ofs | |
60 | + | |
61 | + def _parseResourceRecord(self, message, data, ofs): | |
62 | + name, ofs = self._decodeName(data, ofs) | |
63 | + typ, cls, ttl, rrlen = struct.unpack('!HHLH', data[ofs:ofs+10]) | |
64 | + ofs += 10 | |
65 | + record = data[ofs:ofs+rrlen] | |
66 | + ofs += rrlen | |
67 | + | |
68 | + return ((name, typ, cls, ttl, record), ofs) | |
69 | + | |
70 | + def _decodeName(self, data, ofs): | |
71 | + idx = ofs | |
72 | + compressed = struct.unpack('!H', data[ofs:ofs+2])[0] | |
73 | + | |
74 | + if compressed & int('1100000000000000', 2): | |
75 | + idx = compressed & int('0011111111111111', 2) | |
76 | + name = (self._ofs_names[idx], ofs+2) | |
77 | + else: | |
78 | + length = struct.unpack('B', data[ofs])[0] | |
79 | + parts = [] | |
80 | + while 0 != length: | |
81 | + parts.append(data[ofs+1:ofs+1+length]) | |
82 | + ofs += 1+length | |
83 | + length = struct.unpack('B', data[ofs])[0] | |
84 | + | |
85 | + name = ('.'.join(parts), ofs+1) | |
86 | + self._ofs_names[idx] = name[0] | |
87 | + | |
88 | + return name | |
89 | + | |
90 | +# vim: set ft=python et ts=4 sw=4 sts=4: | ... | ... |
lib/Protocol/Dns/__init__.py
0 → 100644
lib/Protocol/Http/Composer.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | + | |
6 | +import Message | |
7 | + | |
8 | +class Composer(object): | |
9 | + """ | |
10 | + compose to HTTP | |
11 | + ===================================================================== | |
12 | + """ | |
13 | + def composeStartLine(self, message): | |
14 | + """ | |
15 | + compose a HTTP message StartLine... currently this does no check for | |
16 | + the validity of the StartLine. | |
17 | + | |
18 | + returns str The composed HTTP start line (either Request or Status) | |
19 | + | |
20 | + @message: HttpMessage The message that should be composed. | |
21 | + """ | |
22 | + return message.getStartLine() + '\r\n' | |
23 | + | |
24 | + def composeHeaders(self, message): | |
25 | + """ | |
26 | + this creates header lines for each key/value[n] pair. | |
27 | + | |
28 | + returns str All headers composed to an HTTP string. | |
29 | + | |
30 | + @message: HttpMessage The message to compose the header from. | |
31 | + """ | |
32 | + headers = message.getHeaders() | |
33 | + return '\r\n'.join([':'.join(h) for h in headers]) + '\r\n' | |
34 | + | |
35 | + def composeStartLineHeaders(self, message): | |
36 | + """ | |
37 | + Compose the start line and the headers. | |
38 | + | |
39 | + returns str The start line and the headers as HTTP string. | |
40 | + | |
41 | + @message: HttpMessage The message to be composed. | |
42 | + """ | |
43 | + return self.composeStartLine(message) + self.composeHeaders(message) | |
44 | + | |
45 | + def compose(self, message): | |
46 | + """ | |
47 | + Compose the whole message to an HTTP string. | |
48 | + | |
49 | + returns str The whole message as an HTTP string. | |
50 | + | |
51 | + @message: HttpMessage The message to be composed. | |
52 | + """ | |
53 | + return self.composeStartLineHeaders(message) + "\r\n" + message.getBody() | |
54 | + | |
55 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Protocol/Http/Http.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | +from base64 import b64encode, b64decode | |
6 | +from hashlib import sha1 | |
7 | + | |
8 | +from ..Protocol import Protocol | |
9 | + | |
10 | +from Parser import Parser | |
11 | +from Composer import Composer | |
12 | +from Message import Message | |
13 | + | |
14 | +from Protocol.Websocket.Websocket import Websocket | |
15 | + | |
16 | +class Http(Protocol): | |
17 | + def __init__(self): | |
18 | + self.parser = Parser() | |
19 | + self.composer = Composer() | |
20 | + | |
21 | + def getParser(self): | |
22 | + return self.parser | |
23 | + | |
24 | + def getComposer(self): | |
25 | + return self.composer | |
26 | + | |
27 | + def createMessage(self, remote): | |
28 | + return Message(remote) | |
29 | + | |
30 | + def createRequest(self, method=Message.METHOD_GET, uri='/', remote=None): | |
31 | + request = self.createMessage(remote) | |
32 | + request.setRequestLine(method, uri, 'HTTP/1.1') | |
33 | + self._addCommonHeaders(request) | |
34 | + | |
35 | + return request | |
36 | + | |
37 | + def createResponse(self, request, code=200, resp_message='OK', remote=None): | |
38 | + version = request.getHttpVersion() | |
39 | + response = self.createMessage(remote) | |
40 | + response.setStateLine(version, code, resp_message) | |
41 | + | |
42 | + self._addCommonHeaders(response) | |
43 | + response.setHeader('Content-Length', '0') | |
44 | + | |
45 | + con_header = request.getHeader('Connection').lower() | |
46 | + if 'keep-alive' in con_header: | |
47 | + response.setHeader('Connection', 'Keep-Alive') | |
48 | + if 'close' in con_header: | |
49 | + response.setHeader('Connection', 'Close') | |
50 | + | |
51 | + return response | |
52 | + | |
53 | + def createUpgradeRequest(self, host, subprotocol=None): | |
54 | + """ | |
55 | + currently only for websocket updates | |
56 | + """ | |
57 | + request = self.createRequest() | |
58 | + request.setHeaders([ | |
59 | + ('Host', host), | |
60 | + ('Connection', 'Upgrade'), | |
61 | + ('Upgrade', 'websocket'), | |
62 | + ('Sec-WebSocket-Version', '13'), | |
63 | + ('Sec-WebSocket-Key', b64encode(''.join(chr(randint(0,255)) | |
64 | + for _ in range(16))))]) | |
65 | + if subprotocol: | |
66 | + request.setHeader('Sec-WebSocket-Protocol', protocol) | |
67 | + return request | |
68 | + | |
69 | + def createUpgradeResponse(self, request): | |
70 | + """ | |
71 | + currently only for websocket updates | |
72 | + """ | |
73 | + key = request.getHeader('Sec-WebSocket-Key') | |
74 | + if not key: | |
75 | + response = self.createResponse(request, 400, 'Bad Request') | |
76 | + else: | |
77 | + response = self.createResponse(request, 101, 'Switching Protocols') | |
78 | + response.setHeaders([ | |
79 | + ('Connection', 'Upgrade'), | |
80 | + ('Upgrade', 'websocket'), | |
81 | + ('Sec-WebSocket-Accept', b64encode(sha1(key+Websocket.WS_UUID).digest()))]) | |
82 | + | |
83 | + return response | |
84 | + | |
85 | + def upgrade(self, message): | |
86 | + """ | |
87 | + TODO decide by the message which protocol to upgrade to. | |
88 | + """ | |
89 | + return Websocket() | |
90 | + | |
91 | + | |
92 | + def _addCommonHeaders(self, message): | |
93 | + from wsgiref.handlers import format_date_time | |
94 | + from datetime import datetime | |
95 | + from time import mktime | |
96 | + | |
97 | + date = format_date_time(mktime(datetime.now().timetuple())) | |
98 | + message.setHeader('Date', date) | |
99 | + | |
100 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Protocol/Http/Message.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | +from ..Message import Message as BaseMessage | |
6 | + | |
7 | +class Message(BaseMessage): | |
8 | + START_READY = 0x01 | |
9 | + HEADERS_READY = 0x02 | |
10 | + BODY_READY = 0x04 | |
11 | + | |
12 | + METHODS = ('OPTIONS','GET','HEAD','POST','PUT','DELETE','TRACE','CONNECT') | |
13 | + METHOD_OPTIONS = METHODS.index('OPTIONS') | |
14 | + METHOD_GET = METHODS.index('GET') | |
15 | + METHOD_HEAD = METHODS.index('HEAD') | |
16 | + METHOD_POST = METHODS.index('POST') | |
17 | + METHOD_PUT = METHODS.index('PUT') | |
18 | + METHOD_DELETE = METHODS.index('DELETE') | |
19 | + METHOD_TRACE = METHODS.index('TRACE') | |
20 | + METHOD_CONNECT = METHODS.index('CONNECT') | |
21 | + | |
22 | + def __init__(self, remote): | |
23 | + super(Message, self).__init__(remote) | |
24 | + self.state = 0 | |
25 | + | |
26 | + self._chunk_size = 0 | |
27 | + self._chunked = False | |
28 | + | |
29 | + self._headers = {} | |
30 | + self._body = '' | |
31 | + | |
32 | + self._http = None | |
33 | + self._method = None | |
34 | + self._uri = None | |
35 | + self._code = None | |
36 | + self._message = None | |
37 | + | |
38 | + """ | |
39 | + cleaner | |
40 | + ===================================================================== | |
41 | + """ | |
42 | + def resetStartLine(self): | |
43 | + self._http = None | |
44 | + self._uri = None | |
45 | + self._code = None | |
46 | + self._message = None | |
47 | + self.state &= ~Message.START_READY | |
48 | + | |
49 | + def resetHeaders(self): | |
50 | + self._headers = {} | |
51 | + self.state &= ~Message.HEADERS_READY | |
52 | + | |
53 | + def resetBody(self): | |
54 | + self._body = '' | |
55 | + self.state &= ~Message.BODY_READY | |
56 | + self._chunked = False | |
57 | + self._chunk_size = 0 | |
58 | + | |
59 | + def reset(self): | |
60 | + self.resetStartLine() | |
61 | + self.resetHeaders() | |
62 | + self.resetBody() | |
63 | + | |
64 | + def removeHeadersByKey(self, key): | |
65 | + """ | |
66 | + Remove HTTP headers to a given key. This will remove all headers right | |
67 | + now associated to that key. Keys are alwasys stored lower case and | |
68 | + cenverted to title case during composition. | |
69 | + | |
70 | + returns None | |
71 | + | |
72 | + @key: str The header key to remove. | |
73 | + """ | |
74 | + if key.lower() in self._headers: | |
75 | + del(self._headers[key.lower()]) | |
76 | + | |
77 | + def removeHeader(self, header): | |
78 | + """ | |
79 | + Remove a header. | |
80 | + | |
81 | + returns None | |
82 | + | |
83 | + @header: tuple Holds key and value of the header to remove. | |
84 | + """ | |
85 | + key = header[0].lower() | |
86 | + if key in self._headers: | |
87 | + if header[1] in self._headers[key]: | |
88 | + self._headers[key].remove(header[1]) | |
89 | + | |
90 | + """ | |
91 | + setter | |
92 | + ===================================================================== | |
93 | + """ | |
94 | + def setRequestLine(self, method, uri, http): | |
95 | + if self.isResponse(): | |
96 | + raise Exception('try to make a request from a response') | |
97 | + self._method = method | |
98 | + self._uri = uri | |
99 | + self._http = http | |
100 | + | |
101 | + def setStateLine(self, http, code, message): | |
102 | + if self.isRequest(): | |
103 | + raise Exception('try to make a response from a request') | |
104 | + self._http = http | |
105 | + self._code = code | |
106 | + self._message = message | |
107 | + | |
108 | + def setHeader(self, key, value): | |
109 | + """ | |
110 | + Add a header to the message. | |
111 | + Under some circumstances HTTP allows to have multiple headers with | |
112 | + the same key. Thats the reason why the values are handled in a list | |
113 | + here. | |
114 | + | |
115 | + Returns None | |
116 | + | |
117 | + key: The header key (The part before the colon :). | |
118 | + value: The header value (The part behind the colon :). | |
119 | + Value might also be a list a values for this key. | |
120 | + """ | |
121 | + key = key.lower() | |
122 | + if key in self._headers: | |
123 | + self._headers[key] += [v.strip() for v in value.split(',') | |
124 | + if v.strip() not in self._headers[key]] | |
125 | + else: | |
126 | + self._headers[key.lower()] = [v.strip() for v in value.split(',')] | |
127 | + | |
128 | + def replaceHeader(self, key, value): | |
129 | + self._headers[key.lower()] = [v.strip() for v in value.split(',')] | |
130 | + | |
131 | + def setHeaders(self, headers): | |
132 | + """ | |
133 | + This sets a bunch of headers at once. It will add the headers and not | |
134 | + override anything. It is neccessary to clear the headers before calling | |
135 | + this if only the headers given here should be in the message. | |
136 | + | |
137 | + Returns None | |
138 | + | |
139 | + headers: Either a list of tuples [(key,value),...] or | |
140 | + a dictionary {key:value,...}. | |
141 | + In both cases the values should be a list again. | |
142 | + """ | |
143 | + if type(headers) == dict: | |
144 | + headers = headers.items() | |
145 | + | |
146 | + for h in headers: | |
147 | + self.setHeader(h[0], h[1]) | |
148 | + | |
149 | + def setBody(self, data): | |
150 | + """ | |
151 | + Set the body of a message. Currently we do not support sending | |
152 | + chunked message so this is simple... | |
153 | + | |
154 | + Returns None | |
155 | + | |
156 | + data: The data to set in the message body. | |
157 | + """ | |
158 | + self.replaceHeader('Content-Length', '%d'%len(data)) | |
159 | + self._body = data | |
160 | + | |
161 | + """ | |
162 | + getter | |
163 | + ===================================================================== | |
164 | + """ | |
165 | + def getHttpVersion(self): | |
166 | + return self._http | |
167 | + | |
168 | + def getMethod(self): | |
169 | + return self._method | |
170 | + | |
171 | + def getUri(self): | |
172 | + return self._uri | |
173 | + | |
174 | + def getResponseCode(self): | |
175 | + return self._code | |
176 | + | |
177 | + def getResponseMessage(self): | |
178 | + return self._message | |
179 | + | |
180 | + def getStartLine(self): | |
181 | + line = '' | |
182 | + if self.isRequest(): | |
183 | + method = Message.METHODS[self._method] | |
184 | + line = ' '.join((method, self._uri, self._http)) | |
185 | + elif self.isResponse(): | |
186 | + line = ' '.join((self._http, str(self._code), self._message)) | |
187 | + return line | |
188 | + | |
189 | + def getHeaders(self): | |
190 | + return [(k, self.getHeader(k)) for k in self._headers] | |
191 | + | |
192 | + def getHeader(self, key): | |
193 | + """ | |
194 | + Get all values currently associated to this header key. | |
195 | + | |
196 | + returns list All values to the given key. | |
197 | + | |
198 | + @key: str The key to get values for. | |
199 | + """ | |
200 | + key = key.lower() | |
201 | + if key not in self._headers: return '' | |
202 | + return ', '.join(self._headers[key]) | |
203 | + | |
204 | + def getBody(self): | |
205 | + return self._body | |
206 | + | |
207 | + | |
208 | + """ | |
209 | + checker | |
210 | + ===================================================================== | |
211 | + """ | |
212 | + def headerKeyExists(self, key): | |
213 | + return key.lower() in self._headers | |
214 | + | |
215 | + def startlineReady(self): | |
216 | + return Message.START_READY == self.state & Message.START_READY | |
217 | + | |
218 | + def headersReady(self): | |
219 | + return Message.HEADERS_READY == self.state & Message.HEADERS_READY | |
220 | + | |
221 | + def bodyReady(self): | |
222 | + return Message.BODY_READY == self.state & Message.BODY_READY | |
223 | + | |
224 | + def ready(self): | |
225 | + return self.headersReady() and self.bodyReady() | |
226 | + | |
227 | + def isRequest(self): | |
228 | + return self._method is not None | |
229 | + | |
230 | + def isResponse(self): | |
231 | + return self._code is not None | |
232 | + | |
233 | + def isCloseMessage(self): | |
234 | + if self.isRequest(): | |
235 | + # HTTP always expects a response to be send, so a request is | |
236 | + # never the close message. | |
237 | + return False | |
238 | + else: | |
239 | + con_header = self.getHeader('Connection').lower() | |
240 | + if self._http == 'HTTP/1.0': | |
241 | + return 'keep-alive' not in con_header | |
242 | + else: | |
243 | + return 'close' in con_header | |
244 | + | |
245 | + def isUpgradeMessage(self): | |
246 | + con_header = self.getHeader('Connection').lower() | |
247 | + return 'upgrade' in con_header | |
248 | + | |
249 | + def isOptions(self): | |
250 | + return Message.METHOD_OPTIONS == self.getMethod() | |
251 | + | |
252 | + def isGet(self): | |
253 | + return Message.METHOD_GET == self.getMethod() | |
254 | + | |
255 | + def isHead(self): | |
256 | + return Message.METHOD_HEAD == self.getMethod() | |
257 | + | |
258 | + def isPost(self): | |
259 | + return Message.METHOD_POST == self.getMethod() | |
260 | + | |
261 | + def isPut(self): | |
262 | + return Message.METHOD_PUT == self.getMethod() | |
263 | + | |
264 | + def isDelete(self): | |
265 | + return Message.METHOD_DELETE == self.getMethod() | |
266 | + | |
267 | + def isTrace(self): | |
268 | + return Message.METHOD_TRACE == self.getMethod() | |
269 | + | |
270 | + def isConnect(self): | |
271 | + return Message.METHOD_CONNECT == self.getMethod() | |
272 | + | |
273 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Protocol/Http/Parser.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | + | |
6 | +import re | |
7 | +from Message import Message | |
8 | + | |
9 | +class Parser(object): | |
10 | + def __init__(self): | |
11 | + self._header_exp = re.compile(r"([^:]+):(.+)\r\n") | |
12 | + self._chunk_exp = re.compile(r"([\da-f]+).*\r\n") | |
13 | + self._req_exp = re.compile( | |
14 | + r".*(%s) +([^ ]+) +(HTTP/\d\.\d)\r\n"%'|'.join(Message.METHODS)) | |
15 | + self._state_exp = re.compile(r".*(HTTP/\d\.\d) *(\d{3}) *(.*)\r\n") | |
16 | + | |
17 | + def parse(self, message, data): | |
18 | + """ | |
19 | + Parse data into this message. | |
20 | + | |
21 | + Returns 0 when the Message is already complete or the amount of the | |
22 | + successfully parsed data. | |
23 | + | |
24 | + @message: An HttpMessage instance where the data is parsed into. | |
25 | + @data: The data to be parsed. | |
26 | + """ | |
27 | + end = 0 | |
28 | + | |
29 | + if 0 == message.state: | |
30 | + if message.isRequest() or message.isResponse(): | |
31 | + message.reset() | |
32 | + end += self.parseStartLine(message, data) | |
33 | + | |
34 | + if message.startlineReady() and not message.headersReady(): | |
35 | + end += self.parseHeaders(message, data[end:]) | |
36 | + | |
37 | + if message.headersReady() and not message.bodyReady(): | |
38 | + end += self.parseBody(message, data[end:]) | |
39 | + | |
40 | + return end | |
41 | + | |
42 | + def parseStartLine(self, message, data): | |
43 | + """ | |
44 | + Parse data into the HTTP message startline, either a Request- or a | |
45 | + Statusline. This will set the message start_line if the given data | |
46 | + matches the start_exp expression. In that case it will also set | |
47 | + the start_ready flag. | |
48 | + | |
49 | + Returns the position of the data that is not parsed. | |
50 | + | |
51 | + @message: An HttpMessage instance where the data is parsed into. | |
52 | + @data: The data to be parsed. | |
53 | + """ | |
54 | + end = 0 | |
55 | + | |
56 | + match = self._parseRequest(message, data) | |
57 | + if match: end = match.end() | |
58 | + | |
59 | + match = self._parseResponse(message, data) | |
60 | + if match: end = match.end() | |
61 | + | |
62 | + if 0 != end: | |
63 | + message.state |= Message.START_READY | |
64 | + else: | |
65 | + end = self._checkInvalid(message, data[end:]) | |
66 | + | |
67 | + return end | |
68 | + | |
69 | + def parseHeaders(self, message, data): | |
70 | + """ | |
71 | + Parse data into the headers of a message. | |
72 | + | |
73 | + Returns the position of the data that is not parsed. | |
74 | + | |
75 | + @message: An HttpMessage instance where the data is parsed into. | |
76 | + @data: The data to be parsed. | |
77 | + """ | |
78 | + end = 0 | |
79 | + | |
80 | + match = self._header_exp.match(data[end:]) | |
81 | + while match and "\r\n" != data[end:end+2]: | |
82 | + message.setHeader(match.group(1).strip(), match.group(2).strip()) | |
83 | + end += match.end() | |
84 | + match = self._header_exp.match(data[end:]) | |
85 | + | |
86 | + if "\r\n" == data[end:end+2]: | |
87 | + # a single \r\n at the beginning indicates end of headers. | |
88 | + if message.headerKeyExists('Content-Length'): | |
89 | + message._chunk_size = int(message.getHeader('Content-Length')) | |
90 | + elif message.headerKeyExists('Transfer-Encoding') and \ | |
91 | + 'chunked' in message.getHeader('Transfer-Encoding'): | |
92 | + message._chunked = True | |
93 | + else: | |
94 | + message.state |= Message.BODY_READY | |
95 | + | |
96 | + message.state |= Message.HEADERS_READY | |
97 | + end += 2 | |
98 | + else: | |
99 | + end += self._checkInvalid(message, data[end:]) | |
100 | + | |
101 | + return end | |
102 | + | |
103 | + def parseBody(self, message, data): | |
104 | + """ | |
105 | + Parse data into the body of a message. This is also capable of | |
106 | + handling chunked bodies as defined for HTTP/1.1. | |
107 | + | |
108 | + Returns the position of the data that is not parsed. | |
109 | + | |
110 | + @message: An HttpMessage instance where the data is parsed into. | |
111 | + @data: The data to be parsed. | |
112 | + """ | |
113 | + readlen = 0 | |
114 | + | |
115 | + if message._chunked and 0 == message._chunk_size: | |
116 | + match = self._chunk_exp.match(data) | |
117 | + | |
118 | + if match is None: | |
119 | + return 0 | |
120 | + | |
121 | + message._chunk_size = int(match.group(1), 16) | |
122 | + readlen += match.end() | |
123 | + data = data[match.end():] | |
124 | + | |
125 | + if 0 == self._chunk_size: | |
126 | + message.state |= Message.BODY_READY | |
127 | + return readlen + 2 | |
128 | + | |
129 | + available_data = len(data[0:message._chunk_size]) | |
130 | + message._chunk_size -= available_data | |
131 | + readlen += available_data | |
132 | + message._body += data[0:available_data] | |
133 | + | |
134 | + if 0 == message._chunk_size: | |
135 | + if not message._chunked: | |
136 | + message.state |= Message.BODY_READY | |
137 | + return readlen | |
138 | + else: | |
139 | + readlen += 2 | |
140 | + | |
141 | + return readlen | |
142 | + | |
143 | + def _parseRequest(self, message, data): | |
144 | + match = self._req_exp.search(data) | |
145 | + if match: | |
146 | + message._method = Message.METHODS.index(match.group(1)) | |
147 | + message._uri = match.group(2) | |
148 | + message._http = match.group(3) | |
149 | + return match | |
150 | + | |
151 | + def _parseResponse(self, message, data): | |
152 | + match = self._state_exp.search(data) | |
153 | + if match: | |
154 | + message._http = match.group(1) | |
155 | + message._code = int(match.group(2)) | |
156 | + message._message = match.group(3) | |
157 | + return match | |
158 | + | |
159 | + def _checkInvalid(self, message, data): | |
160 | + end = 0 | |
161 | + nl = data.find("\r\n") | |
162 | + if -1 != nl: | |
163 | + # We received an invalid message...ignore it and start again | |
164 | + # TODO This should be logged. | |
165 | + message.reset() | |
166 | + end = nl + 2 | |
167 | + return end | |
168 | + | |
169 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Protocol/Http/__init__.py
0 → 100644
lib/Protocol/Message.py
0 → 100644
lib/Protocol/Protocol.py
0 → 100644
lib/Protocol/Websocket/Composer.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | + | |
6 | +import struct | |
7 | + | |
8 | +class Composer(object): | |
9 | + def compose(self, message): | |
10 | + """ | |
11 | + for now I only encode messages of len less than 126 and | |
12 | + final...this is just for testing. | |
13 | + """ | |
14 | + msglen = len(message) | |
15 | + if msglen > 125: | |
16 | + raise Exception('messages bigger than 125 bytes not supported') | |
17 | + | |
18 | + frame = struct.pack('BB%ds'%msglen, int('10000010', 2), msglen, message) | |
19 | + | |
20 | + return frame | |
21 | + | |
22 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Protocol/Websocket/Message.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | +from ..Message import Message as BaseMessage | |
6 | + | |
7 | +class Message(BaseMessage): | |
8 | + def __init__(self, remote): | |
9 | + super(Message, self).__init__(remote) | |
10 | + _data = None | |
11 | + | |
12 | + def getData(self): | |
13 | + return self._data | |
14 | + | |
15 | + def setData(self, data): | |
16 | + self._data = data | |
17 | + | |
18 | + def ready(self): | |
19 | + return True | |
20 | + | |
21 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Protocol/Websocket/Parser.py
0 → 100644
lib/Protocol/Websocket/Websocket.py
0 → 100644
1 | +""" | |
2 | +Websocket protocol | |
3 | + | |
4 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
5 | +""" | |
6 | +from random import seed, randint | |
7 | +from base64 import b64encode, b64decode | |
8 | +from hashlib import sha1 | |
9 | + | |
10 | +from ..Protocol import Protocol | |
11 | + | |
12 | +from Parser import Parser | |
13 | +from Composer import Composer | |
14 | +from Message import Message | |
15 | + | |
16 | +class Websocket(Protocol): | |
17 | + WS_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' | |
18 | + | |
19 | + @staticmethod | |
20 | + def isHandshake(request): | |
21 | + con = request.getHeader('Connection').lower() | |
22 | + up = request.getHeader('Upgrade').lower() | |
23 | + | |
24 | + return 'upgrade' in con and 'websocket' in up | |
25 | + | |
26 | + def __init__(self): | |
27 | + self._parser = Parser() | |
28 | + self._composer = Composer() | |
29 | + | |
30 | + def getParser(self): | |
31 | + return self._parser | |
32 | + | |
33 | + def getComposer(self): | |
34 | + return self._composer | |
35 | + | |
36 | + def createMessage(self, remote=None): | |
37 | + return Message(remote) | |
38 | + | |
39 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Protocol/Websocket/__init__.py
0 → 100644
lib/Protocol/__init__.py
0 → 100644
lib/Server.py
0 → 100644
1 | +import time | |
2 | + | |
3 | +from Event.EventDispatcher import EventDispatcher | |
4 | +from Event.EventHandler import EventHandler | |
5 | +import Event.Signal as Signal | |
6 | + | |
7 | +from Communication.Manager import CommunicationManager | |
8 | +from Communication.EndPoint import CommunicationEndPoint | |
9 | +from Communication.ConnectEntryPoint import ConnectEntryPoint | |
10 | +from Communication.DatagramEntryPoint import DatagramEntryPoint | |
11 | +from Communication.ProtocolHandler import ProtocolHandler | |
12 | +from Communication.Connector import Connector | |
13 | + | |
14 | +from Transport.IoHandler import IoHandler | |
15 | +from Transport.TcpSocket import TcpSocket | |
16 | +from Transport.UdpSocket import UdpSocket | |
17 | + | |
18 | +class Server(object): | |
19 | + def __init__(self, application): | |
20 | + self._con_mngr = CommunicationManager() | |
21 | + self._dispatcher = EventDispatcher() | |
22 | + | |
23 | + self._dispatcher.registerHandler(self._con_mngr) | |
24 | + self._dispatcher.registerHandler(Connector()) | |
25 | + self._dispatcher.registerHandler(IoHandler()) | |
26 | + self._dispatcher.registerHandler(ProtocolHandler()) | |
27 | + self._dispatcher.registerHandler(application) | |
28 | + Signal.initSignals(self._dispatcher) | |
29 | + | |
30 | + def addEndpoint(self, endpoint): | |
31 | + self._con_mngr.addEndPoint(endpoint) | |
32 | + | |
33 | + def bindTcp(self, ip, port, protocol): | |
34 | + self.addEndpoint(ConnectEntryPoint(TcpSocket(ip, port), protocol)) | |
35 | + | |
36 | + def bindUdp(self, ip, port, protocol): | |
37 | + self.addEndpoint(DatagramEntryPoint(UdpSocket(ip, port), protocol)) | |
38 | + | |
39 | + def addHandler(self, handler): | |
40 | + self._dispatcher.registerHandler(handler) | |
41 | + | |
42 | + def start(self, heartbeat = None): | |
43 | + if heartbeat: | |
44 | + self._dispatcher.setHeartbeat(heartbeat) | |
45 | + | |
46 | + self._dispatcher.start(None) | |
47 | + | |
48 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/SimpleClient.py
0 → 100644
1 | +import time | |
2 | + | |
3 | +from Event.EventDispatcher import EventDispatcher, CLIENT | |
4 | +from Event.EventHandler import EventHandler | |
5 | +import Event.Signal as Signal | |
6 | + | |
7 | +from Communication.Manager import CommunicationManager | |
8 | +from Communication.EndPoint import CommunicationEndPoint | |
9 | +from Communication.ProtocolHandler import ProtocolHandler | |
10 | +from Transport.IoHandler import IoHandler | |
11 | +from Transport.TcpSocket import TcpSocket | |
12 | + | |
13 | +class SimpleClient(EventHandler): | |
14 | + def __init__(self, end_point): | |
15 | + super(SimpleClient, self).__init__() | |
16 | + | |
17 | + self._event_methods = { | |
18 | + EventDispatcher.eventId('user_wait') : self._userInteraction, | |
19 | + CommunicationEndPoint.eventId('new_msg') : self._handleData | |
20 | + } | |
21 | + | |
22 | + self._end_point = end_point | |
23 | + if isinstance(self._end_point.getTransport(), TcpSocket): | |
24 | + self._end_point.getTransport().connect() | |
25 | + | |
26 | + self._remote_addr = end_point.getTransport().getAddr() | |
27 | + | |
28 | + con_mngr = CommunicationManager() | |
29 | + con_mngr.addEndPoint(self._end_point) | |
30 | + | |
31 | + dispatcher = EventDispatcher(CLIENT) | |
32 | + dispatcher.registerHandler(con_mngr) | |
33 | + dispatcher.registerHandler(IoHandler()) | |
34 | + dispatcher.registerHandler(ProtocolHandler()) | |
35 | + dispatcher.registerHandler(self) | |
36 | + Signal.initSignals(dispatcher) | |
37 | + | |
38 | + self._timeout = None | |
39 | + self._starttime = None | |
40 | + self._request = None | |
41 | + self._response = None | |
42 | + self._sendIssued = False | |
43 | + | |
44 | + | |
45 | + def issue(self, request, timeout): | |
46 | + self._starttime = time.time() | |
47 | + self._timeout = timeout | |
48 | + self._request = request | |
49 | + self._response = None | |
50 | + self._sendIssued = False | |
51 | + self._dispatcher[0].start(None) | |
52 | + | |
53 | + return self._response | |
54 | + | |
55 | + def getRemoteAddr(self): | |
56 | + return self._remote_addr | |
57 | + | |
58 | + def getProtocol(self): | |
59 | + return self._end_point.getProtocol() | |
60 | + | |
61 | + def _userInteraction(self, event): | |
62 | + if self._sendIssued: | |
63 | + now = time.time() | |
64 | + | |
65 | + if self._response or self._timeout <= (now - self._starttime): | |
66 | + event.subject.stop() | |
67 | + else: | |
68 | + self.issueEvent( | |
69 | + event.subject, | |
70 | + 'data_wait', | |
71 | + self._timeout - (now - self._starttime) | |
72 | + ) | |
73 | + else: | |
74 | + self.issueEvent(self._end_point, 'send_msg', self._request) | |
75 | + self._sendIssued = True | |
76 | + return True | |
77 | + | |
78 | + def _handleData(self, event): | |
79 | + if event.data.isResponse(): | |
80 | + self._response = event.data | |
81 | + return True | |
82 | + | |
83 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/ThreadedServer.py
0 → 100644
1 | +import time | |
2 | + | |
3 | +from Server import Server | |
4 | +from Event.EventThread import EventThread | |
5 | + | |
6 | +class ThreadedServer(Server): | |
7 | + def __init__(self, application, threads = 1): | |
8 | + super(ThreadedServer, self).__init__(application) | |
9 | + self._threads = [] | |
10 | + | |
11 | + for num in range(1, threads): | |
12 | + self._threads.append( | |
13 | + EventThread(self._dispatcher, 'th' + str(num))) | |
14 | + | |
15 | + def start(self, heartbeat = None): | |
16 | + for thread in self._threads: | |
17 | + thread.start() | |
18 | + | |
19 | + super(ThreadedServer, self).start(heartbeat) | |
20 | + | |
21 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
lib/Transport/IoHandler.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | +from contextlib import contextmanager | |
6 | + | |
7 | +import Transport | |
8 | + | |
9 | +from Event.EventHandler import EventHandler | |
10 | +from Communication.EndPoint import CommunicationEndPoint | |
11 | + | |
12 | +class IoHandler(EventHandler): | |
13 | + def __init__(self): | |
14 | + super(IoHandler, self).__init__() | |
15 | + | |
16 | + self._event_methods = { | |
17 | + CommunicationEndPoint.eventId('read_ready') : self._read, | |
18 | + CommunicationEndPoint.eventId('write_ready') : self._write | |
19 | + } | |
20 | + | |
21 | + @contextmanager | |
22 | + def _doio(self, subject, shutdown_type): | |
23 | + try: | |
24 | + yield | |
25 | + except Transport.Error as error: | |
26 | + if Transport.Error.ERR_REMOTE_CLOSE == error.errno: | |
27 | + self.issueEvent(subject, shutdown_type) | |
28 | + else: | |
29 | + self.issueEvent(subject, 'close') | |
30 | + | |
31 | + def _read(self, event): | |
32 | + with self._doio(event.subject, 'shutdown_read'): | |
33 | + if event.subject.bufferRead(): | |
34 | + self.issueEvent(event.subject, 'new_data') | |
35 | + | |
36 | + def _write(self, event): | |
37 | + with self._doio(event.subject, 'shutdown_write'): | |
38 | + if event.subject.writeBuffered(): | |
39 | + if event.subject.hasPendingData(): | |
40 | + self.issueEvent(event.subject, 'pending_data') | |
41 | + else: | |
42 | + self.issueEvent(event.subject, 'end_data') | |
43 | + if event.subject.shouldClose(): | |
44 | + self.issueEvent(event.subject, 'close') | |
45 | + | |
46 | +# vim: set ft=python et ts=4 sw=4 sts=4: | ... | ... |
lib/Transport/Socket.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | + | |
6 | +import socket | |
7 | +import errno | |
8 | +import sys | |
9 | + | |
10 | +import Transport | |
11 | + | |
12 | +from contextlib import contextmanager | |
13 | + | |
14 | +CONTINUE = (errno.EAGAIN, errno.EWOULDBLOCK) | |
15 | +if 'win32' == sys.platform: | |
16 | + CONTINUE = CONTINUE + (errno.WSAEWOULDBLOCK) | |
17 | + | |
18 | +class Socket(object): | |
19 | + def __init__(self, host, port, socket_type, con_ttl=30): | |
20 | + self.socket = None | |
21 | + self._host = host | |
22 | + self._port = port | |
23 | + self._con_ttl = con_ttl | |
24 | + self._socket_type = socket_type | |
25 | + self._listen = False | |
26 | + self._fin_state = 0 | |
27 | + | |
28 | + def isListen(self): | |
29 | + return self._listen | |
30 | + | |
31 | + def isFin(self): | |
32 | + # TODO important, create something sane here. | |
33 | + return 0 != self._fin_state | |
34 | + | |
35 | + def readReady(self): | |
36 | + return 0 == self._fin_state & 1 | |
37 | + | |
38 | + def writeReady(self): | |
39 | + return 0 == self._fin_state & 2 | |
40 | + | |
41 | + def getHandle(self): | |
42 | + return self.socket | |
43 | + | |
44 | + def getHost(self): | |
45 | + return self._host | |
46 | + | |
47 | + def getPort(self): | |
48 | + return self._port | |
49 | + | |
50 | + def getAddr(self): | |
51 | + return (self._host, self._port) | |
52 | + | |
53 | + @contextmanager | |
54 | + def _addrinfo(self, flags=0): | |
55 | + for res in socket.getaddrinfo( | |
56 | + self._host, self._port, | |
57 | + socket.AF_UNSPEC, self._socket_type, | |
58 | + 0, flags): | |
59 | + af, socktype, proto, canonname, self._sa = res | |
60 | + | |
61 | + try: | |
62 | + if not self.socket: | |
63 | + self.socket = socket.socket(af, socktype, proto) | |
64 | + except socket.error as error: | |
65 | + current_exception = error | |
66 | + self.socket = None | |
67 | + continue | |
68 | + | |
69 | + try: | |
70 | + yield socktype | |
71 | + except socket.error as error: | |
72 | + current_exception = error | |
73 | + self.socket.close() | |
74 | + self.socket = None | |
75 | + continue | |
76 | + break | |
77 | + | |
78 | + if not self.socket: | |
79 | + raise Transport.Error(Transport.Error.ERR_FAILED) | |
80 | + | |
81 | + def bind(self): | |
82 | + with self._addrinfo(socket.AI_PASSIVE): | |
83 | + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
84 | + self.socket.bind(self._sa) | |
85 | + self.socket.setblocking(0) | |
86 | + | |
87 | + def open(self): | |
88 | + with self._addrinfo(socket.AI_PASSIVE): | |
89 | + self.socket.setblocking(0) | |
90 | + | |
91 | + def shutdownRead(self): | |
92 | + try: | |
93 | + if 0 == self._fin_state & 1: | |
94 | + self.socket.shutdown(socket.SHUT_RD) | |
95 | + self._fin_state |= 1 | |
96 | + except socket.error as error: | |
97 | + self._fin_state |= 3 | |
98 | + return self._fin_state | |
99 | + | |
100 | + def shutdownWrite(self): | |
101 | + try: | |
102 | + if 0 == self._fin_state & 2: | |
103 | + self.socket.shutdown(socket.SHUT_WR) | |
104 | + self._fin_state |= 2 | |
105 | + except socket.error as error: | |
106 | + self._fin_state |= 3 | |
107 | + return self._fin_state | |
108 | + | |
109 | + def shutdown(self): | |
110 | + try: | |
111 | + if 0 == self._fin_state: | |
112 | + self.socket.shutdown(socket.SHUT_RDWR) | |
113 | + else: | |
114 | + self.shutdownRead() | |
115 | + self.shutdownWrite() | |
116 | + except socket.error as error: | |
117 | + pass | |
118 | + self._fin_state |= 3 | |
119 | + return self._fin_state | |
120 | + | |
121 | + def close(self): | |
122 | + try: | |
123 | + self.shutdown() | |
124 | + self.socket.close() | |
125 | + except socket.error as error: | |
126 | + pass | |
127 | + | |
128 | + self.socket = None | |
129 | + | |
130 | +# vim: set ft=python et ts=4 sw=4 sts=4: | ... | ... |
lib/Transport/TcpSocket.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | + | |
6 | +import errno | |
7 | +import socket | |
8 | + | |
9 | +import Transport | |
10 | +from Socket import Socket, CONTINUE | |
11 | + | |
12 | +ACC_CONTINUE = CONTINUE + ( | |
13 | + errno.ENETDOWN, | |
14 | + errno.EPROTO, | |
15 | + errno.ENOPROTOOPT, | |
16 | + errno.EHOSTDOWN, | |
17 | + errno.ENONET, | |
18 | + errno.EHOSTUNREACH, | |
19 | + errno.EOPNOTSUPP | |
20 | +) | |
21 | + | |
22 | +class TcpSocket(Socket): | |
23 | + def __init__(self, host, port, con_ttl=30): | |
24 | + super(TcpSocket, self).__init__(host, port, socket.SOCK_STREAM, con_ttl) | |
25 | + self.remote = None | |
26 | + | |
27 | + def bind(self): | |
28 | + super(TcpSocket, self).bind() | |
29 | + self.socket.listen(128) | |
30 | + self._listen = True | |
31 | + | |
32 | + def connect(self): | |
33 | + with self._addrinfo(): | |
34 | + self.socket.settimeout(self._con_ttl) | |
35 | + self.socket.connect(self._sa) | |
36 | + self.socket.settimeout(None) | |
37 | + self.socket.setblocking(0) | |
38 | + | |
39 | + def accept(self): | |
40 | + try: | |
41 | + con, remote = self.socket.accept() | |
42 | + except socket.error as error: | |
43 | + if error.errno not in ACC_CONTINUE: | |
44 | + raise Transport.Error(Transport.Error.ERR_FAILED) | |
45 | + return None | |
46 | + | |
47 | + try: | |
48 | + host, port = con.getpeername() | |
49 | + except Exception as error: | |
50 | + # Here we should destinguish the addr_family... | |
51 | + # Port is only available for INET and INET6 but not for UNIX. | |
52 | + # Currently I don't support UNIX so i don't change it now. | |
53 | + host = addr[0] | |
54 | + port = addr[1] | |
55 | + | |
56 | + con.setblocking(0) | |
57 | + newsock = type(self)(host, port, self._con_ttl) | |
58 | + newsock.socket = con | |
59 | + newsock.remote = remote | |
60 | + | |
61 | + return newsock | |
62 | + | |
63 | + def recv(self, size): | |
64 | + data = '' | |
65 | + try: | |
66 | + data = self.socket.recv(size) | |
67 | + except socket.error as error: | |
68 | + if error.errno not in CONTINUE: | |
69 | + raise Transport.Error(Transport.Error.ERR_FAILED) | |
70 | + return None | |
71 | + | |
72 | + if not data: | |
73 | + raise Transport.Error(Transport.Error.ERR_REMOTE_CLOSE) | |
74 | + | |
75 | + return (data, self.remote) | |
76 | + | |
77 | + def send(self, data, remote=None): | |
78 | + send = 0 | |
79 | + try: | |
80 | + if self.socket: | |
81 | + send = self.socket.send(data) | |
82 | + except socket.error as error: | |
83 | + if error.errno not in CONTINUE: | |
84 | + if error.errno == errno.ECONNRESET: | |
85 | + raise Transport.Error(Transport.Error.ERR_REMOTE_CLOSE) | |
86 | + else: | |
87 | + raise Transport.Error(Transport.Error.ERR_FAILED) | |
88 | + | |
89 | + return send | |
90 | + | |
91 | +# vim: set ft=python et ts=4 sw=4 sts=4: | ... | ... |
lib/Transport/Transport.py
0 → 100644
1 | +""" | |
2 | +Common things for all possible transports... | |
3 | +Currently our only transport is TCP but in theory there might be others... | |
4 | + | |
5 | +Author: Georg Hopp <ghopp@spamtitan.com> | |
6 | +""" | |
7 | + | |
8 | +class Error(Exception): | |
9 | + """ | |
10 | + This simplifies all the possible transport problems down to two cases. | |
11 | + Either the transport has failed completely or the remote side has shutdown | |
12 | + it's endpoint for the operation we are attemting. | |
13 | + """ | |
14 | + ERR_FAILED = 1 | |
15 | + ERR_REMOTE_CLOSE = 2 | |
16 | + | |
17 | + messages = { | |
18 | + ERR_FAILED : 'transport operation failed', | |
19 | + ERR_REMOTE_CLOSE : 'remote endpoint closed' | |
20 | + } | |
21 | + | |
22 | + def __init__(self, errno): | |
23 | + super(Error, self).__init__(Error.messages[errno]) | |
24 | + self.errno = errno | ... | ... |
lib/Transport/UdpSocket.py
0 → 100644
1 | +""" | |
2 | +@author Georg Hopp | |
3 | + | |
4 | +""" | |
5 | +import socket | |
6 | + | |
7 | +import Transport | |
8 | +from Socket import Socket, CONTINUE | |
9 | + | |
10 | +class UdpSocket(Socket): | |
11 | + def __init__(self, host, port, con_ttl=30): | |
12 | + super(UdpSocket, self).__init__(host, port, socket.SOCK_DGRAM, con_ttl) | |
13 | + | |
14 | + """ | |
15 | + TODO: recv and send are pretty similar to the TcpSocket implementation. | |
16 | + It might be a good idea to unify them into the Socket class. | |
17 | + Think about this. | |
18 | + At the end it seems that from the application programmer perspective | |
19 | + there is not really much difference between Udp and Tcp Sockets...well | |
20 | + I guess thats the whole idea behind the Socket API... :D | |
21 | + """ | |
22 | + def recv(self, size): | |
23 | + data_remote = None | |
24 | + try: | |
25 | + data_remote = self.socket.recvfrom(size) | |
26 | + except socket.error as error: | |
27 | + if error.errno not in CONTINUE: | |
28 | + raise Transport.Error(Transport.Error.ERR_FAILED) | |
29 | + return None | |
30 | + | |
31 | + if not data_remote: | |
32 | + raise Transport.Error(Transport.Error.ERR_REMOTE_CLOSE) | |
33 | + | |
34 | + return data_remote | |
35 | + | |
36 | + def send(self, data, remote): | |
37 | + send = 0 | |
38 | + try: | |
39 | + if self.socket: | |
40 | + send = self.socket.sendto(data, remote) | |
41 | + except socket.error as error: | |
42 | + if error.errno not in CONTINUE: | |
43 | + if error.errno == errno.ECONNRESET: | |
44 | + raise Transport.Error(Transport.Error.ERR_REMOTE_CLOSE) | |
45 | + else: | |
46 | + raise Transport.Error(Transport.Error.ERR_FAILED) | |
47 | + | |
48 | + return send | |
49 | + | |
50 | +# vim: set ft=python et ts=4 sw=4 sts=4: | ... | ... |
lib/Transport/__init__.py
0 → 100644
tests/TestAll.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +import TestCommunicationEndPoint | |
5 | +import TestConnection | |
6 | +import TestConnectEntryPoint | |
7 | +import TestConnector | |
8 | +import TestDatagramEntryPoint | |
9 | +import TestDatagramService | |
10 | +import TestCommunicationManager | |
11 | +import TestProtocolHandler | |
12 | +import TestEventHandler | |
13 | +import TestEventSubject | |
14 | +import TestEventDispatcher | |
15 | +import TestDnsClient | |
16 | + | |
17 | +suite = unittest.TestSuite() | |
18 | + | |
19 | +suite.addTest(TestCommunicationEndPoint.suite()) | |
20 | +suite.addTest(TestConnection.suite()) | |
21 | +suite.addTest(TestConnectEntryPoint.suite()) | |
22 | +suite.addTest(TestConnector.suite()) | |
23 | +suite.addTest(TestDatagramEntryPoint.suite()) | |
24 | +suite.addTest(TestDatagramService.suite()) | |
25 | +suite.addTest(TestCommunicationManager.suite()) | |
26 | +suite.addTest(TestProtocolHandler.suite()) | |
27 | +suite.addTest(TestEventHandler.suite()) | |
28 | +suite.addTest(TestEventSubject.suite()) | |
29 | +suite.addTest(TestEventDispatcher.suite()) | |
30 | +suite.addTest(TestDnsClient.suite()) | |
31 | + | |
32 | +unittest.TextTestRunner(verbosity=1).run(suite) | |
33 | + | |
34 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestCommunicationEndPoint.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Communication.EndPoint import CommunicationEndPoint | |
9 | + | |
10 | +class TestCommunicationEndPoint(unittest.TestCase): | |
11 | + def setUp(self): | |
12 | + self._transport = mock.Mock() | |
13 | + self._protocol = mock.Mock() | |
14 | + self._bufsize = 11773 | |
15 | + self._endpoint = CommunicationEndPoint( | |
16 | + self._transport, self._protocol, self._bufsize) | |
17 | + | |
18 | + def testSetClose(self): | |
19 | + self.assertFalse(self._endpoint.shouldClose()) | |
20 | + self._endpoint.setClose() | |
21 | + self.assertTrue(self._endpoint.shouldClose()) | |
22 | + | |
23 | + def testHasProtocol(self): | |
24 | + self.assertTrue(self._endpoint.hasProtocol(mock.Mock)) | |
25 | + | |
26 | + def testHasPendingData(self): | |
27 | + self.assertFalse(self._endpoint.hasPendingData()) | |
28 | + | |
29 | + def testGetTransport(self): | |
30 | + self.assertEqual(self._endpoint.getTransport(), self._transport) | |
31 | + | |
32 | + def testGetProtocol(self): | |
33 | + self.assertEqual(self._endpoint.getProtocol(), self._protocol) | |
34 | + | |
35 | + def testGetHandle(self): | |
36 | + self._transport.getHandle.return_value = 10 | |
37 | + self.assertEqual(self._endpoint.getHandle(), 10) | |
38 | + self._transport.getHandle.assert_call_once() | |
39 | + | |
40 | + def testBufferRead(self): | |
41 | + self._transport.recv.return_value = False | |
42 | + self.assertFalse(self._endpoint.bufferRead()) | |
43 | + self._transport.recv.assert_call_once_with(11773) | |
44 | + | |
45 | + self._transport.reset_mock() | |
46 | + self._endpoint.appendReadData = mock.Mock() | |
47 | + | |
48 | + self._transport.recv.side_effect = iter(['111', '2222', '33333', False]) | |
49 | + self.assertTrue(self._endpoint.bufferRead()) | |
50 | + self._transport.recv.assert_call_with(11773) | |
51 | + self.assertEqual(self._transport.recv.call_count, 4) | |
52 | + self.assertEqual(self._endpoint.appendReadData.call_count, 3) | |
53 | + | |
54 | + def testWriteBuffered(self): | |
55 | + self._endpoint.nextWriteData = mock.Mock() | |
56 | + self._endpoint.nextWriteData.return_value = ('', 1212) | |
57 | + self.assertFalse(self._endpoint.writeBuffered()) | |
58 | + self._endpoint.nextWriteData.assert_called_once_with() | |
59 | + | |
60 | + self._endpoint.nextWriteData.reset_mock() | |
61 | + self._endpoint.nextWriteData.return_value = ('12345', 1212) | |
62 | + self._endpoint.appendWriteData = mock.Mock() | |
63 | + self._transport.send.return_value = 0 | |
64 | + self.assertFalse(self._endpoint.writeBuffered()) | |
65 | + self._endpoint.nextWriteData.assert_called_once_with() | |
66 | + self._transport.send.assert_called_once_with('12345', 1212) | |
67 | + | |
68 | + self._transport.reset_mock() | |
69 | + self._endpoint.nextWriteData.reset_mock() | |
70 | + self._endpoint.nextWriteData.side_effect = iter( | |
71 | + [('111222', 1212), ('333', 1313), ('', 1212)]) | |
72 | + self._endpoint.appendWriteData = mock.Mock() | |
73 | + self._transport.send.return_value = 3 | |
74 | + self.assertTrue(self._endpoint.writeBuffered()) | |
75 | + self._endpoint.nextWriteData.assert_called_with() | |
76 | + self._transport.send.assert_any_call('111222', 1212) | |
77 | + self._transport.send.assert_any_call('222', 1212) | |
78 | + self._transport.send.assert_called_with('333', 1313) | |
79 | + | |
80 | + | |
81 | +def suite(): | |
82 | + return unittest.TestLoader().loadTestsFromTestCase(TestCommunicationEndPoint) | |
83 | + | |
84 | +if __name__ == '__main__': | |
85 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
86 | + | |
87 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestCommunicationManager.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Communication.Manager import CommunicationManager | |
9 | + | |
10 | +class TestCommunicationManager(unittest.TestCase): | |
11 | + def setUp(self): | |
12 | + self._manager = CommunicationManager() | |
13 | + self._endpoint = mock.Mock() | |
14 | + self._transport = mock.Mock() | |
15 | + self._event = mock.Mock() | |
16 | + | |
17 | + self._endpoint.getHandle.return_value = 123 | |
18 | + self._endpoint.getTransport.return_value = self._transport | |
19 | + self._event.subject = self._endpoint | |
20 | + self._manager.issueEvent = mock.Mock() | |
21 | + | |
22 | + def testEndPointAlreadyHandled(self): | |
23 | + """ | |
24 | + there really should be a test for this to ensure the if | |
25 | + is working correctly. | |
26 | + """ | |
27 | + pass | |
28 | + | |
29 | + def testAddEndPoint(self): | |
30 | + self._transport.isListen.return_value = False | |
31 | + self._manager._rcons = mock.Mock() | |
32 | + self._manager.addEndPoint(self._endpoint) | |
33 | + self.assertIn(123, self._manager._cons) | |
34 | + self.assertEqual(self._endpoint, self._manager._cons[123]) | |
35 | + self._manager._rcons.append.assert_called_with(123) | |
36 | + | |
37 | + def testAddListenEndPoint(self): | |
38 | + self._transport.isListen.return_value = True | |
39 | + self._manager._rcons = mock.Mock() | |
40 | + self._manager.addEndPoint(self._endpoint) | |
41 | + self.assertIn(123, self._manager._listen) | |
42 | + self.assertEqual(self._endpoint, self._manager._listen[123]) | |
43 | + self._manager._rcons.append.assert_called_with(123) | |
44 | + | |
45 | + def test_addCon(self): | |
46 | + self._manager.addEndPoint = mock.Mock() | |
47 | + self.assertTrue(self._manager._addCon(self._event)) | |
48 | + self._manager.addEndPoint.assert_called_once_with(self._endpoint) | |
49 | + | |
50 | + def test_enableWriteOnWriteFinTransport(self): | |
51 | + self._transport._fin_state = 2 | |
52 | + self.assertTrue(self._manager._enableWrite(self._event)) | |
53 | + self.assertNotIn(123, self._manager._wcons) | |
54 | + | |
55 | + def test_enableWrite(self): | |
56 | + self._transport._fin_state = 0 | |
57 | + self.assertTrue(self._manager._enableWrite(self._event)) | |
58 | + self.assertIn(123, self._manager._wcons) | |
59 | + | |
60 | + def test_disableWriteNoShutdownRead(self): | |
61 | + self._transport._fin_state = 0 | |
62 | + self.assertTrue(self._manager._disableWrite(self._event)) | |
63 | + self.assertNotIn(123, self._manager._wcons) | |
64 | + self.test_enableWrite() | |
65 | + self.assertTrue(self._manager._disableWrite(self._event)) | |
66 | + self.assertNotIn(123, self._manager._wcons) | |
67 | + | |
68 | + def test_disableWriteNoShutdownRead(self): | |
69 | + self._transport._fin_state = 1 | |
70 | + self.assertTrue(self._manager._disableWrite(self._event)) | |
71 | + self.assertNotIn(123, self._manager._wcons) | |
72 | + self.test_enableWrite() | |
73 | + self.assertTrue(self._manager._disableWrite(self._event)) | |
74 | + self.assertNotIn(123, self._manager._wcons) | |
75 | + self._manager.issueEvent.assert_called_with( | |
76 | + self._endpoint, 'shutdown_write') | |
77 | + | |
78 | + def test_shutdown(self): | |
79 | + self._transport.isListen.return_value = True | |
80 | + endpoint2 = mock.Mock() | |
81 | + transport2 = mock.Mock() | |
82 | + endpoint2.getTransport.return_value = transport2 | |
83 | + endpoint2.getHandle.return_value = 321 | |
84 | + transport2.isListen.return_value = False | |
85 | + self._manager.addEndPoint(self._endpoint) | |
86 | + self._manager.addEndPoint(endpoint2) | |
87 | + self.assertFalse(self._manager._shutdown(None)) | |
88 | + self._manager.issueEvent.assert_any_call(self._endpoint, 'close') | |
89 | + self._manager.issueEvent.assert_any_call(endpoint2, 'close') | |
90 | + self.assertEqual(self._manager._rcons, []) | |
91 | + self.assertEqual(self._manager._wcons, []) | |
92 | + | |
93 | + def test_shutdownReadReadyToClose(self): | |
94 | + self._manager._rcons.append(123) | |
95 | + self._transport.shutdownRead.return_value = 3 | |
96 | + self._endpoint.hasPendingData.return_value = False | |
97 | + self.assertFalse(self._manager._shutdownRead(self._event)) | |
98 | + self._manager.issueEvent.assert_called_with(self._endpoint, 'close') | |
99 | + | |
100 | + def test_shutdownReadReadyToShutdownWrite(self): | |
101 | + self._manager._rcons.append(123) | |
102 | + self._transport.shutdownRead.return_value = 0 | |
103 | + self._endpoint.hasPendingData.return_value = False | |
104 | + self.assertFalse(self._manager._shutdownRead(self._event)) | |
105 | + self._manager.issueEvent.assert_called_with(self._endpoint, 'shutdown_write') | |
106 | + | |
107 | + def test_shutdownReadMarkAsClose(self): | |
108 | + self._manager._rcons.append(123) | |
109 | + self._transport.shutdownRead.return_value = 0 | |
110 | + self._endpoint.hasPendingData.return_value = True | |
111 | + self.assertFalse(self._manager._shutdownRead(self._event)) | |
112 | + self._endpoint.setClose.assert_called_once_with() | |
113 | + | |
114 | + def test_shutdownWriteReadyToClose(self): | |
115 | + self._manager._wcons.append(123) | |
116 | + self._transport.shutdownWrite.return_value = 3 | |
117 | + self.assertFalse(self._manager._shutdownWrite(self._event)) | |
118 | + self._manager.issueEvent.assert_called_once_with(self._endpoint, 'close') | |
119 | + | |
120 | + def test_shutdownWrite(self): | |
121 | + self._manager._wcons.append(123) | |
122 | + self._transport.shutdownWrite.return_value = 0 | |
123 | + self.assertFalse(self._manager._shutdownWrite(self._event)) | |
124 | + | |
125 | + def test_closeCon(self): | |
126 | + self._manager._wcons.append(123) | |
127 | + self._manager._rcons.append(123) | |
128 | + self._manager._cons[123] = self._endpoint | |
129 | + self.assertFalse(self._manager._close(self._event)) | |
130 | + self._transport.shutdown.assert_called_with() | |
131 | + self._transport.close.assert_called_with() | |
132 | + self.assertNotIn(123, self._manager._wcons) | |
133 | + self.assertNotIn(123, self._manager._rcons) | |
134 | + self.assertNotIn(123, self._manager._cons) | |
135 | + | |
136 | + def test_closeListen(self): | |
137 | + self._manager._wcons.append(123) | |
138 | + self._manager._rcons.append(123) | |
139 | + self._manager._listen[123] = self._endpoint | |
140 | + self.assertFalse(self._manager._close(self._event)) | |
141 | + self._transport.shutdown.assert_called_with() | |
142 | + self._transport.close.assert_called_with() | |
143 | + self.assertNotIn(123, self._manager._wcons) | |
144 | + self.assertNotIn(123, self._manager._rcons) | |
145 | + self.assertNotIn(123, self._manager._listen) | |
146 | + | |
147 | +def suite(): | |
148 | + return unittest.TestLoader().loadTestsFromTestCase(TestCommunicationManager) | |
149 | + | |
150 | +if __name__ == '__main__': | |
151 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
152 | + | |
153 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestConnectEntryPoint.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Communication.ConnectEntryPoint import ConnectEntryPoint | |
9 | +from Transport import Transport | |
10 | + | |
11 | +class TestConnectEntryPoint(unittest.TestCase): | |
12 | + def setUp(self): | |
13 | + self._transport = mock.Mock() | |
14 | + self._protocol = mock.Mock() | |
15 | + self._newcon = mock.Mock() | |
16 | + self._entrypoint = ConnectEntryPoint(self._transport, self._protocol) | |
17 | + self._transport.bind.assert_called_once_with() | |
18 | + | |
19 | + def testAccept(self): | |
20 | + self._transport.accept.return_value = None | |
21 | + self.assertFalse(self._entrypoint.accept()) | |
22 | + | |
23 | + self._transport.accept.side_effect = iter( | |
24 | + [self._newcon, self._newcon, None]) | |
25 | + self.assertTrue(self._entrypoint.accept()) | |
26 | + | |
27 | + self._transport.accept.side_effect = iter( | |
28 | + [self._newcon, Transport.Error(1)]) | |
29 | + self.assertTrue(self._entrypoint.accept()) | |
30 | + | |
31 | + def testPop(self): | |
32 | + self.testAccept() | |
33 | + self.assertEqual(self._entrypoint.pop(), self._newcon) | |
34 | + self.assertEqual(self._entrypoint.pop(), self._newcon) | |
35 | + self.assertEqual(self._entrypoint.pop(), self._newcon) | |
36 | + self.assertEqual(self._entrypoint.pop(), None) | |
37 | + | |
38 | +def suite(): | |
39 | + return unittest.TestLoader().loadTestsFromTestCase(TestConnectEntryPoint) | |
40 | + | |
41 | +if __name__ == '__main__': | |
42 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
43 | + | |
44 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestConnection.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Communication.Connection import Connection | |
9 | + | |
10 | +class TestConnection(unittest.TestCase): | |
11 | + def setUp(self): | |
12 | + self._message = mock.Mock() | |
13 | + self._transport = mock.Mock() | |
14 | + self._protocol = mock.Mock() | |
15 | + self._parser = mock.Mock() | |
16 | + self._composer = mock.Mock() | |
17 | + self._bufsize = 11773 | |
18 | + self._connection = Connection( | |
19 | + self._transport, self._protocol, self._bufsize) | |
20 | + | |
21 | + def testHasPendingData(self): | |
22 | + self.assertFalse(self._connection.hasPendingData()) | |
23 | + self._connection._write_buffer = '1234' | |
24 | + self.assertTrue(self._connection.hasPendingData()) | |
25 | + | |
26 | + def testIterInit(self): | |
27 | + self.assertEqual(self._connection.__iter__(), self._connection) | |
28 | + | |
29 | + def testMessageIterator(self): | |
30 | + self._transport.remote = 1212 | |
31 | + self._protocol.createMessage.return_value = self._message | |
32 | + self._protocol.getParser.return_value = self._parser | |
33 | + self._parser.parse.return_value = 0 | |
34 | + self.assertRaises(StopIteration, self._connection.next) | |
35 | + self._protocol.createMessage.assert_called_once_with(1212) | |
36 | + self._protocol.getParser.assert_called_once_with() | |
37 | + self._parser.parse.assert_called_once_with(self._message, '') | |
38 | + | |
39 | + self._transport.reset_mock() | |
40 | + self._protocol.reset_mock() | |
41 | + self._parser.reset_mock() | |
42 | + | |
43 | + self._connection.appendReadData(('111222333', 1212)) | |
44 | + self._transport.remote = 1212 | |
45 | + self._protocol.getParser.return_value = self._parser | |
46 | + self._parser.parse.return_value = 3 | |
47 | + self._message.ready.return_value = False | |
48 | + self.assertRaises(StopIteration, self._connection.next) | |
49 | + self._protocol.getParser.assert_called_once_with() | |
50 | + self._parser.parse.assert_called_once_with(self._message, '111222333') | |
51 | + self.assertEqual(self._message.ready.call_count, 2) | |
52 | + | |
53 | + self._transport.reset_mock() | |
54 | + self._protocol.reset_mock() | |
55 | + self._parser.reset_mock() | |
56 | + | |
57 | + self._transport.remote = 1212 | |
58 | + self._protocol.getParser.return_value = self._parser | |
59 | + self._parser.parse.return_value = 3 | |
60 | + self._message.ready.return_value = True | |
61 | + self.assertEqual(self._connection.next(), self._message) | |
62 | + self._protocol.createMessage.assert_called_once_with(1212) | |
63 | + self._protocol.getParser.assert_called_once_with() | |
64 | + self._parser.parse.assert_called_once_with(self._message, '222333') | |
65 | + self.assertEqual(self._message.ready.call_count, 2) | |
66 | + | |
67 | + def testCompose(self): | |
68 | + self._protocol.getComposer.return_value = self._composer | |
69 | + self._composer.compose.return_value = '111222333' | |
70 | + self.assertTrue(self._connection.compose(self._message)) | |
71 | + self.assertEqual(self._connection._write_buffer, '111222333') | |
72 | + | |
73 | + self._composer.compose.side_effect = Exception('BOOM!') | |
74 | + self.assertFalse(self._connection.compose(self._message)) | |
75 | + self.assertEqual(self._connection._write_buffer, '111222333') | |
76 | + | |
77 | + def testAppendReadData(self): | |
78 | + self._connection.appendReadData(('111', 1212)) | |
79 | + self.assertEqual(self._connection._read_buffer, '111') | |
80 | + self._connection.appendReadData(('222', 1212)) | |
81 | + self.assertEqual(self._connection._read_buffer, '111222') | |
82 | + self._connection.appendReadData(('333', 1212)) | |
83 | + self.assertEqual(self._connection._read_buffer, '111222333') | |
84 | + | |
85 | + def testNextWriteData(self): | |
86 | + self._connection._write_buffer = '111222333' | |
87 | + self.assertEqual(self._connection.nextWriteData(), ('111222333', None)) | |
88 | + self.assertEqual(self._connection._write_buffer, '') | |
89 | + | |
90 | + def testAppendWriteData(self): | |
91 | + self._connection.appendWriteData(('111', 1212)) | |
92 | + self.assertEqual(self._connection._write_buffer, '111') | |
93 | + self._connection.appendWriteData(('222', 1212)) | |
94 | + self.assertEqual(self._connection._write_buffer, '111222') | |
95 | + self._connection.appendWriteData(('333', 1212)) | |
96 | + self.assertEqual(self._connection._write_buffer, '111222333') | |
97 | + | |
98 | +def suite(): | |
99 | + return unittest.TestLoader().loadTestsFromTestCase(TestConnection) | |
100 | + | |
101 | +if __name__ == '__main__': | |
102 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
103 | + | |
104 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestConnector.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Communication.Connector import Connector | |
9 | +from Communication.Connection import Connection | |
10 | +from Transport import Transport | |
11 | + | |
12 | +class TestConnector(unittest.TestCase): | |
13 | + def setUp(self): | |
14 | + self._connector = Connector() | |
15 | + self._connector.issueEvent = mock.Mock() | |
16 | + | |
17 | + self._event = mock.Mock() | |
18 | + self._endpoint = mock.Mock() | |
19 | + self._protocol = mock.Mock() | |
20 | + self._dispatcher = mock.Mock() | |
21 | + self._new_transp = mock.Mock() | |
22 | + | |
23 | + self._event.subject = self._endpoint | |
24 | + self._endpoint.getProtocol.return_value = self._protocol | |
25 | + | |
26 | + def testTransportFail(self): | |
27 | + self._endpoint.accept.side_effect = Transport.Error(1) | |
28 | + self.assertTrue(self._connector._accept(self._event)) | |
29 | + self._endpoint.getProtocol.assert_called_once_with() | |
30 | + self._endpoint.accept.called_once_with() | |
31 | + self._connector.issueEvent.assert_called_once_with( | |
32 | + self._endpoint, 'close') | |
33 | + | |
34 | + def testNoNewTransports(self): | |
35 | + self._endpoint.accept.return_value = False | |
36 | + self.assertTrue(self._connector._accept(self._event)) | |
37 | + self._endpoint.getProtocol.assert_called_once_with() | |
38 | + self._endpoint.accept.called_once_with() | |
39 | + self.assertFalse(self._connector.issueEvent.called) | |
40 | + | |
41 | + def testNewTransports(self): | |
42 | + self._endpoint.accept.return_value = True | |
43 | + self._endpoint.pop.side_effect = iter([self._new_transp, False]) | |
44 | + self.assertTrue(self._connector._accept(self._event)) | |
45 | + self._endpoint.getProtocol.assert_called_once_with() | |
46 | + self._endpoint.accept.called_once_with() | |
47 | + issueEvent_args = self._connector.issueEvent.call_args | |
48 | + self.assertNotEqual(issueEvent_args, None) | |
49 | + if issueEvent_args: | |
50 | + self.assertIsInstance(issueEvent_args[0][0], Connection) | |
51 | + self.assertEqual(issueEvent_args[0][1], 'new_con') | |
52 | + | |
53 | +def suite(): | |
54 | + return unittest.TestLoader().loadTestsFromTestCase(TestConnector) | |
55 | + | |
56 | +if __name__ == '__main__': | |
57 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
58 | + | |
59 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestDatagramEntryPoint.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Communication.DatagramEntryPoint import DatagramEntryPoint | |
9 | + | |
10 | +class TestDatagramEntryPoint(unittest.TestCase): | |
11 | + def setUp(self): | |
12 | + self._transport = mock.Mock() | |
13 | + self._protocol = mock.Mock() | |
14 | + self._entrypoint = DatagramEntryPoint(self._transport, self._protocol) | |
15 | + | |
16 | + def testAny(self): | |
17 | + self._transport.bind.assert_called_once_with() | |
18 | + | |
19 | +def suite(): | |
20 | + return unittest.TestLoader().loadTestsFromTestCase(TestDatagramEntryPoint) | |
21 | + | |
22 | +if __name__ == '__main__': | |
23 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
24 | + | |
25 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestDatagramService.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Communication.DatagramService import DatagramService | |
9 | + | |
10 | +class TestDatagramService(unittest.TestCase): | |
11 | + def setUp(self): | |
12 | + self._transport = mock.Mock() | |
13 | + self._protocol = mock.Mock() | |
14 | + self._message = mock.Mock() | |
15 | + self._parser = mock.Mock() | |
16 | + self._composer = mock.Mock() | |
17 | + self._bufsize = 22655 | |
18 | + self._msginfo = ('111222333', 1212) | |
19 | + self._datagram = DatagramService( | |
20 | + self._transport, self._protocol, self._bufsize) | |
21 | + | |
22 | + self._protocol.getParser.return_value = self._parser | |
23 | + self._protocol.getComposer.return_value = self._composer | |
24 | + self._message.getRemote.return_value = self._msginfo[1] | |
25 | + | |
26 | + def testHasPendingData(self): | |
27 | + self.assertFalse(self._datagram.hasPendingData()) | |
28 | + self._datagram._write_buffer = '12345' | |
29 | + self.assertTrue(self._datagram.hasPendingData()) | |
30 | + | |
31 | + def testIterInit(self): | |
32 | + self.assertEqual(self._datagram.__iter__(), self._datagram) | |
33 | + | |
34 | + def testMessageIteratorNoData(self): | |
35 | + self.assertRaises(StopIteration, self._datagram.next) | |
36 | + | |
37 | + def testMessageIteratorCreateMessageFails(self): | |
38 | + self._datagram._read_buffer = mock.Mock() | |
39 | + self._datagram._read_buffer.popleft.return_value = self._msginfo | |
40 | + self._protocol.createMessage.return_value = None | |
41 | + self.assertRaises(StopIteration, self._datagram.next) | |
42 | + self._datagram._read_buffer.popleft.assert_called_once_with() | |
43 | + self._protocol.createMessage.assert_called_once_with(self._msginfo[1]) | |
44 | + | |
45 | + def testMessageIteratorNoDataParsed(self): | |
46 | + self._datagram._read_buffer = mock.Mock() | |
47 | + self._datagram._read_buffer.popleft.return_value = self._msginfo | |
48 | + self._protocol.createMessage.return_value = self._message | |
49 | + self._parser.parse.return_value = 0 | |
50 | + self.assertRaises(StopIteration, self._datagram.next) | |
51 | + self._datagram._read_buffer.popleft.assert_called_once_with() | |
52 | + self._protocol.createMessage.assert_called_once_with(self._msginfo[1]) | |
53 | + self._parser.parse.assert_called_once_with( | |
54 | + self._message, self._msginfo[0]) | |
55 | + | |
56 | + def testMessageIteratorGetMessage(self): | |
57 | + self._datagram._read_buffer = mock.Mock() | |
58 | + self._datagram._read_buffer.popleft.return_value = self._msginfo | |
59 | + self._protocol.createMessage.return_value = self._message | |
60 | + self._parser.parse.return_value = 10 | |
61 | + self.assertEqual(self._datagram.next(), self._message) | |
62 | + self._datagram._read_buffer.popleft.assert_called_once_with() | |
63 | + self._protocol.createMessage.assert_called_once_with(self._msginfo[1]) | |
64 | + self._parser.parse.assert_called_once_with( | |
65 | + self._message, self._msginfo[0]) | |
66 | + | |
67 | + def testComposeSuccess(self): | |
68 | + self._composer.compose.return_value = '111222333' | |
69 | + self.assertTrue(self._datagram.compose(self._message)) | |
70 | + self.assertIn( | |
71 | + ('111222333', self._msginfo[1]), | |
72 | + self._datagram._write_buffer) | |
73 | + | |
74 | + def testComposeFail(self): | |
75 | + self._composer.compose.side_effect = Exception('Boom!') | |
76 | + self.assertFalse(self._datagram.compose(self._message)) | |
77 | + self.assertFalse(self._datagram._write_buffer) | |
78 | + | |
79 | + def testAppendReadData(self): | |
80 | + self._datagram.appendReadData(('111', 1212)) | |
81 | + self.assertIn(('111', 1212), self._datagram._read_buffer) | |
82 | + self._datagram.appendReadData(('222', 1212)) | |
83 | + self.assertIn(('111', 1212), self._datagram._read_buffer) | |
84 | + self.assertIn(('222', 1212), self._datagram._read_buffer) | |
85 | + self._datagram.appendReadData(('333', 1212)) | |
86 | + self.assertIn(('111', 1212), self._datagram._read_buffer) | |
87 | + self.assertIn(('222', 1212), self._datagram._read_buffer) | |
88 | + self.assertIn(('333', 1212), self._datagram._read_buffer) | |
89 | + | |
90 | + def testNextWriteData(self): | |
91 | + self.assertEqual(self._datagram.nextWriteData(), ('', None)) | |
92 | + self._datagram._write_buffer.append(('111222333', 1212)) | |
93 | + self.assertEqual(self._datagram.nextWriteData(), ('111222333', 1212)) | |
94 | + self.assertNotIn(('111222333', 1212), self._datagram._write_buffer) | |
95 | + | |
96 | + def testAppendWriteData(self): | |
97 | + self._datagram.appendWriteData(('111', 1212)) | |
98 | + self.assertIn(('111', 1212), self._datagram._write_buffer) | |
99 | + self._datagram.appendWriteData(('222', 1212)) | |
100 | + self.assertIn(('111', 1212), self._datagram._write_buffer) | |
101 | + self.assertIn(('222', 1212), self._datagram._write_buffer) | |
102 | + self._datagram.appendWriteData(('333', 1212)) | |
103 | + self.assertIn(('111', 1212), self._datagram._write_buffer) | |
104 | + self.assertIn(('222', 1212), self._datagram._write_buffer) | |
105 | + self.assertIn(('333', 1212), self._datagram._write_buffer) | |
106 | + | |
107 | +def suite(): | |
108 | + return unittest.TestLoader().loadTestsFromTestCase(TestDatagramService) | |
109 | + | |
110 | +if __name__ == '__main__': | |
111 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
112 | + | |
113 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestDnsClient.py
0 → 100644
1 | +import struct | |
2 | +import unittest | |
3 | +import mock | |
4 | + | |
5 | +from os.path import dirname, realpath | |
6 | +from sys import argv, path | |
7 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
8 | + | |
9 | +from DnsClient import DnsClient | |
10 | + | |
11 | +class TestDnsClient(unittest.TestCase): | |
12 | + def setUp(self): | |
13 | + self._remote_addr = ('10.1.0.10', 1212) | |
14 | + | |
15 | + self._client = DnsClient(self._remote_addr[0], self._remote_addr[1]) | |
16 | + self._client._client = mock.Mock() | |
17 | + self._client._proto = mock.Mock() | |
18 | + | |
19 | + def testGetIp(self): | |
20 | + request = mock.Mock() | |
21 | + response = mock.Mock() | |
22 | + response._answers = [('foo', 1, 1, 15, '\x01\x02\x03\x04')] | |
23 | + self._client._proto.createRequest.return_value = request | |
24 | + self._client._client.getRemoteAddr.return_value = self._remote_addr | |
25 | + self._client._client.issue.return_value = response | |
26 | + self.assertEqual(self._client.getIp('foo'), '1.2.3.4') | |
27 | + | |
28 | +def suite(): | |
29 | + return unittest.TestLoader().loadTestsFromTestCase(TestDnsClient) | |
30 | + | |
31 | +if __name__ == '__main__': | |
32 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
33 | + | |
34 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestEventDispatcher.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Event.Event import Event | |
9 | +from Event.EventDispatcher import EventDispatcher | |
10 | + | |
11 | +class TestEventDisptcher(unittest.TestCase): | |
12 | + def setUp(self): | |
13 | + self._dispatcher = EventDispatcher() | |
14 | + self._handler_mock1 = mock.Mock() | |
15 | + self._handler_mock2 = mock.Mock() | |
16 | + | |
17 | + self._handler_mock1.getHandledIds.return_value = [1, 2] | |
18 | + self._handler_mock2.getHandledIds.return_value = [1, 3] | |
19 | + | |
20 | + def testRegisterHandler(self): | |
21 | + self._dispatcher.registerHandler(self._handler_mock1) | |
22 | + self._dispatcher.registerHandler(self._handler_mock2) | |
23 | + | |
24 | + self._handler_mock1.getHandledIds.called_once() | |
25 | + self._handler_mock2.getHandledIds.called_once() | |
26 | + | |
27 | + self._handler_mock1.setDispatcher.called_once() | |
28 | + self._handler_mock2.setDispatcher.called_once() | |
29 | + | |
30 | + self.assertIn(1, self._dispatcher._handler) | |
31 | + self.assertIn(2, self._dispatcher._handler) | |
32 | + self.assertIn(3, self._dispatcher._handler) | |
33 | + self.assertNotIn(4, self._dispatcher._handler) | |
34 | + self.assertIn(self._handler_mock1, self._dispatcher._handler[1]) | |
35 | + self.assertIn(self._handler_mock2, self._dispatcher._handler[1]) | |
36 | + self.assertIn(self._handler_mock1, self._dispatcher._handler[2]) | |
37 | + self.assertNotIn(self._handler_mock2, self._dispatcher._handler[2]) | |
38 | + self.assertIn(self._handler_mock2, self._dispatcher._handler[3]) | |
39 | + | |
40 | + def testSetHeartbeat(self): | |
41 | + self._dispatcher.setHeartbeat(None) | |
42 | + self.assertEqual(self._dispatcher._heartbeat, None) | |
43 | + self.assertEqual(self._dispatcher._nextbeat, 0.0) | |
44 | + self._dispatcher.setHeartbeat(1.0) | |
45 | + self.assertEqual(self._dispatcher._heartbeat, 1.0) | |
46 | + self.assertNotEqual(self._dispatcher._nextbeat, 0.0) | |
47 | + | |
48 | +def suite(): | |
49 | + return unittest.TestLoader().loadTestsFromTestCase(TestEventDisptcher) | |
50 | + | |
51 | +if __name__ == '__main__': | |
52 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
53 | + | |
54 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestEventHandler.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Event.EventHandler import EventHandler | |
9 | + | |
10 | +class HandlerOne(EventHandler): | |
11 | + def __init__(self): | |
12 | + super(HandlerOne, self).__init__() | |
13 | + | |
14 | + self._event_methods = { | |
15 | + 1 : self._handleOne, | |
16 | + 2 : self._handleTwo } | |
17 | + | |
18 | + def _handleOne(self, event): | |
19 | + return 'one' | |
20 | + | |
21 | + def _handleTwo(self, event): | |
22 | + return 'two' | |
23 | + | |
24 | +class TestEventHandler(unittest.TestCase): | |
25 | + def setUp(self): | |
26 | + self._handler = EventHandler() | |
27 | + self._handler_one = HandlerOne() | |
28 | + | |
29 | + self._event_mock1 = mock.Mock() | |
30 | + self._event_mock2 = mock.Mock() | |
31 | + self._event_source_mock = mock.Mock() | |
32 | + self._dispatcher_mock1 = mock.Mock() | |
33 | + self._dispatcher_mock2 = mock.Mock() | |
34 | + | |
35 | + self._event_mock1.name = 'a' | |
36 | + self._event_mock1.type = 1 | |
37 | + self._event_mock1.subject = self._event_source_mock | |
38 | + self._event_mock1.data = None | |
39 | + | |
40 | + self._event_mock2.name = 'b' | |
41 | + self._event_mock2.type = 2 | |
42 | + self._event_mock2.subject = self._event_source_mock | |
43 | + self._event_mock2.data = 'arbitrary data' | |
44 | + | |
45 | + self._event_source_mock.emit.return_value = self._event_mock1 | |
46 | + | |
47 | + def testEmptyHandlerSetDispatcher(self): | |
48 | + self._handler.setDispatcher(self._dispatcher_mock1) | |
49 | + self._handler.setDispatcher(self._dispatcher_mock2) | |
50 | + self.assertIn(self._dispatcher_mock1, self._handler._dispatcher) | |
51 | + self.assertIn(self._dispatcher_mock2, self._handler._dispatcher) | |
52 | + | |
53 | + def testEmptyHandlerGetHandledIds(self): | |
54 | + self.assertEqual(self._handler.getHandledIds(), []) | |
55 | + | |
56 | + def testEmptyHandlerNoDispatcherIssueEvent(self): | |
57 | + self._handler.issueEvent(self._event_source_mock, 'a', None) | |
58 | + self._event_source_mock.emit.assert_called_once_with('a', None) | |
59 | + | |
60 | + def testEmptyHandlerIssueEvent(self): | |
61 | + self._handler.setDispatcher(self._dispatcher_mock1) | |
62 | + self._handler.setDispatcher(self._dispatcher_mock2) | |
63 | + self._handler.issueEvent(self._event_source_mock, 'a', None) | |
64 | + self._event_source_mock.emit.assert_called_once_with('a', None) | |
65 | + self._dispatcher_mock1.queueEvent.called_once_with(self._event_mock1) | |
66 | + self._dispatcher_mock2.queueEvent.called_once_with(self._event_mock1) | |
67 | + | |
68 | + def testEmptyHandlerHandleEvent(self): | |
69 | + self.assertFalse(self._handler.handleEvent(self._event_mock1)) | |
70 | + self.assertFalse(self._handler.handleEvent(self._event_mock2)) | |
71 | + | |
72 | + def testHandlerOneHandleEvent(self): | |
73 | + self.assertEqual(self._handler_one.handleEvent(self._event_mock1), 'one') | |
74 | + self.assertEqual(self._handler_one.handleEvent(self._event_mock2), 'two') | |
75 | + | |
76 | +def suite(): | |
77 | + return unittest.TestLoader().loadTestsFromTestCase(TestEventHandler) | |
78 | + | |
79 | +if __name__ == '__main__': | |
80 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
81 | + | |
82 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestEventSubject.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Event.Event import Event | |
9 | +from Event.EventSubject import EventSubject | |
10 | + | |
11 | +class EventSubject1(EventSubject): | |
12 | + _EVENTS = { | |
13 | + 'a': 0x01 | |
14 | + } | |
15 | + | |
16 | +class EventSubject2(EventSubject1): | |
17 | + _EVENTS = { | |
18 | + 'b': 0x01 | |
19 | + } | |
20 | + | |
21 | +class EventSubject3(EventSubject): | |
22 | + _EVENTS = { | |
23 | + 'a': 0x01 | |
24 | + } | |
25 | + | |
26 | +class TestEventSubject(unittest.TestCase): | |
27 | + def setUp(self): | |
28 | + self._subject = EventSubject() | |
29 | + self._subject1 = EventSubject1() | |
30 | + self._subject2 = EventSubject2() | |
31 | + self._subject3 = EventSubject3() | |
32 | + | |
33 | + def testEventId(self): | |
34 | + self.assertEqual(EventSubject().eventId('a'), None) | |
35 | + self.assertNotEqual(EventSubject1().eventId('a'), None) | |
36 | + self.assertNotEqual(EventSubject2().eventId('a'), None) | |
37 | + self.assertNotEqual(EventSubject2().eventId('b'), None) | |
38 | + self.assertNotEqual(EventSubject2.eventId('a'), EventSubject2.eventId('b')) | |
39 | + self.assertNotEqual(EventSubject1.eventId('a'), EventSubject3.eventId('a')) | |
40 | + | |
41 | + def testEmit(self): | |
42 | + event = self._subject1.emit('a', None) | |
43 | + self.assertEqual(event.name, 'a') | |
44 | + self.assertNotEqual(event.type, None) | |
45 | + self.assertEqual(event.subject, self._subject1) | |
46 | + self.assertEqual(event.data, None) | |
47 | + self.assertEqual(event.sno, 0) | |
48 | + | |
49 | + event = self._subject1.emit('b', None) | |
50 | + self.assertEqual(event.name, 'b') | |
51 | + self.assertEqual(event.type, None) | |
52 | + self.assertEqual(event.subject, self._subject1) | |
53 | + self.assertEqual(event.data, None) | |
54 | + self.assertEqual(event.sno, 1) | |
55 | + | |
56 | + event = self._subject2.emit('a', None) | |
57 | + self.assertEqual(event.name, 'a') | |
58 | + self.assertNotEqual(event.type, None) | |
59 | + self.assertEqual(event.subject, self._subject2) | |
60 | + self.assertEqual(event.data, None) | |
61 | + self.assertEqual(event.sno, 2) | |
62 | + | |
63 | + event = self._subject2.emit('b', 'data') | |
64 | + self.assertEqual(event.name, 'b') | |
65 | + self.assertNotEqual(event.type, None) | |
66 | + self.assertEqual(event.subject, self._subject2) | |
67 | + self.assertEqual(event.data, 'data') | |
68 | + self.assertEqual(event.sno, 3) | |
69 | + | |
70 | +def suite(): | |
71 | + return unittest.TestLoader().loadTestsFromTestCase(TestEventSubject) | |
72 | + | |
73 | +if __name__ == '__main__': | |
74 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
75 | + | |
76 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
tests/TestProtocolHandler.py
0 → 100644
1 | +import unittest | |
2 | +import mock | |
3 | + | |
4 | +from os.path import dirname, realpath | |
5 | +from sys import argv, path | |
6 | +path.append(dirname(dirname(realpath(__file__))) + '/lib') | |
7 | + | |
8 | +from Communication.ProtocolHandler import ProtocolHandler | |
9 | + | |
10 | +class TestProtocolHandler(unittest.TestCase): | |
11 | + def setUp(self): | |
12 | + self._protocol_handler = ProtocolHandler() | |
13 | + | |
14 | + self._endpoint = mock.Mock() | |
15 | + self._message = mock.Mock() | |
16 | + self._event = mock.Mock() | |
17 | + self._protocol = mock.Mock() | |
18 | + self._protocol_handler.issueEvent = mock.Mock() | |
19 | + | |
20 | + self._event.subject = self._endpoint | |
21 | + self._endpoint.__iter__ = mock.Mock(return_value=iter([self._message])) | |
22 | + self._endpoint.getProtocol.return_value = self._protocol | |
23 | + | |
24 | + def test_parseCloseResponse(self): | |
25 | + self._message.isCLoseMessage.return_value = True | |
26 | + self._message.isResponse.return_value = True | |
27 | + self._protocol_handler._parse(self._event) | |
28 | + self._protocol_handler.issueEvent.assert_called_with( | |
29 | + self._endpoint, 'new_msg', self._message) | |
30 | + self._endpoint.setClose.assert_called_with() | |
31 | + | |
32 | + def test_parseCloseRequest(self): | |
33 | + self._message.isCLoseMessage.return_value = True | |
34 | + self._message.isResponse.return_value = False | |
35 | + self._protocol_handler._parse(self._event) | |
36 | + self._protocol_handler.issueEvent.assert_called_with( | |
37 | + self._endpoint, 'new_msg', self._message) | |
38 | + | |
39 | + def test_parseUpgradeResponse(self): | |
40 | + self._message.isCloseMessage.return_value = False | |
41 | + self._message.isUpgradeMessage.return_value = True | |
42 | + self._message.isRequest.return_value = False | |
43 | + self._protocol_handler._parse(self._event) | |
44 | + self._protocol_handler.issueEvent.assert_called_with( | |
45 | + self._endpoint, 'upgrade', self._message) | |
46 | + | |
47 | + def test_parseUpgradeRequest(self): | |
48 | + response = mock.Mock() | |
49 | + self._protocol.createUpgradeResponse.return_value = response | |
50 | + | |
51 | + self._message.isCloseMessage.return_value = False | |
52 | + self._message.isUpgradeMessage.return_value = True | |
53 | + self._message.isRequest.return_value = True | |
54 | + self._protocol_handler._parse(self._event) | |
55 | + self._protocol_handler.issueEvent.assert_called_with( | |
56 | + self._endpoint, 'send_msg', response) | |
57 | + | |
58 | + def test_parseNormalMessage(self): | |
59 | + self._message.isCloseMessage.return_value = False | |
60 | + self._message.isUpgradeMessage.return_value = False | |
61 | + self._protocol_handler._parse(self._event) | |
62 | + self._protocol_handler.issueEvent.assert_called_with( | |
63 | + self._endpoint, 'new_msg', self._message) | |
64 | + | |
65 | + def test_composeRequest(self): | |
66 | + self._event.data = self._message | |
67 | + self._endpoint.compose.return_value = True | |
68 | + self._message.isResponse.return_value = False | |
69 | + self._protocol_handler._compose(self._event) | |
70 | + self._protocol_handler.issueEvent.assert_called_with( | |
71 | + self._endpoint, 'write_ready') | |
72 | + | |
73 | + def test_composeUpgradeResponse(self): | |
74 | + self._event.data = self._message | |
75 | + self._endpoint.compose.return_value = True | |
76 | + self._message.isResponse.return_value = True | |
77 | + self._message.isUpgradeMessage.return_value = True | |
78 | + self._message.isCloseMessage.return_value = False | |
79 | + self._protocol_handler._compose(self._event) | |
80 | + self._protocol_handler.issueEvent.assert_any_call( | |
81 | + self._endpoint, 'write_ready') | |
82 | + self._protocol_handler.issueEvent.assert_any_call( | |
83 | + self._endpoint, 'upgrade', self._message) | |
84 | + | |
85 | + def test_composeCloseResponse(self): | |
86 | + self._event.data = self._message | |
87 | + self._endpoint.compose.return_value = True | |
88 | + self._message.isResponse.return_value = True | |
89 | + self._message.isUpgradeMessage.return_value = False | |
90 | + self._message.isCloseMessage.return_value = True | |
91 | + self._protocol_handler._compose(self._event) | |
92 | + self._protocol_handler.issueEvent.assert_called_with( | |
93 | + self._endpoint, 'write_ready') | |
94 | + self._endpoint.setClose.assert_called_once_with() | |
95 | + | |
96 | +def suite(): | |
97 | + return unittest.TestLoader().loadTestsFromTestCase(TestProtocolHandler) | |
98 | + | |
99 | +if __name__ == '__main__': | |
100 | + unittest.TextTestRunner(verbosity=2).run(suite()) | |
101 | + | |
102 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
websocket.html
0 → 100644
1 | +<html> | |
2 | + <head> | |
3 | + <title>Websocket test</title> | |
4 | + </head> | |
5 | + | |
6 | + <body> | |
7 | + <h1>Websocket Test</h1> | |
8 | + <div id="wstest">Websockets are not supported</div> | |
9 | + <div id="data">Server time</div> | |
10 | + | |
11 | + <script language="Javascript"> | |
12 | + /* <![CDATA[ */ | |
13 | + var wstest = document.getElementById('wstest').firstChild; | |
14 | + var data = document.getElementById('data'); | |
15 | + | |
16 | + var current_time = null; | |
17 | + var recv_date = null; | |
18 | + var recv_at = null; | |
19 | + | |
20 | + function isLittleEndian() { | |
21 | + var a = new ArrayBuffer(4); | |
22 | + var b = new Uint8Array(a); | |
23 | + var c = new Uint32Array(a); | |
24 | + b[0] = 0xa1; | |
25 | + b[1] = 0xb2; | |
26 | + b[2] = 0xc3; | |
27 | + b[3] = 0xd4; | |
28 | + return c[0] == 0xd4c3b2a1; | |
29 | + } | |
30 | + | |
31 | + function swap_first(view) { | |
32 | + var tmp = view[0]; | |
33 | + view[0] = view[1]; | |
34 | + view[1] = tmp; | |
35 | + } | |
36 | + | |
37 | + function ntohs(byteBuffer) { | |
38 | + if (isLittleEndian()) { | |
39 | + swap_first(byteBuffer); | |
40 | + } | |
41 | + } | |
42 | + | |
43 | + function ntohl(shortBuffer) { | |
44 | + if (isLittleEndian()) { | |
45 | + swap_first(shortBuffer); | |
46 | + swap_first(new Uint8Array(shortBuffer.buffer, 0, 2)); | |
47 | + swap_first(new Uint8Array(shortBuffer.buffer, 2, 2)); | |
48 | + } | |
49 | + } | |
50 | + | |
51 | + function ntohll(longBuffer) { | |
52 | + if (isLittleEndian()) { | |
53 | + swap_first(longBuffer); | |
54 | + swap_first(new Uint16Array(longBuffer.buffer, 0, 2)); | |
55 | + swap_first(new Uint16Array(longBuffer.buffer, 4, 2)); | |
56 | + swap_first(new Uint8Array(longBuffer.buffer, 0, 2)); | |
57 | + swap_first(new Uint8Array(longBuffer.buffer, 2, 2)); | |
58 | + swap_first(new Uint8Array(longBuffer.buffer, 4, 2)); | |
59 | + swap_first(new Uint8Array(longBuffer.buffer, 6, 2)); | |
60 | + } | |
61 | + } | |
62 | + | |
63 | + function updateData() { | |
64 | + while (data.lastChild) { | |
65 | + data.removeChild(data.lastChild); | |
66 | + } | |
67 | + data.appendChild( | |
68 | + document.createTextNode(current_time.toISOString())); | |
69 | + data.appendChild( | |
70 | + document.createElement('br')); | |
71 | + data.appendChild( | |
72 | + document.createTextNode(current_time.toUTCString())); | |
73 | + data.appendChild( | |
74 | + document.createElement('br')); | |
75 | + data.appendChild( | |
76 | + document.createTextNode(current_time.toLocaleString())); | |
77 | + } | |
78 | + | |
79 | + if ('WebSocket' in window){ | |
80 | + var interval = null | |
81 | + | |
82 | + wstest.data = 'Websockets are supported'; | |
83 | + connection = new WebSocket('ws://127.0.0.1:8080/'); | |
84 | + console.log('Websockets test'); | |
85 | + | |
86 | + connection.onopen = function() { | |
87 | + interval = setInterval( | |
88 | + function() { | |
89 | + if(current_time) { | |
90 | + current_time = new Date(recv_date.getTime() + (new Date().getTime() - recv_at)); | |
91 | + updateData(); | |
92 | + } | |
93 | + }, 1); | |
94 | + } | |
95 | + | |
96 | + connection.onclose = function() { | |
97 | + window.clearInterval(interval) | |
98 | + } | |
99 | + | |
100 | + connection.onmessage = function(e){ | |
101 | + var reader = new FileReader(); | |
102 | + reader.addEventListener("loadend", function() { | |
103 | + ntohll(new Uint32Array(reader.result, 0, 2)) | |
104 | + current_time = recv_date = new Date(Math.round( | |
105 | + new Float64Array(reader.result)[0] * 1000)) | |
106 | + recv_at = new Date().getTime(); | |
107 | + }); | |
108 | + reader.readAsArrayBuffer(e.data); | |
109 | + } | |
110 | + | |
111 | + connection.onerror = function(e){ | |
112 | + window.clearInterval(interval) | |
113 | + console.log(e.data); | |
114 | + } | |
115 | + }/* ]]> */ | |
116 | + </script> | |
117 | + <img src="http://127.0.0.1:8080/ldap" /> | |
118 | + </body> | |
119 | +</html> | |
120 | +<!-- vim: set ts=4 sw=4: --> | ... | ... |
Please
register
or
login
to post a comment