Commit 299372fd59737229213b2d7832d2dc6427ed04ba

Authored by Georg Hopp
0 parents

initial checkin

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