Jingle: fixes for content-description negotiation.
This commit is contained in:
parent
459c73f961
commit
fdf9d42e57
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue