Browse Source

initial checkin

master
Georg Hopp 10 years ago
commit
299372fd59
  1. 2
      .gitignore
  2. 143
      LdapService.py
  3. 5
      README.md
  4. 85
      ldaptree.py
  5. 41
      lib/Communication/ConnectEntryPoint.py
  6. 66
      lib/Communication/Connection.py
  7. 37
      lib/Communication/Connector.py
  8. 14
      lib/Communication/DatagramEntryPoint.py
  9. 68
      lib/Communication/DatagramService.py
  10. 98
      lib/Communication/EndPoint.py
  11. 175
      lib/Communication/Manager.py
  12. 72
      lib/Communication/ProtocolHandler.py
  13. 0
      lib/Communication/__init__.py
  14. 32
      lib/DnsClient.py
  15. 21
      lib/Event/Event.py
  16. 133
      lib/Event/EventDispatcher.py
  17. 28
      lib/Event/EventHandler.py
  18. 35
      lib/Event/EventSubject.py
  19. 19
      lib/Event/EventThread.py
  20. 17
      lib/Event/Signal.py
  21. 0
      lib/Event/__init__.py
  22. 96
      lib/LdapTree.py
  23. 70
      lib/MultiEndClient.py
  24. 103
      lib/Protocol/Dns/Composer.py
  25. 37
      lib/Protocol/Dns/Dns.py
  26. 103
      lib/Protocol/Dns/Info.txt
  27. 79
      lib/Protocol/Dns/Message.py
  28. 90
      lib/Protocol/Dns/Parser.py
  29. 0
      lib/Protocol/Dns/__init__.py
  30. 55
      lib/Protocol/Http/Composer.py
  31. 100
      lib/Protocol/Http/Http.py
  32. 273
      lib/Protocol/Http/Message.py
  33. 169
      lib/Protocol/Http/Parser.py
  34. 0
      lib/Protocol/Http/__init__.py
  35. 12
      lib/Protocol/Message.py
  36. 7
      lib/Protocol/Protocol.py
  37. 22
      lib/Protocol/Websocket/Composer.py
  38. 21
      lib/Protocol/Websocket/Message.py
  39. 13
      lib/Protocol/Websocket/Parser.py
  40. 39
      lib/Protocol/Websocket/Websocket.py
  41. 0
      lib/Protocol/Websocket/__init__.py
  42. 0
      lib/Protocol/__init__.py
  43. 48
      lib/Server.py
  44. 83
      lib/SimpleClient.py
  45. 21
      lib/ThreadedServer.py
  46. 46
      lib/Transport/IoHandler.py
  47. 130
      lib/Transport/Socket.py
  48. 91
      lib/Transport/TcpSocket.py
  49. 24
      lib/Transport/Transport.py
  50. 50
      lib/Transport/UdpSocket.py
  51. 0
      lib/Transport/__init__.py
  52. 34
      tests/TestAll.py
  53. 87
      tests/TestCommunicationEndPoint.py
  54. 153
      tests/TestCommunicationManager.py
  55. 44
      tests/TestConnectEntryPoint.py
  56. 104
      tests/TestConnection.py
  57. 59
      tests/TestConnector.py
  58. 25
      tests/TestDatagramEntryPoint.py
  59. 113
      tests/TestDatagramService.py
  60. 34
      tests/TestDnsClient.py
  61. 54
      tests/TestEventDispatcher.py
  62. 82
      tests/TestEventHandler.py
  63. 76
      tests/TestEventSubject.py
  64. 102
      tests/TestProtocolHandler.py
  65. 120
      websocket.html

2
.gitignore

@ -0,0 +1,2 @@
*.pyc
showmyad.sh

143
LdapService.py

@ -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:

5
README.md

@ -0,0 +1,5 @@
# ldapscan
# summary
This is some python code to scan and visualize an ldap tree structure.

85
ldaptree.py

@ -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()

41
lib/Communication/ConnectEntryPoint.py

@ -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:

66
lib/Communication/Connection.py

@ -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:

37
lib/Communication/Connector.py

@ -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:

14
lib/Communication/DatagramEntryPoint.py

@ -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:

68
lib/Communication/DatagramService.py

@ -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:

98
lib/Communication/EndPoint.py

@ -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:

175
lib/Communication/Manager.py

@ -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:

72
lib/Communication/ProtocolHandler.py

@ -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
lib/Communication/__init__.py

32
lib/DnsClient.py

@ -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]))

21
lib/Event/Event.py

@ -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:

133
lib/Event/EventDispatcher.py

@ -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:

28
lib/Event/EventHandler.py

@ -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)

35
lib/Event/EventSubject.py

@ -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:

19
lib/Event/EventThread.py

@ -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:

17
lib/Event/Signal.py

@ -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
lib/Event/__init__.py

96
lib/LdapTree.py

@ -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

70
lib/MultiEndClient.py

@ -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:

103
lib/Protocol/Dns/Composer.py

@ -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:

37
lib/Protocol/Dns/Dns.py

@ -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:

103
lib/Protocol/Dns/Info.txt

@ -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.

79
lib/Protocol/Dns/Message.py

@ -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:

90
lib/Protocol/Dns/Parser.py

@ -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
lib/Protocol/Dns/__init__.py

55
lib/Protocol/Http/Composer.py

@ -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:

100
lib/Protocol/Http/Http.py

@ -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:

273
lib/Protocol/Http/Message.py

@ -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:

169
lib/Protocol/Http/Parser.py

@ -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
lib/Protocol/Http/__init__.py

12
lib/Protocol/Message.py

@ -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:

7
lib/Protocol/Protocol.py

@ -0,0 +1,7 @@
"""
@author: Georg Hopp
"""
class Protocol(object):
pass
# vim: set ft=python et ts=8 sw=4 sts=4:

22
lib/Protocol/Websocket/Composer.py

@ -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:

21
lib/Protocol/Websocket/Message.py

@ -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:

13
lib/Protocol/Websocket/Parser.py

@ -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:

39
lib/Protocol/Websocket/Websocket.py

@ -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
lib/Protocol/Websocket/__init__.py

0
lib/Protocol/__init__.py

48
lib/Server.py

@ -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:

83
lib/SimpleClient.py

@ -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:

21
lib/ThreadedServer.py

@ -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:

46
lib/Transport/IoHandler.py

@ -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:

130
lib/Transport/Socket.py

@ -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:

91
lib/Transport/TcpSocket.py

@ -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:

24
lib/Transport/Transport.py

@ -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

50
lib/Transport/UdpSocket.py

@ -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
lib/Transport/__init__.py

34
tests/TestAll.py

@ -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:

87
tests/TestCommunicationEndPoint.py

@ -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:

153
tests/TestCommunicationManager.py

@ -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:

44
tests/TestConnectEntryPoint.py

@ -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:

104
tests/TestConnection.py

@ -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:

59
tests/TestConnector.py

@ -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:

25
tests/TestDatagramEntryPoint.py

@ -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:

113
tests/TestDatagramService.py

@ -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:

34
tests/TestDnsClient.py

@ -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:

54
tests/TestEventDispatcher.py

@ -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:

82
tests/TestEventHandler.py

@ -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:

76
tests/TestEventSubject.py

@ -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:

102
tests/TestProtocolHandler.py

@ -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:

120
websocket.html

@ -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: -->
Loading…
Cancel
Save