libtpproto-py-0.2.5/0000755000175000017500000000000011164431251012504 5ustar timtimlibtpproto-py-0.2.5/setup.cfg0000644000175000017500000000007311164431251014325 0ustar timtim[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 libtpproto-py-0.2.5/PKG-INFO0000644000175000017500000000113011164431251013574 0ustar timtimMetadata-Version: 1.0 Name: libtpproto-py Version: 0.2.5 Summary: Network library for Thousand Parsec Home-page: http://www.thousandparsec.net Author: Tim Ansell Author-email: tim@thousandparsec.net License: LGPL Description: A library of code for both Servers and Clients to support the Thousand Parsec protocol. Includes support for: * Both non-blocking and blocking usage * Version 3 protocol * HTTP/HTTPS Tunneling * Generic Reference System Keywords: thousand parsec space network empire building strategy game Platform: UNKNOWN libtpproto-py-0.2.5/README0000644000175000017500000000502510747221303013367 0ustar timtimIntro ============================================== This provides all the necessary objects to talk to the Thousand Parsec protocol. Support Libraries ============================================== For basic Thousand Parsec functionality no extra Python libraries are needed. It is recommened to install either if you are running a server, python-pyopenssl m2crypto or one of the following if you are running a client, python-pyopenssl m2crypto TLS Lite Installing these libraries will improve SSL support. Clients will not be able to connect to TLS enabled Thousand Parsec servers (but it should be able to connect to SSL3 and SSL2 servers) without one of these libraries. Servers will not be able to serve SSL connections (or tunnel HTTPS connections) without one of these libraries. Library ============================================== The library can run in either non-blocking or blocking mode. In blocking mode operations preformed on the connection will wait for the results to become available before returning. It will never return None. In non-blocking mode operations preformed on the connection will return nothing. The connection must be until the result is obtained. It is safe to preform more operations on the connection before the connection has returned the result, no answer will be lost. Poll will return None until the complete response is available. Poll will return the answers in the same order that the actions where preformed. If you want to wait for an action to complete you can use wait which will wait until the result if ready and return it. Wait will never return None. Blocking Example Usage: >>> >>> import sys >>> >>> from tp import netlib >>> >>> # Create the object and connect to the server >>> c = netlib.Connection("127.0.0.1", 6329) >>> if not c.connect(): >>> print "Could not connect to the server" >>> sys.exit(1) >>> >>> if not c.login("username", "password"): >>> print "Could not login" >>> sys.exit(1) >>> Non-Blocking Example Usage: >>> >>> import sys >>> >>> from tp import netlib >>> >>> # Create the object and connect to the server >>> c = netlib.Connection("127.0.0.1", 6329) >>> >>> c.connect() >>> c.login("username", "password") >>> >>> # Wait for the connection to be complete >>> if not c.wait(): >>> print "Could not connect to the server" >>> sys.exit(1) >>> >>> r = c.poll() >>> while r == None: >>> r = c.poll() >>> >>> # Do some other stuff! >>> pass >>> >>> if not r: >>> print "Could not login" >>> sys.exit(1) >>> >>> # Disconnect and cleanup >>> c.disconnect() >>> libtpproto-py-0.2.5/setup.py0000755000175000017500000000165610747221303014232 0ustar timtim#!/usr/bin/env python from tp.netlib import __version__ as version version = "%s.%s.%s" % version[0:3] from setuptools import setup setup( name ="libtpproto-py", version =version, license ="LGPL", description ="Network library for Thousand Parsec", long_description="""\ A library of code for both Servers and Clients to support the Thousand Parsec protocol. Includes support for: * Both non-blocking and blocking usage * Version 3 protocol * HTTP/HTTPS Tunneling * Generic Reference System """, author ="Tim Ansell", author_email="tim@thousandparsec.net", url ="http://www.thousandparsec.net", keywords ="thousand parsec space network empire building strategy game", namespace_packages = ['tp'], packages=[ \ 'tp', 'tp.netlib', 'tp.netlib.objects', 'tp.netlib.objects.ObjectExtra', 'tp.netlib.objects.OrderExtra', 'tp.netlib.discover', 'tp.netlib.discover.pyZeroconf', 'tp.netlib.support', ], ) libtpproto-py-0.2.5/libtpproto_py.egg-info/0000755000175000017500000000000011164431251017104 5ustar timtimlibtpproto-py-0.2.5/libtpproto_py.egg-info/PKG-INFO0000644000175000017500000000113011164431251020174 0ustar timtimMetadata-Version: 1.0 Name: libtpproto-py Version: 0.2.5 Summary: Network library for Thousand Parsec Home-page: http://www.thousandparsec.net Author: Tim Ansell Author-email: tim@thousandparsec.net License: LGPL Description: A library of code for both Servers and Clients to support the Thousand Parsec protocol. Includes support for: * Both non-blocking and blocking usage * Version 3 protocol * HTTP/HTTPS Tunneling * Generic Reference System Keywords: thousand parsec space network empire building strategy game Platform: UNKNOWN libtpproto-py-0.2.5/libtpproto_py.egg-info/SOURCES.txt0000644000175000017500000000715111164431251020774 0ustar timtimREADME setup.py libtpproto_py.egg-info/PKG-INFO libtpproto_py.egg-info/SOURCES.txt libtpproto_py.egg-info/dependency_links.txt libtpproto_py.egg-info/namespace_packages.txt libtpproto_py.egg-info/top_level.txt tp/__init__.py tp/netlib/GenericRS.py tp/netlib/__init__.py tp/netlib/client.py tp/netlib/common.py tp/netlib/httpdemo.py tp/netlib/server.py tp/netlib/twist.py tp/netlib/version.py tp/netlib/xstruct.py tp/netlib/discover/__init__.py tp/netlib/discover/avahi_browser.py tp/netlib/discover/avahi_server.py tp/netlib/discover/bonjour_browser.py tp/netlib/discover/browse.py tp/netlib/discover/game.py tp/netlib/discover/metaserver_browser.py tp/netlib/discover/metaserver_server.py tp/netlib/discover/pyzeroconf_browser.py tp/netlib/discover/pyzeroconf_server.py tp/netlib/discover/server.py tp/netlib/discover/servers.py tp/netlib/discover/pyZeroconf/Zeroconf.py tp/netlib/discover/pyZeroconf/__init__.py tp/netlib/objects/Account.py tp/netlib/objects/Base.py tp/netlib/objects/Board.py tp/netlib/objects/Board_Get.py tp/netlib/objects/Board_GetID.py tp/netlib/objects/Board_IDSequence.py tp/netlib/objects/Category.py tp/netlib/objects/Category_Add.py tp/netlib/objects/Category_Get.py tp/netlib/objects/Category_GetID.py tp/netlib/objects/Category_IDSequence.py tp/netlib/objects/Category_Remove.py tp/netlib/objects/Component.py tp/netlib/objects/Component_Get.py tp/netlib/objects/Component_GetID.py tp/netlib/objects/Component_IDSequence.py tp/netlib/objects/Connect.py tp/netlib/objects/Description.py tp/netlib/objects/Design.py tp/netlib/objects/Design_Add.py tp/netlib/objects/Design_Get.py tp/netlib/objects/Design_GetID.py tp/netlib/objects/Design_IDSequence.py tp/netlib/objects/Design_Modify.py tp/netlib/objects/Design_Remove.py tp/netlib/objects/Fail.py tp/netlib/objects/Feature.py tp/netlib/objects/Feature_Get.py tp/netlib/objects/Game.py tp/netlib/objects/Games_Get.py tp/netlib/objects/Header.py tp/netlib/objects/Login.py tp/netlib/objects/Message.py tp/netlib/objects/Message_Get.py tp/netlib/objects/Message_Insert.py tp/netlib/objects/Message_Remove.py tp/netlib/objects/OK.py tp/netlib/objects/Object.py tp/netlib/objects/ObjectDesc.py tp/netlib/objects/ObjectDesc_Get.py tp/netlib/objects/Object_GetById.py tp/netlib/objects/Object_GetID.py tp/netlib/objects/Object_GetID_ByContainer.py tp/netlib/objects/Object_GetID_ByPos.py tp/netlib/objects/Object_IDSequence.py tp/netlib/objects/Order.py tp/netlib/objects/OrderDesc.py tp/netlib/objects/OrderDesc_Get.py tp/netlib/objects/OrderDesc_GetID.py tp/netlib/objects/OrderDesc_IDSequence.py tp/netlib/objects/Order_Get.py tp/netlib/objects/Order_Insert.py tp/netlib/objects/Order_Probe.py tp/netlib/objects/Order_Remove.py tp/netlib/objects/Ping.py tp/netlib/objects/Player.py tp/netlib/objects/Player_Get.py tp/netlib/objects/Property.py tp/netlib/objects/Property_Get.py tp/netlib/objects/Property_GetID.py tp/netlib/objects/Property_IDSequence.py tp/netlib/objects/Redirect.py tp/netlib/objects/Resource.py tp/netlib/objects/Resource_Get.py tp/netlib/objects/Resource_GetID.py tp/netlib/objects/Resource_IDSequence.py tp/netlib/objects/Sequence.py tp/netlib/objects/TimeRemaining.py tp/netlib/objects/TimeRemaining_Get.py tp/netlib/objects/TurnFinished.py tp/netlib/objects/__init__.py tp/netlib/objects/constants.py tp/netlib/objects/ObjectExtra/Fleet.py tp/netlib/objects/ObjectExtra/Galaxy.py tp/netlib/objects/ObjectExtra/Planet.py tp/netlib/objects/ObjectExtra/StarSystem.py tp/netlib/objects/ObjectExtra/Universe.py tp/netlib/objects/ObjectExtra/Wormhole.py tp/netlib/objects/ObjectExtra/__init__.py tp/netlib/objects/OrderExtra/__init__.py tp/netlib/support/__init__.py tp/netlib/support/output.pylibtpproto-py-0.2.5/libtpproto_py.egg-info/namespace_packages.txt0000644000175000017500000000000311164431251023430 0ustar timtimtp libtpproto-py-0.2.5/libtpproto_py.egg-info/dependency_links.txt0000644000175000017500000000000111164431251023152 0ustar timtim libtpproto-py-0.2.5/libtpproto_py.egg-info/top_level.txt0000644000175000017500000000000311164431251021627 0ustar timtimtp libtpproto-py-0.2.5/tp/0000755000175000017500000000000011164431251013127 5ustar timtimlibtpproto-py-0.2.5/tp/__init__.py0000644000175000017500000000062310755215071015246 0ustar timtimtry: try: import pkg_resources pkg_resources.declare_namespace(__name__) except ImportError: import pkgutil __path__ = pkgutil.extend_path(__path__, __name__) except Exception, e: import warnings warnings.warn(e, RuntimeWarning) try: import modulefinder for p in __path__: modulefinder.AddPackagePath(__name__, p) except Exception, e: import warnings warnings.warn(e, RuntimeWarning) libtpproto-py-0.2.5/tp/netlib/0000755000175000017500000000000011164431251014404 5ustar timtimlibtpproto-py-0.2.5/tp/netlib/__init__.py0000644000175000017500000000067210755215071016527 0ustar timtim import sys from os import path sys.path.insert(0, path.dirname(__file__)) from version import version as vi from version import installpath as ip __version__ = vi __installpath__ = ip from client import ClientConnection, failed from server import Server, ServerConnection Connection = ClientConnection Server = Server ServerConnection = ServerConnection import objects from objects import constants import GenericRS sys.path.pop(0) libtpproto-py-0.2.5/tp/netlib/xstruct.py0000644000175000017500000002260211152473213016475 0ustar timtim"""\ An advanced version of pack/unpack which works with extra TP strutures. Everything is assumed to be network order, ie you don't need to prepend every struct with ! Normal stuff from the struct module: c Char b Int8 (8 bit integer) B UInt8 (8 bit unsigned integer) h Int16 (16 bit integer) H UInt16 (16 bit unsigned integer) i Int32 (32 bit integer) I UInt32 (32 bit unsigned integer) q Int64 (64 bit integer) Q UInt64 (64 bit unsigned integer) f float (32 bit floating point number) d double (64 bit floating point number) Extra stuff defined by this module: S String Y Padded String [ List Start (unsigned int32 length) ] List End { List Start (unsigned int64 length) } List End n SInt16 (16 bit semi-signed integer) j SInt32 (32 bit semi-signed integer) p SInt64 (64 bit semi-signed integer) t timestamp (32 bit unsigned integer) T timestamp (64 bit unsigned integer) The structure of the data in the list is described by the data inside the brackets. Example: [L] would be a list of unsigned longs It is actually transmitted as Obviously you can't calculate size of an xstruct string. The unpack function will return the unused data. """ import pprint import struct import sys import string from types import * # Squash errors about hex/oct import warnings _error = struct.error _pack = struct.pack _unpack = struct.unpack _calcsize = struct.calcsize semi = {'n':(16, 'H'), 'j':(32, 'I'), 'p':(64, 'Q')} smallints = "njbBhHiI" times = {'T':'Q', 't':'I'} def hexbyte(string): """\ Takes a string and prints out the bytes in hex. """ # return repr(string) s = "" for i in string: if (ord(i) >= ord('A') and ord(i) <= ord('z')) \ or (ord(i) >= ord('0') and ord(i) <= ord('9')) \ or (ord(i) == ord(" ")): s += "%s" % i else: s += "\\x%02x" % ord(i) # s += " " return s def smartsplit(struct, openbrace, closebrace): pos = 0 open = 1 while open > 0: if struct[pos] == openbrace: open += 1 if struct[pos] == closebrace: open -= 1 pos += 1 return struct[:pos-1], struct[pos:] noarg = "0123456789 !x" def pack(sstruct, *aargs): """\ Takes a structure string and the arguments to pack in the format specified by string. """ struct = sstruct args = list(aargs) args_no = len(aargs) output = "" try: while len(struct) > 0: char = struct[0] struct = struct[1:] if (not char in noarg) and len(args) == 0: raise TypeError('Ran out of arguments, still had %s%s left of the structure' % (char, struct)) if char == ' ' or char == '!': continue elif char == '{': # Find the closing brace substruct, struct = string.split(struct, '}', maxsplit=1) output += pack_list('L', substruct, args.pop(0)) elif char == '[': substruct, struct = smartsplit(struct, '[', ']') # Find the closing brace output += pack_list('I', substruct, args.pop(0)) elif char in 'Tt': output += pack_time(args.pop(0), times[char]) elif char == 'S': if not isinstance(args[0], (str, unicode, buffer)): raise TypeError("Argument should be an string (to pack to %s), not a %s" % (char, type(args[0]))) output += pack_string(args.pop(0)) elif char in string.digits: # Get all the numbers substruct = char while struct[0] in string.digits: substruct += struct[0] struct = struct[1:] # And the value the number applies to substruct += struct[0] struct = struct[1:] number = int(substruct[:-1]) if substruct[-1] == 's': output += _pack("!"+substruct, args.pop(0)) elif substruct[-1] == 'x': output += "\0" * number else: # Get the correct number of arguments new_args = [] while len(new_args) < number: new_args.append(args.pop(0)) output += apply(_pack, ["!"+substruct,] + new_args) else: if char in smallints and isinstance(args[0], long): args[0] = int(args[0]) a = args.pop(0) # Check the type of the argument if not isinstance(a, (int, long)): raise TypeError("Argument should be an int or long (to pack to %s), not a %s" % (char, type(a))) if char in semi.keys(): if a == -1: a = 2**semi[char][0]-1 char = semi[char][1] elif char.upper() == char and a < 0: raise TypeError("Argument must be positive (to pack to %s) not %s" % (char, a)) try: output += _pack("!"+char, a) except _error, e: # FIXME: Should do something better here! #print "Struct", char, "Args '%s'" % (a,) raise except (TypeError, _error), e: traceback = sys.exc_info()[2] while not traceback.tb_next is None: traceback = traceback.tb_next raise TypeError, "%i argument was the cause ('%s' %s)\n\t%s" % (len(aargs)-len(args)-1, sstruct, repr(aargs)[1:-1], str(e).replace('\n', '\n\t')), traceback if len(args) > 0: raise TypeError("Had too many arguments! Still got the following remaining %r" % args) return output def unpack(struct, s): """\ Takes a structure string and a data string. ((values1,value2), remaining_data) """ output = [] while len(struct) > 0: char = struct[0] struct = struct[1:] if char == ' ' or char == '!': continue elif char == '{': # Find the closing brace substruct, struct = string.split(struct, '}', maxsplit=1) data, s = unpack_list("L", substruct, s) output.append(data) elif char == '[': # Find the closing brace substruct, struct = smartsplit(struct, '[', ']') data, s = unpack_list("I", substruct, s) output.append(data) elif char in 'Tt': data, s = unpack_time(s, times[char]) output.append(data) elif char == 'S': data, s = unpack_string(s) output.append(data) elif char in string.digits: # Get all the numbers substruct = char while struct[0] in string.digits: substruct += struct[0] struct = struct[1:] # And the value the number applies to substruct += struct[0] struct = struct[1:] size = _calcsize(substruct) size = _calcsize(substruct) if size > len(s): raise TypeError("Not enough data for %s, needed %s bytes got %r (%s bytes)" % (substruct[1:], size, s, len(s))) data = _unpack("!"+substruct, s[:size]) s = s[size:] output += data else: if char in semi.keys(): substruct = "!"+semi[char][1] else: substruct = "!"+char size = _calcsize(substruct) if size > len(s): raise TypeError("Not enough data for %s, needed %s bytes got %r (%s bytes)" % (substruct[1:], size, s, len(s))) try: data = _unpack(substruct, s[:size]) except _error, e: print "Struct", substruct, "Args '%s'" % (s[:size],) raise s = s[size:] if char in semi.keys(): if data[0] == 2**semi[char][0]-1: data = (-1,) output += data return tuple(output), s def pack_list(length_struct, struct, args): """\ *Internal* Packs the id list so it can be send. """ # The length output = pack(length_struct, len(args)) # The list for id in args: if type(id) == ListType or type(id) == TupleType: args = [struct] + list(id) output += apply(pack, args) else: output += pack(struct, id) return output def unpack_list(length_struct, struct, s): """\ *Internal* Returns the first string from the input data and any remaining data. """ output, s = unpack(length_struct, s) length, = output list = [] for i in range(0, length): try: output, s = unpack(struct, s) except TypeError, e: raise TypeError("Problem unpacking list item (index %s): %s" % (i, e)) if len(output) == 1: list.append(output[0]) else: list.append(output) return list, s def pack_string(s): """\ *Internal* Prepares a string to be send out on a wire. It appends the string length to the beginning and adds a null terminator. """ s = unicode(s).encode('utf-8') temp = s return pack("!I", len(temp)) + temp import encodings import encodings.utf_8 def unpack_string(s): """\ *Internal* Returns the first string from the input data and any remaining data. """ # Totally empty string if len(s) == 0: return "", s # Remove the length try: (l, ), s = unpack("I", s) except TypeError, e: raise TypeError("Problem unpacking length of string: %s" % e) if l > 0: # Get the string, (we don't need the null terminator so nuke it) if len(s) < l: raise TypeError("Not enough data for string, length said %s bytes got %r (%s bytes)" % (l, s, len(s))) output = s[:l] s = s[l:] # Remove any extra null terminators. if output[-1] == '\0': output = output[:-1] # Make sure the string is a valid utf-8 string # If the sender is well behaved this does nothing... output = encodings.utf_8.decode(output, errors='ignore')[0] return output, s else: return "", s import time from datetime import datetime def unpack_time(s, type='I'): """\ *Internal* Returns the datetime object and any remaining data. """ try: (l,), s = unpack("!"+type, s) except TypeError, e: raise TypeError("Problem unpacking time: %s" % e) if l < 0: return None return datetime.fromtimestamp(l), s def pack_time(t, type='I'): """\ *Internal* Returns the datetime object and any remaining data. """ if t is None: t = -1 elif isinstance(t, datetime): t = long(time.mktime(t.timetuple())) elif isinstance(t, float): # FIXME: This should be a depreciated warning... print "Warning! pack_time called with float" t = long(t) elif not isinstance(t, (int, long)): raise TypeError("Not a valid type for pack_time") s = pack("!"+type, t) return s libtpproto-py-0.2.5/tp/netlib/server.py0000644000175000017500000002262210755215071016275 0ustar timtim# Python Imports import errno import select import socket import sys import traceback # Local imports import objects constants = objects.constants from common import Connection, BUFFER_SIZE socket_error = (socket.error,) socket_fatal = (IOError,) class ServerConnection(Connection): def __init__(self, s, address, debug=False): Connection.__init__(self) self.address = address self.setup(s, debug=debug, nb=True) self.poll = self.initalpoll def initalpoll(self): """\ Checks to see if any packets are on the line """ print "Inital Poll" buffer = self.buffered['bytes-received'] buffer.write(self.s.recv(BUFFER_SIZE)) if buffer.peek(2) == "TP": if self.debug: print "Got a normal tp connection..." self.poll = self.tppoll return self.poll() if buffer.peek(17).startswith("POST /"): if self.debug: print "Got a http connection..." self.s.recv(len(self.buffer)) # Clear all the already recived data... self.poll = self.httppoll return self.poll() # We have gotten to much data, we need to close this connection now if buffer.left() > 18: raise IOError("No valid connection header found...") def httppoll(self): print "HTTP Poll" buffer = self.buffered['bytes-received'] buffer.write(self.s.recv(BUFFER_SIZE)) # FIXME: This is broken if self.buffer.endswith("\r\n\r\n"): if self.debug: print "Finished the http headers..." print self.buffer # Send the http headers self.s.send("HTTP/1.0 200 OK") self.s.send("Cache-Control: no-cache, private\n") self.s.send("Content-Type: application/binary\n") self.s.send("\n") self.buffer = "" self.poll = self.tppoll return self.poll() # We have gotten to much data, we need to close this connection now if buffer.left() > 1024: raise IOError("HTTP Request was to large!") def tppoll(self): print "TP Poll" # Get the packets try: self._recv(-1) except socket_error, e: print self, e # FIXME: Possible denial of service attack... # If this connection keeps getting data - none of the other connections get serviced... # Fix: # Only get data from the socket once, service all data # Poll will go off with the data checked = set() while len(set(self.buffered['frames-received'].keys()).difference(checked)) > 0: sequences = self.buffered['frames-received'].keys() sequences.sort() p = self._recv(sequences[0]) checked.add(p) if not p: continue success = False bases = [objects.Header.mapping[p._type]] while len(bases) > 0: c = bases.pop(0) function = "On" + c.__name__ if hasattr(self, function): print function try: success = getattr(self, function)(p) except: type, val, tb = sys.exc_info() print ''.join(traceback.format_exception(type, val, tb)) break bases += list(c.__bases__) if not success: self._send(objects.Fail(p.sequence, constants.FAIL_PERM, "Service unavalible.")) def _description_error(self, p): self._send(objects.Fail(p.sequence, constants.FAIL_FRAME, "Packet which doesn't have a possible description.")) def _error(self, p): type, val, tb = sys.exc_info() print ''.join(traceback.format_exception(type, val, tb)) self._send(objects.Fail(p.sequence, constants.FAIL_FRAME, "Packet wasn't valid.")) def OnInit(self): pass def OnConnect(self, p): self._send(objects.OK(p.sequence, "Welcome to py-server!")) return True def OnPing(self, p): self._send(objects.OK(p.sequence, "PONG!")) return True class SSLSocket(object): def __init__(self, s, pem): global socket_error, socket_fatal try: import OpenSSL.crypto import OpenSSL.SSL as SSL context = SSL.Context(SSL.SSLv23_METHOD) context.set_verify(SSL.VERIFY_NONE, lambda x: True) context.use_certificate(OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, pem)) context.use_privatekey(OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, pem)) self.s = SSL.Connection(context, s) socket_error = tuple([SSL.WantReadError] + list(socket_error)) socket_error = tuple([SSL.WantWriteError] + list(socket_error)) socket_fatal = tuple([SSL.Error] + list(socket_fatal)) print "Found pyopenssl" return except ImportError, e: print "Unable to import pyopenssl" try: from tempfile import NamedTemporaryFile import M2Crypto import M2Crypto.SSL as SSL context = SSL.Context('sslv23') context.set_verify(SSL.verify_none, 4, lambda x: True) f = NamedTemporaryFile(mode='w+b') f.write(pem); f.flush() context.load_cert(f.name) f.close() self.s = SSL.Connection(context, s) socket_fatal = tuple([SSL.SSLError] + list(socket_fatal)) return except ImportError, e: print "Unable to import M2Crypto" raise ImportError("Unable to find SSL library") def __getattr__(self, key): return getattr(self.s, key) def __str__(self): return object.__str__(self) class Server(object): """\ Select based, single threaded, polling server. """ handler = ServerConnection def __init__(self, address, port=None, sslport=None, ports=None, sslports=None): if ports is None: ports = [] if not port is None: ports.append(port) if sslports is None: sslports = [] if not sslport is None: sslports.append(sslport) self.ports = ports self.sslports = sslports print "Ports", self.ports, self.sslports self.s = [] for port in ports+sslports: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if port in sslports: pem = """\ -----BEGIN RSA PRIVATE KEY----- MIIBOwIBAAJBAOTnGJZ1npXzEpchNblVMLOF7Bnv4R+zTrd93nweSEZb6u024o+U y2Y9s/79f2ytS8csVVxjrFn7Bisw6maXz0MCAwEAAQJAfS7JKpe+l+DsPMyDtgyZ 6sQF4BVo98428XCbuSNSgW8AaWGyqIC1baf0FvNE8OSNrO43Mhqy9C2BG5YQve6K sQIhAPwHcln2CiPGJ6Rru1SF3MEvC8WImmTrtWVA9IHVNXDbAiEA6IJepK7qvtYc SoKObjZ+nG0OyGi9b6M9GSO52kWbE7kCIQC7TcV8elB62c+ocLBeVsYDhLVY7vbf vhWn1KhivVPkNQIhAKaRLwg/n0BT1zSxzyO5un6JyntcPcoKYazu4SgzkWNRAiBn qEzVAP7TdKkfE2CtVvd2JkGQHQmD7bgOkmhZTIpENg== -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- MIICTjCCAfigAwIBAgIBADANBgkqhkiG9w0BAQQFADBWMRkwFwYDVQQKExBXZWJt aW4gV2Vic2VydmVyMRAwDgYDVQQLEwdsZXN0ZXIuMQowCAYDVQQDFAEqMRswGQYJ KoZIhvcNAQkBFgxyb290QGxlc3Rlci4wHhcNMDQxMDA1MTU0NzQ2WhcNMDkxMDA0 MTU0NzQ2WjBWMRkwFwYDVQQKExBXZWJtaW4gV2Vic2VydmVyMRAwDgYDVQQLEwds ZXN0ZXIuMQowCAYDVQQDFAEqMRswGQYJKoZIhvcNAQkBFgxyb290QGxlc3Rlci4w XDANBgkqhkiG9w0BAQEFAANLADBIAkEA5OcYlnWelfMSlyE1uVUws4XsGe/hH7NO t33efB5IRlvq7Tbij5TLZj2z/v1/bK1LxyxVXGOsWfsGKzDqZpfPQwIDAQABo4Gw MIGtMB0GA1UdDgQWBBTqK6UJRH7+NpEwgEmJzse910voYTB+BgNVHSMEdzB1gBTq K6UJRH7+NpEwgEmJzse910voYaFapFgwVjEZMBcGA1UEChMQV2VibWluIFdlYnNl cnZlcjEQMA4GA1UECxMHbGVzdGVyLjEKMAgGA1UEAxQBKjEbMBkGCSqGSIb3DQEJ ARYMcm9vdEBsZXN0ZXIuggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQAD QQBkj8SEY4RAm9WRDtPJ8qPgmIHeiiwDKsJup1ixsbiQOAV7zG/pMCYM4VWVhmR+ trYiuEhD5HiV/W6DM4WBMg+5 -----END CERTIFICATE-----""" try: s = SSLSocket(s, pem) except ImportError: print "Unable to find a SSL library which I can use :/" continue s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((address, port)) s.listen(5) s.setblocking(False) self.s.append(s) self.connections = {} def poll(self): """\ This function is called in the serve_forever loop. If data is coming in then it is called every time data is ready to be read. Otherwise it should be called at roughly every 100ms. """ pass def serve_forever(self, timeout=None): poller = select.poll() for s in self.s: poller.register(s, select.POLLIN) self.connections[s.fileno()] = s oldready = [] while True: ready = [] errors = [] # Check if there is any socket to accept or with data try: events = poller.poll(timeout) except select.error, e: if e[0] == errno.EINTR: continue raise for fileno, event in events: if event & select.POLLIN: ready.append(self.connections[fileno]) if event & (select.POLLERR|select.POLLHUP|select.POLLNVAL) > 0: errors.append(self.connections[fileno]) self.poll() # Poll any ready sockets.. for s in ready+oldready: if s in self.s: # Accept a new connection n, address = s.accept() print "Accepting connection from %s on %s" % (address, s.getsockname()) connection = self.handler(n, address, debug=self.debug) poller.register(connection, select.POLLIN|select.POLLERR|select.POLLHUP|select.POLLNVAL) self.connections[connection.fileno()] = connection else: # Poll the connection as it's ready try: s.poll() if s in oldready: oldready.remove(s) except socket_error, e: if s in oldready: oldready.remove(s) errors.append(s) else: oldready.append(s) except socket_fatal, e: print "fatal fallout", s, e errors.append(s) except Exception, e: print "Ekk! uncaught exception, closing this connection..." type, val, tb = sys.exc_info() print ''.join(traceback.format_exception(type, val, tb)) print e errors.append(s) # Cleanup any old sockets for s in errors: print "Removing", s try: s.s.close() except Exception, e: print "Unable to close socket", e try: poller.unregister(s) except Exception, e: print "Unable to unregister socket", e try: del self.connections[s.fileno()] except KeyError, e: print e if __name__ == "__main__": port = 6924 while True: try: s = Server("127.0.0.1", port) except: print "This port in use...", port port += 1 continue s.serve_forever() libtpproto-py-0.2.5/tp/netlib/httpdemo.py0000644000175000017500000000023010755215071016602 0ustar timtim import client import server c = client.ClientConnection("http://127.0.0.1:6923") #c = client.ClientConnection("http://mithro.dyndns.org") c.connect() libtpproto-py-0.2.5/tp/netlib/objects/0000755000175000017500000000000011164431251016035 5ustar timtimlibtpproto-py-0.2.5/tp/netlib/objects/Account.py0000644000175000017500000000162710755215071020016 0ustar timtim from xstruct import pack from Header import Processed class Account(Processed): """\ The Account packet consists of: * A String, a requested username * A String, the password * A String, the email address * A String, a comment FIXME: * A String, the game (some servers may support multiple games on one server) """ no = 62 struct = "SSSS" def __init__(self, sequence, username, password, email, comment): Processed.__init__(self, sequence) # Length is: # * 4 bytes (uint32 - string length) # * the string # self.length = \ 4 + len(username) + \ 4 + len(password) + \ 4 + len(email) + \ 4 + len(comment) self.username = username self.password = password self.email = email self.comment = comment def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.username, self.password, self.email, self.comment) return output libtpproto-py-0.2.5/tp/netlib/objects/TimeRemaining.py0000644000175000017500000000073411051706206021143 0ustar timtim from xstruct import pack from Header import Processed class TimeRemaining(Processed): """\ The TimeRemaining packet consists of: * UInt32, Seconds left till the turn ends. """ no = 15 struct = "j" def __init__(self, sequence, time): Processed.__init__(self, sequence) # Length is: # * 4 bytes UInt32 # self.length = 4 self.time = time def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.time) return output libtpproto-py-0.2.5/tp/netlib/objects/Board_GetID.py0000644000175000017500000000012710755215071020457 0ustar timtimfrom Base import GetIDSequence class Board_GetID(GetIDSequence): """\ """ no = 35 libtpproto-py-0.2.5/tp/netlib/objects/Design_GetID.py0000644000175000017500000000013010755215071020633 0ustar timtimfrom Base import GetIDSequence class Design_GetID(GetIDSequence): """\ """ no = 52 libtpproto-py-0.2.5/tp/netlib/objects/__init__.py0000644000175000017500000002035511051706206020153 0ustar timtim# Mapping to store between type numbers and classes mapping = {} # Constants import constants constants = constants # Header from objects.Header import Header, Processed, SetVersion, GetVersion Header = Header Header.mapping = mapping Processed = Processed SetVersion = SetVersion GetVersion = GetVersion from objects.Redirect import Redirect Redirect = Redirect # Special Description Stuff from objects.Description import DescriptionError, Describable, Description DescriptionError = DescriptionError Describable = Describable Description = Description # Base from objects.Base import GetWithID GetWithID = GetWithID from objects.Base import GetWithIDandSlot GetWithIDandSlot = GetWithIDandSlot from objects.Base import GetIDSequence GetIDSequence = GetIDSequence from objects.Base import IDSequence IDSequence = IDSequence # Generic Responses from objects.OK import OK OK = OK mapping[OK.no] = OK from objects.Fail import Fail Fail = Fail mapping[Fail.no] = Fail from objects.Sequence import Sequence Sequence = Sequence mapping[Sequence.no] = Sequence # Connecting from objects.Connect import Connect Connect = Connect mapping[Connect.no] = Connect from objects.Login import Login Login = Login mapping[Login.no] = Login from objects.Ping import Ping Ping = Ping mapping[Ping.no] = Ping from objects.Account import Account Account = Account mapping[Account.no] = Account # Features from objects.Feature_Get import Feature_Get Feature_Get = Feature_Get mapping[Feature_Get.no] = Feature_Get from objects.Feature import Feature Feature = Feature mapping[Feature.no] = Feature # Objects from objects.Object_GetById import Object_GetById Object_GetById = Object_GetById mapping[Object_GetById.no] = Object_GetById from objects.Object import Object Object = Object mapping[Object.no] = Object from objects.Object_GetID import Object_GetID Object_GetID = Object_GetID mapping[Object_GetID.no] = Object_GetID from objects.Object_GetID_ByPos import Object_GetID_ByPos Object_GetID_ByPos = Object_GetID_ByPos mapping[Object_GetID_ByPos.no] = Object_GetID_ByPos from objects.Object_GetID_ByContainer import Object_GetID_ByContainer Object_GetID_ByContainer = Object_GetID_ByContainer mapping[Object_GetID_ByContainer.no] = Object_GetID_ByContainer from objects.Object_IDSequence import Object_IDSequence Object_IDSequence = Object_IDSequence mapping[Object_IDSequence.no] = Object_IDSequence from objects.ObjectDesc import descriptions ObjectDescs = descriptions # Orders from objects.OrderDesc_Get import OrderDesc_Get OrderDesc_Get = OrderDesc_Get mapping[OrderDesc_Get.no] = OrderDesc_Get from objects.OrderDesc import OrderDesc, DynamicBaseOrder, descriptions OrderDesc = OrderDesc DynamicBaseOrder = DynamicBaseOrder OrderDescs = descriptions mapping[OrderDesc.no] = OrderDesc from objects.OrderDesc_GetID import OrderDesc_GetID OrderDesc_GetID = OrderDesc_GetID mapping[OrderDesc_GetID.no] = OrderDesc_GetID from objects.OrderDesc_IDSequence import OrderDesc_IDSequence OrderDesc_IDSequence = OrderDesc_IDSequence mapping[OrderDesc_IDSequence.no] = OrderDesc_IDSequence from objects.Order_Get import Order_Get Order_Get = Order_Get mapping[Order_Get.no] = Order_Get from objects.Order import Order Order = Order mapping[Order.no] = Order from objects.Order_Insert import Order_Insert Order_Insert = Order_Insert mapping[Order_Insert.no] = Order_Insert from objects.Order_Remove import Order_Remove Order_Remove = Order_Remove mapping[Order_Remove.no] = Order_Remove from objects.Order_Probe import Order_Probe Order_Probe = Order_Probe mapping[Order_Probe.no] = Order_Probe # Time from objects.TimeRemaining_Get import TimeRemaining_Get TimeRemaining_Get = TimeRemaining_Get mapping[TimeRemaining_Get.no] = TimeRemaining_Get from objects.TimeRemaining import TimeRemaining TimeRemaining = TimeRemaining mapping[TimeRemaining.no] = TimeRemaining from objects.TurnFinished import TurnFinished TurnFinished = TurnFinished mapping[TurnFinished.no] = TurnFinished # Messages from objects.Board_Get import Board_Get Board_Get = Board_Get mapping[Board_Get.no] = Board_Get from objects.Board import Board Board = Board mapping[Board.no] = Board from objects.Board_GetID import Board_GetID Board_GetID = Board_GetID mapping[Board_GetID.no] = Board_GetID from objects.Board_IDSequence import Board_IDSequence Board_IDSequence = Board_IDSequence mapping[Board_IDSequence.no] = Board_IDSequence from objects.Message_Get import Message_Get Message_Get = Message_Get mapping[Message_Get.no] = Message_Get from objects.Message import Message Message = Message mapping[Message.no] = Message from objects.Message_Insert import Message_Insert Message_Insert = Message_Insert mapping[Message_Insert.no] = Message_Insert from objects.Message_Remove import Message_Remove Message_Remove = Message_Remove mapping[Message_Remove.no] = Message_Remove # Resource Stuff from objects.Resource_Get import Resource_Get Resource_Get = Resource_Get mapping[Resource_Get.no] = Resource_Get from objects.Resource import Resource Resource = Resource mapping[Resource.no] = Resource from objects.Resource_GetID import Resource_GetID Resource_GetID = Resource_GetID mapping[Resource_GetID.no] = Resource_GetID from objects.Resource_IDSequence import Resource_IDSequence Resource_IDSequence = Resource_IDSequence mapping[Resource_IDSequence.no] = Resource_IDSequence # Design Categories from objects.Category_Get import Category_Get Category_Get = Category_Get mapping[Category_Get.no] = Category_Get from objects.Category import Category Category = Category mapping[Category.no] = Category from objects.Category_Add import Category_Add Category_Add = Category_Add mapping[Category_Add.no] = Category_Add from objects.Category_Remove import Category_Remove Category_Remove = Category_Remove mapping[Category_Remove.no] = Category_Remove from objects.Category_GetID import Category_GetID Category_GetID = Category_GetID mapping[Category_GetID.no] = Category_GetID from objects.Category_IDSequence import Category_IDSequence Category_IDSequence = Category_IDSequence mapping[Category_IDSequence.no] = Category_IDSequence # Design Designs from objects.Design_Get import Design_Get Design_Get = Design_Get mapping[Design_Get.no] = Design_Get from objects.Design import Design Design = Design mapping[Design.no] = Design from objects.Design_Add import Design_Add Design_Add = Design_Add mapping[Design_Add.no] = Design_Add from objects.Design_Remove import Design_Remove Design_Remove = Design_Remove mapping[Design_Remove.no] = Design_Remove from objects.Design_GetID import Design_GetID Design_GetID = Design_GetID mapping[Design_GetID.no] = Design_GetID from objects.Design_IDSequence import Design_IDSequence Design_IDSequence = Design_IDSequence mapping[Design_IDSequence.no] = Design_IDSequence # Design Components from objects.Component_Get import Component_Get Component_Get = Component_Get mapping[Component_Get.no] = Component_Get from objects.Component import Component Component = Component mapping[Component.no] = Component from objects.Component import Component Component = Component mapping[Component.no] = Component from objects.Component_GetID import Component_GetID Component_GetID = Component_GetID mapping[Component_GetID.no] = Component_GetID from objects.Component_IDSequence import Component_IDSequence Component_IDSequence = Component_IDSequence mapping[Component_IDSequence.no] = Component_IDSequence # Design Properties from objects.Property_Get import Property_Get Property_Get = Property_Get mapping[Property_Get.no] = Property_Get from objects.Property import Property Property = Property mapping[Property.no] = Property from objects.Property import Property Property = Property mapping[Property.no] = Property from objects.Property_GetID import Property_GetID Property_GetID = Property_GetID mapping[Property_GetID.no] = Property_GetID from objects.Property_IDSequence import Property_IDSequence Property_IDSequence = Property_IDSequence mapping[Property_IDSequence.no] = Property_IDSequence # Player frames from objects.Player_Get import Player_Get Player_Get = Player_Get mapping[Player_Get.no] = Player_Get from objects.Player import Player Player = Player mapping[Player.no] = Player # Game frames from objects.Game import Game Game = Game mapping[Game.no] = Game from objects.Games_Get import Games_Get Games_Get = Games_Get mapping[Games_Get.no] = Games_Get libtpproto-py-0.2.5/tp/netlib/objects/Sequence.py0000644000175000017500000000123410755215071020164 0ustar timtim from xstruct import pack from Header import Processed class Sequence(Processed): """\ The Sequence packet consists of: * A uint32 error code """ no = 2 struct = "I" def __init__(self, sequence, number): if 1 > sequence: raise ValueError("Sequence is a reply packet so needs a valid sequence number (%i)" % sequence) Processed.__init__(self, sequence) # Length is: # * 4 bytes (uint32 - error code # * 4 bytes (uint32 - string length) # * the string # * null terminator # self.length = 4 self.number = number def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.number) return output libtpproto-py-0.2.5/tp/netlib/objects/Component_IDSequence.py0000644000175000017500000000013210755215071022417 0ustar timtimfrom Base import IDSequence class Component_IDSequence(IDSequence): """\ """ no = 57 libtpproto-py-0.2.5/tp/netlib/objects/ObjectDesc_Get.py0000644000175000017500000000012211051706206021206 0ustar timtimfrom Base import GetWithID class Object_DescGet(GetWithID): """\ """ no = -1 libtpproto-py-0.2.5/tp/netlib/objects/Ping.py0000644000175000017500000000072410755215071017314 0ustar timtim from xstruct import pack from Header import Processed class Ping(Processed): """\ The Ping frame is empty and is only used to keep a connection alive that would possibly terminate otherwise. No more then 1 ping frame every second should be sent and then only if no other data has been sent. """ no = 27 struct = "" def __init__(self, sequence): Processed.__init__(self, sequence) self.length = 0 def __str__(self): return Processed.__str__(self) libtpproto-py-0.2.5/tp/netlib/objects/Category_IDSequence.py0000644000175000017500000000013110755215071022231 0ustar timtimfrom Base import IDSequence class Category_IDSequence(IDSequence): """\ """ no = 46 libtpproto-py-0.2.5/tp/netlib/objects/Message.py0000644000175000017500000000302710755215071020002 0ustar timtim from warnings import warn from xstruct import pack from Header import Processed import GenericRS class Message(Processed): """\ The Message packet consists of: * UInt32, ID of Board * SInt32, Slot of Message * a list of UInt32, Type of message * a string, Subject of message * a string, Body of the message * UInt32, turn number the messages was generated on * a list of Int32, UInt32 references """ no = 19 struct = "Ij[I]SSI[iI]" def __init__(self, sequence, id, slot, types, subject, body, turn, references): Processed.__init__(self, sequence) if not isinstance(references, GenericRS.References): references = GenericRS.References(references) # Length is: # self.length = 4 + 4 + \ 4 + len(types)*4 + \ 4 + len(subject) + \ 4 + len(body) + \ 4 + \ 4 + len(references)*(4*4) self.id = id self.slot = slot self.types = types self.subject = subject self.body = body self.turn = turn self.references = references def get_types(self): warn("Messages.types is deperciated use the Message.references instead.", DeprecationWarning, stacklevel=2) return self.__types def set_types(self, value): self.__types = value if len(value) != 0: warn("Messages.types is deperciated use the Message.references instead.", DeprecationWarning, stacklevel=2) types = property(get_types, set_types) def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.id, self.slot, [], self.subject, self.body, self.turn, self.references.references) return output libtpproto-py-0.2.5/tp/netlib/objects/Category_GetID.py0000644000175000017500000000013210755215071021201 0ustar timtimfrom Base import GetIDSequence class Category_GetID(GetIDSequence): """\ """ no = 45 libtpproto-py-0.2.5/tp/netlib/objects/Property.py0000644000175000017500000000305010755215071020236 0ustar timtim from xstruct import pack from Header import Processed class Property(Processed): """\ The Property packet consists of: * a UInt32, property ID * a UInt64, the last modified time * a list of, * a UInt32, category ID the property is in * a UInt32, rank of property * a String, name of property (must be a valid TPCL identifier) * a String, display name of property * a String, description of the property * a String, NCL "Calculate" function * a String, NCL "Requirements" function * a UInt32, a list of special attributes, possible attributes include * 0x000001 - Hidden, do not display this property - it is only use in calculations """ no = 59 struct = "IT[I]ISSSSS" def __init__(self, sequence, id, modify_time, categories, rank, name, display_name, description, calculate, requirements): Processed.__init__(self, sequence) # Length is: # self.length = 4 + 8 + \ 4 + len(categories)*4 + \ 4 + \ 4 + len(name) + \ 4 + len(display_name) + \ 4 + len(description) + \ 4 + len(calculate) + \ 4 + len(requirements) self.id = id self.modify_time = modify_time self.categories = categories self.rank = rank self.name = name self.display_name = display_name self.description = description self.calculate = calculate self.requirements = requirements def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.id, self.modify_time, self.categories, self.rank, self.name, self.display_name, self.description, self.calculate, self.requirements) return output libtpproto-py-0.2.5/tp/netlib/objects/Component_GetID.py0000644000175000017500000000013310755215071021367 0ustar timtimfrom Base import GetIDSequence class Component_GetID(GetIDSequence): """\ """ no = 56 libtpproto-py-0.2.5/tp/netlib/objects/Design_Add.py0000644000175000017500000000067410755215071020404 0ustar timtim import copy from Design import Design class Design_Add(Design): no = 49 def __init__(self, sequence, \ id, \ *args, **kw): self.no = 49 apply(Design.__init__, (self, sequence, id,)+args, kw) def __str__(self): output1 = pack(self.struct, self.id, -1, self.categories, self.name, self.desc, -1, self.owner, self.components, "", []) self.length = len(output1) output2 = Processed.__str__(self) return output2+output1 libtpproto-py-0.2.5/tp/netlib/objects/Games_Get.py0000644000175000017500000000057310755215071020254 0ustar timtim from xstruct import pack from Header import Processed class Games_Get(Processed): """\ The Games_Get packet has no data. """ no = 65 struct = "" def __init__(self, sequence): Processed.__init__(self, sequence) # Length is: # * 4 bytes (uint32 - error code # * 4 bytes (uint32 - string length) # * the string # * null terminator # self.length = 0 libtpproto-py-0.2.5/tp/netlib/objects/Design_Modify.py0000644000175000017500000000032010755215071021127 0ustar timtim import copy from Design import Design class Design_Modify(Design): no = 50 def __init__(self, sequence, \ id, \ *args, **kw): self.no = 50 apply(Design.__init__, (self, sequence, id,)+args, kw) libtpproto-py-0.2.5/tp/netlib/objects/Object_GetID_ByPos.py0000644000175000017500000000135610755215071021757 0ustar timtim from xstruct import pack from Header import Processed class Object_GetID_ByPos(Processed): """\ The Object_GetIDByPos packet consists of: * 3 by int64, center of sphere * a uint64, radius of sphere """ no = 29 struct = "3q Q" def __init__(self, sequence, posx, posy, posz, size): if sequence != 0: raise ValueError("Object_Get is a normal packet so needs a zero sequence number (%i)" % sequence) Processed.__init__(self, sequence) # Length is: # * 24 bytes (position) # * 8 bytes (radius) self.length = 32 self.pos = [posx, posy, posz] self.size = size def __str__(self): output = Processed.__str__(self) output += pack(self.struct, \ self.pos[0], self.pos[1], self.pos[2], self.size) return output libtpproto-py-0.2.5/tp/netlib/objects/Description.py0000644000175000017500000000237011051706206020674 0ustar timtim from xstruct import pack, unpack from Header import Processed class DescriptionError(Exception): """\ Thrown by objects which can't find the description which describes them. """ pass class Describable(Processed): """\ The Describable packet uses other packets to describe "extra" details about the packet. """ substruct = "" def __process__(self, data, **kw): # Unpack the first lot of data args, leftover = unpack(self.struct, data) self.__init__(self.sequence, *args, **kw) # Unpack the second lot of data try: moreargs, leftover2 = unpack(self.substruct, leftover) if len(leftover2) > 0: raise ValueError("\nError when processing %s.\nClass Structure: %s, Given Data: %r\nExtra data found: %r " % (self.__class__, self.substruct, leftover, leftover2)) except TypeError, e: raise ValueError("\nError when processing %s.\nClass Structure: %s, Given Data: %r\nNot enough data found: %s" % (self.__class__, self.substruct, leftover, e)) self.__init__(self.sequence, *(args + moreargs)) class Description(Processed): """\ The Description packet contains the description of another type of packet. The id is equal to the subtype. """ def __init__(self, sequence, id): Processed.__init__(self, sequence) self.id = id libtpproto-py-0.2.5/tp/netlib/objects/OK.py0000644000175000017500000000126610755215071016732 0ustar timtim from xstruct import pack from Header import Processed class OK(Processed): """\ The OK packet consists of: * A string (the string can be safely ignored - however it may contain useful information for debugging purposes) """ no = 0 struct = "S" def __init__(self, sequence, s=""): if 1 > sequence: raise ValueError("OK is a reply packet so needs a valid sequence number (%i)" % sequence) Processed.__init__(self, sequence) # Length is: # * 4 bytes (32 bit integer) # * the string # * null terminator # self.length = 4 + len(s) self.s = s def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.s) return output libtpproto-py-0.2.5/tp/netlib/objects/OrderDesc_GetID.py0000644000175000017500000000013310755215071021277 0ustar timtimfrom Base import GetIDSequence class OrderDesc_GetID(GetIDSequence): """\ """ no = 32 libtpproto-py-0.2.5/tp/netlib/objects/Resource_Get.py0000644000175000017500000000025210755215071021001 0ustar timtimfrom Base import GetWithID class Resource_Get(GetWithID): """\ The Resource_Get packet consists of: * A list of uint32, IDs of message boards to get. """ no = 22 libtpproto-py-0.2.5/tp/netlib/objects/Base.py0000644000175000017500000000571211051706206017266 0ustar timtim from xstruct import pack from Header import Processed class GetWithID(Processed): """\ A Get with ID frame consist of: * a list of UInt32, IDs of the things requested This packet is used to get things using their IDs. Such things would be objects, message boards, etc. """ struct = "[j]" def __init__(self, sequence, ids): Processed.__init__(self, sequence) # Length is: # * 4 bytes (uint32 - id) # self.length = 4 + 4 * len(ids) self.ids = ids def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.ids) return output class GetWithIDandSlot(Processed): """\ Get with ID and Slot frame consist of: * a UInt32, id of base thing * a list of UInt32, slot numbers of contained things be requested This packet is used to get things which are in "slots" on a parent. Examples would be orders (on objects), messages (on boards), etc. Note: If this is really a Remove frame then slot numbers should be in decrementing value if you don't want strange things to happen. (IE 10, 4, 1) """ struct = "I[j]" def __init__(self, sequence, id, slots): Processed.__init__(self, sequence) # Length is: # * 4 bytes (uint32 - id) # self.length = 4 + 4 + 4 * len(slots) self.id = id self.slots = slots def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.id, self.slots) return output class GetIDSequence(Processed): """\ Get ID Sequence frame consist of: * a SInt32, the sequence key * a UInt32, the starting number in the sequence * a SInt32, the number of IDs to get Requirements: * To start a sequence, the key of -1 should be sent in the first request * Subsequent requests in a sequence should use the key which is returned * All requests must be continuous and ascending * Only one sequence key can exist at any time, starting a new sequence causes the old one to be discarded * Key persist for only as long as the connection remains and there are IDs left in the sequence """ struct = "jIj" def __init__(self, sequence, key, start, amount): Processed.__init__(self, sequence) self.length = 4 + 4 + 4 self.key = key self.start = start self.amount = amount def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.key, self.start, self.amount) return output class IDSequence(Processed): """\ ID Sequence frame consist of: * a SInt32, the sequence key * a SInt32, the number of IDs remaining * a list of * a UInt32, the IDs * a UInt64, the last modified time of this ID These IDs are not guaranteed to be in any order. """ struct = "jj[IT]" def __init__(self, sequence, key, left, ids): Processed.__init__(self, sequence) self.length = 4 + 4 + 4 + (4+8) * len(ids) self.key = key self.left = left self.ids = ids def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.key, self.left, self.ids) return output libtpproto-py-0.2.5/tp/netlib/objects/Player_Get.py0000644000175000017500000000031210755215071020443 0ustar timtimfrom Base import GetWithID class Player_Get(GetWithID): """\ The Player_Get packet consists of: * A list of uint32, IDs of players to get. A player ID of 0 is the current player. """ no = 39 libtpproto-py-0.2.5/tp/netlib/objects/Category_Add.py0000644000175000017500000000031610755215071020741 0ustar timtim import copy from Order import Order class Category_Add(Order): no = 43 def __init__(self, sequence, \ id, \ *args, **kw): self.no = 43 apply(Category.__init__, (self, sequence, id,)+args, kw) libtpproto-py-0.2.5/tp/netlib/objects/Design.py0000644000175000017500000000305510755215071017630 0ustar timtim from xstruct import pack from Header import Processed class Design(Processed): """\ The Design packet consists of: * a SInt32, design ID * a SInt64, the last modified time * a list of, * a UInt32, category this design is in * a String, name of the design * a String, description of the design * a SInt32, number of times in use * a SInt32, owner of the design * a list of, * a UInt32, the ID of the component * a UInt32, the number of this component * a String, design feedback * a list of, * a UInt32, property id * a String, property display string """ no = 48 struct = "jT[I]SSjj[II]S[IS]" def __init__(self, sequence, id, modify_time, categories, name, desc, used, owner, components, feedback, properties): Processed.__init__(self, sequence) # Length is: # self.length = 4 + 8 + \ 4 + len(categories)*4 + \ 4 + len(name) + \ 4 + len(desc) + \ 4 + 4 + \ 4 + len(components)*8 + \ 4 + len(feedback) + \ 4 for x, s in properties: self.length += 4 + 4 + len(s) self.id = id self.modify_time = modify_time self.categories = categories self.name = name self.desc = desc self.used = used self.owner = owner self.components = components self.feedback = feedback self.properties = properties def __str__(self): output1 = pack(self.struct, self.id, self.modify_time, self.categories, self.name, self.desc, self.used, self.owner, self.components, self.feedback, self.properties) self.length = len(output1) output2 = Processed.__str__(self) return output2+output1 libtpproto-py-0.2.5/tp/netlib/objects/Object_GetById.py0000644000175000017500000000023010755215071021164 0ustar timtimfrom Base import GetWithID class Object_GetById(GetWithID): """\ The Object_Get packet consists of: * A SInt32, ID of object to get. """ no = 5 libtpproto-py-0.2.5/tp/netlib/objects/Order_Insert.py0000644000175000017500000000023710755215071021015 0ustar timtim import copy from Order import Order class Order_Insert(Order): no = 12 def __init__(self, *args, **kw): self.no = 12 Order.__init__(self, *args, **kw) libtpproto-py-0.2.5/tp/netlib/objects/Fail.py0000644000175000017500000000217111051706206017263 0ustar timtim from xstruct import pack from Header import Processed class Fail(Processed): """\ The Fail packet consists of: * A UInt32, error code * A String, may contain useful information for debugging purposes """ no = 1 struct = "IS" reasons = { 0 : "Protocol Error", 1 : "Frame Error", 2 : "Unavailable Permanently", 3 : "Unavailable Temporarily", 4 : "No such thing", 5 : "Permission Denied", } def __init__(self, sequence, errno, s=""): if errno != 0 and sequence < 1: raise ValueError("Fail is a reply packet so needs a valid sequence number (%i)" % sequence) Processed.__init__(self, sequence) # Length is: # * 4 bytes (uint32 - error code # * 4 bytes (uint32 - string length) # * the string # * null terminator # self.length = 4 + 4 + len(s) self.errno = errno self.s = s def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.errno, self.s) return output def reason(self): if self.reasons.has_key(self.errno): return self.reasons[self.errno] return "Unknown" reason = property(reason, doc="A text string representation of the errno") libtpproto-py-0.2.5/tp/netlib/objects/Message_Insert.py0000644000175000017500000000034410755215071021325 0ustar timtim import copy from Message import Message class Message_Insert(Message): no = 20 def __init__(self, sequence, \ id, slot, type, \ *args, **kw): self.no = 20 apply(Order.__init__, (self, sequence, id, slot)+args, kw) libtpproto-py-0.2.5/tp/netlib/objects/Board_Get.py0000644000175000017500000000024410755215071020242 0ustar timtimfrom Base import GetWithID class Board_Get(GetWithID): """\ The Board_Get packet consists of: * A list of uint32, IDs of message boards to get. """ no = 16 libtpproto-py-0.2.5/tp/netlib/objects/Player.py0000644000175000017500000000115710755215071017654 0ustar timtim from xstruct import pack from Header import Processed class Player(Processed): """\ The Player packet consists of: * a UInt32, the Player id * a String, the Player's name * a String, the Race's name """ no = 40 struct = "ISS" def __init__(self, sequence, id, name, race_name): Processed.__init__(self, sequence) # Length is: # self.length = 4 + \ 4 + len(name) + \ 4 + len(race_name) self.id = id self.name = name self.race_name = race_name def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.id, self.name, self.race_name) return output libtpproto-py-0.2.5/tp/netlib/objects/Object_GetID_ByContainer.py0000644000175000017500000000012110755215071023125 0ustar timtimfrom Base import GetWithID class Object_GetID_ByContainer(GetWithID): no = 30 libtpproto-py-0.2.5/tp/netlib/objects/OrderDesc_IDSequence.py0000644000175000017500000000013210755215071022327 0ustar timtimfrom Base import IDSequence class OrderDesc_IDSequence(IDSequence): """\ """ no = 33 libtpproto-py-0.2.5/tp/netlib/objects/Resource_IDSequence.py0000644000175000017500000000013110755215071022243 0ustar timtimfrom Base import IDSequence class Resource_IDSequence(IDSequence): """\ """ no = 38 libtpproto-py-0.2.5/tp/netlib/objects/Order_Remove.py0000644000175000017500000000037010755215071021004 0ustar timtim from xstruct import pack from Base import GetWithIDandSlot class Order_Remove(GetWithIDandSlot): """\ The Order_Remove packet consists of: * A UInt32, object to remove the orders from. * a list of, * A SInt32, order slots """ no = 13 libtpproto-py-0.2.5/tp/netlib/objects/Category_Get.py0000644000175000017500000000023310755215071020766 0ustar timtimfrom Base import GetWithID class Category_Get(GetWithID): """\ The Category_Get packet consists of: * A SInt32, ID of category to get. """ no = 41 libtpproto-py-0.2.5/tp/netlib/objects/Order_Get.py0000644000175000017500000000035710755215071020273 0ustar timtim from xstruct import pack from Base import GetWithIDandSlot class Order_Get(GetWithIDandSlot): """\ The Order_Get packet consists of: * A UInt32, object to get the orders from. * a list of, * A SInt32, order slots """ no = 10 libtpproto-py-0.2.5/tp/netlib/objects/Property_Get.py0000644000175000017500000000023310755215071021035 0ustar timtimfrom Base import GetWithID class Property_Get(GetWithID): """\ The Property_Get packet consists of: * A SInt32, ID of property to get. """ no = 58 libtpproto-py-0.2.5/tp/netlib/objects/Component_Get.py0000644000175000017500000000023610755215071021156 0ustar timtimfrom Base import GetWithID class Component_Get(GetWithID): """\ The Component_Get packet consists of: * A SInt32, ID of component to get. """ no = 54 libtpproto-py-0.2.5/tp/netlib/objects/Object.py0000644000175000017500000000513211153231142017611 0ustar timtim from xstruct import pack, unpack from Description import Describable, DescriptionError from ObjectDesc import descriptions class Object(Describable): """\ The Object packet consists of: * a UInt32, object ID * a UInt32, object type * a String, name of object * a UInt64, size of object (diameter) * 3 by Int64, position of object * 3 by Int64, velocity of object * a list of UInt32, object IDs of objects contained in the current object * a list of UInt32, order types that the player can send to this object * a UInt32, number of orders currently on this object * a UInt64, the last modified time * 2 by UInt32 of padding, for future expansion of common attributes * extra data, as defined by each object type """ no = 7 struct = "IIS Q 3q 3q [I] [I] I T 8x" _name = "Unknown Object" def __init__(self, sequence, \ id, subtype, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time, \ *args, **kw): Describable.__init__(self, sequence) self.length = \ 4 + 4 + \ 4 + len(name) + \ 8 + 3*8 + 3*8 + \ 4 + len(contains)*4 + \ 4 + len(order_types)*4 + \ 4 + 8 + 8 self.id = id self._subtype = subtype self.name = name self.size = size self.pos = (posx, posy, posz) self.vel = (velx, vely, velz) self.contains = contains self.order_types = order_types self.order_number = order_number self.modify_time = modify_time if self.__class__ == Object: try: if kw.has_key('force'): cls = kw['force'] else: cls = descriptions()[subtype] self.__class__ = cls if len(args) > 0: self.__init__(sequence, \ id, subtype, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time, *args) except KeyError, e: raise DescriptionError(sequence, subtype) def __str__(self): output = Describable.__str__(self) output += pack(self.struct, \ self.id, \ self._subtype, \ self.name, \ self.size, \ self.pos[0], \ self.pos[1], \ self.pos[2], \ self.vel[0], \ self.vel[1], \ self.vel[2], \ self.contains, \ self.order_types, \ self.order_number, \ self.modify_time) return output def __repr__(self): """\ Return a reconisable string. """ return "<%s @ %s>" % \ (self.__class__.__name__, self.id) return "<%s @ %s (seq: %i length: %i)>" % \ (self.__class__.__name__, hex(id(self)), self.sequence, self.length) libtpproto-py-0.2.5/tp/netlib/objects/Feature_Get.py0000644000175000017500000000061410755215071020607 0ustar timtim from xstruct import pack from Header import Processed class Feature_Get(Processed): """\ The Get Features frame has no data. Get the features this server supports. This frame can be sent before Connect. """ no = 25 struct = "" def __init__(self, sequence): Processed.__init__(self, sequence) self.length = 0 def __str__(self): output = Processed.__str__(self) return output libtpproto-py-0.2.5/tp/netlib/objects/Login.py0000644000175000017500000000134410755215071017466 0ustar timtim from xstruct import pack from Header import Processed class Login(Processed): """\ The Login packet consists of: * A string, Username to login with. * A string, Password for the username. """ no = 4 struct = "SS" def __init__(self, sequence, username, password): Processed.__init__(self, sequence) # Length is: # * 4 bytes (uint32 - string length) # * the string # * null terminator # * 4 bytes (uint32 - string length) # * the string # * null terminator # self.length = 4 + len(username) + 4 + len(password) self.username = username self.password = password def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.username, self.password) return output libtpproto-py-0.2.5/tp/netlib/objects/TimeRemaining_Get.py0000644000175000017500000000037710755215071021752 0ustar timtim from xstruct import pack from Header import Processed class TimeRemaining_Get(Processed): """\ The TimeRemaining_Get packet is empty. """ no = 14 struct = "" def __init__(self, sequence): Processed.__init__(self, sequence) self.length = 0 libtpproto-py-0.2.5/tp/netlib/objects/Board.py0000644000175000017500000000152211034131705017433 0ustar timtim from xstruct import pack from Header import Processed class Board(Processed): """\ The Board packet consists of: * UInt32, ID of Board 0 - Private system board for current player * A String, Name of the Board * A String, Description of the Board * UInt32, Number of messages on the Board. """ no = 17 struct = "ISSIT" def __init__(self, sequence, id, name, description, number, modify_time): Processed.__init__(self, sequence) # Length is: # self.length = 4 + \ 4 + len(name) + \ 4 + len(description) + \ 4 + 8 self.id = id self.name = name self.description = description self.number = number self.modify_time = modify_time def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.id, self.name, self.description, self.number, self.modify_time) return output libtpproto-py-0.2.5/tp/netlib/objects/Header.py0000644000175000017500000000520311051706206017577 0ustar timtim from xstruct import pack, unpack, hexbyte # Squash warnings about hex/oct import warnings versions = ["TP03"] version = "TP03" def SetVersion(i): global version if i in versions: version = i return True else: return False def GetVersion(): global version return version _marker = [] class FrameError(Exception): pass class Header(object): """\ Base class for all packets. Includes all the common parts for the packets. The class can be instansated however it will morph into into the correct packet type once the process function is called with data. Example: p = Header(data) str(p) '' """ size=4+4+4+4 struct="4sIII" class VersionError(Exception): pass def __init__(self, protocol, sequence, type, length): """\ Create a new header object. It takes a string which contains the "header" data. """ self.protocol = protocol self.sequence = sequence self._type = type self.length = length # Upgrade the class to the real type if self.__class__ == Header: try: self.__class__ = self.mapping[type] except KeyError, e: raise ValueError("Unknown packet type %i." % type) def __eq__(self, other): if type(self) == type(other): for key in self.__dict__.keys(): if key.startswith('_'): continue if getattr(other, key, None) != self.__dict__[key]: return False return True return False def __repr__(self): """\ Return a reconisable string. """ return "<%s - %s @ %s (seq: %i length: %i)>" % \ (self.__class__.__module__, self.__class__.__name__, hex(id(self)), self.sequence, self.length) def __str__(self): """\ Produce a string suitable to be send over the wire. """ output = pack(Header.struct, self.protocol, self.sequence, self._type, self.length) return output def fromstr(cls, data): """\ Look at the packet type and morph this object into the correct type. """ args, extra = unpack(Header.struct, data) if len(extra) > 0: raise ValueError('Got too much data! %s bytes remaining' % len(extra)) return cls(*args) fromstr = classmethod(fromstr) def data_set(self, data=None): """\ Processes the data of the packet. """ if self.data == None: self.length = 0 else: self.length = len(data) class Processed(Header): """\ Base class for packets. """ def __init__(self, sequence): Header.__init__(self, version, sequence, self.no, -1) def __process__(self, data): args, leftover = unpack(self.struct, data) if len(leftover) > 0: raise ValueError("Left over data found for %r: '%r'" % (self.__class__.__name__, leftover)) self.__init__(self.sequence, *args) libtpproto-py-0.2.5/tp/netlib/objects/OrderExtra/0000755000175000017500000000000011164431251020114 5ustar timtimlibtpproto-py-0.2.5/tp/netlib/objects/OrderExtra/__init__.py0000644000175000017500000000000010755215071022220 0ustar timtimlibtpproto-py-0.2.5/tp/netlib/objects/Object_GetID.py0000644000175000017500000000013010755215071020630 0ustar timtimfrom Base import GetIDSequence class Object_GetID(GetIDSequence): """\ """ no = 28 libtpproto-py-0.2.5/tp/netlib/objects/Message_Get.py0000644000175000017500000000036510755215071020603 0ustar timtim from xstruct import pack from Base import GetWithIDandSlot class Message_Get(GetWithIDandSlot): """\ The Message_Get packet consists of: * A UInt32, board to get the message from. * a list of, * A SInt32, message slots """ no = 18 libtpproto-py-0.2.5/tp/netlib/objects/OrderDesc_Get.py0000644000175000017500000000022711051706206021061 0ustar timtimfrom Base import GetWithID class OrderDesc_Get(GetWithID): """\ The Object_Get packet consists of: * A UInt32, ID of object to get. """ no = 8 libtpproto-py-0.2.5/tp/netlib/objects/Object_IDSequence.py0000644000175000017500000000012710755215071021667 0ustar timtimfrom Base import IDSequence class Object_IDSequence(IDSequence): """\ """ no = 31 libtpproto-py-0.2.5/tp/netlib/objects/TurnFinished.py0000644000175000017500000000037710755215071021025 0ustar timtim from xstruct import pack from Header import Processed class TurnFinished(Processed): """\ The TurnFinished packet consists of no data. """ no = 63 struct = "" def __init__(self, sequence): Processed.__init__(self, sequence) self.length = 0 libtpproto-py-0.2.5/tp/netlib/objects/ObjectExtra/0000755000175000017500000000000011164431251020247 5ustar timtimlibtpproto-py-0.2.5/tp/netlib/objects/ObjectExtra/__init__.py0000644000175000017500000000000011051706206022346 0ustar timtimlibtpproto-py-0.2.5/tp/netlib/objects/ObjectExtra/Universe.py0000644000175000017500000000152111051706206022420 0ustar timtim from xstruct import pack from objects import Object class Universe(Object): """\ The Universe is the top level object, everyone can always get it. It does not handle much itself. It only has one piece of data, that is the int32 turn number, also know as the year since game start. """ subtype = 0 substruct = "I" def __init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time, \ turn): Object.__init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time) self.length += 4 self.turn = turn def __str__(self): output = Object.__str__(self) output += pack(self.substruct, self.turn) return output libtpproto-py-0.2.5/tp/netlib/objects/ObjectExtra/StarSystem.py0000644000175000017500000000117111051706206022737 0ustar timtim from xstruct import pack from objects import Object class StarSystem(Object): """\ A Star System contains one or more stars and any related objects. The star itself is not yet modeled. Star System objects do not have any extra data. """ subtype = 2 substruct = "" def __init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, modify_time): Object.__init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time) libtpproto-py-0.2.5/tp/netlib/objects/ObjectExtra/Planet.py0000644000175000017500000000241511051706206022046 0ustar timtim from xstruct import pack from objects import Object class Planet(Object): """\ A planet is any body in space which is very large and naturally occuring. * a SInt32, the id of the player who "owns" this planet or -1 if not owned or unknown * a list of, * a UInt32, the resource id * a UInt32, the units of this resource on the "surface" * a UInt32, the maximum units of this resource remaining which are minable * a UInt32, the maximum units of this resource remaining which are inaccessable """ subtype = 3 substruct = "j[jjjj]" def __init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time, \ owner, resources): Object.__init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time) self.length += 4 + 4 + 16 * len(resources) self.owner = owner for r in resources: if len(r) != 4: raise TypeError("Resources should be 4 length, ") self.resources = resources def __str__(self): output = Object.__str__(self) output += pack(self.substruct, self.owner, self.resources) return output libtpproto-py-0.2.5/tp/netlib/objects/ObjectExtra/Wormhole.py0000644000175000017500000000164611051706206022424 0ustar timtim from xstruct import pack from objects import Object class Wormhole(Object): """\ The Wormhole is a top level object that links to locations together. It was added as a quick hack to make the Risk ruleset a little easier to play. It has 3 int64 arguments which are the "other end" of the wormhole. """ subtype = 5 substruct = "qqq" def __init__(self, sequence, \ id, type, name, \ size, \ startx, starty, startz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time, \ endx, endy, endz): Object.__init__(self, sequence, \ id, type, name, \ size, \ startx, starty, startz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time) self.length += 8*3 self.start = self.pos self.end = (endx, endy, endz) def __str__(self): output = Object.__str__(self) output += pack(self.substruct, *self.end) return output libtpproto-py-0.2.5/tp/netlib/objects/ObjectExtra/Galaxy.py0000644000175000017500000000116311051706206022047 0ustar timtim from xstruct import pack from objects import Object class Galaxy(Object): """\ The Galaxy is a container for a large group of close star systems, like the Milky Way. The Galaxy contains no extra data. """ subtype = 1 substruct = "" def __init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time): Object.__init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time) self.length += 0 libtpproto-py-0.2.5/tp/netlib/objects/ObjectExtra/Fleet.py0000644000175000017500000000155711051706206021670 0ustar timtim from xstruct import pack from objects import Object class Fleet(Object): """\ A fleet is a collection of ships. Many different ships can make up a fleet. A fleet has an owner, Int32 Player ID. """ subtype = 4 substruct = "j[II]I" def __init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time, \ owner, ships, damage): Object.__init__(self, sequence, \ id, type, name, \ size, \ posx, posy, posz, \ velx, vely, velz, \ contains, \ order_types, \ order_number, \ modify_time) self.length += 4 + 4 + len(ships) * 8 + 4 self.owner = owner self.ships = ships self.damage = damage def __str__(self): output = Object.__str__(self) output += pack(self.substruct, self.owner, self.ships, self.damage) return output libtpproto-py-0.2.5/tp/netlib/objects/Connect.py0000644000175000017500000000103510755215071020004 0ustar timtim from xstruct import pack from Header import Processed class Connect(Processed): """\ The Connect packet consists of: * A String, may contain useful information for stat purposes """ no = 3 struct = "S" def __init__(self, sequence, s=""): Processed.__init__(self, sequence) # Length is: # * 4 bytes (uint32 - string length) # * the string # * null terminator # self.length = 4 + len(s) self.s = s def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.s) return output libtpproto-py-0.2.5/tp/netlib/objects/OrderDesc.py0000644000175000017500000001214111051706206020260 0ustar timtim from xstruct import pack from Description import Description from constants import * # Import prebuild orders from ObjectDesc import * _descriptions = None def descriptions(added=None): global _descriptions if _descriptions == None: try: _descriptions = import_subtype(edir(__file__), py2exe=__loader__) except NameError, e: try: import sys if sys.frozen == "macosx_app": raise AttributeError("macosx_app") print sys.frozen import carchive this = carchive.CArchive(sys.executable).openEmbeddedZlib("out1.pyz") _descriptions = import_subtype("tp.netlib.objects.OrderExtra", installer=this.contents()) except AttributeError, e: _descriptions = import_subtype(edir(__file__)) if added != None: _descriptions[ added.subtype ] = added return _descriptions ARG_STRUCTMAP = { ARG_ABS_COORD: ("qqq", 3), ARG_TIME: ("Ij", 2), ARG_OBJECT: ("I", 1), ARG_PLAYER: ("II", 2), ARG_REL_COORD: ("Iqqq", 3), ARG_RANGE: ("iiii", 4), ARG_LIST: ("[ISj][II]", 2), ARG_STRING: ("IS", 2), } class ClassNicePrint(type): def __str__(self): return "" % (self._name, self.subtype, hex(id(self))) __repr__ = __str__ from Header import Header from Order import Order class DynamicBaseOrder(Order): """\ An Order Type built by a OrderDesc. """ substruct = "" subtype = -1 name = "Base" __metaclass__ = ClassNicePrint ARG_STRUCTMAP = ARG_STRUCTMAP ARG_NAMEMAP = ARG_NAMEMAP def __init__(self, sequence, id, slot, subtype, turns, resources, *args, **kw): Order.__init__(self, sequence, id, slot, subtype, turns, resources) assert subtype == self.subtype, "Type %s does not match this class %s" % (subtype, self.__class__) # Figure out if we are in single or multiple mode # Short mode: NOp(*args, (0, 1)) # Multiple Mode: NOp(*args, 0, 1) short = (len(args) == len(self.names)) for name, type in self.names: struct, size = ARG_STRUCTMAP[type] if short: size = 1 if size == 1: setattr(self, name, args[0]) else: if len(args) < size: raise ValueError("Incorrect number of arguments, the arguments required for %s (of type %s) are %s" % (name, type, struct)) setattr(self, name, args[:size]) args = args[size:] # FIXME: Need to figure out the length a better way self.length = len(self.__str__()) - Header.size def __str__(self): args = [] for name, type in self.names: struct, size = ARG_STRUCTMAP[type] attr = getattr(self, name) if size == 1: args.append(attr) else: args += list(attr) output = Order.__str__(self) try: output += pack(self.substruct, *args) return output except TypeError, e: s = str(e) causedby = '%s %s' % self.names[int(s[:s.find(' ')])] being = getattr(self, name) traceback = sys.exc_info()[2] while not traceback.tb_next is None: traceback = traceback.tb_next raise TypeError, '%s was %s\n%s' % (causedby, being, e), traceback def __repr__(self): return "" % (self._name, hex(id(self))) class OrderDesc(Description): """\ The OrderDesc packet consists of: * a UInt32, order type * a String, name * a String, description * a list of * a String, argument name * a UInt32, argument type * a String, description * a UInt64, the last time the description was modified IE ID: 1001 Name: Drink With Friends Description: Go to the pub and drink with friends. Arguments: Name: How Long Type: ARG_TIME Description: How many turns to drink for. Name: Who With Type: ARG_PLAYER Description: Which player to drink with. Name: Where Type: ARG_COORD Description: Where to go drinking. Name: Cargo Type: ARG_INT Description: How much beer to drink. """ no = 9 struct="I SS [SIS] T" def __init__(self, sequence, \ id, name, description, \ arguments, modify_time): Description.__init__(self, sequence, id) self._name = name self.description = description self.arguments = arguments self.modify_time = modify_time self.length = 4 + \ 4 + len(name) + \ 4 + len(description) + \ 4 + 8 for argument in arguments: self.length += \ 4 + len(argument[0]) + \ 4 + \ 4 + len(argument[2]) def __str__(self): output = Description.__str__(self) output += pack(self.struct, \ self.id, \ self._name, \ self.description, \ self.arguments, \ self.modify_time) return output def build(self): """\ *Internal* Builds a class from this description. """ class DynamicOrder(DynamicBaseOrder): pass DynamicOrder._name = self._name DynamicOrder.doc = self.description # Arguments DynamicOrder.names = [] DynamicOrder.subtype = self.id DynamicOrder.substruct = "" for name, type, desc in self.arguments: struct, size = ARG_STRUCTMAP[type] DynamicOrder.names.append((name, type)) DynamicOrder.substruct += struct setattr(DynamicOrder, name + "__doc__", desc) DynamicOrder.modify_time = self.modify_time DynamicOrder.packet = self return DynamicOrder def register(self): descriptions(self.build()) __all__ = ["descriptions", "OrderDesc"] libtpproto-py-0.2.5/tp/netlib/objects/Board_IDSequence.py0000644000175000017500000000012610755215071021507 0ustar timtimfrom Base import IDSequence class Board_IDSequence(IDSequence): """\ """ no = 36 libtpproto-py-0.2.5/tp/netlib/objects/Feature.py0000644000175000017500000000271610755215071020015 0ustar timtim from xstruct import pack from constants import * from Header import Processed class Feature(Processed): """\ The Features frame consists of: * a List of UInt32, ID code of feature """ no = 26 struct = "[I]" possible = { FEATURE_SECURE_THIS: "Secure Connection available on this port", FEATURE_SECURE_OTHER: "Secure Connection available on another port", FEATURE_HTTP_THIS: "HTTP Tunneling available on this port", FEATURE_HTTP_OTHER: "HTTP Tunneling available on another port", FEATURE_KEEPALIVE: "Supports Keep Alive frames", FEATURE_ORDERED_OBJECT: "Sends Object ID Sequences in decending modified time order", FEATURE_ORDERED_ORDERDESC: "Sends Order Description ID Sequences in decending modified time order", FEATURE_ORDERED_BOARD: "Sends Board ID Sequences in decending modified time order", FEATURE_ORDERED_RESOURCE: "Sends Resource Description ID Sequences in decending modified time order", FEATURE_ORDERED_CATEGORY: "Sends Category Description ID Sequences in decending modified time order", FEATURE_ORDERED_COMPONENT: "Sends Component ID Sequences in decending modified time order", FEATURE_ACCOUNT_REGISTER: "Client can register an account through the client", } def __init__(self, sequence, features): Processed.__init__(self, sequence) self.length = 4 + len(features) * 4 self.features = features def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.features) return output libtpproto-py-0.2.5/tp/netlib/objects/Component.py0000644000175000017500000000237310755215071020363 0ustar timtim from xstruct import pack from Header import Processed class Component(Processed): """\ The Component packet consists of: * a UInt32, Component ID * a UInt64, the last modified time * a list of, * a UInt32, Category ID the Component is in * a String, name of component * a String, description of the component * a String, NCL "Requirements" function * a list of, * a UInt32, Property ID * a String, NCL "Property Value" function """ no = 55 struct = "IT[I]SSS[IS]" def __init__(self, sequence, id, modify_time, categories, name, description, requirements, properties): Processed.__init__(self, sequence) # Length is: # self.length = 4 + 4 + 8 + \ 4 + len(categories)*4 + \ 4 + len(name) + \ 4 + len(description) + \ 4 + len(requirements) for x, value in properties: self.length += 4 + 4 + len(value) self.id = id self.modify_time = modify_time self.categories = categories self.name = name self.description = description self.requirements = requirements self.properties = properties def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.id, self.modify_time, self.categories, self.name, self.description, self.requirements, self.properties) return output libtpproto-py-0.2.5/tp/netlib/objects/Category_Remove.py0000644000175000017500000000024410755215071021506 0ustar timtimfrom Base import GetWithID class Category_Remove(GetWithID): """\ The Category_Remove packet consists of: * A SInt32, ID of category to remove. """ no = 44 libtpproto-py-0.2.5/tp/netlib/objects/Category.py0000644000175000017500000000136210755215071020173 0ustar timtim from xstruct import pack from Header import Processed class Category(Processed): """\ The Category packet consists of: * a UInt32, Category ID * a SInt64, the last modified time * a String, name of the category * a String, description of the category """ no = 42 struct = "ITSS" def __init__(self, sequence, id, modify_time, name, description): Processed.__init__(self, sequence) # Length is: # self.length = 4 + 8 + \ 4 + len(name) + \ 4 + len(description) self.id = id self.modify_time = modify_time self.name = name self.description = description def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.id, self.modify_time, self.name, self.description) return output libtpproto-py-0.2.5/tp/netlib/objects/Message_Remove.py0000644000175000017500000000037410755215071021321 0ustar timtim from xstruct import pack from Base import GetWithIDandSlot class Message_Remove(GetWithIDandSlot): """\ The Message_Remove packet consists of: * A UInt32, board to get the messages from. * a list of, * A SInt32, message slots """ no = 21 libtpproto-py-0.2.5/tp/netlib/objects/constants.py0000644000175000017500000000156511051706206020432 0ustar timtim # Constants for Description ARG_ABS_COORD = 0 ARG_TIME = 1 ARG_OBJECT = 2 ARG_PLAYER = 3 ARG_REL_COORD = 4 ARG_RANGE = 5 ARG_LIST = 6 ARG_STRING = 7 ARG_NAMEMAP = { 0: 'ARG_ABS_COORD', 1: 'ARG_TIME', 2: 'ARG_OBJECT', 3: 'ARG_PLAYER', 4: 'ARG_REL_COORD', 5: 'ARG_RANGE', 6: 'ARG_LIST', 7: 'ARG_STRING'} # Constants for Fail FAIL_PROTOCOL = 0 FAIL_FRAME = 1 FAIL_PERM = 2 FAIL_TEMP = 3 FAIL_NOSUCH = 4 # Constants for Feature FEATURE_SECURE_THIS = 1 FEATURE_SECURE_OTHER = 2 FEATURE_HTTP_THIS = 3 FEATURE_HTTP_OTHER = 4 FEATURE_KEEPALIVE = 5 FEATURE_ORDERED_PLAYER = 65535 FEATURE_ORDERED_OBJECT = 65536 FEATURE_ORDERED_ORDERDESC = 65537 FEATURE_ORDERED_BOARD = 65538 FEATURE_ORDERED_RESOURCE = 65539 FEATURE_ORDERED_CATEGORY = 65540 FEATURE_ORDERED_DESIGN = 65541 FEATURE_ORDERED_COMPONENT = 65542 FEATURE_ORDERED_PROPERTY = 65543 FEATURE_ACCOUNT_REGISTER = 1000 libtpproto-py-0.2.5/tp/netlib/objects/Property_GetID.py0000644000175000017500000000013210755215071021250 0ustar timtimfrom Base import GetIDSequence class Property_GetID(GetIDSequence): """\ """ no = 60 libtpproto-py-0.2.5/tp/netlib/objects/Order.py0000644000175000017500000000353611051706206017471 0ustar timtim from xstruct import pack, unpack from Description import Describable, DescriptionError from OrderDesc import descriptions class Order(Describable): """\ An Order Packet or Insert Order packet. The Order packet consists of: * a UInt32, Object ID of the order is on/to be placed on * a SInt32, Slot number of the order/to be put in, -1 will insert at the last position, otherwise it is inserted before the number * a UInt32, Order type ID * a UInt32, (Read Only) The number of turns the order will take * a list of * a UInt32, The resource ID * a UInt32, The units of that resource required * extra data, as defined by each order type """ no = 11 struct = "IjIj [II]" _name = "Unknown Order" def __init__(self, sequence, \ id, slot, subtype, turns, resources, *args, **kw): Describable.__init__(self, sequence) self.length = \ 4 + 4 + 4 + 4 + \ 4 + len(resources)*(4+4) self.id = id self.slot = slot self._subtype = subtype self.turns = turns self.resources = resources if self.__class__ == Order \ or str(self.__class__.__name__).endswith("Order_Insert") \ or str(self.__class__.__name__).endswith("Order_Probe"): try: if kw.has_key('force'): cls = kw['force'] del kw['force'] else: cls = descriptions()[subtype] self.__class__ = cls if len(args) > 0: self.__init__(sequence, id, slot, subtype, turns, resources, *args, **kw) except KeyError, e: raise DescriptionError(sequence, subtype) def __str__(self): output = Describable.__str__(self) output += pack(self.struct, self.id, self.slot, self._subtype, self.turns, self.resources) return output def __repr__(self): """\ Return a reconisable string. """ return "" % \ (self.__class__.__name__, hex(id(self)), self.sequence, self.length) libtpproto-py-0.2.5/tp/netlib/objects/Game.py0000644000175000017500000000635511051706206017271 0ustar timtim from xstruct import pack from Header import Processed class Game(Processed): """\ A Game Description frame consist of: * a String, Game name * a String, Key * a list of Strings, List of protocol versions supported * a String, Server Version * a String, Server Type * a String, Ruleset Name * a String, Ruleset Version * a list of, * a String, Connection Type * a String, Resolvable DNS name * a String, IP Address * a UInt32, Port * a list of, * a UInt32, Optional Paramater ID * a String, String Value * a UInt32, Int Value """ no = 66 struct = "SS[S]SSSS[SSSI][ISI]" options = { 1: ("plys", "players", "Number of Players in the game."), 2: ("cons", "connected", "Number of Clients currently connected."), 3: ("objs", "objects", "Number of objects in the game universe."), 4: ("admin", "admin", "Admin email address."), 5: ("cmt", "comment", "Comment about the game."), 6: ("turn", "turn", "When the next turn is generated."), } def __init__(self, sequence, name, key, \ tp, server, sertype, rule, rulever, \ locations, optional): Processed.__init__(self, sequence) # Length is: # self.length = \ 4 + len(name) + \ 4 + len(key) + \ 4 + len(server) + \ 4 + len(sertype) + \ 4 + len(rule) + \ 4 + len(rulever) self.length += 4 for version in tp: self.length += 4 + len(version) self.length += 4 for location in locations: self.length += \ 4 + len(location[0]) + \ 4 + len(location[1]) + \ 4 + len(location[2]) + \ 4 self.length += 4 if isinstance(optional, list): for option in optional: self.length += 4 + 4 + len(option[1]) + 4 else: for option in optional.values(): if isinstance(option, (str, unicode)): self.length += 4 + 4 + len(option) + 4 else: self.length += 4 + 4 + 0 + 4 self.name = name self.key = key self.tp = tp self.server = server self.sertype = sertype self.rule = rule self.rulever = rulever self.locations = locations self._optional = optional def _optional_set(self, optional): for option in optional: if option[0] in Game.options: value = option[1] if len(value) == 0: value = option[2] setattr(self, Game.options[option[0]][0], value) def _optional_get(self): optional = [] for key, (short, long, comment) in Game.options.items(): if hasattr(self, short): value = getattr(self, short) if isinstance(value, (str, unicode)): optional.append((key, value, 0)) else: optional.append((key, "", value)) return optional _optional = property(_optional_get, _optional_set) def optional_get(self): optional = {} for key, (short, long, comment) in Game.options.items(): if hasattr(self, short): optional[short] = getattr(self, short) return optional optional = property(optional_get) def required_get(self): required = {} for i in ['tp', 'server', 'sertype', 'rule', 'rulever']: required[i] = getattr(self, i) return required required = property(required_get) def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.name, \ self.key, self.tp, \ self.server, self.sertype, \ self.rule, self.rulever, \ self.locations, \ self._optional) return output libtpproto-py-0.2.5/tp/netlib/objects/Order_Probe.py0000644000175000017500000000023610755215071020617 0ustar timtim import copy from Order import Order class Order_Probe(Order): no = 34 def __init__(self, *args, **kw): self.no = 34 Order.__init__(self, *args, **kw) libtpproto-py-0.2.5/tp/netlib/objects/Resource_GetID.py0000644000175000017500000000013210755215071021213 0ustar timtimfrom Base import GetIDSequence class Resource_GetID(GetIDSequence): """\ """ no = 37 libtpproto-py-0.2.5/tp/netlib/objects/ObjectDesc.py0000644000175000017500000000431511051706206020417 0ustar timtim import sys import site import string import os from os import path def splitall(p, extra = []): bits = [] while not p in ['', '..', '.'] \ and not p in getattr(site, 'sitedirs', ()) \ and not p in sys.path \ and not p in extra: p, c = os.path.split(p) bits.append(c) bits.reverse() return bits def edir(p): extra = path.splitext(path.basename(p))[0][:-4] + "Extra" d = path.join(path.dirname(p), extra) return d def import_subtype(p, py2exe=None, installer=None): subtypes = {} if py2exe != None: paths = splitall(p, py2exe.archive) elif installer != None: paths = p.split(".") else: paths = splitall(p) import_base = string.join(paths, ".") if py2exe != None: files = [] for thing in py2exe._files.values(): file = thing[0] if p in file: files.append(file) elif installer != None: files = [] for file in installer: if file.startswith(import_base + "."): files.append(file[len(import_base)+1:] + ".py") else: files = os.listdir(p) for file in files: name, ext = path.splitext(path.basename(file)) if not ext in [".py", ".pyc", ".pyo"] or name == "__init__": continue try: s = "from %s import %s\nlib = %s" % (import_base, name, name) exec(s) # lib = __import__("%s.%s" % (import_base, name), globals(), locals(), [name]) except ImportError, e: print "Import Error", e if not hasattr(lib, name): continue cl = getattr(lib, name) if not hasattr(cl, "subtype"): continue if subtypes.has_key(cl.subtype): continue subtypes[cl.subtype] = cl return subtypes _descriptions = None def descriptions(added=None): global _descriptions if _descriptions == None: try: _descriptions = import_subtype(edir(__file__), py2exe=__loader__) except NameError, e: try: import sys if sys.frozen == "macosx_app": raise AttributeError("macosx_app") print sys.frozen import carchive this = carchive.CArchive(sys.executable).openEmbeddedZlib("out1.pyz") _descriptions = import_subtype("tp.netlib.objects.ObjectExtra", installer=this.contents()) except AttributeError, e: _descriptions = import_subtype(edir(__file__)) if added != None: _descriptions[ added.subtype ] = added return _descriptions libtpproto-py-0.2.5/tp/netlib/objects/Design_Remove.py0000644000175000017500000000023610755215071021143 0ustar timtimfrom Base import GetWithID class Design_Remove(GetWithID): """\ The Design_Remove packet consists of: * A SInt32, ID of design to remove. """ no = 51 libtpproto-py-0.2.5/tp/netlib/objects/Design_IDSequence.py0000644000175000017500000000012710755215071021672 0ustar timtimfrom Base import IDSequence class Design_IDSequence(IDSequence): """\ """ no = 53 libtpproto-py-0.2.5/tp/netlib/objects/Property_IDSequence.py0000644000175000017500000000013110755215071022300 0ustar timtimfrom Base import IDSequence class Property_IDSequence(IDSequence): """\ """ no = 61 libtpproto-py-0.2.5/tp/netlib/objects/Resource.py0000644000175000017500000000306710755215071020211 0ustar timtim from xstruct import pack from Header import Processed class Resource(Processed): """\ A Resource Description frame consist of: * a UInt32, Resource ID * a String, singular name of the resource * a String, plural name of the resource * a String, singular name of the resources unit * a String, plural name of the resources unit * a String, description of the resource * a UInt32, weight per unit of resource (0 for not applicable) * a UInt32, size per unit of resource (0 for not applicable) * a UInt64, the last modified time of this resource description """ no = 23 struct = "ISSSSSIIT" def __init__(self, sequence, id, \ name_singular, name_plural, \ unit_singular, unit_plural, \ description, weight, size, modify_time): Processed.__init__(self, sequence) # Length is: # self.length = 4 + \ 4 + len(name_singular) + \ 4 + len(name_plural) + \ 4 + len(unit_singular) + \ 4 + len(unit_plural) + \ 4 + len(description) + \ 4 + 4 + 8 self.id = id self.name_singular, self.name_plural = name_singular, name_plural self.unit_singular, self.unit_plural = unit_singular, unit_plural self.description, self.weight, self.size, self.modify_time = description, weight, size, modify_time def name(self): return self.name_singular name = property(name) def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.id, \ self.name_singular, self.name_plural, \ self.unit_singular, self.unit_plural, \ self.description, self.weight, self.size, self.modify_time) return output libtpproto-py-0.2.5/tp/netlib/objects/Design_Get.py0000644000175000017500000000022510755215071020423 0ustar timtimfrom Base import GetWithID class Design_Get(GetWithID): """\ The Design_Get packet consists of: * A SInt32, ID of design to get. """ no = 47 libtpproto-py-0.2.5/tp/netlib/objects/Redirect.py0000644000175000017500000000203510755215071020155 0ustar timtim from xstruct import pack from Header import Processed class Redirect(Processed): """\ Redirect frame consist of: * a String, the URI of the new server to connect too This URI will be of the standard format. A server won't redirect to a different type of service (IE If you using the tunnel service it will only redirect to another tunnel service). Example URIs: * tp://mithro.dyndns.org/ - Connect on standard tp port * tps://mithro.dyndns.org/ - Connect on standard tps port using SSL * tp://mithro.dyndns.org:6999/ - Connect on port 6999 * http://mithro.dyndns.org/ - Connect using http tunneling * https://mithro.dyndns.org/ - Connect using https tunneling """ no = 24 struct = "S" def __init__(self, sequence, uri=""): Processed.__init__(self, sequence) # Length is: # * 4 bytes (uint32 - string length) # * the string # * null terminator # self.length = 4 + len(uri) self.uri = uri def __str__(self): output = Processed.__str__(self) output += pack(self.struct, self.uri) return output libtpproto-py-0.2.5/tp/netlib/twist.py0000644000175000017500000000364110755215071016141 0ustar timtim import sys, traceback from twisted.internet import protocol, reactor import xstruct from common import ConnectionCommon, StringQueue from objects import Fail from support.output import red, green class TwistedConnection(ConnectionCommon, protocol.Protocol): def __init__(self, *args, **kw): ConnectionCommon.__init__(self) self.buffered['bytes-received'] = StringQueue() def _sendBytes(self, bytes=''): """\ Send bytes onto the socket. """ if self.debug and len(bytes) > 0: green("Sending: %s \n" % xstruct.hexbyte(bytes)) self.transport.write(bytes) def _recvBytes(self, size, peek=False): """\ Receive a bunch of bytes onto the socket. """ buffer = self.buffered['bytes-received'] if len(buffer) < size: return '' else: return [buffer.read, buffer.peek][peek](size) def dataReceived(self, data): """\ """ if self.debug and len(data) > 0: red("Received: %s \n" % xstruct.hexbyte(data)) # Push the data onto the buffer buffer = self.buffered['bytes-received'] buffer.write(data) self._recvFrame(-1) sequences = self.buffered['frames-received'].keys() sequences.sort() for sequence in sequences: p = self._recvFrame(sequence) if not p: continue bases = [p.__class__] while len(bases) > 0: c = bases.pop(0) function = "On" + c.__name__ if hasattr(self, function): try: success = getattr(self, function)(p) except: type, val, tb = sys.exc_info() print ''.join(traceback.format_exception(type, val, tb)) break else: print "No handler for packet of %s" % c.__name__ bases += list(c.__bases__) if len(bases) == 0: self._sendFrame(Fail(p.sequence, 2, "Service Unavailable...")) class TwistedFactory(protocol.ServerFactory): protocol = TwistedConnection def run(): from twisted.internet import reactor reactor.listenTCP(6923, TwistedFactory()) reactor.run() if __name__ == "__main__": run() libtpproto-py-0.2.5/tp/netlib/version.py0000644000175000017500000000342311164370300016442 0ustar timtim version = (0, 2, 5) # Add the git version if in a git tree... import os, os.path __path__ = os.path.realpath(os.path.dirname(__file__)) installpath = os.path.split(os.path.split(__path__)[0])[0] # Get the git version this tree is based on if os.path.exists(os.path.join(installpath, '.git')): # Read in git's 'HEAD' file which points to the correct reff to look at h = open(os.path.join(installpath, '.git', 'HEAD')).readline().strip() try: # Read in the ref ref = h.split(': ', 1)[1] # This file has the SHA1 p = open(os.path.join(installpath, '.git', ref)) del ref version_git = p.read().strip() except IndexError: version_git = h # What version are we trying to get too? import time if version[2] >= 99: version_target = (version[0], version[1]+1, 0) else: version_target = (version[0], version[1], version[2]+1) version_target_str = "%i.%i.%i" % version_target version_str = "%i.%i.%i" % version[:3] if __name__ == "__main__": import sys if len(sys.argv) > 1 and sys.argv[1] == '--fix': print """ import os, os.path __path__ = os.path.realpath(os.path.dirname(__file__)) installpath = os.path.split(os.path.split(__path__)[0])[0] """ for value in dir(): if value.startswith('__') or value in ('installpath',): continue if isinstance(eval(value), (tuple, str, unicode)): exec("print '%s =', repr(%s)" % (value, value)) print """ try: print version_str+'+'+version_target_str, "(git %s)" % version_git, "(installed at %s)" % installpath except ValueError: print version_str, "(installed at %s)" % installpath """ sys.exit(0) if os.path.exists(os.path.join(installpath, '.git')): print version_str+'+'+version_target_str, "(git %s)" % version_git, "(installed at %s)" % installpath else: print version_str, "(installed at %s)" % installpath libtpproto-py-0.2.5/tp/netlib/client.py0000644000175000017500000011474611051706206016251 0ustar timtim"""\ This module contains the client based connections. Blocking Example Usage: >>> # Create the object and connect to the server >>> c = netlib.Connection("127.0.0.1", 6329) >>> if failed(c.connect()): >>> print "Could not connect to the server" >>> sys.exit(1) >>> >>> if failed(c.login("username", "password")): >>> print "Could not login" >>> sys.exit(1) >>> >>> c.disconnect() >>> Non-Blocking Example Usage: >>> # Create the object and connect to the server >>> c = netlib.Connection("127.0.0.1", 6329, nb=1) >>> >>> c.connect() >>> c.login("username", "password") >>> >>> # Wait for the connection to be complete >>> if failed(c.wait()): >>> print "Could not connect to the server" >>> sys.exit(1) >>> >>> r = c.poll() >>> while r == None: >>> r = c.poll() >>> >>> # Do some other stuff! >>> pass >>> >>> if failed(r): >>> print "Could not login" >>> sys.exit(1) >>> >>> # Disconnect and cleanup >>> c.disconnect() >>> """ # Python Imports import encodings.idna import re import socket import types import urllib # Local imports import xstruct import objects constants = objects.constants from version import version from common import Connection, l, SSLWrapper, _continue def failed(object): if type(object) == types.TupleType: return failed(object[0]) else: if isinstance(object, objects.Fail): return True if isinstance(object, bool): return not object return False sequence_max = 4294967296 def url2bits(line): urlspliter = r'(.*?://)?(((.*):(.*)@)|(.*)@)?(.*?)(:(.*?))?(/.*?)?$' groups = re.compile(urlspliter, re.M).search(line).groups() proto = groups[0] if not groups[3] is None: username = groups[3] elif not groups[5] is None: username = groups[5] else: username = None server = groups[6] port = groups[8] password = groups[4] if not password is None: if password[-1] is '@': password = password[:-1] game = groups[9] if not game is None: game = urllib.unquote_plus(game) if game[0] == '/': game = game[1:] if len(game) == 0: game = None if proto is None: one = server else: one = "%s%s" % (proto, server) if not port is None: one = "%s:%s" % (one, port) return (one, username, game, password) class ClientConnection(Connection): """\ Class for a connection from the client side. """ def __init__(self, host=None, port=None, nb=0, debug=False): Connection.__init__(self) self.buffered['undescribed'] = {} self.buffered['store'] = {} if host != None: self.setup(host, port, nb, debug) self.__desc = False def setup(self, host, port=None, nb=0, debug=False, proxy=None): """\ *Internal* Sets up the socket for a connection. """ hoststring = host self.proxy = None if hoststring.startswith("tphttp://") or hoststring.startswith("tphttps://"): hoststring = hoststring[2:] if hoststring.startswith("http://") or hoststring.startswith("https://"): import urllib opener = None # use enviroment varibles if proxy == None: opener = urllib.FancyURLopener() elif proxy == "": # Don't use any proxies opener = urllib.FancyURLopener({}) else: if hoststring.startswith("http://"): opener = urlib.FancyURLopener({'http': proxy}) elif hoststring.startswith("https://"): opener = urlib.FancyURLopener({'https': proxy}) else: raise "URL Error..." import random, string url = "/" for i in range(0, 12): url += random.choice(string.letters+string.digits) o = opener.open(hoststring + url, "") s = socket.fromfd(o.fileno(), socket.AF_INET, socket.SOCK_STREAM) ## # Read in the headers ## buffer = "" ## while not buffer.endswith("\r\n\r\n"): ## print "buffer:", repr(buffer) ## try: ## buffer += s.recv(1) ## except socket.error, e: ## pass ## print "Finished the http headers..." else: if hoststring.startswith("tp://") or hoststring.startswith("tps://"): if hoststring.startswith("tp://"): host = hoststring[5:] if not port: port = 6923 elif hoststring.startswith("tps://"): host = hoststring[6:] if not port: port = 6924 if host.count(":") > 0: host, port = host.split(':', 1) port = int(port) else: if hoststring.count(":") > 0: host, port = hoststring.split(':', 1) port = int(port) else: host = hoststring if not port: port = 6923 print "Connecting to", host, type(host), port, type(port) s = None for af, socktype, proto, cannoname, sa in \ socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): try: s = socket.socket(af, socktype, proto) if debug: print "Trying to connect to connect: (%s, %s)" % (host, port) s.connect(sa) break except socket.error, msg: if debug: print "Connect fail: (%s, %s)" % (host, port) if s: s.close() s = None continue if not s: raise socket.error, msg if hoststring.startswith("tps://"): print "Creating SSL wrapper...." s = SSLWrapper(s) self.hoststring = hoststring self.host = host self.port = port Connection.setup(self, s, nb=nb, debug=debug) self.no = 1 def _description_error(self, p): # Need to figure out how to do non-blocking properly... #Send a request for the description if not self.__desc: q = objects.OrderDesc_Get(p.sequence-1, [p._subtype]) self._send(q) self.__desc = True q = self._recv(p.sequence-1) if q != None and isinstance(q, objects.Sequence): q = self._recv(p.sequence-1) if q != None and isinstance(q, objects.Description): self.__desc = False # Register the desciption q.register() def _common(self): """\ *Internal* Does all the common goodness. """ # Increase the no number self.no += 1 def _okfail(self, no): """\ *Internal* Completes the an ok or fail function. """ p = self._recv(no) if not p: return None # Check it's the reply we are after if isinstance(p, objects.OK): return True, p.s elif isinstance(p, objects.Fail): return False, p.s else: # We got a bad packet raise IOError("Bad Packet was received") def _get_single(self, type, no): """\ *Internal* Completes the function which only get a signle packet returned. """ p = self._recv(no) if not p: return None if isinstance(p, type): return p elif isinstance(p, objects.Fail): return False, p.s else: raise IOError("Bad Pakcet was received") def _get_header(self, type, no, callback=None): """\ *Internal* Completes the get_* function. """ p = self._recv(no) if not p: return None if isinstance(p, objects.Fail): # The whole command failed :( return (False, p.s) # return [(False, p.s)] elif isinstance(p, type): # Must only be one, so return if not callback is None: callback(p) return [p] elif not isinstance(p, objects.Sequence): # We got a bad packet raise IOError("Bad Packet was received %s" % p) # We have to wait on multiple packets self.buffered['store'][no] = [] #print "Packets to get", p.number if self._noblock(): # Do the commands in non-blocking mode self._next(self._get_finish, no) for i in range(0, p.number): self._next(self._get_data, type, no, callback) # Keep the polling going return _continue else: # Do the commands in blocking mode for i in range(0, p.number): self._get_data(type, no, callback) return self._get_finish(no) def _get_data(self, type, no, callback=None): """\ *Internal* Completes the get_* function. """ p = self._recv(no) if p != None: if not callback is None: callback(p) if isinstance(p, objects.Fail): p = (False, p.s) elif not isinstance(p, type): raise IOError("Bad Packet was received %s" % p) self.buffered['store'][no].append(p) if self._noblock(): return _continue def _get_finish(self, no): """\ *Internal* Completes the get_* functions. """ store = self.buffered['store'][no] del self.buffered['store'][no] return l(store) def _get_ids(self, type, key, position, amount=-1, raw=False): """\ *Internal* Send a GetID packet and setup for a IDSequence result. """ self._common() p = type(self.no, key, position, amount) self._send(p) if self._noblock(): self._append(self._get_idsequence, self.no, False, raw) return None else: return self._get_idsequence(self.no, False, raw) def _get_idsequence(self, no, iter=False, raw=False): """\ *Internal* Finishes any function which gets an IDSequence. """ p = self._recv(no) if not p: return None # Check it's the reply we are after if isinstance(p, objects.Fail): return False, p.s elif isinstance(p, objects.IDSequence): if iter: return p.iter() elif raw: return p else: return p.ids else: # We got a bad packet raise IOError("Bad Packet was received") class IDIter(object): """\ *Internal* Class used to iterate over an ID list. It will get more IDs as needed. On a non-blocking connection the IDIter will return (None, None) while no data is ready. This makes it good to use in an event loop. On a blocking connection the IDIter will wait till the information is ready. """ def __init__(self, connection, type, amount=30): """\ IDIter(connection, type, amount=10) connection Is a ClientConnection type Is the ID of the GetID packet to send amount Is the amount of IDs to get at one time """ self.connection = connection self.type = type self.amount = amount self.total = None self.key = None self.remaining = None self.position = None self.ids = None # Send out a packet if we are non-blocking if self.connection._noblock(): self.next() def __iter__(self): return self def next(self): """\ Get the next (ids, modified time) pair. """ # Get the total number of IDs if self.key is None and self.remaining is None: if self.ids is None: self.ids = [] p = self.connection._get_ids(self.type, -1, 0, 0, raw=True) else: p = self.connect.poll() # Check for Non-blocking mode if p is None: return (None, None) # Check for an error elif failed(p): raise IOError("Failed to get remaining IDs (%s)" % (p[1])) if self.total == None: self.total = p.left self.remaining = p.left self.key = p.key self.position = 0 # Get more IDs if len(self.ids) <= 0: no = self.remaining if no <= 0: raise StopIteration() elif no > self.amount: no = self.amount p = self.connection._get_ids(self.type, self.key, self.position, no, raw=True) # Check for Non-blocking mode if p is None: return (None, None) # Check for an error elif failed(p): raise IOError("Failed to get remaining IDs") self.ids = p.ids self.remaining = p.left self.position += 1 return self.ids.pop(0) def connect(self, str=""): """\ Connects to a Thousand Parsec Server. (True, "Welcome to ABC") = connect("MyWonderfulClient") (False, "To busy atm!") = connect("MyWonderfulClient") You can used the "failed" function to check the result. """ self._common() # Send a connect packet from version import version p = objects.Connect(self.no, ("libtpproto-py/%i.%i.%i " % version[:3]) + str) self._send(p) if self._noblock(): self._append(self._connect, self.no) return None # and wait for a response return self._connect(self.no) def _connect(self, no): """\ *Internal* Completes the connect function, which will automatically change to an older version if server only supports it. """ p = self._recv([0, no]) if not p: return None # Check it's the reply we are after if isinstance(p, objects.OK): return True, p.s elif isinstance(p, objects.Fail): if p.protocol != objects.GetVersion(): print "Changing version." if objects.SetVersion(p.protocol): return self.connect() return False, p.s elif isinstance(p, objects.Redirect): self.setup(p.s, nb=self._noblock(), debug=self.debug, proxy=self.proxy) return self.connect() else: # We got a bad packet raise IOError("Bad Packet was received") def account(self, username, password, email, comment=""): """\ Tries to create an account on a Thousand Parsec Server. You can used the "failed" function to check the result. """ self._common() # Send a connect packet from version import version p = objects.Account(self.no, username, password, email, comment) self._send(p) if self._noblock(): self._append(self._okfail, self.no) return None # and wait for a response return self._okfail(self.no) def ping(self): """\ Pings the Thousand Parsec Server. (True, "Pong!") = ping() (False, "") = ping() You can used the "failed" function to check the result. """ self._common() # Send a connect packet p = objects.Ping(self.no) self._send(p) if self._noblock(): self._append(self._okfail, self.no) return None # and wait for a response return self._okfail(self.no) def login(self, username, password): """\ Login to the server using this username/password. (True, "Welcome Mithro!") = login("mithro", "mypassword") (False, "Go away looser!") = login("mithro", "mypassword") You can used the "failed" function to check the result. """ self._common() self.username = username p = objects.Login(self.no, username, password) self._send(p) if self._noblock(): self._append(self._okfail, self.no) return None # and wait for a response return self._okfail(self.no) def games(self): """\ Get all games which are on a server. """ self._common() self._send(objects.Games_Get(self.no)) if self._noblock(): self._append(self._get_header, objects.Game, self.no) return None else: return self._get_header(objects.Game, self.no) def features(self): """\ Gets the features the Thousand Parsec Server supports. FIXME: This documentation should be completed. """ self._common() # Send a connect packet p = objects.Feature_Get(self.no) self._send(p) if self._noblock(): self._append(self._features, self.no) return None # and wait for a response return self._features(self.no) def _features(self, no): """\ *Internal* Completes the features function. """ p = self._recv(no) if not p: return None # Check it's the reply we are after if isinstance(p, objects.Feature): return p.features elif isinstance(p, objects.Fail): return False, p.s else: # We got a bad packet raise IOError("Bad Packet was received") def time(self): """\ Gets the time till end of turn from a Thousand Parsec Server. FIXME: This documentation should be completed. """ self._common() # Send a connect packet p = objects.TimeRemaining_Get(self.no) self._send(p) if self._noblock(): self._append(self._time, self.no) return None # and wait for a response return self._time(self.no) def _time(self, no): """\ *Internal* Completes the time function. """ p = self._recv(no) if not p: return None # Check it's the reply we are after if isinstance(p, objects.TimeRemaining): # FIXME: This will cause any truth check to fail if p.time is zero! return p.time elif isinstance(p, objects.Fail): return False, p.s else: # We got a bad packet raise IOError("Bad Packet was received") def disconnect(self): """\ Disconnect from a server. This has no return. This function will either succeed or throw and exception. """ if self._noblock() and len(self.nb) > 0: raise IOError("Still waiting on non-blocking commands!") self.s.close() def get_object_ids(self, a=None, y=None, z=None, r=None, x=None, id=None, iter=False): """\ Get objects ids from the server, # Get all object ids (plus modification times) [(25, 10029436), ...] = get_object_ids() # Get all object ids (plus modification times) via an Iterator = get_object_ids(iter=True) # Get all object ids (plus modification times) at a location [(25, 10029436), ..] = get_objects_ids(x, y, z, radius) [(25, 10029436), ..] = get_objects_ids(x=x, y=y, z=z, r=radius) # Get all object ids (plus modification times) at a location via an Iterator = get_objects_ids(x, y, z, radius, iter=True) = get_objects_ids(x=x, y=y, z=z, r=radius, iter=True) # Get all object ids (plus modification times) contain by an object [(25, 10029436), ..] = get_objects_ids(id) [(25, 10029436), ..] = get_objects_ids(id=id) # Get all object ids (plus modification times) contain by an object via an Iterator = get_object_ids(id, iter=True) = get_object_ids(id=id, iter=True) """ self._common() if a != None and y != None and z != None and r != None: x = a elif a != None: id = a p = None if x != None: p = objects.Object_GetID_ByPos(self.no, x, y, z, r) elif id != None: p = objects.Object_GetID_ByContainer(self.no, id) else: if iter: return self.IDIter(self, objects.Object_GetID) p = objects.Object_GetID(self.no, -1, 0, -1) self._send(p) if self._noblock(): self._append(self._get_idsequence, self.no, iter) return None else: return self._get_idsequence(self.no, iter) def get_objects(self, a=None, id=None, ids=None, callback=None): """\ Get objects from the server, # Get the object with id=25 [] = get_objects(25) [] = get_objects(id=25) [] = get_objects(ids=[25]) [] = get_objects([id]) # Get the objects with ids=25, 36 [, ] = get_objects([25, 36]) [, ] = get_objects(ids=[25, 36]) """ self._common() if a != None: if hasattr(a, '__getitem__'): ids = a else: id = a if id != None: ids = [id] p = objects.Object_GetById(self.no, ids) self._send(p) if self._noblock(): self._append(self._get_header, objects.Object, self.no, callback) return None else: return self._get_header(objects.Object, self.no, callback) def get_orders(self, oid, *args, **kw): """\ Get orders from an object, # Get the order in slot 5 from object 2 [] = get_orders(2, 5) [] = get_orders(2, slot=5) [] = get_orders(2, slots=[5]) [] = get_orders(2, [5]) # Get the orders in slots 5 and 10 from object 2 [, ] = get_orders(2, [5, 10]) [, ] = get_orders(2, slots=[5, 10]) # Get all the orders from object 2 [, ...] = get_orders(2) """ self._common() if kw.has_key('slots'): slots = kw['slots'] elif kw.has_key('slot'): slots = [kw['slot']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): slots = args[0] else: slots = args if kw.has_key('callback'): callback = kw['callback'] else: callback = None p = objects.Order_Get(self.no, oid, slots) self._send(p) if self._noblock(): self._append(self._get_header, objects.Order, self.no, callback) return None else: return self._get_header(objects.Order, self.no, callback) def insert_order(self, oid, slot, otype, *args, **kw): """\ Add a new order to an object, (True, "Order inserted success") = insert_order(oid, slot, otype, [arguments for order]) (False, "Order couldn't be inserted") = insert_order(oid, slot, [Order Object]) You can used the "failed" function to check the result. """ self._common() o = None if isinstance(otype, objects.Order) or isinstance(otype, objects.Order_Insert): o = otype o.no = objects.Order_Insert.no o._type = objects.Order_Insert.no o.id = oid o.slot = slot o.sequence = self.no else: o = objects.Order_Insert(self.no, oid, slot, otype, 0, [], *args) self._send(o) if self._noblock(): self._append(self._okfail, self.no) return None # and wait for a response return self._okfail(self.no) def remove_orders(self, oid, *args, **kw): """\ Removes orders from an object, # Remove the order in slot 5 from object 2 [] = remove_orders(2, 5) [] = remove_orders(2, slot=5) [] = remove_orders(2, slots=[5]) [(False, "No order 5")] = remove_orders(2, [5]) # Remove the orders in slots 5 and 10 from object 2 [, (False, "No order 10")] = remove_orders(2, [5, 10]) [, (False, "No order 10")] = remove_orders(2, slots=[5, 10]) """ self._common() if kw.has_key('slots'): slots = kw['slots'] elif kw.has_key('slot'): slots = [kw['slot']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): slots = args[0] else: slots = args p = objects.Order_Remove(self.no, oid, slots) self._send(p) if self._noblock(): self._append(self._get_header, objects.OK, self.no) return None else: return self._get_header(objects.OK, self.no) def get_orderdesc_ids(self, iter=False): """\ Get orderdesc ids from the server, # Get all order description ids (plus modification times) [(25, 10029436), ...] = get_orderdesc_ids() # Get all order description ids (plus modification times) via an Iterator = get_orderdesc_ids(iter=True) """ self._common() if iter: return self.IDIter(self, objects.OrderDesc_GetID) p = objects.OrderDesc_GetID(self.no, -1, 0, -1) self._send(p) if self._noblock(): self._append(self._get_idsequence, self.no, iter) return None else: return self._get_idsequence(self.no, iter) def get_orderdescs(self, *args, **kw): """\ Get order descriptions from the server. Note: When the connection gets an order which hasn't yet been described it will automatically get an order description for that order, you don't need to do this manually. # Get the order description for id 5 [] = get_orderdescs(5) [] = get_orderdescs(id=5) [] = get_orderdescs(ids=[5]) [(False, "No desc 5")] = get_orderdescs([5]) # Get the order description for id 5 and 10 [, (False, "No desc 10")] = get_orderdescs([5, 10]) [, (False, "No desc 10")] = get_orderdescs(ids=[5, 10]) """ self._common() if kw.has_key('ids'): ids = kw['ids'] elif kw.has_key('id'): ids = [kw['id']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): ids = args[0] else: ids = args if kw.has_key('callback'): callback = kw['callback'] else: callback = None p = objects.OrderDesc_Get(self.no, ids) self._send(p) if self._noblock(): self._append(self._get_header, objects.OrderDesc, self.no, callback) return None else: return self._get_header(objects.OrderDesc, self.no, callback) def get_board_ids(self, iter=False): """\ Get board ids from the server, # Get all board ids (plus modification times) [(25, 10029436), ...] = get_board_ids() # Get all board ids (plus modification times) via an Iterator = get_board_ids(iter=True) """ self._common() if iter: return self.IDIter(self, objects.Board_GetID) p = objects.Board_GetID(self.no, -1, 0, -1) self._send(p) if self._noblock(): self._append(self._get_idsequence, self.no, iter) return None else: return self._get_idsequence(self.no, iter) def get_boards(self, x=None, id=None, ids=None, callback=None): """\ Get boards from the server, # Get the board with id=25 [] = get_boards(25) [] = get_boards(id=25) [] = get_boards(ids=[25]) [(False, "No such board")] = get_boards([id]) # Get the boards with ids=25, 36 [, (False, "No board")] = get_boards([25, 36]) [, (False, "No board")] = get_boards(ids=[25, 36]) """ self._common() # Setup arguments if id != None: ids = [id] if hasattr(x, '__getitem__'): ids = x elif x != None: ids = [x] p = objects.Board_Get(self.no, ids) self._send(p) if self._noblock(): self._append(self._get_header, objects.Board, self.no, callback) return None else: return self._get_header(objects.Board, self.no, callback) def get_messages(self, bid, *args, **kw): """\ Get messages from an board, # Get the message in slot 5 from board 2 [] = get_messages(2, 5) [] = get_messages(2, slot=5) [] = get_messages(2, slots=[5]) [(False, "No such 5")] = get_messages(2, [5]) # Get the messages in slots 5 and 10 from board 2 [, (False, "No such 10")] = get_messages(2, [5, 10]) [, (False, "No such 10")] = get_messages(2, slots=[5, 10]) """ self._common() if kw.has_key('slots'): slots = kw['slots'] elif kw.has_key('slot'): slots = [kw['slot']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): slots = args[0] else: slots = args if kw.has_key('callback'): callback = kw['callback'] else: callback = None p = objects.Message_Get(self.no, bid, slots) self._send(p) if self._noblock(): self._append(self._get_header, objects.Message, self.no, callback) return None else: return self._get_header(objects.Message, self.no, callback) def insert_message(self, bid, slot, message, *args, **kw): """\ Add a new message to an board. Forms are [] = insert_message(bid, slot, [arguments for message]) [(False, "Insert failed")] = insert_message(bid, slot, [Message Object]) """ self._common() o = None if isinstance(message, objects.Message) or isinstance(message, objects.Message_Insert): o = message o._type = objects.Message_Insert.no o.sequence = self.no else: o = apply(objects.Message_Insert, (self.no, bid, slot, message,)+args, kw) self._send(o) if self._noblock(): self._append(self._okfail, self.no) return None # and wait for a response return self._okfail(self.no) def remove_messages(self, oid, *args, **kw): """\ Removes messages from an board, # Remove the message in slot 5 from board 2 [] = remove_messages(2, 5) [] = remove_messages(2, slot=5) [] = remove_messages(2, slots=[5]) [(False, "Insert failed")] = remove_messages(2, [5]) # Remove the messages in slots 5 and 10 from board 2 [, (False, "No such 10")] = remove_messages(2, [10, 5]) [, (False, "No such 10")] = remove_messages(2, slots=[10, 5]) """ self._common() if kw.has_key('slots'): slots = kw['slots'] elif kw.has_key('slot'): slots = [kw['slot']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): slots = args[0] else: slots = args p = objects.Message_Remove(self.no, oid, slots) self._send(p) if self._noblock(): self._append(self._get_header, objects.OK, self.no) return None else: return self._get_header(objects.OK, self.no) def get_resource_ids(self, iter=False): """\ Get resource ids from the server, # Get all resource ids (plus modification times) [(25, 10029436), ...] = get_resource_ids() # Get all object ids (plus modification times) via an Iterator = get_resource_ids(iter=True) """ self._common() if iter: return self.IDIter(self, objects.Resource_GetID) p = objects.Resource_GetID(self.no, -1, 0, -1) self._send(p) if self._noblock(): self._append(self._get_idsequence, self.no, iter) return None else: return self._get_idsequence(self.no, iter) def get_resources(self, x=None, id=None, ids=None, callback=None): """\ Get resources from the server, # Get the resources with id=25 [] = get_resources(25) [] = get_resources(id=25) [] = get_resources(ids=[25]) [(False, "No such board")] = get_resources([id]) # Get the resources with ids=25, 36 [, (False, "No board")] = get_resources([25, 36]) [, (False, "No board")] = get_resources(ids=[25, 36]) """ self._common() # Setup arguments if id != None: ids = [id] if hasattr(x, '__getitem__'): ids = x elif x != None: ids = [x] p = objects.Resource_Get(self.no, ids) self._send(p) if self._noblock(): self._append(self._get_header, objects.Resource, self.no, callback) return None else: return self._get_header(objects.Resource, self.no, callback) def get_category_ids(self, iter=False): """\ Get category ids from the server, # Get all category ids (plus modification times) [(25, 10029436), ...] = get_category_ids() # Get all order category ids (plus modification times) via an Iterator = get_category_ids(iter=True) """ self._common() if iter: return self.IDIter(self, objects.Category_GetID) p = objects.Category_GetID(self.no, -1, 0, -1) self._send(p) if self._noblock(): self._append(self._get_idsequence, self.no, iter) return None else: return self._get_idsequence(self.no, iter) def get_categories(self, *args, **kw): """\ Get category information, # Get the information for category 5 [] = get_categories(5) [] = get_categories(id=5) [] = get_categories(ids=[5]) [(False, "No such 5")] = get_categories([5]) # Get the information for category 5 and 10 [, (False, "No such 10")] = get_categories([5, 10]) [, (False, "No such 10")] = get_categories(ids=[5, 10]) """ self._common() if kw.has_key('ids'): ids = kw['ids'] elif kw.has_key('id'): ids = [kw['id']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): ids = args[0] else: ids = args if kw.has_key('callback'): callback = kw['callback'] else: callback = None p = objects.Category_Get(self.no, ids) self._send(p) if self._noblock(): self._append(self._get_header, objects.Category, self.no, callback) return None else: return self._get_header(objects.Category, self.no, callback) def insert_category(self, *args, **kw): """\ Add a new category. = insert_category(id, [arguments for category]) = insert_category([Category Object]) """ self._common() d = None if isinstance(args[0], objects.Category) or isinstance(args[0], objects.Category_Add): d = args[0] d.no = objects.Category_Add.no d._type = objects.Category_Add.no d.sequence = self.no else: d = apply(objects.Category_Add, (self.no,)+args, kw) self._send(d) if self._noblock(): self._append(self._get_single, objects.Category, self.no) return None else: return self._get_single(objects.Category, self.no) def remove_categories(self, a=None, id=None, ids=None): """\ Remove categories from the server, # Get the category with id=25 [] = remove_categories(25) [] = remove_categories(id=25) [] = remove_categories(ids=[25]) [] = remove_categories([id]) # Get the categories with ids=25, 36 [, ] = remove_categories([25, 36]) [, ] = remove_categories(ids=[25, 36]) """ self._common() if a != None: if hasattr(a, '__getitem__'): ids = a else: id = a if id != None: ids = [id] p = objects.Category_Remove(self.no, ids) self._send(p) if self._noblock(): self._append(self._okfail, self.no) return None else: return self._okfail(self.no) def get_design_ids(self, iter=False): """\ Get design ids from the server, # Get all design ids (plus modification times) [(25, 10029436), ...] = get_design_ids() # Get all order design ids (plus modification times) via an Iterator = get_design_ids(iter=True) """ self._common() if iter: return self.IDIter(self, objects.Design_GetID) p = objects.Design_GetID(self.no, -1, 0, -1) self._send(p) if self._noblock(): self._append(self._get_idsequence, self.no, iter) return None else: return self._get_idsequence(self.no, iter) def get_designs(self, *args, **kw): """\ Get designs descriptions, # Get the information for design 5 [] = get_designs(5) [] = get_designs(id=5) [] = get_designs(ids=[5]) [(False, "No such 5")] = get_designs([5]) # Get the information for design 5 and 10 [, (False, "No such 10")] = get_designs([5, 10]) [, (False, "No such 10")] = get_designs(ids=[5, 10]) """ self._common() if kw.has_key('ids'): ids = kw['ids'] elif kw.has_key('id'): ids = [kw['id']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): ids = args[0] else: ids = args if kw.has_key('callback'): callback = kw['callback'] else: callback = None p = objects.Design_Get(self.no, ids) self._send(p) if self._noblock(): self._append(self._get_header, objects.Design, self.no, callback) return None else: return self._get_header(objects.Design, self.no, callback) def insert_design(self, *args, **kw): """\ Add a new design. = insert_design(id, [arguments for design]) = insert_design([Design Object]) """ self._common() d = None if isinstance(args[0], objects.Design) or isinstance(args[0], objects.Design_Add): d = args[0] d.no = objects.Design_Add.no d._type = objects.Design_Add.no d.sequence = self.no else: d = apply(objects.Design_Add, (self.no,)+args, kw) self._send(d) if self._noblock(): self._append(self._get_single, objects.Design, self.no) return None else: return self._get_single(objects.Design, self.no) def change_design(self, *args, **kw): """\ Change a new design. = change_design(id, [arguments for design]) = change_design([Design Object]) """ self._common() d = None if isinstance(args[0], objects.Design) or isinstance(args[0], objects.Design_Add): d = args[0] d.no = objects.Design_Add.no d._type = objects.Design_Add.no d.sequence = self.no else: d = apply(objects.Design_Add, (self.no,)+args, kw) self._send(d) if self._noblock(): self._append(self._get_single, objects.Design, self.no) return None else: return self._get_single(objects.Design, self.no) def remove_designs(self, a=None, id=None, ids=None): """\ Remove designs from the server, # Get the design with id=25 [] = remove_designs(25) [] = remove_designs(id=25) [] = remove_designs(ids=[25]) [] = remove_designs([id]) # Get the designs with ids=25, 36 [, ] = remove_designs([25, 36]) [, ] = remove_designs(ids=[25, 36]) """ self._common() if a != None: if hasattr(a, '__getitem__'): ids = a else: id = a if id != None: ids = [id] p = objects.Design_Remove(self.no, ids) self._send(p) if self._noblock(): self._append(self._okfail, self.no) return None else: return self._okfail(self.no) def get_component_ids(self, iter=False): """\ Get component ids from the server, # Get all component ids (plus modification times) [(25, 10029436), ...] = get_component_ids() # Get all order component ids (plus modification times) via an Iterator = get_component_ids(iter=True) """ self._common() if iter: return self.IDIter(self, objects.Component_GetID) p = objects.Component_GetID(self.no, -1, 0, -1) self._send(p) if self._noblock(): self._append(self._get_idsequence, self.no, iter) return None else: return self._get_idsequence(self.no, iter) def get_components(self, *args, **kw): """\ Get components descriptions, # Get the description for components 5 [] = get_components(5) [] = get_components(id=5) [] = get_components(ids=[5]) [(False, "No such 5")] = get_components([5]) # Get the descriptions for components 5 and 10 [, (False, "No such 10")] = get_components([5, 10]) [, (False, "No such 10")] = get_components(ids=[5, 10]) """ self._common() if kw.has_key('ids'): ids = kw['ids'] elif kw.has_key('id'): ids = [kw['id']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): ids = args[0] else: ids = args if kw.has_key('callback'): callback = kw['callback'] else: callback = None p = objects.Component_Get(self.no, ids) self._send(p) if self._noblock(): self._append(self._get_header, objects.Component, self.no, callback) return None else: return self._get_header(objects.Component, self.no, callback) def get_property_ids(self, iter=False): """\ Get property ids from the server, # Get all property ids (plus modification times) [(25, 10029436), ...] = get_property_ids() # Get all order property ids (plus modification times) via an Iterator = get_property_ids(iter=True) """ self._common() if iter: return self.IDIter(self, objects.Property_GetID) p = objects.Property_GetID(self.no, -1, 0, -1) self._send(p) if self._noblock(): self._append(self._get_idsequence, self.no, iter) return None else: return self._get_idsequence(self.no, iter) def get_properties(self, *args, **kw): """\ Get properties descriptions, # Get the description for properties 5 [] = get_properties(5) [] = get_properties(id=5) [] = get_properties(ids=[5]) [(False, "No such 5")] = get_properties([5]) # Get the descriptions for properties 5 and 10 [, (False, "No such 10")] = get_properties([5, 10]) [, (False, "No such 10")] = get_properties(ids=[5, 10]) """ self._common() if kw.has_key('ids'): ids = kw['ids'] elif kw.has_key('id'): ids = [kw['id']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): ids = args[0] else: ids = args if kw.has_key('callback'): callback = kw['callback'] else: callback = None p = objects.Property_Get(self.no, ids) self._send(p) if self._noblock(): self._append(self._get_header, objects.Property, self.no, callback) return None else: return self._get_header(objects.Property, self.no, callback) def get_players(self, *args, **kw): """\ Get players descriptions, # Get the description for players 5 [] = get_players(5) [] = get_players(id=5) [] = get_players(ids=[5]) [(False, "No such 5")] = get_players([5]) # Get the descriptions for players 5 and 10 [, (False, "No such 10")] = get_players([5, 10]) [, (False, "No such 10")] = get_players(ids=[5, 10]) """ self._common() if kw.has_key('ids'): ids = kw['ids'] elif kw.has_key('id'): ids = [kw['id']] elif len(args) == 1 and hasattr(args[0], '__getitem__'): ids = args[0] else: ids = args if kw.has_key('callback'): callback = kw['callback'] else: callback = None p = objects.Player_Get(self.no, ids) self._send(p) if self._noblock(): self._append(self._get_header, objects.Player, self.no, callback) return None else: return self._get_header(objects.Player, self.no, callback) def turnfinished(self): """\ Tell the server that you have finished a turn. """ self._common() # Send a connect packet p = objects.TurnFinished(self.no) self._send(p) if self._noblock(): self._append(self._okfail, self.no) return None # and wait for a response return self._okfail(self.no) libtpproto-py-0.2.5/tp/netlib/GenericRS.py0000644000175000017500000000617611007744030016607 0ustar timtim class References(dict): def __init__(self, references): self.references = references self.dirty = True def types(self): if len(self.references) > 0: if self.dirty: self.__types = zip(*self.references)[0] return self.__types return [] types = property(types) def __len__(self): return len(self.references) def __iter__(self): return self.references.__iter__() def __str__(self): s = " 0: s += "\t%s: %s\n" % (Types[type], value) else: value = globals()[Types[type].replace(" ","")][value] s += "\t%s: '%s'\n" % (Types[type], value) return s[:-1]+">" def GetReferenceValue(self, type, value): if type <= 0: return globals()[Types[type].replace(" ","")][value] else: return value class ReferenceDict(dict): def __init__(self, dict): self.reverse = {} for key, value in dict.items(): self.reverse[value[0]] = key self.forward = {} for key, value in dict.items(): self.forward[key] = value[0] self.__help = {} for key, value in dict.items(): self.__help[key] = value[1] self.__help[value[0]] = value[1] def help(self, key): """\ A description of the object. """ return self.__help[key] def __getitem__(self, key): if self.forward.has_key(key): return self.forward[key] elif self.reverse.has_key(key): return self.reverse[key] raise KeyError("%s doesn't exist in the dictionary.") def __setitem__(self, key, value): raise RuntimeError("This dictionary is read only") def __len__(self): return len(self.forward) Types = ReferenceDict({ -1000: ("Server Specific", ""), -5: ("Design Action", ""), -4: ("Player Action", ""), -3: ("Message Action", ""), -2: ("Order Action", ""), -1: ("Object Action", ""), 0: ("Misc", ""), 1: ("Object", ""), 2: ("Order Type", ""), 3: ("Order Instance", ""), 4: ("Board", ""), 5: ("Message", ""), 6: ("Resource Description", ""), 7: ("Player", ""), }) Misc = ReferenceDict({ 1: ("System", "This message is from a the internal game system."), 2: ("Administration","This message is an message from game administrators."), 3: ("Important", "This message is flagged to be important."), 4: ("Unimportant", "This message is flagged as unimportant."), }) PlayerAction = ReferenceDict({ 1: ("Player Eliminated", "This message refers to the elimination of a player from the game"), 2: ("Player Quit", "This message refers to a player leaving the game"), 3: ("Player Joined", "This message refers to a new player joining the game"), }) OrderAction = ReferenceDict({ 1: ("Order Completion", "This message refers to a completion of an order."), 2: ("Order Canceled", "This message refers to the cancellation of an order."), 3: ("Order Incompatible", "This message refers to the inability to complete an order."), 4: ("Order Invalid", "This message refers to an order which is invalid."), }) ObjectAction = ReferenceDict({ 1: ("Object Idle", "this message refers to an object having nothing to do"), }) if __name__ == "__main__": print Misc[1], Misc["System"], Misc.help(1), Misc.help("System") print References([(-1, 1), (-2, 3)]) libtpproto-py-0.2.5/tp/netlib/common.py0000644000175000017500000002522511025104440016245 0ustar timtim # Python imports import socket import select import time # Local imports import xstruct import objects from support.output import * _continue = [] try: set() except NameError: from sets import Set as set class l(list): def __str__(self): return "[%s]" % ", ".join([str(i) for i in self]) class NotImplimented(Exception): pass class SSLWrapper(object): def __init__(self, s): self.base = s try: import OpenSSL.crypto import OpenSSL.SSL as SSL context = SSL.Context(SSL.SSLv23_METHOD) context.set_verify(SSL.VERIFY_NONE, lambda a, b, c, d, e: True) self.s = SSL.Connection(context, s) self.s.set_connect_state() self.socket_error = (SSL.WantReadError, SSL.WantWriteError,) print "Found pyopenssl" return except ImportError, e: print "Unable to import pyopenssl" try: from tlslite.api import TLSConnection class tlslite_wrapper(object): def __init__(self, s): self.s = TLSConnection(s) self.s.handshakeClientCert() self.writing = None self.reading = None def write(self, data): if self.writing is None: self.writing = (len(data), self.s.writeAsync(data)) try: self.writing[1].next() return 0 except StopIteration: pass sent = self.writing[0] self.writing = None return sent def read(self, amount): d = None while d == None: try: d = self.reading.next() except (StopIteration, AttributeError): self.reading = self.s.readAsync(amount) try: len(d) except: raise socket.error("No data ready to be read.") return d self.s = tlslite_wrapper(s) self.socket_error = tuple() return except ImportError, e: print "Unable to import tlslite" try: self.s = socket.ssl(s) self.socket_error = (IOError,) print "Using crappy inbuilt SSL connection, be warned there can be no verification." except ValueError, e: raise IOError("Unable to find a working SSL library." + e) def send(self, data): no = 0 while no != len(data): try: no += self.s.write(data) except self.socket_error, e: print "SSL send", e.__class__, e return no def recv(self, amount): try: data = self.s.read(amount) return data except self.socket_error, e: print "SSL recv", e.__class__, e def setblocking(self, t): self.base.setblocking(t) from StringIO import StringIO class StringQueue(StringIO): def __init__(self, *args, **kw): StringIO.__init__(self, *args, **kw) self._read_pos = 0 self._write_pos = 0 def left(self): return int(self._write_pos - self._read_pos) __len__ = left def read(self, *args, **kw): self.seek(self._read_pos) try: return StringIO.read(self, *args, **kw) finally: self._read_pos = self.tell() if self._read_pos > 1024*8: self.trim() def truncate(self, size=None): if size is None: size = self._read_pos StringIO.truncate(self, size) self._read_pos = 0 self._write_pos = 0 def trim(self): # Read the remaining stuff self.seek(self._read_pos) waiting = StringIO.read(self) self.truncate(0) self.write(waiting) def write(self, *args, **kw): self.seek(self._write_pos) try: return StringIO.write(self, *args, **kw) finally: self._write_pos = self.tell() def peek(self, amount=None): self.seek(self._read_pos) if amount is None: return StringIO.read(self) else: return StringIO.read(self, amount) def seekto(self, s): """\ Seek forward until an a string is found. Return None if not found. """ pass def __str__(self): return "" % self.left() __repr__ = __str__ BUFFER_SIZE = 4096 class ConnectionCommon(object): """\ Base class for Thousand Parsec protocol connections. Methods common to both server and client connections go here. """ def __init__(self): self.debug = False self.buffered = {} self.buffered['frames-received'] = {} self.buffered['frames-tosend'] = [] self.buffered['frames-async'] = [] def _recvBytes(self, size, peek=False): pass def _sendBytes(self, bytes=''): pass def _sendFrame(self, frame=None): """\ Write a single TP packet to the socket. """ buffer = self.buffered['frames-tosend'] if not frame is None: if self.debug: green("Sending: %r \n" % frame) buffer.append(frame) while len(buffer) > 0: data = str(buffer[0]) buffer.pop(0) amount = self._sendBytes(data) if amount < BUFFER_SIZE: return else: self._sendBytes() def _processBytes(self): size = objects.Header.size frames = self.buffered['frames-received'] # Get the header data = self._recvBytes(size, peek=True) if len(data) < size: return q = objects.Header.fromstr(data) # Get the body data = self._recvBytes(size+q.length, peek=True) if len(data) < size+q.length: return q._data = data[size:] # Remove all the extra bytes... self._recvBytes(size+q.length) # Store the frames if not frames.has_key(q.sequence): frames[q.sequence] = [] frames[q.sequence].append(q) def _processFrame(self, sequences): frames = self.buffered['frames-received'] # Return any frames we have received for ready in sequences: if not frames.has_key(ready): continue # Cleanup any empty buffers if len(frames[ready]) == 0: del frames[ready] continue # Return the first frame p = frames[ready][0] try: if hasattr(p, '_data'): p.__process__(p._data) del p._data if self.debug: red("Receiving: %r\n" % p) del frames[ready][0] return p except objects.DescriptionError: self._description_error(p) continue except Exception, e: self._error(p) del frames[ready][0] def _recvFrame(self, sequence): """\ Reads a single TP packet with correct sequence number from the socket. """ if not hasattr(sequence, '__getitem__'): sequences = set([sequence]) else: sequences = set(sequence) frames = self.buffered['frames-received'] size = objects.Header.size # Pump the output queue self._sendFrame() # Split the bytes to frames self._processBytes() # Try and get any frames return self._processFrame(sequences) def _description_error(self, packet): """\ Called when we get a packet which hasn't be described. The packet will be of type Header ready to be morphed by calling process as follows, p.process(p._data) """ raise objects.DescriptionError("Can not deal with an undescribed packet.") def _version_error(self, h): """\ Called when a packet of the wrong version is found. The function should either raise the error or return a packet with the correct version. """ raise def _error(self, packet): raise class Connection(ConnectionCommon): """\ Base class for Thousand Parsec protocol connections. Methods common to both server and client connections go here. """ def __init__(self): ConnectionCommon.__init__(self) self.buffered['bytes-received'] = StringQueue() self.buffered['bytes-tosend'] = StringQueue() def setup(self, s, nb=False, debug=False): """\ *Internal* Sets up the socket for a connection. """ self.s = s self.s.setblocking(False) self.setblocking(nb) self.debug = debug def _recvBytes(self, size, peek=False): """\ Receive a bunch of bytes onto the socket. """ buffer = self.buffered['bytes-received'] try: data = self.s.recv(size) if data == '': raise IOError("Socket.recv returned no data, connection must have been closed...") if self.debug and len(data) > 0: red("Received: %s \n" % xstruct.hexbyte(data)) buffer.write(data) except socket.error, e: pass # if self.debug: # print "Recv Socket Error", e if len(buffer) < size: return '' else: return [buffer.read, buffer.peek][peek](size) def _sendBytes(self, bytes=''): """\ Send a bunch of bytes onto the socket. """ buffer = self.buffered['bytes-tosend'] # Add the bytes to the buffer buffer.write(bytes) sent = 0 try: if len(buffer) > 0: sent = self.s.send(buffer.peek(BUFFER_SIZE)) except socket.error, e: if self.debug: print "Send Socket Error", e if not self._noblock(): time.sleep(0.1) if self.debug and sent > 0: green("Sending: %s \n" % xstruct.hexbyte(buffer.peek(sent))) buffer.read(sent) return sent def fileno(self): """\ Returns the file descriptor number. """ # This is cached so our fileno doesn't go away when the socket dies.. if not hasattr(self, "_fileno"): self._fileno = self.s.fileno() return self._fileno def setblocking(self, nb): """\ Sets the connection to either blocking or non-blocking. """ if not nb and self._noblock(): # Check we arn't waiting on anything if len(self.nb) > 0: raise IOError("Still waiting on non-blocking commands!") del self.nb elif nb and not self._noblock(): self.nb = [] def pump(self): """\ Causes the connection to read and process stuff from the buffer. This will allow you to read out of band messages. Calling oob will also cause the connection to be pumped. """ if not hasattr(self, 's'): return noblock = self._noblock() if not noblock: self.setblocking(1) self._send() while True: frame = self._recv(0) if frame == None: break self.buffered['frames-async'].append(frame) if not noblock: self.setblocking(0) def _noblock(self): """\ *Internal* Returns if the connection is blocking. """ return hasattr(self, 'nb') def _send(self, p=None): """\ *Internal* Sends a single TP packet to the socket. """ self._sendFrame(p) def _recv(self, sequence): """\ *Internal* Reads a single TP packet with correct sequence number from the socket. """ while True: r = self._recvFrame(sequence) if r != None or self._noblock(): return r ############################################ # Non-blocking helpers ############################################ def poll(self): """\ Checks to see if a command can complete. """ if not self._noblock(): raise IOError("Not a non-blocking connection!") ret = _continue while ret is _continue: if len(self.nb) == 0: return None func, args = self.nb[0] ret = func(*args) if ret != None: self.nb.pop(0) return ret def _insert(self, function, *args): """\ *Internal* Queues a fuction for polling before the current one. """ self.nb.insert(0, (function, args)) def _next(self, function, *args): """\ *Internal* Queues a fuction for polling after the current one. """ self.nb.insert(1, (function, args)) def _append(self, function, *args): """\ *Internal* Queues a fuction for polling. """ self.nb.append((function, args)) def _pop(self): """\ *Internal* Removes the top of the queue. """ if self._noblock(): self.nb.pop(0) libtpproto-py-0.2.5/tp/netlib/support/0000755000175000017500000000000011164431251016120 5ustar timtimlibtpproto-py-0.2.5/tp/netlib/support/__init__.py0000644000175000017500000000000010755215071020224 0ustar timtimlibtpproto-py-0.2.5/tp/netlib/support/output.py0000644000175000017500000000077010755215071020043 0ustar timtim import sys def green(string, flush=1): if sys.platform.startswith('linux'): sys.stdout.write("") sys.stdout.write(string) if sys.platform.startswith('linux'): sys.stdout.write("") if flush: try: sys.stdout.flush() except: pass def red(string, flush=1): if sys.platform.startswith('linux'): sys.stdout.write("") sys.stdout.write(string) if sys.platform.startswith('linux'): sys.stdout.write("") if flush: try: sys.stdout.flush() except: pass libtpproto-py-0.2.5/tp/netlib/discover/0000755000175000017500000000000011164431251016222 5ustar timtimlibtpproto-py-0.2.5/tp/netlib/discover/__init__.py0000644000175000017500000000124410755215071020341 0ustar timtim try: from avahi_browser import ZeroConfBrowser as LocalBrowser if not LocalBrowser.check(): raise ImportError("Can't use avahi as it isn't running!") print "Using avahi ZeroConf implimentation..." except ImportError, e: print e try: from bonjour_browser import ZeroConfBrowser as LocalBrowser print "Using bonjour ZeroConf implimentation..." except ImportError, e: print e try: from pyzeroconf_browser import ZeroConfBrowser as LocalBrowser print "Using pyZeroConf ZeroConf implimentation..." except ImportError, e: print e from metaserver_browser import MetaServerBrowser as RemoteBrowser if __name__ == '__main__': a = LocalBrowser() a.run() libtpproto-py-0.2.5/tp/netlib/discover/browse.py0000644000175000017500000000437610755215071020114 0ustar timtimclass Browser(object): def GameFound(self, game): print "Found new game", game def GameUpdate(self, game): print "Updated old game", game def GameGone(self, game): print "Game disappeared", game from game import Game class ZeroConfBrowser(Browser): ZeroconfMap = { 'tp' : 'tp', 'tps' : 'tps', 'tp-http' : 'tp+http', 'tp-https': 'tp+https' } def __init__(self): self.games = {} def ServiceFound(self, name, type, addr, required, optional): """\ ServiceFound(name, type, addr, tpproto, required, optional) type in ['tp', 'tps', 'tp+http', 'tp+https'] addr is (dns, ip, port) Required Parameters: tp, is a list of version strings server, version of the server servtype, server type (tpserver-cpp, tpserver-py) rule, ruleset name (MiniSec, TPSec, MyCustomRuleset) rulever, version of the ruleset Optional parameters: plys, number of players in the game cons, number of clients currently connected objs, number of "objects" in the game universe admin, admin email address cmt, comment about the game turn, unixtime stamp (GMT) when next turn is generated Called when a new server is found. """ type = self.ZeroconfMap[type] required_keys = ['tp', 'server', 'sertype', 'rule', 'rulever'] for r in required_keys: if not r in required: print TypeError("Required parameter %s not found!" % r) if not self.games.has_key(name): self.games[name] = Game(name) else: self.games[name].new = False game = self.games[name] game.addLocation(type, addr) game.updateRequired(required) game.updateOptional(optional) if game.new: self.GameFound(game) else: self.GameUpdate(game) def ServiceGone(self, name, type, addr): """\ Called when a server goes away. GameGone(name, type, addr) type in ['tp', 'tps', 'tp+http', 'tp+https'] addr is (dns, ip, port) """ type = self.ZeroconfMap[type] if not self.games.has_key(name): # FIXME: Should print error return if self.games[name].removeLocation(type, addr): game = self.games[name] del self.games[name] self.GameGone(game) def GameFound(self, game): print "Found new game", game def GameUpdate(self, game): print "Updated old game", game def GameGone(self, game): print "Game disappeared", game libtpproto-py-0.2.5/tp/netlib/discover/pyzeroconf_server.py0000644000175000017500000000475310764164163022403 0ustar timtimimport os, sys import traceback, socket import time globals()['_GLOBAL_DONE'] = False from pyZeroconf import Zeroconf from server import ZeroConfServer as ZeroConfServerBase class ZeroConfServer(ZeroConfServerBase): def check(): return True check = staticmethod(check) def __init__(self): ZeroConfServerBase.__init__(self) self.tocall = [] self.services = {} self.server = Zeroconf.Zeroconf("0.0.0.0") def ServiceRemove(self, name, type, addr): self.post(self.__ServiceRemove, (name, type, addr), {}) def __ServiceRemove(self, name, type, addr): key = (name, type, addr) if key in self.services: service = self.services[key] del self.services[key] self.server.unregisterService(service) def ServiceAdd(self, name, type, addr, required, optional): self.post(self.__ServiceAdd, (name, type, addr, required, optional), {}) def __ServiceAdd(self, name, type, addr, required, optional): prop = {} prop.update(optional) prop.update(required) stype = "_%s._tcp.local." % type sname = "%s.%s" % (name, stype) svc = Zeroconf.ServiceInfo(stype, sname, server=addr[0], address=socket.inet_aton(addr[1]), port=addr[2], properties=prop) self.server.registerService(svc, 30) self.services[(name, type, addr)] = svc ###################################### # Callback functions ###################################### def post(self, method, args, kw): self.tocall.append((method, args, kw)) def run(self): while not globals()['_GLOBAL_DONE']: try: if len(self.tocall) > 0: method, args, kw = self.tocall.pop(0) method(*args, **kw) self.server.run() except Exception, e: print e traceback.print_exc() globals()['_GLOBAL_DONE'] = True def exit(self): globals()['_GLOBAL_DONE'] = True def main(): from game import Game game1 = Game("testing 1") game1.updateRequired({'tp': '0.3', 'server': 'None', 'sertype':'Avahi Testing Script', 'rule': "Testing Avahi!", 'rulever': 'None'}) game1.addLocation("tp", ("mithro.local", "10.1.1.1", 80)) game2 = Game("testing 2") game2.updateRequired({'tp': '0.3', 'server': 'None', 'sertype':'Avahi Testing Script', 'rule': "Testing Avahi!", 'rulever': 'None'}) game2.addLocation("tp", ("mithro.local", "10.1.1.1", 8080)) # game2.addLocation("tp", ("mithro.local", "10.1.1.1", 443)) -- Can't have to services on the same name and type game2.addLocation("tps", ("mithro.local", "10.1.1.1", 90)) a = ZeroConfServer() a.GameAdd(game1) a.GameAdd(game2) a.run() if __name__ == "__main__": main() libtpproto-py-0.2.5/tp/netlib/discover/pyzeroconf_browser.py0000644000175000017500000000275010755215071022546 0ustar timtim import traceback, socket from pyZeroconf import Zeroconf from browse import ZeroConfBrowser as ZeroConfBrowserBase class ZeroConfBrowser(ZeroConfBrowserBase): def __init__(self): ZeroConfBrowserBase.__init__(self) types = [] for stype in ['_tp', '_tps', '_tp-http', '_tp-https']: types.append(stype+'._tcp.local.') self.types = types try: zeroconf = Zeroconf.Zeroconf("0.0.0.0") self.browser = Zeroconf.ServiceBrowser(zeroconf, self.types, self) except socket.error, e: print "Unable to create pyZeroconf Browser", e self.browser = None def removeService(self, server, type, name): name = name[:-len(type)-1] self.ServiceGone(name, type.split('.')[0][1:], None) def addService(self, server, type, name): # Request more information about the service i = 0 while i < 15: info = server.getServiceInfo(type, name) fname = name[:-len(type)-1] if not info is None: self.ServiceFound(fname, type.split('.')[0][1:], \ (info.getServer()[:-1], str(socket.inet_ntoa(info.getAddress())), info.getPort()), \ info.getProperties(), info.getProperties()) break print "Unable to get service info for %s (%s) :(" % (name, type) i+=1 def run(self): try: if self.browser != None: self.browser.run() except Exception, e: print e traceback.print_exc() globals()['_GLOBAL_DONE'] = True def exit(self): setattr(Zeroconf, '_GLOBAL_DONE', True) def main(): a = ZeroConfBrowser() a.run() if __name__ == "__main__": main() libtpproto-py-0.2.5/tp/netlib/discover/server.py0000644000175000017500000000545010764152413020113 0ustar timtim from game import Game class Server(object): def __init__(self): self.games = {} def GameAdd(self, game): """\ Add a new game to be advitised by ZeroConf. All the locations will be advertised. """ required_keys = ['tp', 'server', 'sertype', 'rule', 'rulever'] # for r in required_keys: # if not r in game.required: # print TypeError("Required parameter %s not found!" % r) if self.games.has_key(game.name): raise SyntaxException("Game with that name already exists!") self.games[game.name] = game def GameUpdate(self, game): """\ Update a game which is already advertised by ZeroConf. """ if not self.games.has_key(game.name): raise SyntaxException("Game with that name does not exist!") # Remove the old details self.GameRemove(self.games[game.name]) # Add the new details self.GameAdd(game) def GameRemove(self, game): """\ Remove a game which is being advertised by ZeroConf. """ if not self.games.has_key(game.name): raise SyntaxException("Game with that name does not exist!") oldgame = self.games[game.name] del self.games[game.name] return oldgame class ZeroConfServer(Server): def __init__(self): self.games = {} def GameAdd(self, game): Server.GameAdd(self, game) for type in game.locations.keys(): for location in game.locations[type]: type = type.replace('+', '-') self.ServiceAdd(game.name, type, location, game.required, game.optional) def GameRemove(self, game): """\ Remove a game which is being advertised by ZeroConf. """ oldgame = Server.GameRemove(self, game) for type in oldgame.locations.keys(): for location in oldgame.locations[type]: type = type.replace('+', '-') self.ServiceRemove(game.name, type, location) def ServiceRemove(self, name, type, addr): """\ ServiceRemove(name, type, addr, tpproto, required, optional) type in ['tp', 'tps', 'tp+http', 'tp+https'] addr is (dns, ip, port) Called to remove an old service. """ print "ServiceAdd", self, name, type, addr def ServiceAdd(self, name, type, addr, required, optional): """\ ServiceAdd(name, type, addr, tpproto, required, optional) type in ['tp', 'tps', 'tp+http', 'tp+https'] addr is (dns, ip, port) Required Parameters: tp, is a list of version strings server, version of the server servtype, server type (tpserver-cpp, tpserver-py) rule, ruleset name (MiniSec, TPSec, MyCustomRuleset) rulever, version of the ruleset Optional parameters: plys, number of players in the game cons, number of clients currently connected objs, number of "objects" in the game universe admin, admin email address cmt, comment about the game turn, unixtime stamp (GMT) when next turn is generated Called to add a new service. """ print "ServiceAdd", self, name, type, addr, required, optional libtpproto-py-0.2.5/tp/netlib/discover/bonjour_browser.py0000644000175000017500000000626710755215071022035 0ustar timtim#!/usr/bin/python2 # # Bonjour browsing sample # # Usage: python browse.py # # e.g.python browse.py _daap._tcp import sys import bonjour import select import socket import struct from browse import ZeroConfBrowser as ZeroConfBrowserBase escaping= { r'\.': '.', r'\032': ' ', } class ZeroConfBrowser(ZeroConfBrowserBase): def txt2dict(self, txt): bits = [] while len(txt) > 0: l = struct.unpack('!b', txt[0])[0] bits.append(txt[1:l+1]) txt = txt[l+1:] properties = {} for bit in bits: r = bit.split('=') properties[r[0]] = "=".join(r[1:]) return properties # Callback for service resolving def ResolveCallback(self, sdRef, flags, interfaceIndex, errorCode, fullname, hosttarget, port, txtLen, txtRecord, userdata): fullname = unicode(fullname, 'utf-8') # Cleanup and split the fullname i = fullname.find('.') while fullname[i-1] == '\\' and i > 0 and i <= (len(fullname)-1): i = fullname.find('.', i+1) name, type = fullname[:i], fullname[i+1:-1] # Replace the escaped values for fom, to in escaping.items(): name = name.replace(fom, to) addr = (hosttarget[:-1], socket.gethostbyname(hosttarget), port) details = self.txt2dict(txtRecord) self.ServiceFound(name, type.split('.')[0][1:], addr, details, details) # Callback for service browsing def BrowseCallback(self, sdRef,flags,interfaceIndex, errorCode,serviceName,regtype, replyDomain, userdata): if flags & bonjour.kDNSServiceFlagsAdd: sdRef2 = bonjour.AllocateDNSServiceRef() ret = bonjour.pyDNSServiceResolve(sdRef2, 0, 0, serviceName, regtype, replyDomain, self.ResolveCallback, None ); bonjour.DNSServiceProcessResult(sdRef2) elif flags == 0: self.ServiceGone(serviceName, regtype.split('.')[0][1:], None) def __init__(self): ZeroConfBrowserBase.__init__(self) self._exit = False def exit(self): self._exit = True def run(self): serviceRefs = {} for stype in ['_tp', '_tps', '_tp-http', '_tp-https']: stype = stype+'._tcp' print "registering for", stype # Allocate a service discovery ref and browse for the specified service type serviceRef = bonjour.AllocateDNSServiceRef() ret = bonjour.pyDNSServiceBrowse( serviceRef, # DNSServiceRef *sdRef, 0, # DNSServiceFlags flags, 0, # uint32_t interfaceIndex, stype, # const char *regtype, 'local.', # const char *domain, /* may be NULL */ self.BrowseCallback, # DNSServiceBrowseReply callBack, None) if ret != bonjour.kDNSServiceErr_NoError: print "ret = %d; exiting" % ret fd = bonjour.DNSServiceRefSockFD(serviceRef) serviceRefs[fd] = serviceRef # Get socket descriptor and loop while not self._exit: ret = select.select(serviceRefs.keys(),[],[], 0.5) for fd in ret[0]: ret = bonjour.DNSServiceProcessResult(serviceRefs[fd]) if ret != bonjour.kDNSServiceErr_NoError: print "ret = %d; exiting" % ret # Deallocate the service discovery ref bonjour.DNSServiceRefDeallocate(serviceRef) def main(): a = ZeroConfBrowser() a.run() if __name__ == "__main__": main() libtpproto-py-0.2.5/tp/netlib/discover/avahi_server.py0000644000175000017500000001006410764151043021256 0ustar timtimimport os, sys import traceback import time import avahi, avahi.ServiceTypeDatabase import gobject import dbus if getattr(dbus, 'version', (0,0,0)) >= (0,41,0): import dbus.glib service_type_browsers = {} service_browsers = {} service_type_db = avahi.ServiceTypeDatabase.ServiceTypeDatabase() service_seen = {} from server import ZeroConfServer as ZeroConfServerBase class ZeroConfServer(ZeroConfServerBase): def check(): bus = dbus.SystemBus() try: server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) print "avahi version", server.GetVersionString() except dbus.DBusException, e: print e return False return False check = staticmethod(check) def __init__(self): ZeroConfServerBase.__init__(self) self.groups = {} self.topublish = {} bus = dbus.SystemBus() self.server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) def ServiceRemove(self, name, type, addr): #print "ServiceRemove" key = (name, type, addr) if key in self.groups: group = self.groups[key] del self.groups[key] group.Reset() def ServiceAdd(self, name, type, addr, required, optional): #print "ServiceAdd" stype = "_%s._tcp" % type entrygroup = self.server.EntryGroupNew() group = dbus.Interface(dbus.SystemBus().get_object(avahi.DBUS_NAME, entrygroup), avahi.DBUS_INTERFACE_ENTRY_GROUP) def entry_group_state_changed(state, hrm, self=self, entrygroup=entrygroup): #print "entry_group_state_changed...." #print state, hrm, self, entrygroup group = dbus.Interface(dbus.SystemBus().get_object(avahi.DBUS_NAME, self.server.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP) self.entry_group_state_changed(group, state) group.connect_to_signal('StateChanged', entry_group_state_changed) #print "Adding service '%s' of type '%s' ..." % (name, stype) txt = self.dict_to_pair(required) + self.dict_to_pair(optional) #print txt, addr, avahi.string_array_to_txt_array(txt) group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, name, stype, "", "", dbus.UInt16(addr[2]), avahi.string_array_to_txt_array(txt)) group.Commit() self.groups[(name, type, addr)] = group def entry_group_state_changed(self, group, state): #print "entry_group_state_changed..." if state == avahi.ENTRY_GROUP_ESTABLISHED: print group, "Service established." elif state == avahi.ENTRY_GROUP_COLLISION: n_rename = n_rename + 1 if n_rename >= 12: print "ERROR: No suitable service name found after %i retries, exiting." % n_rename else: name = self.server.GetAlternativeServiceName(name) print "WARNING: Service name collision, changing name to '%s' ..." % name def dict_to_pair(self, l): res = [] for key, value in l.items(): if value is None: res.append(key) else: res.append("%s=%s" % (key, value)) return res ###################################### # Callback functions ###################################### def run(self): #print "avahi_browse", self self.mainloop = gobject.MainLoop() gcontext = self.mainloop.get_context() bus = dbus.SystemBus() while not self.mainloop is None: if gcontext.pending(): gcontext.iteration() else: time.sleep(0.01) def exit(self): if hasattr(self, 'mainloop'): self.mainloop.quit() self.mainloop = None def main(): from game import Game game1 = Game("testing 1") game1.updateRequired({'tp': '0.3', 'server': 'None', 'sertype':'Avahi Testing Script', 'rule': "Testing Avahi!", 'rulever': 'None'}) game1.addLocation("tp", ("mithro.local", "10.1.1.1", 80)) game2 = Game("testing 2") game2.updateRequired({'tp': '0.3', 'server': 'None', 'sertype':'Avahi Testing Script', 'rule': "Testing Avahi!", 'rulever': 'None'}) game2.addLocation("tp", ("mithro.local", "10.1.1.1", 8080)) # game2.addLocation("tp", ("mithro.local", "10.1.1.1", 443)) -- Can't have to services on the same name and type game2.addLocation("tps", ("mithro.local", "10.1.1.1", 90)) a = ZeroConfServer() a.GameAdd(game1) a.GameAdd(game2) a.run() if __name__ == "__main__": main() libtpproto-py-0.2.5/tp/netlib/discover/game.py0000644000175000017500000000315310755215071017514 0ustar timtim class Game(object): def __init__(self, name): self.name = name self.locations = {} self.required = {} self.optional = {} self.new = True def __str__(self): s = " 7] for addr in self.locations[type]: s+= "%s (%s) p %s, " % addr s = s[:-2] + "\n" s = s[:-1] + ">" return s __repr__ = __str__ def addLocation(self, type, addr): if not self.locations.has_key(type): self.locations[type] = [] if not addr in self.locations[type]: self.locations[type].append(addr) def removeLocation(self, type, addr=None): """\ Removes a location (type, addr) from this game. Returns true is that was the last location. """ if self.locations.has_key(type): if addr is None: del self.locations[type] elif addr in self.locations[type]: self.locations[type].remove(addr) if len(self.locations[type]) == 0: del self.locations[type] return len(self.locations) == 0 preference = ("tps", "tp", "tp+https", "tp+http") def bestLocation(self): for type in self.preference: if self.locations.has_key(type): return (type, self.locations[type][0]) return None def updateOptional(self, optional): self.optional.update(optional) def updateRequired(self, required): self.required.update(required) def __getattr__(self, key): if key in self.__dict__: return self.__dict__[key] if self.required.has_key(key): return self.required[key] if self.optional.has_key(key): return self.optional[key] raise AttributeError("No such attribute %s" % key) libtpproto-py-0.2.5/tp/netlib/discover/servers.py0000644000175000017500000000114010755215071020266 0ustar timtim try: from avahi_server import ZeroConfServer as LocalServer if not LocalServer.check(): raise ImportError("Can't use avahi as it isn't running!") print "Using avahi ZeroConf implimentation..." except ImportError, e: print e try: from bonjour_server import ZeroConfServer as LocalServer print "Using bonjour ZeroConf implimentation..." except ImportError, e: print e try: from pyzeroconf_server import ZeroConfServer as LocalServer print "Using pyZeroConf ZeroConf implimentation..." except ImportError, e: print e from metaserver_server import MetaServerServer as RemoteServer libtpproto-py-0.2.5/tp/netlib/discover/pyZeroconf/0000755000175000017500000000000011164431251020360 5ustar timtimlibtpproto-py-0.2.5/tp/netlib/discover/pyZeroconf/__init__.py0000644000175000017500000000000010755215071022464 0ustar timtimlibtpproto-py-0.2.5/tp/netlib/discover/pyZeroconf/Zeroconf.py0000644000175000017500000013270110766216527022541 0ustar timtim""" Multicast DNS Service Discovery for Python, v0.12 Copyright (C) 2003, Paul Scott-Murphy This module provides a framework for the use of DNS Service Discovery using IP multicast. It has been tested against the JRendezvous implementation from StrangeBerry, and against the mDNSResponder from Mac OS X 10.3.8. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ """0.12 update - allow selection of binding interface typo fix - Thanks A. M. Kuchlingi removed all use of word 'Rendezvous' - this is an API change""" """0.11 update - correction to comments for addListener method support for new record types seen from OS X - IPv6 address - hostinfo ignore unknown DNS record types fixes to name decoding works alongside other processes using port 5353 (e.g. on Mac OS X) tested against Mac OS X 10.3.2's mDNSResponder corrections to removal of list entries for service browser""" """0.10 update - Jonathon Paisley contributed these corrections: always multicast replies, even when query is unicast correct a pointer encoding problem can now write records in any order traceback shown on failure better TXT record parsing server is now separate from name can cancel a service browser modified some unit tests to accommodate these changes""" """0.09 update - remove all records on service unregistration fix DOS security problem with readName""" """0.08 update - changed licensing to LGPL""" """0.07 update - faster shutdown on engine pointer encoding of outgoing names ServiceBrowser now works new unit tests""" """0.06 update - small improvements with unit tests added defined exception types new style objects fixed hostname/interface problem fixed socket timeout problem fixed addServiceListener() typo bug using select() for socket reads tested on Debian unstable with Python 2.2.2""" """0.05 update - ensure case insensitivty on domain names support for unicast DNS queries""" """0.04 update - added some unit tests added __ne__ adjuncts where required ensure names end in '.local.' timeout on receiving socket for clean shutdown""" __author__ = "Paul Scott-Murphy" __email__ = "paul at scott dash murphy dot com" __version__ = "0.12" import string import time import struct import socket import select import traceback __all__ = ["Zeroconf", "ServiceInfo", "ServiceBrowser"] # hook for threads globals()['_GLOBAL_DONE'] = False # Some timing constants _UNREGISTER_TIME = 125 _CHECK_TIME = 175 _REGISTER_TIME = 225 _LISTENER_TIME = 200 _BROWSER_TIME = 500 # Some DNS constants _MDNS_ADDR = '224.0.0.251' _MDNS_PORT = 5353; _DNS_PORT = 53; _DNS_TTL = 60 * 60; # one hour default TTL _MAX_MSG_TYPICAL = 1460 # unused _MAX_MSG_ABSOLUTE = 8972 _FLAGS_QR_MASK = 0x8000 # query response mask _FLAGS_QR_QUERY = 0x0000 # query _FLAGS_QR_RESPONSE = 0x8000 # response _FLAGS_AA = 0x0400 # Authorative answer _FLAGS_TC = 0x0200 # Truncated _FLAGS_RD = 0x0100 # Recursion desired _FLAGS_RA = 0x8000 # Recursion available _FLAGS_Z = 0x0040 # Zero _FLAGS_AD = 0x0020 # Authentic data _FLAGS_CD = 0x0010 # Checking disabled _CLASS_IN = 1 _CLASS_CS = 2 _CLASS_CH = 3 _CLASS_HS = 4 _CLASS_NONE = 254 _CLASS_ANY = 255 _CLASS_MASK = 0x7FFF _CLASS_UNIQUE = 0x8000 _TYPE_A = 1 _TYPE_NS = 2 _TYPE_MD = 3 _TYPE_MF = 4 _TYPE_CNAME = 5 _TYPE_SOA = 6 _TYPE_MB = 7 _TYPE_MG = 8 _TYPE_MR = 9 _TYPE_NULL = 10 _TYPE_WKS = 11 _TYPE_PTR = 12 _TYPE_HINFO = 13 _TYPE_MINFO = 14 _TYPE_MX = 15 _TYPE_TXT = 16 _TYPE_AAAA = 28 _TYPE_SRV = 33 _TYPE_ANY = 255 # Mapping constants to names _CLASSES = { _CLASS_IN : "in", _CLASS_CS : "cs", _CLASS_CH : "ch", _CLASS_HS : "hs", _CLASS_NONE : "none", _CLASS_ANY : "any" } _TYPES = { _TYPE_A : "a", _TYPE_NS : "ns", _TYPE_MD : "md", _TYPE_MF : "mf", _TYPE_CNAME : "cname", _TYPE_SOA : "soa", _TYPE_MB : "mb", _TYPE_MG : "mg", _TYPE_MR : "mr", _TYPE_NULL : "null", _TYPE_WKS : "wks", _TYPE_PTR : "ptr", _TYPE_HINFO : "hinfo", _TYPE_MINFO : "minfo", _TYPE_MX : "mx", _TYPE_TXT : "txt", _TYPE_AAAA : "quada", _TYPE_SRV : "srv", _TYPE_ANY : "any" } # utility functions def currentTimeMillis(): """Current system time in milliseconds""" return time.time() * 1000 # Exceptions class NonLocalNameException(Exception): pass class NonUniqueNameException(Exception): pass class NamePartTooLongException(Exception): pass class AbstractMethodException(Exception): pass class BadTypeInNameException(Exception): pass # implementation classes class DNSEntry(object): """A DNS entry""" def __init__(self, name, type, clazz): self.key = string.lower(name) self.name = name self.type = type self.clazz = clazz & _CLASS_MASK self.unique = (clazz & _CLASS_UNIQUE) != 0 def __eq__(self, other): """Equality test on name, type, and class""" if isinstance(other, DNSEntry): return self.name == other.name and self.type == other.type and self.clazz == other.clazz return 0 def __ne__(self, other): """Non-equality test""" return not self.__eq__(other) def getClazz(self, clazz): """Class accessor""" try: return _CLASSES[clazz] except KeyError: return "?(%s)" % (clazz) def getType(self, type): """Type accessor""" try: return _TYPES[type] except KeyError: return "?(%s)" % (type) def toString(self, hdr, other): """String representation with additional information""" result = "%s[%s,%s" % (hdr, self.getType(self.type), self.getClazz(self.clazz)) if self.unique: result += "-unique," else: result += "," result += self.name if other is not None: result += ",%s]" % (other) else: result += "]" return result class DNSQuestion(DNSEntry): """A DNS question entry""" def __init__(self, name, type, clazz): if not name.endswith(".local."): raise NonLocalNameException, "Name (%s) does not end with .local" % name DNSEntry.__init__(self, name, type, clazz) def answeredBy(self, rec): """Returns true if the question is answered by the record""" return self.clazz == rec.clazz and (self.type == rec.type or self.type == _TYPE_ANY) and self.name == rec.name def __repr__(self): """String representation""" return DNSEntry.toString(self, "question", None) class DNSRecord(DNSEntry): """A DNS record - like a DNS entry, but has a TTL""" def __init__(self, name, type, clazz, ttl): DNSEntry.__init__(self, name, type, clazz) self.ttl = ttl self.created = currentTimeMillis() def __eq__(self, other): """Tests equality as per DNSRecord""" if isinstance(other, DNSRecord): return DNSEntry.__eq__(self, other) return 0 def suppressedBy(self, msg): """Returns true if any answer in a message can suffice for the information held in this record.""" for record in msg.answers: if self.suppressedByAnswer(record): return 1 return 0 def suppressedByAnswer(self, other): """Returns true if another record has same name, type and class, and if its TTL is at least half of this record's.""" if self == other and other.ttl > (self.ttl / 2): return 1 return 0 def getExpirationTime(self, percent): """Returns the time at which this record will have expired by a certain percentage.""" return self.created + (percent * self.ttl * 10) def getRemainingTTL(self, now): """Returns the remaining TTL in seconds.""" return max(0, (self.getExpirationTime(100) - now) / 1000) def isExpired(self, now): """Returns true if this record has expired.""" return self.getExpirationTime(100) <= now def isStale(self, now): """Returns true if this record is at least half way expired.""" return self.getExpirationTime(50) <= now def resetTTL(self, other): """Sets this record's TTL and created time to that of another record.""" self.created = other.created self.ttl = other.ttl def write(self, out): """Abstract method""" raise AbstractMethodException, "Abstract method." def toString(self, other): """String representation with addtional information""" arg = "%s/%s,%s" % (self.ttl, self.getRemainingTTL(currentTimeMillis()), other) return DNSEntry.toString(self, "record", arg) class DNSAddress(DNSRecord): """A DNS address record""" def __init__(self, name, type, clazz, ttl, address): DNSRecord.__init__(self, name, type, clazz, ttl) self.address = address def write(self, out): """Used in constructing an outgoing packet""" out.writeString(self.address, len(self.address)) def __eq__(self, other): """Tests equality on address""" if isinstance(other, DNSAddress): return self.address == other.address return 0 def __repr__(self): """String representation""" try: return socket.inet_ntoa(self.address) except: traceback.print_exc() return self.address class DNSHinfo(DNSRecord): """A DNS host information record""" def __init__(self, name, type, clazz, ttl, cpu, os): DNSRecord.__init__(self, name, type, clazz, ttl) self.cpu = cpu self.os = os def write(self, out): """Used in constructing an outgoing packet""" out.writeString(self.cpu, len(self.cpu)) out.writeString(self.os, len(self.os)) def __eq__(self, other): """Tests equality on cpu and os""" if isinstance(other, DNSHinfo): return self.cpu == other.cpu and self.os == other.os return 0 def __repr__(self): """String representation""" return self.cpu + " " + self.os class DNSPointer(DNSRecord): """A DNS pointer record""" def __init__(self, name, type, clazz, ttl, alias): DNSRecord.__init__(self, name, type, clazz, ttl) self.alias = alias def write(self, out): """Used in constructing an outgoing packet""" out.writeName(self.alias) def __eq__(self, other): """Tests equality on alias""" if isinstance(other, DNSPointer): return self.alias == other.alias return 0 def __repr__(self): """String representation""" return self.toString(self.alias) class DNSText(DNSRecord): """A DNS text record""" def __init__(self, name, type, clazz, ttl, text): DNSRecord.__init__(self, name, type, clazz, ttl) self.text = text def write(self, out): """Used in constructing an outgoing packet""" out.writeString(self.text, len(self.text)) def __eq__(self, other): """Tests equality on text""" if isinstance(other, DNSText): return self.text == other.text return 0 def __repr__(self): """String representation""" if len(self.text) > 10: return self.toString(self.text[:7] + "...") else: return self.toString(self.text) class DNSService(DNSRecord): """A DNS service record""" def __init__(self, name, type, clazz, ttl, priority, weight, port, server): DNSRecord.__init__(self, name, type, clazz, ttl) self.priority = priority self.weight = weight self.port = port self.server = server def write(self, out): """Used in constructing an outgoing packet""" out.writeShort(self.priority) out.writeShort(self.weight) out.writeShort(self.port) out.writeName(self.server) def __eq__(self, other): """Tests equality on priority, weight, port and server""" if isinstance(other, DNSService): return self.priority == other.priority and self.weight == other.weight and self.port == other.port and self.server == other.server return 0 def __repr__(self): """String representation""" return self.toString("%s:%s" % (self.server, self.port)) class DNSIncoming(object): """Object representation of an incoming DNS packet""" def __init__(self, data): """Constructor from string holding bytes of packet""" self.offset = 0 self.data = data self.questions = [] self.answers = [] self.numQuestions = 0 self.numAnswers = 0 self.numAuthorities = 0 self.numAdditionals = 0 self.readHeader() self.readQuestions() self.readOthers() def readHeader(self): """Reads header portion of packet""" format = '!HHHHHH' length = struct.calcsize(format) info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length self.id = info[0] self.flags = info[1] self.numQuestions = info[2] self.numAnswers = info[3] self.numAuthorities = info[4] self.numAdditionals = info[5] def readQuestions(self): """Reads questions section of packet""" format = '!HH' length = struct.calcsize(format) for i in range(0, self.numQuestions): name = self.readName() info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length try: question = DNSQuestion(name, info[0], info[1]) self.questions.append(question) except NonLocalNameException, e: print "%s %s" % (type(e), e) def readInt(self): """Reads an integer from the packet""" format = '!I' length = struct.calcsize(format) info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length return info[0] def readCharacterString(self): """Reads a character string from the packet""" length = ord(self.data[self.offset]) self.offset += 1 return self.readString(length) def readString(self, len): """Reads a string of a given length from the packet""" format = '!' + str(len) + 's' length = struct.calcsize(format) info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length return info[0] def readUnsignedShort(self): """Reads an unsigned short from the packet""" format = '!H' length = struct.calcsize(format) info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length return info[0] def readOthers(self): """Reads the answers, authorities and additionals section of the packet""" format = '!HHiH' length = struct.calcsize(format) n = self.numAnswers + self.numAuthorities + self.numAdditionals for i in range(0, n): domain = self.readName() info = struct.unpack(format, self.data[self.offset:self.offset+length]) self.offset += length rec = None if info[0] == _TYPE_A: rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(4)) elif info[0] == _TYPE_CNAME or info[0] == _TYPE_PTR: rec = DNSPointer(domain, info[0], info[1], info[2], self.readName()) elif info[0] == _TYPE_TXT: rec = DNSText(domain, info[0], info[1], info[2], self.readString(info[3])) elif info[0] == _TYPE_SRV: rec = DNSService(domain, info[0], info[1], info[2], self.readUnsignedShort(), self.readUnsignedShort(), self.readUnsignedShort(), self.readName()) elif info[0] == _TYPE_HINFO: rec = DNSHinfo(domain, info[0], info[1], info[2], self.readCharacterString(), self.readCharacterString()) elif info[0] == _TYPE_AAAA: rec = DNSAddress(domain, info[0], info[1], info[2], self.readString(16)) else: # Try to ignore types we don't know about # this may mean the rest of the name is # unable to be parsed, and may show errors # so this is left for debugging. New types # encountered need to be parsed properly. # #print "UNKNOWN TYPE = " + str(info[0]) #raise BadTypeInNameException pass if rec is not None: self.answers.append(rec) def isQuery(self): """Returns true if this is a query""" return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_QUERY def isResponse(self): """Returns true if this is a response""" return (self.flags & _FLAGS_QR_MASK) == _FLAGS_QR_RESPONSE def readUTF(self, offset, len): """Reads a UTF-8 string of a given length from the packet""" result = self.data[offset:offset+len].decode('utf-8') return result def readName(self): """Reads a domain name from the packet""" result = '' off = self.offset next = -1 first = off while 1: len = ord(self.data[off]) off += 1 if len == 0: break t = len & 0xC0 if t == 0x00: result = ''.join((result, self.readUTF(off, len) + '.')) off += len elif t == 0xC0: if next < 0: next = off + 1 off = ((len & 0x3F) << 8) | ord(self.data[off]) if off >= first: raise "Bad domain name (circular) at " + str(off) first = off else: raise "Bad domain name at " + str(off) if next >= 0: self.offset = next else: self.offset = off return result class DNSOutgoing(object): """Object representation of an outgoing packet""" def __init__(self, flags, multicast = 1): self.finished = 0 self.id = 0 self.multicast = multicast self.flags = flags self.names = {} self.data = [] self.size = 12 self.questions = [] self.answers = [] self.authorities = [] self.additionals = [] def addQuestion(self, record): """Adds a question""" self.questions.append(record) def addAnswer(self, inp, record): """Adds an answer""" if not record.suppressedBy(inp): self.addAnswerAtTime(record, 0) def addAnswerAtTime(self, record, now): """Adds an answer if if does not expire by a certain time""" if record is not None: if now == 0 or not record.isExpired(now): self.answers.append((record, now)) def addAuthorativeAnswer(self, record): """Adds an authoritative answer""" self.authorities.append(record) def addAdditionalAnswer(self, record): """Adds an additional answer""" self.additionals.append(record) def writeByte(self, value): """Writes a single byte to the packet""" format = '!c' self.data.append(struct.pack(format, chr(value))) self.size += 1 def insertShort(self, index, value): """Inserts an unsigned short in a certain position in the packet""" format = '!H' self.data.insert(index, struct.pack(format, value)) self.size += 2 def writeShort(self, value): """Writes an unsigned short to the packet""" format = '!H' self.data.append(struct.pack(format, value)) self.size += 2 def writeInt(self, value): """Writes an unsigned integer to the packet""" format = '!I' self.data.append(struct.pack(format, value)) self.size += 4 def writeString(self, value, length): """Writes a string to the packet""" format = '!' + str(length) + 's' self.data.append(struct.pack(format, value)) self.size += length def writeUTF(self, s): """Writes a UTF-8 string of a given length to the packet""" utfstr = s.encode('utf-8') length = len(utfstr) if length > 64: raise NamePartTooLongException, "Name %s part is way too long (%s)!" % (utfstr, length) self.writeByte(length) self.writeString(utfstr, length) def writeName(self, name): """Writes a domain name to the packet""" try: # Find existing instance of this name in packet # index = self.names[name] except KeyError: # No record of this name already, so write it # out as normal, recording the location of the name # for future pointers to it. # self.names[name] = self.size parts = name.split('.') if parts[-1] == '': parts = parts[:-1] for part in parts: self.writeUTF(part) self.writeByte(0) return # An index was found, so write a pointer to it # self.writeByte((index >> 8) | 0xC0) self.writeByte(index) def writeQuestion(self, question): """Writes a question to the packet""" self.writeName(question.name) self.writeShort(question.type) self.writeShort(question.clazz) def writeRecord(self, record, now): """Writes a record (answer, authoritative answer, additional) to the packet""" self.writeName(record.name) self.writeShort(record.type) if record.unique and self.multicast: self.writeShort(record.clazz | _CLASS_UNIQUE) else: self.writeShort(record.clazz) if now == 0: self.writeInt(record.ttl) else: self.writeInt(record.getRemainingTTL(now)) index = len(self.data) # Adjust size for the short we will write before this record # self.size += 2 record.write(self) self.size -= 2 length = len(''.join(self.data[index:])) self.insertShort(index, length) # Here is the short we adjusted for def packet(self): """Returns a string containing the packet's bytes No further parts should be added to the packet once this is done.""" if not self.finished: self.finished = 1 for question in self.questions: self.writeQuestion(question) for answer, time in self.answers: self.writeRecord(answer, time) for authority in self.authorities: self.writeRecord(authority, 0) for additional in self.additionals: self.writeRecord(additional, 0) self.insertShort(0, len(self.additionals)) self.insertShort(0, len(self.authorities)) self.insertShort(0, len(self.answers)) self.insertShort(0, len(self.questions)) self.insertShort(0, self.flags) if self.multicast: self.insertShort(0, 0) else: self.insertShort(0, self.id) return ''.join(self.data) class DNSCache(object): """A cache of DNS entries""" def __init__(self): self.cache = {} def add(self, entry): """Adds an entry""" try: list = self.cache[entry.key] except (KeyError, ValueError): list = self.cache[entry.key] = [] list.append(entry) def remove(self, entry): """Removes an entry""" try: list = self.cache[entry.key] list.remove(entry) except (KeyError, ValueError): pass def get(self, entry): """Gets an entry by key. Will return None if there is no matching entry.""" try: list = self.cache[entry.key] return list[list.index(entry)] except (KeyError, ValueError): return None def getByDetails(self, name, type, clazz): """Gets an entry by details. Will return None if there is no matching entry.""" entry = DNSEntry(name, type, clazz) return self.get(entry) def entriesWithName(self, name): """Returns a list of entries whose key matches the name.""" try: return self.cache[name] except KeyError: return [] def entries(self): """Returns a list of all entries""" def add(x, y): return x+y try: return reduce(add, self.cache.values()) except TypeError: return [] class Engine(object): """An engine wraps read access to sockets, allowing objects that need to receive data from sockets to be called back when the sockets are ready. A reader needs a handle_read() method, which is called when the socket it is interested in is ready for reading. Writers are not implemented here, because we only send short packets. """ def __init__(self, zeroconf): self.zeroconf = zeroconf self.readers = {} # maps socket to reader self.timeout = 1 def run(self): rs = self.getReaders() if len(rs) == 0: return self.timeout*1000 else: try: rr = [1] while len(rr) > 0: rr, wr, er = select.select(rs, [], [], self.timeout) for sock in rr: try: self.readers[sock].handle_read() except (IOError, socket.error), e: print type(e), e except (IOError, socket.error), e: print type(e), e return 0 def getReaders(self): result = [] result = self.readers.keys() return result def addReader(self, reader, socket): self.readers[socket] = reader def delReader(self, socket): del(self.readers[socket]) class Listener(object): """A Listener is used by this module to listen on the multicast group to which DNS messages are sent, allowing the implementation to cache information as it arrives. It requires registration with an Engine object in order to have the read() method called when a socket is availble for reading.""" def __init__(self, zeroconf): self.zeroconf = zeroconf self.zeroconf.engine.addReader(self, self.zeroconf.socket) def handle_read(self): data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE) self.data = data msg = DNSIncoming(data) if msg.isQuery(): # Always multicast responses # if port == _MDNS_PORT: self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) # If it's not a multicast query, reply via unicast # and multicast # elif port == _DNS_PORT: self.zeroconf.handleQuery(msg, addr, port) self.zeroconf.handleQuery(msg, _MDNS_ADDR, _MDNS_PORT) else: self.zeroconf.handleResponse(msg) class Reaper(object): """A Reaper is used by this module to remove cache entries that have expired.""" def __init__(self, zeroconf): self.zeroconf = zeroconf def run(self): if globals()['_GLOBAL_DONE']: return now = currentTimeMillis() for record in self.zeroconf.cache.entries(): if record.isExpired(now): self.zeroconf.updateRecord(now, record) self.zeroconf.cache.remove(record) return 10*1000 class ServiceBrowser(object): """Used to browse for a service of a specific type. The listener object will have its addService() and removeService() methods called when this browser discovers changes in the services availability.""" def __init__(self, zeroconf, type, listener): """Creates a browser for a specific type""" self.zeroconf = zeroconf self.type = type self.listener = listener self.services = {} self.nextTime = currentTimeMillis() self.engineTime = currentTimeMillis() self.delay = _BROWSER_TIME self.list = [] self.done = 0 for type in self.type: self.zeroconf.addListener(self, DNSQuestion(type, _TYPE_PTR, _CLASS_IN)) def updateRecord(self, zeroconf, now, record): """Callback invoked by Zeroconf when new information arrives. Updates information required by browser in the Zeroconf cache.""" if record.type == _TYPE_PTR and record.name in self.type: expired = record.isExpired(now) try: oldrecord = self.services[record.alias.lower()] if not expired: oldrecord.resetTTL(record) else: del(self.services[record.alias.lower()]) callback = lambda x: self.listener.removeService(x, record.name, record.alias) self.list.append(callback) return except KeyError, e: print type(e), e if not expired: self.services[record.alias.lower()] = record callback = lambda x: self.listener.addService(x, record.name, record.alias) self.list.append(callback) expires = record.getExpirationTime(75) if expires < self.nextTime: self.nextTime = expires def cancel(self): self.done = 1 def run(self, force=False): while not globals()['_GLOBAL_DONE']: event = None now = currentTimeMillis() if self.engineTime <= now or force: self.engineTime = self.zeroconf.run() if self.nextTime <= now or force: for type in self.type: out = DNSOutgoing(_FLAGS_QR_QUERY) out.addQuestion(DNSQuestion(type, _TYPE_PTR, _CLASS_IN)) for record in self.services.values(): if not record.isExpired(now): out.addAnswerAtTime(record, now) self.zeroconf.send(out) self.nextTime = now + self.delay self.delay = min(20 * 1000, self.delay * 2) if len(self.list) > 0: event = self.list.pop(0) if event is not None: event(self.zeroconf) if len(self.list) > 0: continue #print self.nextTime, self.engineTime, now #print self.nextTime-now, self.engineTime-now try: if self.nextTime is None or self.engineTime is None: break time.sleep((min(self.nextTime, self.engineTime)-now)) except IOError, e: pass class ServiceInfo(object): """Service information""" def __init__(self, type, name, address=None, port=None, weight=0, priority=0, properties=None, server=None): """Create a service description. type: fully qualified service type name name: fully qualified service name address: IP address as unsigned short, network byte order port: port that the service runs on weight: weight of the service priority: priority of the service properties: dictionary of properties (or a string holding the bytes for the text field) server: fully qualified name for service host (defaults to name)""" if not name.endswith(type): raise BadTypeInNameException, "Name (%s) does not end in %s" % (name, type) self.type = type self.name = name self.address = address self.port = port self.weight = weight self.priority = priority if server: self.server = server else: self.server = name self.setProperties(properties) def setProperties(self, properties): """Sets properties and text of this info from a dictionary""" if isinstance(properties, dict): self.properties = properties list = [] result = '' for key in properties: value = properties[key] if value is None: suffix = ''.encode('utf-8') elif isinstance(value, str): suffix = value.encode('utf-8') elif isinstance(value, int): if value: suffix = 'true' else: suffix = 'false' else: suffix = ''.encode('utf-8') list.append('='.join((key, suffix))) for item in list: result = ''.join((result, struct.pack('!c', chr(len(item))), item)) self.text = result else: self.text = properties def setText(self, text): """Sets properties and text given a text field""" self.text = text try: result = {} end = len(text) index = 0 strs = [] while index < end: length = ord(text[index]) index += 1 strs.append(text[index:index+length]) index += length for s in strs: eindex = s.find('=') if eindex == -1: # No equals sign at all key = s value = 0 else: key = s[:eindex] value = s[eindex+1:] if value == 'true': value = 1 elif value == 'false' or not value: value = 0 # Only update non-existent properties if key and result.get(key) == None: result[key] = value self.properties = result except: traceback.print_exc() self.properties = None def getType(self): """Type accessor""" return self.type def getName(self): """Name accessor""" if self.type is not None and self.name.endswith("." + self.type): return self.name[:len(self.name) - len(self.type) - 1] return self.name def getAddress(self): """Address accessor""" return self.address def getPort(self): """Port accessor""" return self.port def getPriority(self): """Pirority accessor""" return self.priority def getWeight(self): """Weight accessor""" return self.weight def getProperties(self): """Properties accessor""" return self.properties def getText(self): """Text accessor""" return self.text def getServer(self): """Server accessor""" return self.server def updateRecord(self, zeroconf, now, record): """Updates service information from a DNS record""" if record is not None and not record.isExpired(now): if record.type == _TYPE_A: #if record.name == self.name: if record.name == self.server: self.address = record.address elif record.type == _TYPE_SRV: if record.name == self.name: self.server = record.server self.port = record.port self.weight = record.weight self.priority = record.priority #self.address = None self.updateRecord(zeroconf, now, zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN)) elif record.type == _TYPE_TXT: if record.name == self.name: self.setText(record.text) def request(self, zeroconf, timeout): """Returns true if the service could be discovered on the network, and updates this object with details discovered. """ now = currentTimeMillis() delay = _LISTENER_TIME next = now + delay last = now + timeout result = 0 try: zeroconf.addListener(self, DNSQuestion(self.name, _TYPE_ANY, _CLASS_IN)) while self.server is None or self.address is None or self.text is None: if last <= now: return 0 if next <= now: out = DNSOutgoing(_FLAGS_QR_QUERY) out.addQuestion(DNSQuestion(self.name, _TYPE_SRV, _CLASS_IN)) out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_SRV, _CLASS_IN), now) out.addQuestion(DNSQuestion(self.name, _TYPE_TXT, _CLASS_IN)) out.addAnswerAtTime(zeroconf.cache.getByDetails(self.name, _TYPE_TXT, _CLASS_IN), now) if self.server is not None: out.addQuestion(DNSQuestion(self.server, _TYPE_A, _CLASS_IN)) out.addAnswerAtTime(zeroconf.cache.getByDetails(self.server, _TYPE_A, _CLASS_IN), now) zeroconf.send(out) next = now + delay delay = delay * 2 # zeroconf.run(True) zeroconf.wait(min(next, last) - now) now = currentTimeMillis() result = 1 finally: zeroconf.removeListener(self) return result def __eq__(self, other): """Tests equality of service name""" if isinstance(other, ServiceInfo): return other.name == self.name return 0 def __ne__(self, other): """Non-equality test""" return not self.__eq__(other) def __repr__(self): """String representation""" result = "service[%s,%s:%s," % (self.name, socket.inet_ntoa(self.getAddress()), self.port) if self.text is None: result += "None" else: if len(self.text) < 20: result += self.text else: result += self.text[:17] + "..." result += "]" return result class Zeroconf(object): """Implementation of Zeroconf Multicast DNS Service Discovery Supports registration, unregistration, queries and browsing. """ def __init__(self, bindaddress=None): """Creates an instance of the Zeroconf class, establishing multicast communications, listening and reaping threads.""" globals()['_GLOBAL_DONE'] = False if bindaddress is None: self.intf = socket.gethostbyname(socket.gethostname()) else: self.intf = bindaddress self.group = ('', _MDNS_PORT) self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) except AttributeError: # SO_REUSEADDR should be equivalent to SO_REUSEPORT for # multicast UDP sockets (p 731, "TCP/IP Illustrated, # Volume 2"), but some BSD-derived systems require # SO_REUSEPORT to be specified explicity. Also, not all # versions of Python have SO_REUSEPORT available. So # if you're on a BSD-based system, and haven't upgraded # to Python 2.3 yet, you may find this library doesn't # work as expected. # pass self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 255) self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, True) try: self.socket.bind(self.group) except socket.error: # Some versions of linux raise an exception even though # the SO_REUSE* options have been set, so ignore it # traceback.print_exc() pass self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(self.intf) + socket.inet_aton('0.0.0.0')) self.socket.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) self.listeners = [] self.browsers = [] self.services = {} self.servicetypes = {} self.cache = DNSCache() self.engine = Engine(self) self.enginewait = 0 self.listener = Listener(self) self.reaper = Reaper(self) self.reaperwait = 0 def isLoopback(self): return self.intf.startswith("127.0.0.1") def isLinklocal(self): return self.intf.startswith("169.254.") def getServiceInfo(self, type, name, timeout=5000): """Returns network's service information for a particular name and type, or None if no service matches by the timeout, which defaults to 3 seconds.""" info = ServiceInfo(type, name) if info.request(self, timeout): return info return None def addServiceListener(self, type, listener): """Adds a listener for a particular service type. This object will then have its updateRecord method called when information arrives for that type.""" self.removeServiceListener(listener) self.browsers.append(ServiceBrowser(self, type, listener)) def removeServiceListener(self, listener): """Removes a listener from the set that is currently listening.""" for browser in self.browsers: if browser.listener == listener: browser.cancel() del(browser) def registerService(self, info, ttl=_DNS_TTL): """Registers service information to the network with a default TTL of 60 seconds. Zeroconf will then respond to requests for information for that service. The name of the service may be changed if needed to make it unique on the network.""" self.checkService(info) self.services[info.name.lower()] = info if self.servicetypes.has_key(info.type): self.servicetypes[info.type]+=1 else: self.servicetypes[info.type]=1 now = currentTimeMillis() nextTime = now i = 0 while i < 3: if now < nextTime: self.wait(nextTime - now) now = currentTimeMillis() continue out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, ttl, info.name), 0) out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, ttl, info.priority, info.weight, info.port, info.server), 0) out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, ttl, info.text), 0) if info.address: out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, ttl, info.address), 0) self.send(out) i += 1 nextTime += _REGISTER_TIME def unregisterService(self, info): """Unregister a service.""" try: del(self.services[info.name.lower()]) if self.servicetypes[info.type]>1: self.servicetypes[info.type]-=1 else: del self.servicetypes[info.type] except: traceback.print_exc() now = currentTimeMillis() nextTime = now i = 0 while i < 3: if now < nextTime: self.wait(nextTime - now) now = currentTimeMillis() continue out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.name), 0) out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) if info.address: out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) self.send(out) i += 1 nextTime += _UNREGISTER_TIME def unregisterAllServices(self): """Unregister all registered services.""" if len(self.services) > 0: now = currentTimeMillis() nextTime = now i = 0 while i < 3: if now < nextTime: self.wait(nextTime - now) now = currentTimeMillis() continue out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) for info in self.services.values(): out.addAnswerAtTime(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, 0, info.name), 0) out.addAnswerAtTime(DNSService(info.name, _TYPE_SRV, _CLASS_IN, 0, info.priority, info.weight, info.port, info.server), 0) out.addAnswerAtTime(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0, info.text), 0) if info.address: out.addAnswerAtTime(DNSAddress(info.server, _TYPE_A, _CLASS_IN, 0, info.address), 0) self.send(out) i += 1 nextTime += _UNREGISTER_TIME def checkService(self, info): """Checks the network for a unique service name, modifying the ServiceInfo passed in if it is not unique.""" now = currentTimeMillis() nextTime = now i = 0 while i < 3: for record in self.cache.entriesWithName(info.type): if record.type == _TYPE_PTR and not record.isExpired(now) and record.alias == info.name: if (info.name.find('.') < 0): info.name = info.name + ".[" + info.address + ":" + info.port + "]." + info.type self.checkService(info) return raise NonUniqueNameException, "Name %s is not unquie" % info.name if now < nextTime: self.wait(nextTime - now) now = currentTimeMillis() continue out = DNSOutgoing(_FLAGS_QR_QUERY | _FLAGS_AA) self.debug = out out.addQuestion(DNSQuestion(info.type, _TYPE_PTR, _CLASS_IN)) out.addAuthorativeAnswer(DNSPointer(info.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, info.name)) self.send(out) i += 1 nextTime += _CHECK_TIME def addListener(self, listener, question): """Adds a listener for a given question. The listener will have its updateRecord method called when information is available to answer the question.""" now = currentTimeMillis() self.listeners.append(listener) if question is not None: for record in self.cache.entriesWithName(question.name): if question.answeredBy(record) and not record.isExpired(now): listener.updateRecord(self, now, record) def removeListener(self, listener): """Removes a listener.""" try: self.listeners.remove(listener) except: traceback.print_exc() def updateRecord(self, now, rec): """Used to notify listeners of new information that has updated a record.""" for listener in self.listeners: listener.updateRecord(self, now, rec) def handleResponse(self, msg): """Deal with incoming response packets. All answers are held in the cache, and listeners are notified.""" now = currentTimeMillis() for record in msg.answers: expired = record.isExpired(now) if record in self.cache.entries(): if expired: self.cache.remove(record) else: entry = self.cache.get(record) if entry is not None: entry.resetTTL(record) record = entry else: self.cache.add(record) self.updateRecord(now, record) def handleQuery(self, msg, addr, port): """Deal with incoming query packets. Provides a response if possible.""" out = None # Support unicast client responses # if port != _MDNS_PORT: out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA, 0) for question in msg.questions: out.addQuestion(question) for question in msg.questions: if question.type == _TYPE_PTR: if question.name == "_services._dns-sd._udp.local.": for stype in self.servicetypes.keys(): if out is None: out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out.addAnswer(msg, DNSPointer("_services._dns-sd._udp.local.", _TYPE_PTR, _CLASS_IN, _DNS_TTL, stype)) for service in self.services.values(): if question.name == service.type: if out is None: out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) out.addAnswer(msg, DNSPointer(service.type, _TYPE_PTR, _CLASS_IN, _DNS_TTL, service.name)) else: try: if out is None: out = DNSOutgoing(_FLAGS_QR_RESPONSE | _FLAGS_AA) # Answer A record queries for any service addresses we know if question.type == _TYPE_A or question.type == _TYPE_ANY: for service in self.services.values(): if service.server == question.name.lower(): out.addAnswer(msg, DNSAddress(question.name, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) service = self.services.get(question.name.lower(), None) if not service: continue if question.type == _TYPE_SRV or question.type == _TYPE_ANY: out.addAnswer(msg, DNSService(question.name, _TYPE_SRV, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.priority, service.weight, service.port, service.server)) if question.type == _TYPE_TXT or question.type == _TYPE_ANY: out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text)) if question.type == _TYPE_SRV: out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) except: traceback.print_exc() if out is not None and out.answers: out.id = msg.id self.send(out, addr, port) def send(self, out, addr = _MDNS_ADDR, port = _MDNS_PORT): """Sends an outgoing packet.""" # This is a quick test to see if we can parse the packets we generate #temp = DNSIncoming(out.packet()) try: bytes_sent = self.socket.sendto(out.packet(), 0, (addr, port)) if bytes_sent != len(out.packet()): pass # FIXME: Should error here except (IOError, socket.error), e: # Ignore this, it may be a temporary loss of network connection traceback.print_exc() def close(self): """Ends the background threads, and prevent this instance from servicing further queries.""" if globals()['_GLOBAL_DONE'] == False: globals()['_GLOBAL_DONE'] = True self.unregisterAllServices() self.socket.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, socket.inet_aton(_MDNS_ADDR) + socket.inet_aton('0.0.0.0')) self.socket.close() def wait(self, time): now = currentTimeMillis() till = now+time while now < till: #print "Waiting", now, till self.run(True) now = currentTimeMillis() def run(self, force=False): now = currentTimeMillis() if self.enginewait < now or force: self.enginewait = self.engine.run() if self.enginewait is None: return None self.enginewait += now if self.reaperwait < now or force: self.reaperwait = self.reaper.run() if self.reaperwait is None: return None self.reaperwait += now return min(self.enginewait, self.reaperwait) # Test a few module features, including service registration, service # query (for Zoe), and service unregistration. if __name__ == '__main__': print "Multicast DNS Service Discovery for Python, version", __version__ r = Zeroconf() print "1. Testing registration of a service..." desc = {'version':'0.10','a':'test value', 'b':'another value'} info = ServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.", socket.inet_aton("127.0.0.1"), 1234, 0, 0, desc) print " Registering service..." r.registerService(info) print " Registration done." print "2. Testing query of service information..." print " Getting ZOE service:", str(r.getServiceInfo("_http._tcp.local.", "ZOE._http._tcp.local.")) print " Query done." print "3. Testing query of own service..." print " Getting self:", str(r.getServiceInfo("_http._tcp.local.", "My Service Name._http._tcp.local.")) print " Query done." print "4. Testing unregister of service information..." r.unregisterService(info) print " Unregister done." r.close() libtpproto-py-0.2.5/tp/netlib/discover/avahi_browser.py0000644000175000017500000001337110755215071021441 0ustar timtim import avahi_disabled import os, sys import traceback import time import avahi, avahi.ServiceTypeDatabase import gobject import dbus from dbus.mainloop.glib import DBusGMainLoop service_type_browsers = {} service_browsers = {} service_type_db = avahi.ServiceTypeDatabase.ServiceTypeDatabase() service_seen = {} from browse import ZeroConfBrowser as ZeroConfBrowserBase import threading class ZeroConfBrowser(ZeroConfBrowserBase): def check(): bus = dbus.SystemBus(mainloop=DBusGMainLoop(set_as_default=False)) try: server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) print "avahi version", server.GetVersionString() except dbus.DBusException, e: print e return False return True check = staticmethod(check) ###################################### # Helper functions ###################################### def protoname(self, protocol): if protocol == avahi.PROTO_INET: return "IPv4" if protocol == avahi.PROTO_INET6: return "IPv6" return "n/a" def siocgifname(self, interface): if interface <= 0: return "n/a" else: return self.server.GetNetworkInterfaceNameByIndex(interface) def get_interface_name(self, interface, protocol): if interface == avahi.IF_UNSPEC and protocol == avahi.PROTO_UNSPEC: return "Wide Area" else: return str(self.siocgifname(interface)) + " " + str(self.protoname(protocol)) def lookup_type(self, stype): global service_type_db try: return service_type_db[stype] except KeyError: return stype def print_error(self, err): print "Error:", str(err) def pair_to_dict(self, l): res = dict() for el in l: if "=" not in el: res[el]='' else: tmp = el.split('=',1) if len(tmp[0]) > 0: res[tmp[0]] = tmp[1] return res ###################################### # Callback functions ###################################### def callback(self, function): def wrapper(*args, **kw): self.pending.append((function, args, kw)) return wrapper def service_resolved(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): """\ Called when all the information about a service is avaliable. """ assert threading.currentThread() == self.dbusthread if len(txt) != 0: details = self.pair_to_dict(avahi.txt_array_to_string_array(txt)) else: details = [] self.ServiceFound(name, stype.split('.')[0][1:], (host, address, port), details, details) def new_service(self, interface, protocol, name, stype, domain, flags): """\ Called when a new service is found. """ assert threading.currentThread() == self.dbusthread self.server.ResolveService( interface, protocol, name, stype, domain, \ avahi.PROTO_UNSPEC, dbus.UInt32(0), reply_handler=self.callback(self.service_resolved), error_handler=self.callback(self.print_error)) def remove_service(self, interface, protocol, name, stype, domain, flags): """\ Called when a service disappears. """ assert threading.currentThread() == self.dbusthread self.ServiceGone(name, stype.split('.')[0][1:], None) def new_domain(self, interface, protocol, domain, flags): """\ Called when a new domain appears. """ assert threading.currentThread() == self.dbusthread ifn = self.get_interface_name(interface, protocol) if domain != "local": self.browse_domain(interface, protocol, domain) def browse_domain(self, interface, protocol, domain): """ Register to browse a given domain. """ assert threading.currentThread() == self.dbusthread # FIXME: This is probably quite bad! global service_type_browsers # Are we already browsing this domain? if service_type_browsers.has_key((interface, protocol, domain)): return try: b = dbus.Interface( \ self.bus.get_object(avahi.DBUS_NAME, \ self.server.ServiceTypeBrowserNew(interface, protocol, domain, dbus.UInt32(0)) ), avahi.DBUS_INTERFACE_SERVICE_TYPE_BROWSER) except dbus.DBusException, e: print e traceback.print_exc() for stype in ['_tp', '_tps', '_tp-http', '_tp-https']: stype = stype+'._tcp' b = dbus.Interface( \ self.bus.get_object(avahi.DBUS_NAME, \ self.server.ServiceBrowserNew(interface, protocol, stype, domain, dbus.UInt32(0)) ), avahi.DBUS_INTERFACE_SERVICE_BROWSER) service_browsers[(interface, protocol, stype, domain)] = b b.connect_to_signal('ItemNew', self.callback(self.new_service)) b.connect_to_signal('ItemRemove', self.callback(self.remove_service)) def __init__(self): ZeroConfBrowserBase.__init__(self) import dbus.mainloop.glib dbus.mainloop.glib.threads_init() self.pending = [] def run(self): self.dbusthread = threading.currentThread() from dbus.mainloop.glib import DBusGMainLoop mainloop = DBusGMainLoop(set_as_default=True) self.bus = dbus.SystemBus(mainloop=mainloop) self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) # Explicitly browse .local self.browse_domain(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "local") # Browse for other browsable domains db = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.DomainBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "", avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))), avahi.DBUS_INTERFACE_DOMAIN_BROWSER) db.connect_to_signal('ItemNew', self.new_domain) self.mainloop = gobject.MainLoop() gcontext = self.mainloop.get_context() while not self.mainloop is None: if len(self.pending) > 0: command, args, kw = self.pending.pop(0) command(*args, **kw) if gcontext.pending(): gcontext.iteration() else: time.sleep(0.01) def exit(self): if hasattr(self, 'mainloop'): self.mainloop.quit() self.mainloop = None def main(): a = ZeroConfBrowser() a.run() if __name__ == "__main__": main() libtpproto-py-0.2.5/tp/netlib/discover/metaserver_server.py0000644000175000017500000000374210764150204022346 0ustar timtim import time import urllib from server import Server, Game metaserver = "http://metaserver.thousandparsec.net/" class MetaServerServer(Server): def __init__(self, metaserver=metaserver, timeout=60*10): self.server = metaserver self.timeout = timeout self.waittill = 0 self.games = {} self._exit = False def GameAdd(self, game): if self.games.has_key(game.name): raise SyntaxException("Game with that name already exists!") self.games[game.name] = game # Reset the wait time self.waittill = time.time() def GameRemove(self, game): if not self.games.has_key(game.name): raise SyntaxException("Game with that name does not exist!") del self.games[game.name] def exit(self): self._exit = True def run(self): print "metaserver_server", self while not self._exit: now = time.time() if self.waittill < now: for game in self.games.values(): param = {} param['action'] = 'update' param['name'] = game.name for n, v in game.required.items(): param[n] = v for n, v in game.optional.items(): param[n] = v i = 0 for type, locations in game.locations.items(): for location in locations: param['type%i' % i] = type param['dns%i' % i] = location[0] param['ip%i' % i] = location[1] param['port%i' % i] = location[2] i += 1 print param data = urllib.urlopen(self.server, urllib.urlencode(param)).read() print data self.waittill = now + self.timeout try: time.sleep(min(60, self.waittill - time.time())) except OSError, e: print e, self.waittill - time.time() def main(): a = MetaServerServer('localhost') g = Game('testing!') g.updateRequired({'key': 'abc', 'tp': '0.3', 'server': 'bazillion!', 'sertype': 'none!', 'rule':'Testing', 'rulever': '0.0.1'}) g.updateOptional({'admin': 'abc@peanut!', 'cmt':'Hello!'}) g.addLocation('tp+http', ('localhost', '127.0.0.1', 6923)) a.GameAdd(g) a.run() if __name__ == "__main__": main() libtpproto-py-0.2.5/tp/netlib/discover/metaserver_browser.py0000644000175000017500000000402110755215071022516 0ustar timtim import time import urllib from browse import Browser, Game metaserver = "http://metaserver.thousandparsec.net/" def getpacket(data): header, data = data[:objects.Header.size], data[objects.Header.size:] p = objects.Header.fromstr(header) p.__process__(data[:p.length]) return p, data[p.length:] from tp.netlib import objects class MetaServerBrowser(Browser): def __init__(self, timeout=60*10): self.timeout = timeout self.waittill = 0 self.games = {} self._exit = False def exit(self): self._exit = True def run(self): print "metaserver_browse", self while not self._exit: now = time.time() if self.waittill < now: data = urllib.urlopen(metaserver, urllib.urlencode({'action': 'get'})).read() if data[:4] == "TP03": p, data = getpacket(data) if isinstance(p, objects.Fail): print p.error elif isinstance(p, objects.Sequence): g = self.games for game in g.values(): game.notfound = True for i in range(0, p.number): p, data = getpacket(data) if not isinstance(p, objects.Game): raise TypeError("The metaserver returned an incorrect response (expected Game packet)...") if not p.name in g: game = Game(p.name) else: game = g[p.name] game.updateOptional(p.optional) game.updateRequired(p.required) game.locations = {} for type, dns, ip, port in p.locations: game.addLocation(type, (dns, ip, port)) if game.new: self.GameFound(game) game.new = False else: self.GameUpdate(game) game.notfound = False g[game.name] = game for k, game in g.items(): if game.notfound: del g[k] self.GameGone(game) else: raise TypeError("The metaserver returned an incorrect response (expected Failure or Sequence)...") self.waittill = now + self.timeout time.sleep(min(1, self.waittill - time.time())) def main(): a = MetaServerBrowser() a.run() if __name__ == "__main__": main()