2011-01-30 14:59:29 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
|
|
|
|
# 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
|
2010-08-28 21:15:05 +00:00
|
|
|
self.serverdata = {}
|
2011-01-30 14:59:29 +00:00
|
|
|
self.txq = Queue()
|
|
|
|
self.setTimeout(25.0)
|
|
|
|
self.login()
|
|
|
|
|
2010-08-28 21:15:05 +00:00
|
|
|
def connectionLost(self, reason):
|
|
|
|
self.serverdata = {}
|
|
|
|
BufferingLineReceiver.connectionLost(self, reason)
|
|
|
|
|
2011-01-30 14:59:29 +00:00
|
|
|
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:
|