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