From a4f8019c4a4e770aa443a900c88688162d079209 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 20 Nov 2011 00:50:11 +0100 Subject: [PATCH] Added MCCP (compression of data stream between server-client). Not fully functioning yet, tintin++ tends to complain of compression errors after a while (the server degrades back to uncompressed mode gracefully though). MCCP is thus deactivated in the server at the moment. --- src/server/mccp.py | 64 ++++++++++++++++++++++++++++++++++++++++++++ src/server/telnet.py | 43 ++++++++++++++++++++++++----- src/server/ttype.py | 8 +++--- 3 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 src/server/mccp.py diff --git a/src/server/mccp.py b/src/server/mccp.py new file mode 100644 index 0000000000..04580a2a4d --- /dev/null +++ b/src/server/mccp.py @@ -0,0 +1,64 @@ +""" + +MCCP - Mud Client Compression Protocol + +The implements the MCCP v2 telnet protocol as per +http://tintin.sourceforge.net/mccp/. MCCP allows for the server to +compress data when sending to supporting clients, reducing bandwidth +by 70-90%.. The compression is done using Python's builtin zlib +library. If the client doesn't support MCCP, server sends uncompressed +instead. Note: On modern hardware you are not likely to notice the +effect of MCCP unless you have extremely heavy traffic or sits on a +terribly slow connection. + +""" +import zlib + +# negotiations for v1 and v2 of the protocol +MCCP = chr(86) +FLUSH = zlib.Z_SYNC_FLUSH + +def mccp_compress(protocol, data): + "Handles zlib compression, if applicable" + if hasattr(protocol, 'zlib'): + data = protocol.zlib.compress(data) + data += protocol.zlib.flush(FLUSH) + return data + +class Mccp(object): + """ + Implements the MCCP protocol. Add this to a + variable on the telnet protocol to set it up. + """ + + def __init__(self, protocol): + """ + initialize MCCP by storing protocol on + ourselves and calling the client to see if + it supports MCCP. Sets callbacks to + start zlib compression in that case. + """ + + self.protocol = protocol + self.protocol.protocol_flags['MCCP'] = False + # ask if client will mccp, connect callbacks to handle answer + self.protocol.will(MCCP).addCallbacks(self.do_mccp, self.no_mccp) + + def no_mccp(self, option): + """ + If client doesn't support mccp, don't do anything. + """ + print "deactivating mccp ..." + if hasattr(self.protocol, 'zlib'): + del self.protocol.zlib + self.protocol.protocol_flags['MCCP'] = False + + def do_mccp(self, option): + """ + The client supports MCCP. Set things up by + creating a zlib compression stream. + """ + print "activating mccp ..." + self.protocol.protocol_flags['MCCP'] = True + self.protocol.requestNegotiation(MCCP, '') + self.protocol.zlib = zlib.compressobj(9) diff --git a/src/server/telnet.py b/src/server/telnet.py index 7c910cecc5..e55bf0a47c 100644 --- a/src/server/telnet.py +++ b/src/server/telnet.py @@ -7,9 +7,9 @@ sessions etc. """ -from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE +from twisted.conch.telnet import Telnet, StatefulTelnetProtocol, IAC, LINEMODE, DO, DONT from src.server.session import Session -from src.server import ttype +from src.server import ttype, mccp from src.utils import utils, ansi class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): @@ -27,8 +27,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): client_address = self.transport.client self.init_session("telnet", client_address, self.factory.sessionhandler) - # setup ttype - self.ttype = ttype.Ttype(self) + # setup ttype (client info) + #self.ttype = ttype.Ttype(self) + + # setup mccp (data compression) + # self.mccp = mccp.Mccp(self) #TODO: mccp doesn't work quite right yet. # add us to sessionhandler self.sessionhandler.connect(self) @@ -38,7 +41,24 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): This sets up the options we allow for this protocol. """ return (option == LINEMODE or - option == ttype.TTYPE) + option == ttype.TTYPE or + option == mccp.MCCP) + + def enableLocal(self, option): + """ + Allow certain options on this protocol + """ + if option == mccp.MCCP: + #self.mccp.do_mccp(option) + return True + + def disableLocal(self, option): + if option == mccp.MCCP: + self.mccp.no_mccp(option) + return True + else: + return super(TelnetProtocol, self).disableLocal(option) + def connectionLost(self, reason): """ @@ -58,8 +78,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): # print "dataRcv:", data, # try: # for b in data: - # print ord(b), - # if b == chr(24): print "ttype found!" + # print ord(b), # print "" # except Exception, e: # print str(e) + ":", str(data) @@ -68,6 +87,16 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): super(TelnetProtocol, self).dataReceived(data) else: StatefulTelnetProtocol.dataReceived(self, data) + + def _write(self, byt): + "hook overloading the one used in plain telnet" + #print "_write (%s): %s" % (self.state, " ".join(str(ord(c)) for c in byt)) + super(TelnetProtocol, self)._write(mccp.mccp_compress(self, byt)) + + def sendLine(self, line): + "hook overloading the one used linereceiver" + #print "sendLine (%s):\n%s" % (self.state, line) + super(TelnetProtocol, self).sendLine(mccp.mccp_compress(self, line)) def lineReceived(self, string): """ diff --git a/src/server/ttype.py b/src/server/ttype.py index 92ee8622b5..701b06c29e 100644 --- a/src/server/ttype.py +++ b/src/server/ttype.py @@ -1,4 +1,6 @@ """ +TTYPE (MTTS) - Mud Terminal Type Standard + This module implements the TTYPE telnet protocol as per http://tintin.sourceforge.net/mtts/. It allows the server to ask the client about its capabilities. If the client also supports TTYPE, it @@ -42,9 +44,9 @@ class Ttype(object): self.protocol.protocol_flags['TTYPE'] = {} # setup protocol to handle ttype initialization and negotiation - self.protocol.negotiationMap[TTYPE] = self.negotiate_ttype + self.protocol.negotiationMap[TTYPE] = self.do_ttype # ask if client will ttype, connect callback if it does. - self.protocol.will(TTYPE).addCallbacks(self.negotiate_ttype, self.no_ttype) + self.protocol.will(TTYPE).addCallbacks(self.do_ttype, self.no_ttype) def no_ttype(self, option): """ @@ -52,7 +54,7 @@ class Ttype(object): """ pass - def negotiate_ttype(self, option): + def do_ttype(self, option): """ Handles negotiation of the ttype protocol once the client has confirmed that it supports the ttype