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