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