Implemented GMCP with the new oobhandler. Resolves #208.

This commit is contained in:
Griatch 2015-02-14 19:46:25 +01:00
parent 00aa28004c
commit 08525f11ee
8 changed files with 172 additions and 144 deletions

View file

@ -191,17 +191,22 @@ class PortalSessionHandler(SessionHandler):
Helper method for each session to use to parse oob structures
(The 'oob' kwarg of the msg() method).
Allowed input oob structures are:
cmdname
((cmdname,), (cmdname,))
(cmdname,(arg, ))
(cmdname,(arg1,arg2))
(cmdname,{key:val,key2:val2})
(cmdname, (args,), {kwargs})
((cmdname, (arg1,arg2)), cmdname, (cmdname, (arg1,)))
outputs an ordered structure on the form
((cmdname, (args,), {kwargs}), ...), where the two last
parts of each tuple may be empty
Args: oobstruct (str or iterable): A structure representing
an oob command on one of the following forms:
- "cmdname"
- "cmdname", "cmdname"
- ("cmdname", arg)
- ("cmdname",(args))
- ("cmdname",{kwargs}
- ("cmdname", (args), {kwargs})
- (("cmdname", (args,), {kwargs}), ("cmdname", (args,), {kwargs}))
and any combination of argument-less commands or commands with only
args, only kwargs or both.
Returns:
structure (tuple): A generic OOB structure on the form
`((cmdname, (args,), {kwargs}), ...)`, where the two last
args and kwargs may be empty
"""
def _parse(oobstruct):
slen = len(oobstruct)
@ -217,17 +222,17 @@ class PortalSessionHandler(SessionHandler):
return (oobstruct[0], (), {})
elif slen == 2:
if isinstance(oobstruct[1], dict):
# cmdname, {kwargs}
# (cmdname, {kwargs})
return (oobstruct[0], (), dict(oobstruct[1]))
elif isinstance(oobstruct[1], (tuple, list)):
# cmdname, (args,)
return (oobstruct[0], list(oobstruct[1]), {})
# (cmdname, (args,))
return (oobstruct[0], tuple(oobstruct[1]), {})
else:
# cmdname, cmdname
return ((oobstruct[0], (), {}), (oobstruct[1].lower(), (), {}))
# (cmdname, arg)
return (oobstruct[0], (oobstruct[1],), {})
else:
# cmdname, (args,), {kwargs}
return (oobstruct[0], list(oobstruct[1]), dict(oobstruct[2]))
# (cmdname, (args,), {kwargs})
return (oobstruct[0], tuple(oobstruct[1]), dict(oobstruct[2]))
if hasattr(oobstruct, "__iter__"):
# differentiate between (cmdname, cmdname),
@ -264,7 +269,9 @@ class PortalSessionHandler(SessionHandler):
if session:
# convert oob to the generic format
if "oob" in kwargs:
print "oobstruct_parser in:", kwargs["oob"]
kwargs["oob"] = self.oobstruct_parser(kwargs["oob"])
print "oobstruct_parser out:", kwargs["oob"]
session.data_out(text=text, **kwargs)
PORTAL_SESSIONS = PortalSessionHandler()

View file

@ -26,8 +26,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
"""
def connectionMade(self):
"""
This is called when the connection is first
established.
This is called when the connection is first established.
"""
# initialize the session
self.iaw_mode = False
@ -35,7 +34,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
client_address = self.transport.client
# this number is counted down for every handshake that completes.
# when it reaches 0 the portal/server syncs their data
self.handshakes = 6 # naws, ttype, mccp, mssp, oob, mxp
self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp
self.init_session("telnet", client_address, self.factory.sessionhandler)
# negotiate client size
@ -47,7 +46,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.mccp = Mccp(self)
# negotiate mssp (crawler communication)
self.mssp = mssp.Mssp(self)
# oob communication (MSDP, GMCP)
# oob communication (MSDP, GMCP) - two handshake calls!
self.oob = telnet_oob.TelnetOOB(self)
# mxp support
self.mxp = Mxp(self)
@ -67,7 +66,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
When all have reported, a sync with the server is performed.
The system will force-call this sync after a small time to handle
clients that don't reply to handshakes at all.
info - debug text from the protocol calling
"""
if self.handshakes > 0:
if force:
@ -239,7 +237,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if "oob" in kwargs and "OOB" in self.protocol_flags:
# oob is a list of [(cmdname, arg, kwarg), ...]
for cmdname, args, kwargs in kwargs["oob"]:
print "telnet oob data_out:", cmdname, args, kwargs
#print "telnet oob data_out:", cmdname, args, kwargs
self.oob.data_out(cmdname, *args, **kwargs)
# parse **kwargs, falling back to ttype if nothing is given explicitly

View file

@ -33,7 +33,7 @@ MSDP_ARRAY_OPEN = chr(5)
MSDP_ARRAY_CLOSE = chr(6)
# GMCP
GMCP = chr(200)
GMCP = chr(201)
# General Telnet
IAC = chr(255)
@ -71,16 +71,18 @@ class TelnetOOB(object):
self.protocol.protocol_flags['OOB'] = False
self.MSDP = False
self.GMCP = False
# detect MSDP first
self.protocol.negotiationMap[MSDP] = self.data_in
# ask for the available protocols and assign decoders
# (note that handshake_done() will be called twice!)
self.protocol.negotiationMap[MSDP] = self.decode_msdp
self.protocol.negotiationMap[GMCP] = self.decode_gmcp
self.protocol.will(MSDP).addCallbacks(self.do_msdp, self.no_msdp)
self.protocol.will(GMCP).addCallbacks(self.do_gmcp, self.no_gmcp)
self.oob_reported = {}
def no_msdp(self, option):
"No msdp supported or wanted"
# no msdp, check GMCP
self.protocol.negotiationMap[GMCP] = self.data_in
self.protocol.will(GMCP).addCallbacks(self.do_oob, self.no_oob)
self.protocol.handshake_done()
def do_msdp(self, option):
"MSDP supported by client"
@ -122,16 +124,16 @@ class TelnetOOB(object):
if cmdname == "MSDP_ARRAY":
msdp_string = "".join(["%s%s" % (MSDP_VAL, val) for val in args])
else:
msdp_string = "%s%s%s" % (MSDP_VAR, cmdname.upper(), "".join(
msdp_string = "%s%s%s" % (MSDP_VAR, cmdname, "".join(
"%s%s" % (MSDP_VAL, val) for val in args))
elif kwargs:
if cmdname == "MSDP_TABLE":
msdp_string = "".join(["%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, val)
for key, val in kwargs.items()])
else:
msdp_string = "%s%s%s" % (MSDP_VAR. cmdname.upper(), "".join(
msdp_string = "%s%s%s" % (MSDP_VAR. cmdname, "".join(
["%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, val) for key, val in kwargs.items()]))
print "encode msdp result:", cmdname, args, kwargs, "->", msdp_string
#print "encode msdp result:", cmdname, args, kwargs, "->", msdp_string
return force_str(msdp_string)
def encode_gmcp(self, cmdname, *args, **kwargs):
@ -145,8 +147,8 @@ class TelnetOOB(object):
cmdname is generally recommended to be a string on the form
Module.Submodule.Function
"""
if cmdname in ("SEND", "ECHO", "REPORT", "UNREPORT", "LIST"):
# we wrap the standard MSDP commands in a MSDP. submodule
if cmdname in ("SEND", "REPORT", "UNREPORT", "LIST"):
# we wrap the standard MSDP commands in a MSDP.submodule
# here as far as GMCP is concerned.
cmdname = "MSDP.%s" % cmdname
elif cmdname in ("MSDP_ARRAY", "MSDP_TABLE"):
@ -156,15 +158,16 @@ class TelnetOOB(object):
gmcp_string = "%s %s" % (cmdname, json.dumps(args))
elif kwargs:
gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs))
print "gmcp_encode", cmdname, args, kwargs, "->", gmcp_string
return force_str(gmcp_string).strip()
def decode_msdp(self, data):
"""
Decodes incoming MSDP data
cmdname, var --> cmdname arg
cmdname, array --> cmdname *args
cmdname, table --> cmdname **kwargs
cmdname var --> cmdname arg
cmdname array --> cmdname *args
cmdname table --> cmdname **kwargs
"""
tables = {}
@ -174,8 +177,6 @@ class TelnetOOB(object):
if hasattr(data, "__iter__"):
data = "".join(data)
#logger.log_infomsg("MSDP SUBNEGOTIATION: %s" % data)
# decode
for key, table in msdp_regex_table.findall(data):
tables[key] = {}
@ -190,9 +191,9 @@ class TelnetOOB(object):
for varval in msdp_regex_var.split(msdp_regex_array.sub("", msdp_regex_table.sub("", data))):
# get remaining varvals after cleaning away tables/arrays
parts = msdp_regex_val.split(varval)
variables[parts[0].upper()] = tuple(parts[1:]) if len(parts) > 1 else ("", )
variables[parts[0]] = tuple(parts[1:]) if len(parts) > 1 else ("", )
print "OOB: MSDP decode:", data, "->", variables, arrays, tables
#print "OOB: MSDP decode:", data, "->", variables, arrays, tables
# send to the sessionhandler
if data:
@ -215,16 +216,27 @@ class TelnetOOB(object):
cmdname {key:arg, key:arg, ...} -> cmdname **kwargs
"""
if hasattr(data, "__iter__"):
data = "".join(data)
print "decode_gmcp:", data
if data:
splits = data.split(" ", 1)
cmdname = splits[0]
if len(splits) < 2:
self.protocol.data_in(oob=(cmdname, (), {}))
else:
struct = json.loads(splits[1])
self.protocol.data_in(oob=(cmdname,
struct if isinstance(struct, list) else (),
struct if isinstance(struct, dict) else {}))
elif splits[1]:
struct = json.loads(json.dumps(splits[1]))
args, kwargs = (), {}
if hasattr(struct, "__iter__"):
if isinstance(struct, dict):
kwargs = struct
else:
args = tuple(struct)
else:
args = (struct,)
print "gmcp decode:", data, "->", cmdname, args, kwargs
self.protocol.data_in(oob=(cmdname, args, kwargs))
# access methods
@ -232,20 +244,10 @@ class TelnetOOB(object):
"""
Return a msdp-valid subnegotiation across the protocol.
"""
#print "data_out:", encoded_oob
if self.MSDP:
encoded_oob = self.encode_msdp(cmdname, *args, **kwargs)
print "data_out:", encoded_oob
self.protocol._write(IAC + SB + MSDP + encoded_oob + IAC + SE)
else:
if self.GMCP:
encoded_oob = self.encode_gmcp(cmdname, *args, **kwargs)
self.protocol._write(IAC + SB + GMCP + encoded_oob + IAC + SE)
def data_in(self, data):
"""
Send oob data to Evennia. The self.decode_* methods send to
protocol.data_in() themselves.
"""
if self.MSDP:
self.decode_msdp(data)
else:
self.decode_gmcp(data)