Jingle: fixes for content-description negotiation.

This commit is contained in:
Tomasz Melcer 2007-08-07 16:34:09 +00:00
parent 459c73f961
commit fdf9d42e57
2 changed files with 104 additions and 81 deletions

View File

@ -1192,8 +1192,7 @@ class ChatControl(ChatControlBase):
widget.get_active()) widget.get_active())
def _on_start_voip_menuitem_activate(self, *things): def _on_start_voip_menuitem_activate(self, *things):
print 'Start VoiP' gajim.connections[self.account].startVoiP(self.contact.jid+'/'+self.contact.resource)
gajim.connections[self.account].startVoiP(self.contact.jid)
def _update_gpg(self): def _update_gpg(self):
tb = self.xml.get_widget('gpg_togglebutton') tb = self.xml.get_widget('gpg_togglebutton')

View File

@ -30,7 +30,7 @@ class NoCommonCodec(Exception): pass
class JingleSession(object): class JingleSession(object):
''' This represents one jingle session. ''' ''' This represents one jingle session. '''
__metaclass__=meta.VerboseClassType __metaclass__=meta.VerboseClassType
def __init__(self, con, weinitiate, jid): def __init__(self, con, weinitiate, jid, sid=None):
''' con -- connection object, ''' con -- connection object,
weinitiate -- boolean, are we the initiator? weinitiate -- boolean, are we the initiator?
jid - jid of the other entity''' jid - jid of the other entity'''
@ -47,19 +47,26 @@ class JingleSession(object):
self.weinitiate=weinitiate self.weinitiate=weinitiate
# what state is session in? (one from JingleStates) # what state is session in? (one from JingleStates)
self.state=JingleStates.ended self.state=JingleStates.ended
self.sid=con.connection.getAnID() # sessionid if not sid:
sid=con.connection.getAnID()
self.sid=sid # sessionid
# callbacks to call on proper contents # callbacks to call on proper contents
# use .prepend() to add new callbacks # use .prepend() to add new callbacks, especially when you're going
self.callbacks=dict((key, [self.__defaultCB]) for key in # to send error instead of ack
('content-add', 'content-modify', self.callbacks={
'content-remove', 'session-accept', 'session-info', 'content-accept': [self.__contentAcceptCB, self.__defaultCB],
'session-initiate', 'session-terminate', 'content-add': [self.__defaultCB],
'transport-info')) 'content-modify': [self.__defaultCB],
self.callbacks['iq-result']=[] 'content-remove': [self.__defaultCB],
self.callbacks['iq-error']=[] 'session-accept': [self.__contentAcceptCB, self.__defaultCB],
'session-info': [self.__defaultCB],
self.callbacks['content-accept']=[self.__contentAcceptCB, self.__defaultCB] 'session-initiate': [self.__sessionInitiateCB, self.__defaultCB],
'session-terminate': [self.__defaultCB],
'transport-info': [self.__defaultCB],
'iq-result': [],
'iq-error': [],
}
''' Middle-level functions to manage contents. Handle local content ''' Middle-level functions to manage contents. Handle local content
cache and send change notifications. ''' cache and send change notifications. '''
@ -93,7 +100,7 @@ class JingleSession(object):
''' Middle-level function to do stanza exchange. ''' ''' Middle-level function to do stanza exchange. '''
def startSession(self): def startSession(self):
''' Start session. ''' ''' Start session. '''
self.__sessionInitiate(self) self.__sessionInitiate()
def sendSessionInfo(self): pass def sendSessionInfo(self): pass
def sendTransportInfo(self): pass def sendTransportInfo(self): pass
@ -128,8 +135,7 @@ class JingleSession(object):
''' Default callback for action stanzas -- simple ack ''' Default callback for action stanzas -- simple ack
and stop processing. ''' and stop processing. '''
response = stanza.buildReply('result') response = stanza.buildReply('result')
self.connection.send(response) self.connection.connection.send(response)
raise xmpp.NodeProcessed
def __contentAcceptCB(self, stanza, jingle, error): def __contentAcceptCB(self, stanza, jingle, error):
''' Called when we get content-accept stanza or equivalent one ''' Called when we get content-accept stanza or equivalent one
@ -140,10 +146,37 @@ class JingleSession(object):
name = content['name'] name = content['name']
def sessionInitiateCB(self, stanza):
''' We got a jingle session request from other entity,
therefore we are the receiver... Unpack the data. '''
jingle = stanza.getTag('jingle')
self.initiator = jingle['initiator']
self.responder = self.ourjid
self.jid = self.initiator
fail = True
for element in jingle.iterTags('content'):
# checking what kind of session this will be
desc_ns = element.getTag('description').getNamespace()
tran_ns = element.getTag('transport').getNamespace()
if desc_ns==xmpp.NS_JINGLE_AUDIO and tran_ns==xmpp.NS_JINGLE_ICE_UDP:
# we've got voip content
self.addContent(element['name'], JingleVoiP(self, node=element), 'peer')
fail = False
if fail:
# TODO: we should send <unsupported-content/> inside too
self.connection.connection.send(
xmpp.Error(stanza, xmpp.NS_STANZAS + 'feature-not-implemented'))
self.connection.deleteJingle(self)
raise xmpp.NodeProcessed
self.state = JingleStates.pending
''' Methods that make/send proper pieces of XML. They check if the session ''' Methods that make/send proper pieces of XML. They check if the session
is in appropriate state. ''' is in appropriate state. '''
def __makeJingle(self, action): def __makeJingle(self, action):
stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.jid)) stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid))
jingle = stanza.addChild('jingle', attrs={ jingle = stanza.addChild('jingle', attrs={
'xmlns': 'http://www.xmpp.org/extensions/xep-0166.html#ns', 'xmlns': 'http://www.xmpp.org/extensions/xep-0166.html#ns',
'action': action, 'action': action,
@ -161,7 +194,6 @@ class JingleSession(object):
else: else:
jingle.addChild('content', jingle.addChild('content',
attrs={'name': content.name, 'creator': content.creator}) attrs={'name': content.name, 'creator': content.creator})
return c
def __appendContents(self, jingle, full=True): def __appendContents(self, jingle, full=True):
''' Append all <content/> elements to <jingle/>.''' ''' Append all <content/> elements to <jingle/>.'''
@ -174,13 +206,13 @@ class JingleSession(object):
assert self.state==JingleStates.ended assert self.state==JingleStates.ended
stanza, jingle = self.__makeJingle('session-initiate') stanza, jingle = self.__makeJingle('session-initiate')
self.__appendContents(jingle) self.__appendContents(jingle)
self.connection.send(jingle) self.connection.connection.send(stanza)
def __sessionAccept(self): def __sessionAccept(self):
assert self.state==JingleStates.pending assert self.state==JingleStates.pending
stanza, jingle = self.__jingle('session-accept') stanza, jingle = self.__jingle('session-accept')
self.__appendContents(jingle, False) self.__appendContents(jingle, False)
self.connection.send(stanza) self.connection.connection.send(stanza)
self.state=JingleStates.active self.state=JingleStates.active
def __sessionInfo(self, payload=None): def __sessionInfo(self, payload=None):
@ -188,12 +220,12 @@ class JingleSession(object):
stanza, jingle = self.__jingle('session-info') stanza, jingle = self.__jingle('session-info')
if payload: if payload:
jingle.addChild(node=payload) jingle.addChild(node=payload)
self.connection.send(stanza) self.connection.connection.send(stanza)
def __sessionTerminate(self): def __sessionTerminate(self):
assert self.state!=JingleStates.ended assert self.state!=JingleStates.ended
stanza, jingle = self.__jingle('session-terminate') stanza, jingle = self.__jingle('session-terminate')
self.connection.send(stanza) self.connection.connection.send(stanza)
def __contentAdd(self): def __contentAdd(self):
assert self.state==JingleStates.active assert self.state==JingleStates.active
@ -211,59 +243,49 @@ class JingleSession(object):
assert self.state!=JingleStates.ended assert self.state!=JingleStates.ended
'''Callbacks''' '''Callbacks'''
def sessionInitiateCB(self, stanza):
''' We got a jingle session request from other entity,
therefore we are the receiver... Unpack the data. '''
jingle = stanza.getTag('jingle')
self.initiator = jingle['initiator']
self.responder = self.ourjid
self.jid = self.initiator
self.state = JingleStates.pending
self.sid = jingle['sid']
for element in jingle.iterTags('content'):
content={'creator': 'initiator',
'name': element['name'],
'description': element.getTag('description'),
'transport': element.getTag('transport')}
if element.has_attr('profile'):
content['profile']=element['profile']
self.contents[('initiator', content['name'])]=content
def sessionTerminateCB(self, stanza): pass def sessionTerminateCB(self, stanza): pass
class Codec(object):
''' This class keeps description of a single codec. '''
def __init__(self, name, id=None, **params):
''' Create new codec description. '''
self.name = name
self.id = id
self.attrs = {'name': self.name, 'id': self.id, 'channels': 1}
for key in ('channels', 'clockrate', 'maxptime', 'ptime'):
if key in params:
self.attrs[key]=params[key]
del params[key]
self.params = params
def __eq__(a, b):
''' Compare two codec descriptions. '''
# TODO: check out what should be tested...
if a.name!=b.name: return False
# ...
return True
def toXML(self):
return xmpp.Node('payload',
attrs=self.attrs,
payload=(xmpp.Node('parameter', {'name': k, 'value': v}) for k,v in self.params))
class JingleAudioSession(object): class JingleAudioSession(object):
__metaclass__=meta.VerboseClassType __metaclass__=meta.VerboseClassType
class Codec(object): def __init__(self, content, fromNode):
''' This class keeps description of a single codec. '''
def __init__(self, name, id=None, **params):
''' Create new codec description. '''
self.name = name
self.id = id
self.attrs = {'name': self.name, 'id': self.id, 'channels': 1}
for key in ('channels', 'clockrate', 'maxptime', 'ptime'):
if key in params:
self.attrs[key]=params[key]
del params[key]
self.params = params
def __eq__(a, b):
''' Compare two codec descriptions. '''
# TODO: check out what should be tested...
if a.name!=b.name: return False
# ...
return True
def toXML(self):
return xmpp.Node('payload',
attrs=self.attrs,
payload=(xmpp.Node('parameter', {'name': k, 'value': v}) for k,v in self.params))
def __init__(self, content):
self.content = content self.content = content
self.initiator_codecs=[] self.initiator_codecs=[]
self.responder_codecs=[] self.responder_codecs=[]
if fromNode:
# read all codecs peer understand
for payload in fromNode.iterTags('payload-type'):
attrs = fromNode.getAttrs().copy()
for param in fromNode.iterTags('parameter'):
attrs[param['name']]=param['value']
self.initiator_codecs.append(Codec(**attrs))
def sessionInitiateCB(self, stanza, ourcontent): def sessionInitiateCB(self, stanza, ourcontent):
pass pass
@ -284,7 +306,7 @@ class JingleAudioSession(object):
our_l = supported_codecs[:] our_l = supported_codecs[:]
out = [] out = []
ids = range(128) ids = range(128)
for codec in other: for codec in other_l:
if codec in our_l: if codec in our_l:
out.append(codec) out.append(codec)
our_l.remove(codec) our_l.remove(codec)
@ -304,8 +326,7 @@ class JingleAudioSession(object):
''' Methods for making proper pieces of XML. ''' ''' Methods for making proper pieces of XML. '''
def __codecsList(self, codecs): def __codecsList(self, codecs):
''' Prepares a description element with codecs given as a parameter. ''' ''' Prepares a description element with codecs given as a parameter. '''
return xmpp.Node('description', return xmpp.Node(xmpp.NS_JINGLE_AUDIO+' description',
xmlns=xmpp.NS_JINGLE_AUDIO,
payload=(codec.toXML() for codec in codecs)) payload=(codec.toXML() for codec in codecs))
def toXML(self): def toXML(self):
@ -329,25 +350,26 @@ class JingleICEUDPSession(object):
def toXML(self): def toXML(self):
''' ICE-UDP doesn't send much in its transport stanza... ''' ''' ICE-UDP doesn't send much in its transport stanza... '''
return xmpp.Node('transport', xmlns=xmpp.JINGLE_ICE_UDP) return xmpp.Node(xmpp.NS_JINGLE_ICE_UDP+' transport')
class JingleVoiP(object): class JingleVoiP(object):
''' Jingle VoiP sessions consist of audio content transported ''' Jingle VoiP sessions consist of audio content transported
over an ICE UDP protocol. ''' over an ICE UDP protocol. '''
__metaclass__=meta.VerboseClassType __metaclass__=meta.VerboseClassType
def __init__(self): def __init__(self, session, node=None):
self.audio = JingleAudioSession(self) self.session = session
if node is None:
self.audio = JingleAudioSession(self)
else:
self.audio = JingleAudioSession(self, node.getTag('content'))
self.transport = JingleICEUDPSession(self) self.transport = JingleICEUDPSession(self)
def toXML(self): def toXML(self):
''' Return proper XML for <content/> element. ''' ''' Return proper XML for <content/> element. '''
return xmpp.Node('content', return xmpp.Node('content',
attrs={'name': self.name, 'creator': self.creator, 'profile': 'RTP/AVP'}, attrs={'name': self.name, 'creator': self.creator, 'profile': 'RTP/AVP'},
childs=[self.audio.toXML(), self.transport.toXML()]) payload=[self.audio.toXML(), self.transport.toXML()])
def _sessionInitiateCB(self):
''' Called when we initiate the session. '''
self.transport._sessionInitiateCB()
class ConnectionJingle(object): class ConnectionJingle(object):
''' This object depends on that it is a part of Connection class. ''' ''' This object depends on that it is a part of Connection class. '''
@ -391,12 +413,14 @@ class ConnectionJingle(object):
# do we need to create a new jingle object # do we need to create a new jingle object
if (jid, sid) not in self.__sessions: if (jid, sid) not in self.__sessions:
# we should check its type here... # TODO: we should check its type here...
newjingle = JingleAudioSession(con=self, weinitiate=False, jid=jid) newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid)
self.addJingle(newjingle) self.addJingle(newjingle)
# we already have such session in dispatcher... # we already have such session in dispatcher...
return self.__sessions[(jid, sid)].stanzaCB(stanza) self.__sessions[(jid, sid)].stanzaCB(stanza)
raise xmpp.NodeProcessed
def addJingleIqCallback(self, jid, id, jingle): def addJingleIqCallback(self, jid, id, jingle):
self.__iq_responses[(jid, id)]=jingle self.__iq_responses[(jid, id)]=jingle