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.

This commit is contained in:
Griatch 2011-11-20 00:50:11 +01:00
parent 2104fd391b
commit a4f8019c4a
3 changed files with 105 additions and 10 deletions

64
src/server/mccp.py Normal file
View file

@ -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)

View file

@ -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):
"""

View file

@ -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