mccp module

"S.Aeschbacher" <[email protected]> Sun, 12 Dec 2004 23:48:16 +0100
Newsgroups gmane.comp.games.mud.client.lyntin
Message-ID <[email protected]>
Hi
I hacked together a module which would offer MCCP functionality.
On the server I use it SOMETIMES works (with MCCPv2).

One problem I see is the following:
According to the specification of MCCP [1], the server starts
to send compressed data directly after having sent the telnet
option (IAC SB COMPRESS2 IAC SE for MCCPv2) which indicates the start
of the compressed data. So this option can be at the start of a
chunk of data that is compressed (all except the option).
The problem is, _filterIncomingData has already been called when
the handleNego method is running. When this first part is not sent
through the decompressor object, the whole stream is out of
sync and nothing works...

At the moment I don't see how I can repass the data through the 
decompressor. Any hints or ideas?
Or am I searching in the wrong place?

regards

Stefan


[1] (http://mccp.afkmud.com/protocol.html)
mccp.py (text/plain, 3.1 KB)
from lyntin import manager, exported, constants
import zlib

IAC = chr(255)
DONT = chr(254)
DO   = chr(253)
WONT = chr(252)
WILL = chr(251)
SB   = chr(250)
SE   = chr(240)
COMPRESS = chr(85)
COMPRESS2 = chr(86)

do_debug = True

def debug(ses, msg):
	if do_debug:
		exported.write_message('##MCCP## ' + ses.getName() + ' ' + msg)

class MCCPManager(manager.Manager):
	def __init__(self):
		self._sessions = {}

	def decompress_data(self, args):
		ses = args['session']
		dataadj = args['dataadj']
		if not self._sessions.has_key(ses):
			debug(ses, 'Unknown session')
			return dataadj

		decompressor = self._sessions[ses]['DECOMPRESS']
		if decompressor != None:
			try:
				newdataadj = decompressor.decompress(dataadj)
				debug(ses, 'Successfully decompressed')
				return newdataadj
			except:
				debug(ses, 'An error occured during MCCP decompression')

		return dataadj

	def addSession(self, newsession, basession=None):
		# XXX basesession ignored for the moment
		debug(newsession, 'New Session Created')
		session_state = {}
		session_state['MCCPv1'] = False
		session_state['MCCPv2'] = False
		session_state['DECOMPRESS'] = None
		self._sessions[newsession] = session_state

	def removeSession(self, ses):
		self._sessions.pop(ses)

	def handle_telnet_options(self, args):
		ses = args['session']
		option = args['data']

		if not self._sessions.has_key(ses):
			debug(ses, 'Unknown session')
			return

		session_state = self._sessions[ses]

		if option == IAC + WILL + COMPRESS:
			# Negotiate MCCPv1
			# If MCCPv2 has been negotiated beforehand (and 
			# according to the protocol definition it has to come
			# first), MCCPv1 is denied.
			if session_state['MCCPv2'] == True:
				debug(ses, 'Denied MCCPv1, MCCPv2 already here')
				ses._socket.write(IAC + DONT + COMPRESS, 0)
				raise exported.StopSpammingException()
			else:
				debug(ses, 'Accepting MCCPv1')
				ses._socket.write(IAC + DO + COMPRESS, 0)
				session_state['MCCPv1'] = True
				raise exported.StopSpammingException()

		elif option == IAC + WILL + COMPRESS2:
			# Negotiate MCCPv2
			# MCCPv2 negotiation should always be before MCCPv1
			debug(ses, 'Accepting MCCPv2')
			ses._socket.write(IAC + DO + COMPRESS2, 0)
			session_state['MCCPv2'] = True
			raise exported.StopSpammingException()

		elif option == IAC + SB + COMPRESS + WILL + SE:
			if session_state['MCCPv1'] == True:
				debug(ses, 'Starting decompression via MCCPv1')
				session_state['DECOMPRESS'] = zlib.decompressobj()
				raise exported.StopSpammingException()

		elif option == IAC + SB + COMPRESS2 + IAC + SE:
			if session_state['MCCPv2'] == True:
				debug(ses, 'Starting decompression via MCCPv2')
				session_state['DECOMPRESS'] = zlib.decompressobj()
				raise exported.StopSpammingException()

def load():
	global mm
	mm = MCCPManager()
	exported.add_manager('MCCP', mm)
	exported.hook_register("net_handle_telnet_option", mm.handle_telnet_options, constants.FIRST)
	exported.hook_register("net_read_data_filter", mm.decompress_data, constants.FIRST)

def unload():
	global mm
	exported.hook_unregister("net_handle_telnet_option", mm.handle_telnet_options)
	exported.hook_unregister("net_read_data_filter", mm.decompress_data)