# -*- coding: utf-8 -*- # # Copyright 2010 Maurizio Porrato # See LICENSE.txt for copyright info from Queue import Queue from twisted.internet.protocol import ReconnectingClientFactory from twisted.protocols.policies import TimeoutMixin from twisted.internet.task import LoopingCall from twisted.python import log from frn.protocol import versions from frn.user import FRNUser from frn.protocol.common import BufferingLineReceiver from frn.utility import * class FRNClient(BufferingLineReceiver, TimeoutMixin): def connectionMade(self): BufferingLineReceiver.connectionMade(self) self.user.VX = versions.client self.serverdata = {} self.txq = Queue() self.setTimeout(25.0) self.login() def connectionLost(self, reason): self.serverdata = {} BufferingLineReceiver.connectionLost(self, reason) def ready(self): self.status = 'READY' self.msgbuffer = [] self.phase = 0 self.expectRawData(1) def startMultiLineMessage(self, msgtype): self.status = msgtype self.phase = None self.setLineMode() def stopMultiLineMessage(self): handler = getattr(self, 'decode'+self.status, None) status = self.status message = self.msgbuffer self.ready() if handler is not None: handler(message) else: self.unimplemented(status, message) def collectMultiLineMessage(self, line): if self.phase is None: self.expected_lines = int(line.strip()) self.msgbuffer = [] self.phase = 0 else: self.msgbuffer.append(line) self.phase += 1 if self.phase >= self.expected_lines: self.stopMultiLineMessage() def lineReceived(self, line): if self.status == 'AUTH': if self.phase == 0: self.latest_client_version = int(line.strip()) self.phase = 1 else: self.serverdata = parseSimpleXML(line.strip()) if int(self.serverdata['SV']) > 2009004: self.sendLine(responseToChallange( self.serverdata['KP'])) self.ready() self.setTimeout(10.0) self.factory.resetDelay() self.loginResponse(self.serverdata) else: self.collectMultiLineMessage(line) def expectedReceived(self, data): self.resetTimeout() if self.status == 'READY': packet_type = ord(data[0]) if packet_type == 0: # Keepalive self.ready() self.pong() elif packet_type == 1: # TX ack self.status = 'TX' self.expectRawData(2) elif packet_type == 2: # Audio self.status = 'AUDIO' self.expectRawData(327) # Two ID bytes + 10 GSM frames elif packet_type == 3: # Client list self.status = 'CLIENTS' self.expectRawData(2) # Discard two null bytes elif packet_type == 4: # Text self.startMultiLineMessage('TEXT') elif packet_type == 5: # Channel list self.startMultiLineMessage('NETWORKS') elif packet_type == 6: # Admin list self.startMultiLineMessage('ADMIN') elif packet_type == 7: # Access list self.startMultiLineMessage('ACCESS') elif packet_type == 8: # Block list self.startMultiLineMessage('BLOCK') elif packet_type == 9: # Mute list self.startMultiLineMessage('MUTE') elif packet_type == 10: # Access list flags self.startMultiLineMessage('ACCESSFLAGS') else: log.err("Unknown packet type %d" % packet_type) elif self.status == 'CLIENTS': self.startMultiLineMessage('CLIENTS') elif self.status == 'AUDIO': self.ready() self.decodeAUDIO(ord(data[0])*256+ord(data[1]), data[2:]) elif self.status == 'TX': self.ready() self.decodeTX(ord(data[0])*256+ord(data[1])) def login(self): ap = "CT:"+self.user.asXML( 'VX','EA','PW','ON','BC','DS','NN','CT','NT') self.status = 'AUTH' self.phase = 0 self.sendLine(ap) def pong(self): self.sendLine('P') def setStatus(self, status): self.sendLine('ST:%s' % str(status)) def stopTransmission(self): self.sendLine('RX0') def startTransmission(self): self.sendLine('TX0') def sendAudioFrame(self, frame): self.resetTimeout() self.sendLine('TX1') self.transport.write(frame) def streamStep(self, count): if count > 1: log.msg("WARNING: lost %d ticks" % (count-1)) for i in range(count): self.sendAudioFrame(self.txq.get_nowait()) def stopStreaming(self): self.txtimer.stop() def _streamAck(self): self.txtimer = LoopingCall.withCount(self.streamStep) self.txtimer.start(0.20).addCallback( lambda _: self.stopTransmission()).addErrback( lambda _: self.stopTransmission()) def feedStreaming(self, frames): if type(frames) == list: for frame in frames: self.txq.put_nowait(frame) else: self.txq.put_nowait(frames) def startStreaming(self): self.startTransmission() def sendTextMessage(self, dest, text): self.sendLine('TM:'+formatSimpleXML(dict(ID=dest, MS=text))) def addAdmin(self, client_ip): self.sendLine("AA:"+formatSimpleXML(dict(IP=client_ip))) def removeAdmin(self, client_ip): self.sendLine("DA:"+formatSimpleXML(dict(IP=client_ip))) def addMute(self, client_ip): self.sendLine("MC:"+formatSimpleXML(dict(IP=client_ip))) def removeMute(self, client_ip): self.sendLine("UM:"+formatSimpleXML(dict(IP=client_ip))) def addBlock(self, client_ip): self.sendLine("BC:"+formatSimpleXML(dict(IP=client_ip))) def removeBlock(self, client_ip): self.sendLine("UC:"+formatSimpleXML(dict(IP=client_ip))) def addAccess(self, email): self.sendLine("AT:"+formatSimpleXML(dict(EA=email))) def removeAccess(self, email): self.sendLine("DT:"+formatSimpleXML(dict(EA=email))) def addTalk(self, email): self.sendLine("ETX:"+formatSimpleXML(dict(EA=email))) def removeTalk(self, email): self.sendLine("RTX:"+formatSimpleXML(dict(EA=email))) def accessFlagEnable(self, enable): if enable: v = 1 else: v = 0 self.sendLine("ENA:%d" % v) def accessFlagTalk(self, enable): if enable: v = 1 else: v = 0 self.sendLine("TXR:%d" % v) def unimplemented(self, status, msg): log.msg("Unimplemented: %s: %s" % (status, msg)) def decodeAUDIO(self, from_id, frames): self.audioFrameReceived(from_id, frames) def decodeTX(self, my_id): self._streamAck() def decodeTEXT(self, msg): self.textMessageReceived(msg[0], msg[1], msg[2]) def decodeCLIENTS(self, msg): self.clientsListUpdated([parseSimpleXML(x) for x in msg]) def decodeNETWORKS(self, msg): self.networksListUpdated(msg) def decodeADMIN(self, msg): self.adminListUpdated([parseSimpleXML(x) for x in msg]) def decodeACCESS(self, msg): self.accessListUpdated([parseSimpleXML(x) for x in msg]) def decodeBLOCK(self, msg): self.blockListUpdated([parseSimpleXML(x) for x in msg]) def decodeMUTE(self, msg): self.muteListUpdated([parseSimpleXML(x) for x in msg]) def decodeACCESSFLAGS(self, msg): self.accessFlagsUpdated(msg[0], msg[1]) def decodeUNKNOWN(self, code, msg): log.msg("%s: %s" % (code, msg)) def loginResponse(self, info): pass def audioFrameReceived(self, from_id, frame): pass def textMessageReceived(self, client, message, target): pass def clientsListUpdated(self, clients): pass def networksListUpdated(self, networks): pass def adminListUpdated(self, admins): pass def accessListUpdated(self, access): pass def blockListUpdated(self, blocks): pass def muteListUpdated(self, mutes): pass def accessFlagsUpdated(self, access, talk): pass class FRNClientFactory(ReconnectingClientFactory): protocol = FRNClient maxRetries = 10 def __init__(self, user): self.user = user def startedConnecting(self, connector): log.msg('Started to connect') def buildProtocol(self, addr): log.msg('Connected') p = ReconnectingClientFactory.buildProtocol(self, addr) p.user = self.user return p def clientConnectionLost(self, connector, reason): log.msg('Lost connection. Reason: %s' % reason) ReconnectingClientFactory.clientConnectionLost( self, connector, reason) def clientConnectionFailed(self, connector, reason): log.err('Connection failed. Reason: %s' % reason) ReconnectingClientFactory.clientConnectionFailed( self, connector, reason) # vim: set et ai sw=4 ts=4 sts=4: