from struct import *
# global message list is a collection
# of tuplets connecting message IDs to
# message formatting functions. The idea
# here is to abstract and make messages
# more portable.
defaultMessageFormats = []
NODATA = b''
PACK = True
UNPACK = False
# either packs or unpacks the data relative
# to an ident and returns it in either a bytes object
# or a tuple of sorts. Expects the reverse depending on
# whether or not you're packing.
def formatmessage(ident, data, packing, formatList=defaultMessageFormats):
for formatter in formatList:
if formatter[0] == ident:
return formatter[1](data, packing)
print("Unknown message type: " + str(data))
#no handler, return nothing
if packing:
return b''
else:
return ()
# Return a string name that pairs with each message id. This
# is useful for debugging purposes.
def getidentname(ident, formatList=defaultMessageFormats):
# Loop through the list of formatters and
# return the name of the matching id.
for formatter in formatList:
if formatter[0] == ident:
return formatter[2]
# The id is unknown
return "Unknown"
#________
# UTILITY
# strip the identifier out of a bytes object
# and return a pair: identifier, bytes_data
def stripident(rawdata):
if len(rawdata) == 2:
return unpack("!H", rawdata)[0], b''
elif len(rawdata) > 2:
return unpack("!H", rawdata[0:2])[0], rawdata[2:]
else:
return 0
# clipstring is a utility function used for
# taking oversized strings (due to fixed length
# network messages) and shrinking them down
# to their actual size
def clipstring(oversized):
for i in range(len(oversized)):
if oversized[i] is 0 or oversized[i] == '\x00':
return oversized[0:i]
return oversized
#_________
# MESSAGES
id_iterator = 0
def AllocID():
global id_iterator
out = id_iterator
id_iterator += 1
return out
# No message
MESSAGE_NONE = AllocID()
# PING ID
MESSAGE_PING = AllocID()
# PING FORMATTER
def fm_ping(data, packing):
if packing:
return b''
else:
return ()
# APPENDING PING
defaultMessageFormats.append((MESSAGE_PING, fm_ping, "Ping"))
# PONG ID (reply to ping)
MESSAGE_PONG = AllocID()
# PONG FORMATTER
def fm_pong(data, packing):
if packing:
return b''
else:
return ()
# APPENDING PONG
defaultMessageFormats.append((MESSAGE_PONG, fm_pong, "Pong"))
#__________________________
# UDP PUNCHTHROUGH MESSAGES
# PUNCH ACK
# An acknowledgement of a full punchthrough
# The flow is as such:
# Ping -> Pong -> Punch Ack
MESSAGE_PUNCHACK = AllocID()
# REGISTER PUNCH ACK FORMATTER
def fm_punchack(data, packing):
if packing:
return b''
else:
return ()
# APPENDING PUNCHACK
defaultMessageFormats.append((MESSAGE_PUNCHACK, fm_punchack, "Punch Acknowledge"))
# REGISTER UUID
# From the client to facilitator registering
# a uuid with this IP address. The UUID allows
# clients to recognize each other even if they
# are behind the same public address.
MESSAGE_REGISTERUUID = AllocID()
# REGISTER UUID FORMATTER
def fm_registeruuid(data, packing):
if packing:
# as a one length tuple it could accidentally be passed
# in as a string. ("string",) is proper for one length tuples.
if type(data) is str:
data = (data,)
# data tuple expected as (ip,)
return pack("!32s", data[0])
else:
unpacked = unpack("!32s", data)
cleaned = (clipstring(unpacked[0]).decode('ascii'),)
return cleaned
# APPENDING UREGISTER UID FORMATTER
defaultMessageFormats.append((MESSAGE_REGISTERUUID, fm_registeruuid, "Register UUID"))
# REQUEST PUNCHTHROUGH
# From client to facilitator requesting a
# connection to a remote ip.
MESSAGE_REQPUNCH = AllocID()
# REQUEST PUNCHTHROUGH FORMATTER
def fm_reqpunch(data, packing):
if packing:
# as a one length tuple it could accidentally be passed
# in as a string. ("string",) is proper for one length tuples.
if type(data) is str:
data = (data,)
# data tuple expected as (ip,)
return pack("!16s", data[0])
else:
unpacked = unpack("!16s", data)
cleaned = (clipstring(unpacked[0]).decode('ascii'),)
return cleaned
# APPENDING REQUEST PUNCH
defaultMessageFormats.append((MESSAGE_REQPUNCH, fm_reqpunch, "Request IP Punchthrough"))
# REQUEST PUNCTHROUGH VIA UUID
# From the client to the facilitator requesting
# a connection to a remote peer via their
# uuid as registered on the facilitator.
MESSAGE_REQPUNCHUUID = AllocID()
# REQUEST PUNCHTHROUGH VIA UUID FORMATTER
def fm_reqpunchuuid(data, packing):
if packing:
# as a one length tuple it could accidentally be passed
# in as a string. ("string",) is proper for one length tuples.
if type(data) is str:
data = (data,)
# data tuple expected as (ip,)
return pack("!32s", data[0])
else:
unpacked = unpack("!32s", data)
cleaned = (clipstring(unpacked[0]).decode('ascii'),)
return cleaned
# APPENDING PUNCHTHROUGH VIA UUID
defaultMessageFormats.append((MESSAGE_REQPUNCHUUID, fm_reqpunchuuid, "Request UUID Punchthrough"))
# EXECUTE PUNCHTHROUGH
# From the server to the clients containing
# an IP and PORT to connect to. The clients
# are expected to execute the punch routine.
MESSAGE_EXCPUNCH = AllocID()
# EXECUTE PUNCH FORMATTER
def fm_excpunch(data, packing):
if packing:
# data tuple expected as (ip, port)
return pack("!16sI", data[0], data[1])
else:
unpacked = unpack("!16sI", data)
cleaned = (clipstring(unpacked[0]).decode('ascii'), unpacked[1])
return cleaned
# APPENDING EXECUTE PUNCH
defaultMessageFormats.append((MESSAGE_EXCPUNCH, fm_excpunch, "Execute Punchthrough"))
#_____________
# P2P MESSAGES
# REQUEST PEERS
# From the peer/client to the host
# Request a list of peers to connect to.
# This is generic and leaves the host
# the power to decide who should connect
# to whom.
MESSAGE_REQPEERS = AllocID()
# REQPEER FORMATTER
def fm_reqpeers(data, packing):
if packing:
return b''
else:
return ()
# APPEND REQPEERS
defaultMessageFormats.append((MESSAGE_REQPEERS, fm_reqpeers, "Request Peers"))
# CONNECT PEER
# From the host to the peer/client
# Sends a peer ip to connect to.
# As a note, these should be sent
# to both end, or a punchthrough
# won't work. Sends the ip of
# a peer that is registered on the
# facilitator. The client still
# needs to interface with the facilitator
# to actually connect.
MESSAGE_CONPEER = AllocID()
# CONPEER FORMATTER
def fm_conpeer(data, packing):
if packing:
# as a one length tuple it could accidentally be passed
# in as a string. ("string",) is proper for one length tuples.
if type(data) is str:
data = (data,)
return pack("!16s", data[0])
else:
return (unpack("!16s", data)[0].decode('ascii'),)
# APPENDING CONPEER
defaultMessageFormats.append((MESSAGE_CONPEER, fm_conpeer, "Connect Peer IP"))
# CONNECT PEER UUID
# From the host to the peer/client
# Tells the peer the uuid of a peer
# that the client shoudl try to connect
# to that is registered on the facilitator.
MESSAGE_CONPEERUUID = AllocID()
# CONPEERUUID FORMATTER
def fm_conpeeruuid(data, packing):
if packing:
# as a one length tuple it could accidentally be passed
# in as a string. ("string",) is proper for one length tuples.
if type(data) is str:
data = (data,)
return pack("!32s", data[0])
else:
return (unpack("!32s", data)[0].decode('ascii'),)
# APPENDING CONPEERUUID
defaultMessageFormats.append((MESSAGE_CONPEERUUID, fm_conpeeruuid, "Connect Peer UUID"))
# ASSIGN UUID
# the host assigns each peer a uuid so they may be mapped
# even if they are on the same NAT showing the same public
# ip address. This message is also used when the peer registers
# itself on the facilitator.
MESSAGE_ASSIGNUUID = AllocID()
# ASSIGN UUID FORMATTER
def fm_assignuuid(data, packing):
if packing:
# as a one length tuple it could accidentally be passed
# in as a string. ("string",) is proper for one length tuples.
if type(data) is str:
data = (data,)
return pack("!32s", data[0])
else:
return (unpack("!32s", data)[0].decode('ascii'),)
# APPENDING ASSIGN UUID
defaultMessageFormats.append((MESSAGE_ASSIGNUUID, fm_assignuuid, "Assign UUID"))