Fix a bunch of issues in common.jingle*.
This commit is contained in:
parent
57fb80f1fa
commit
ad9370afa8
|
@ -142,6 +142,10 @@ SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
|||
# zeroconf account name
|
||||
ZEROCONF_ACC_NAME = 'Local'
|
||||
|
||||
# These will be set in gajim.gui_interface.
|
||||
idlequeue = None
|
||||
socks5queue = None
|
||||
|
||||
HAVE_ZEROCONF = True
|
||||
try:
|
||||
__import__('avahi')
|
||||
|
|
|
@ -28,19 +28,21 @@ Handles the jingle signalling protocol
|
|||
# * config:
|
||||
# - codecs
|
||||
|
||||
import logging
|
||||
|
||||
import nbxmpp
|
||||
from common import helpers
|
||||
from common import gajim
|
||||
|
||||
from common.jingle_session import JingleSession, JingleStates
|
||||
if gajim.HAVE_FARSTREAM:
|
||||
from common.jingle_rtp import JingleAudio, JingleVideo
|
||||
from common.jingle_ft import JingleFileTransfer
|
||||
from common.jingle_transport import JingleTransportSocks5, JingleTransportIBB
|
||||
if gajim.HAVE_FARSTREAM:
|
||||
from common.jingle_rtp import JingleAudio, JingleVideo
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger('gajim.c.jingle')
|
||||
|
||||
|
||||
class ConnectionJingle(object):
|
||||
"""
|
||||
This object depends on that it is a part of Connection class.
|
||||
|
@ -80,7 +82,7 @@ class ConnectionJingle(object):
|
|||
try:
|
||||
jid = helpers.get_full_jid_from_iq(stanza)
|
||||
except helpers.InvalidFormat:
|
||||
logger.warn('Invalid JID: %s, ignoring it' % stanza.getFrom())
|
||||
logger.warning('Invalid JID: %s, ignoring it', stanza.getFrom())
|
||||
return
|
||||
id_ = stanza.getID()
|
||||
if (jid, id_) in self.__iq_responses.keys():
|
||||
|
@ -102,14 +104,14 @@ class ConnectionJingle(object):
|
|||
if sid not in self._sessions:
|
||||
#TODO: tie-breaking and other things...
|
||||
newjingle = JingleSession(con=self, weinitiate=False, jid=jid,
|
||||
iq_id=id_, sid=sid)
|
||||
iq_id=id_, sid=sid)
|
||||
self._sessions[sid] = newjingle
|
||||
# we already have such session in dispatcher...
|
||||
self._sessions[sid].collect_iq_id(id_)
|
||||
self._sessions[sid].on_stanza(stanza)
|
||||
# Delete invalid/unneeded sessions
|
||||
if sid in self._sessions and \
|
||||
self._sessions[sid].state == JingleStates.ended:
|
||||
self._sessions[sid].state == JingleStates.ENDED:
|
||||
self.delete_jingle_session(sid)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
|
@ -132,20 +134,20 @@ class ConnectionJingle(object):
|
|||
jingle = self.get_jingle_session(jid, media='audio')
|
||||
if jingle:
|
||||
jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
|
||||
out_xid=out_xid))
|
||||
out_xid=out_xid))
|
||||
else:
|
||||
jingle = JingleSession(self, weinitiate=True, jid=jid)
|
||||
self._sessions[jingle.sid] = jingle
|
||||
jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid,
|
||||
out_xid=out_xid))
|
||||
out_xid=out_xid))
|
||||
jingle.start_session()
|
||||
return jingle.sid
|
||||
|
||||
def start_file_transfer(self, jid, file_props, request=False):
|
||||
logger.info("start file transfer with file: %s" % file_props)
|
||||
logger.info("start file transfer with file: %s", file_props)
|
||||
contact = gajim.contacts.get_contact_with_highest_priority(self.name,
|
||||
gajim.get_jid_without_resource(jid))
|
||||
if gajim.contacts.is_gc_contact(self.name,jid):
|
||||
gajim.get_jid_without_resource(jid))
|
||||
if gajim.contacts.is_gc_contact(self.name, jid):
|
||||
gcc = jid.split('/')
|
||||
if len(gcc) == 2:
|
||||
contact = gajim.contacts.get_gc_contact(self.name, gcc[0], gcc[1])
|
||||
|
@ -162,7 +164,8 @@ class ConnectionJingle(object):
|
|||
elif contact.supports(nbxmpp.NS_JINGLE_IBB):
|
||||
transport = JingleTransportIBB()
|
||||
c = JingleFileTransfer(jingle, transport=transport,
|
||||
file_props=file_props, use_security=use_security)
|
||||
file_props=file_props,
|
||||
use_security=use_security)
|
||||
file_props.algo = self.__hash_support(contact)
|
||||
jingle.add_content('file' + helpers.get_random_string_16(), c)
|
||||
jingle.start_session()
|
||||
|
|
|
@ -37,7 +37,7 @@ class JingleContentSetupException(Exception):
|
|||
"""
|
||||
|
||||
|
||||
class JingleContent(object):
|
||||
class JingleContent:
|
||||
"""
|
||||
An abstraction of content in Jingle sessions
|
||||
"""
|
||||
|
@ -48,8 +48,8 @@ class JingleContent(object):
|
|||
# will be filled by JingleSession.add_content()
|
||||
# don't uncomment these lines, we will catch more buggy code then
|
||||
# (a JingleContent not added to session shouldn't send anything)
|
||||
#self.creator = None
|
||||
#self.name = None
|
||||
self.creator = None
|
||||
self.name = None
|
||||
self.accepted = False
|
||||
self.sent = False
|
||||
self.negotiated = False
|
||||
|
@ -59,35 +59,39 @@ class JingleContent(object):
|
|||
self.senders = 'both' #FIXME
|
||||
self.allow_sending = True # Used for stream direction, attribute 'senders'
|
||||
|
||||
# These were found by the Politie
|
||||
self.file_props = None
|
||||
self.use_security = None
|
||||
|
||||
self.callbacks = {
|
||||
# these are called when *we* get stanzas
|
||||
'content-accept': [self.__on_transport_info,
|
||||
self.__on_content_accept],
|
||||
'content-add': [self.__on_transport_info],
|
||||
'content-modify': [],
|
||||
'content-reject': [],
|
||||
'content-remove': [],
|
||||
'description-info': [],
|
||||
'security-info': [],
|
||||
'session-accept': [self.__on_transport_info,
|
||||
self.__on_content_accept],
|
||||
'session-info': [],
|
||||
'session-initiate': [self.__on_transport_info],
|
||||
'session-terminate': [],
|
||||
'transport-info': [self.__on_transport_info],
|
||||
'transport-replace': [self.__on_transport_replace],
|
||||
'transport-accept': [],
|
||||
'transport-reject': [],
|
||||
'iq-result': [],
|
||||
'iq-error': [],
|
||||
# these are called when *we* sent these stanzas
|
||||
'content-accept-sent': [self.__fill_jingle_stanza,
|
||||
self.__on_content_accept],
|
||||
'content-add-sent': [self.__fill_jingle_stanza],
|
||||
'session-initiate-sent': [self.__fill_jingle_stanza],
|
||||
'session-accept-sent': [self.__fill_jingle_stanza,
|
||||
self.__on_content_accept],
|
||||
'session-terminate-sent': [],
|
||||
# these are called when *we* get stanzas
|
||||
'content-accept': [self.__on_transport_info,
|
||||
self.__on_content_accept],
|
||||
'content-add': [self.__on_transport_info],
|
||||
'content-modify': [],
|
||||
'content-reject': [],
|
||||
'content-remove': [],
|
||||
'description-info': [],
|
||||
'security-info': [],
|
||||
'session-accept': [self.__on_transport_info,
|
||||
self.__on_content_accept],
|
||||
'session-info': [],
|
||||
'session-initiate': [self.__on_transport_info],
|
||||
'session-terminate': [],
|
||||
'transport-info': [self.__on_transport_info],
|
||||
'transport-replace': [self.__on_transport_replace],
|
||||
'transport-accept': [],
|
||||
'transport-reject': [],
|
||||
'iq-result': [],
|
||||
'iq-error': [],
|
||||
# these are called when *we* sent these stanzas
|
||||
'content-accept-sent': [self.__fill_jingle_stanza,
|
||||
self.__on_content_accept],
|
||||
'content-add-sent': [self.__fill_jingle_stanza],
|
||||
'session-initiate-sent': [self.__fill_jingle_stanza],
|
||||
'session-accept-sent': [self.__fill_jingle_stanza,
|
||||
self.__on_content_accept],
|
||||
'session-terminate-sent': [],
|
||||
}
|
||||
|
||||
def is_ready(self):
|
||||
|
@ -134,8 +138,8 @@ class JingleContent(object):
|
|||
if payload is None:
|
||||
payload = []
|
||||
return nbxmpp.Node('content',
|
||||
attrs={'name': self.name, 'creator': self.creator},
|
||||
payload=payload)
|
||||
attrs={'name': self.name, 'creator': self.creator},
|
||||
payload=payload)
|
||||
|
||||
def send_candidate(self, candidate):
|
||||
"""
|
||||
|
@ -191,17 +195,16 @@ class JingleContent(object):
|
|||
file_tag.addChild(node=node)
|
||||
if self.file_props.type_ == 'r':
|
||||
if self.file_props.hash_:
|
||||
h = file_tag.addChild('hash', attrs={
|
||||
'algo': self.file_props.algo}, namespace=nbxmpp.NS_HASHES,
|
||||
payload=self.file_props.hash_)
|
||||
file_tag.addChild('hash', attrs={'algo': self.file_props.algo},
|
||||
namespace=nbxmpp.NS_HASHES,
|
||||
payload=self.file_props.hash_)
|
||||
else:
|
||||
# if the file is less than 10 mb, then it is small
|
||||
# lets calculate it right away
|
||||
if self.file_props.size < 10000000 and not \
|
||||
self.file_props.hash_:
|
||||
h = self._calcHash()
|
||||
if h:
|
||||
file_tag.addChild(node=h)
|
||||
if self.file_props.size < 10000000 and not self.file_props.hash_:
|
||||
hash_data = content._compute_hash()
|
||||
if hash_data:
|
||||
file_tag.addChild(node=hash_data)
|
||||
pjid = gajim.get_jid_without_resource(self.session.peerjid)
|
||||
file_info = {'name' : self.file_props.name,
|
||||
'file-name' : self.file_props.file_name,
|
||||
|
@ -223,9 +226,9 @@ class JingleContent(object):
|
|||
cert = load_cert_file(certpath)
|
||||
if cert:
|
||||
try:
|
||||
digest_algo = cert.get_signature_algorithm().decode('utf-8'
|
||||
).split('With')[0]
|
||||
except AttributeError as e:
|
||||
digest_algo = (cert.get_signature_algorithm()
|
||||
.decode('utf-8').split('With')[0])
|
||||
except AttributeError:
|
||||
# Old py-OpenSSL is missing get_signature_algorithm
|
||||
digest_algo = "sha256"
|
||||
security.addChild('fingerprint').addData(cert.digest(
|
||||
|
@ -240,5 +243,3 @@ class JingleContent(object):
|
|||
def destroy(self):
|
||||
self.callbacks = None
|
||||
del self.session.contents[(self.creator, self.name)]
|
||||
|
||||
|
||||
|
|
|
@ -19,41 +19,47 @@
|
|||
Handles Jingle File Transfer (XEP 0234)
|
||||
"""
|
||||
|
||||
import os
|
||||
import hashlib
|
||||
from common import gajim
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from enum import IntEnum
|
||||
import nbxmpp
|
||||
from common import gajim
|
||||
from common import configpaths
|
||||
from . import jingle_xtls
|
||||
from common import jingle_xtls
|
||||
from common.jingle_content import contents, JingleContent
|
||||
from common.jingle_transport import *
|
||||
from common.jingle_transport import JingleTransportSocks5, TransportType
|
||||
from common import helpers
|
||||
from common.connection_handlers_events import FileRequestReceivedEvent
|
||||
import threading
|
||||
import logging
|
||||
from common.jingle_ftstates import *
|
||||
from common.jingle_ftstates import (
|
||||
StateInitialized, StateCandSent, StateCandReceived, StateTransfering,
|
||||
StateCandSentAndRecv, StateTransportReplace)
|
||||
|
||||
log = logging.getLogger('gajim.c.jingle_ft')
|
||||
|
||||
STATE_NOT_STARTED = 0
|
||||
STATE_INITIALIZED = 1
|
||||
# We send the candidates and we are waiting for a reply
|
||||
STATE_CAND_SENT = 2
|
||||
# We received the candidates and we are waiting to reply
|
||||
STATE_CAND_RECEIVED = 3
|
||||
# We have sent and received the candidates
|
||||
# This also includes any candidate-error received or sent
|
||||
STATE_CAND_SENT_AND_RECEIVED = 4
|
||||
STATE_TRANSPORT_REPLACE = 5
|
||||
# We are transfering the file
|
||||
STATE_TRANSFERING = 6
|
||||
|
||||
class State(IntEnum):
|
||||
NOT_STARTED = 0
|
||||
INITIALIZED = 1
|
||||
# We send the candidates and we are waiting for a reply
|
||||
CAND_SENT = 2
|
||||
# We received the candidates and we are waiting to reply
|
||||
CAND_RECEIVED = 3
|
||||
# We have sent and received the candidates
|
||||
# This also includes any candidate-error received or sent
|
||||
CAND_SENT_AND_RECEIVED = 4
|
||||
TRANSPORT_REPLACE = 5
|
||||
# We are transfering the file
|
||||
TRANSFERING = 6
|
||||
|
||||
|
||||
class JingleFileTransfer(JingleContent):
|
||||
|
||||
def __init__(self, session, transport=None, file_props=None,
|
||||
use_security=False):
|
||||
use_security=False):
|
||||
JingleContent.__init__(self, session, transport)
|
||||
log.info("transport value: %s" % transport)
|
||||
log.info("transport value: %s", transport)
|
||||
# events we might be interested in
|
||||
self.callbacks['session-initiate'] += [self.__on_session_initiate]
|
||||
self.callbacks['session-initiate-sent'] += [
|
||||
|
@ -85,29 +91,31 @@ class JingleFileTransfer(JingleContent):
|
|||
self.file_props.sid = session.sid
|
||||
self.file_props.transfered_size = []
|
||||
self.file_props.transport_sid = self.transport.sid
|
||||
log.info("FT request: %s" % file_props)
|
||||
log.info("FT request: %s", file_props)
|
||||
if transport is None:
|
||||
self.transport = JingleTransportSocks5()
|
||||
self.transport.set_connection(session.connection)
|
||||
self.transport.set_file_props(self.file_props)
|
||||
self.transport.set_our_jid(session.ourjid)
|
||||
log.info('ourjid: %s' % session.ourjid)
|
||||
log.info('ourjid: %s', session.ourjid)
|
||||
self.session = session
|
||||
self.media = 'file'
|
||||
self.nominated_cand = {}
|
||||
if gajim.contacts.is_gc_contact(session.connection.name,
|
||||
session.peerjid):
|
||||
session.peerjid):
|
||||
roomjid = session.peerjid.split('/')[0]
|
||||
dstaddr = hashlib.sha1(('%s%s%s' % (self.file_props.sid,
|
||||
session.ourjid, roomjid)).encode('utf-8')).hexdigest()
|
||||
session.ourjid, roomjid))
|
||||
.encode('utf-8')).hexdigest()
|
||||
self.file_props.dstaddr = dstaddr
|
||||
self.state = STATE_NOT_STARTED
|
||||
self.states = {STATE_INITIALIZED : StateInitialized(self),
|
||||
STATE_CAND_SENT : StateCandSent(self),
|
||||
STATE_CAND_RECEIVED : StateCandReceived(self),
|
||||
STATE_TRANSFERING : StateTransfering(self),
|
||||
STATE_TRANSPORT_REPLACE : StateTransportReplace(self),
|
||||
STATE_CAND_SENT_AND_RECEIVED : StateCandSentAndRecv(self)
|
||||
self.state = State.NOT_STARTED
|
||||
self.states = {
|
||||
State.INITIALIZED : StateInitialized(self),
|
||||
State.CAND_SENT : StateCandSent(self),
|
||||
State.CAND_RECEIVED : StateCandReceived(self),
|
||||
State.TRANSFERING : StateTransfering(self),
|
||||
State.TRANSPORT_REPLACE : StateTransportReplace(self),
|
||||
State.CAND_SENT_AND_RECEIVED : StateCandSentAndRecv(self)
|
||||
}
|
||||
|
||||
if jingle_xtls.PYOPENSSL_PRESENT:
|
||||
|
@ -129,8 +137,10 @@ class JingleFileTransfer(JingleContent):
|
|||
def __on_session_initiate(self, stanza, content, error, action):
|
||||
log.debug("Jingle FT request received")
|
||||
gajim.nec.push_incoming_event(FileRequestReceivedEvent(None,
|
||||
conn=self.session.connection, stanza=stanza, jingle_content=content,
|
||||
FT_content=self))
|
||||
conn=self.session.connection,
|
||||
stanza=stanza,
|
||||
jingle_content=content,
|
||||
FT_content=self))
|
||||
if self.session.request:
|
||||
# accept the request
|
||||
self.session.approve_content(self.media, self.name)
|
||||
|
@ -141,10 +151,11 @@ class JingleFileTransfer(JingleContent):
|
|||
|
||||
def __send_hash(self):
|
||||
# Send hash in a session info
|
||||
checksum = nbxmpp.Node(tag='checksum', payload=[nbxmpp.Node(tag='file',
|
||||
payload=[self._calcHash()])])
|
||||
checksum = nbxmpp.Node(tag='checksum',
|
||||
payload=[nbxmpp.Node(tag='file',
|
||||
payload=[self._compute_hash()])])
|
||||
checksum.setNamespace(nbxmpp.NS_JINGLE_FILE_TRANSFER)
|
||||
self.session.__session_info(checksum )
|
||||
self.session.__session_info(checksum)
|
||||
pjid = gajim.get_jid_without_resource(self.session.peerjid)
|
||||
file_info = {'name' : self.file_props.name,
|
||||
'file-name' : self.file_props.file_name,
|
||||
|
@ -155,13 +166,13 @@ class JingleFileTransfer(JingleContent):
|
|||
}
|
||||
self.session.connection.set_file_info(file_info)
|
||||
|
||||
def _calcHash(self):
|
||||
def _compute_hash(self):
|
||||
# Caculates the hash and returns a xep-300 hash stanza
|
||||
if self.file_props.algo == None:
|
||||
if self.file_props.algo is None:
|
||||
return
|
||||
try:
|
||||
file_ = open(self.file_props.file_name, 'rb')
|
||||
except:
|
||||
except IOError:
|
||||
# can't open file
|
||||
return
|
||||
h = nbxmpp.Hashes()
|
||||
|
@ -192,30 +203,31 @@ class JingleFileTransfer(JingleContent):
|
|||
fingerprint = fingerprint.getData()
|
||||
self.x509_fingerprint = fingerprint
|
||||
if not jingle_xtls.check_cert(gajim.get_jid_without_resource(
|
||||
self.session.responder), fingerprint):
|
||||
self.session.responder), fingerprint):
|
||||
id_ = jingle_xtls.send_cert_request(con,
|
||||
self.session.responder)
|
||||
self.session.responder)
|
||||
jingle_xtls.key_exchange_pend(id_,
|
||||
self.continue_session_accept, [stanza])
|
||||
self.continue_session_accept,
|
||||
[stanza])
|
||||
raise nbxmpp.NodeProcessed
|
||||
self.continue_session_accept(stanza)
|
||||
|
||||
def continue_session_accept(self, stanza):
|
||||
con = self.session.connection
|
||||
if self.state == STATE_TRANSPORT_REPLACE:
|
||||
if self.state == State.TRANSPORT_REPLACE:
|
||||
# If we are requesting we don't have the file
|
||||
if self.session.werequest:
|
||||
raise nbxmpp.NodeProcessed
|
||||
# We send the file
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
self.__state_changed(State.TRANSFERING)
|
||||
raise nbxmpp.NodeProcessed
|
||||
self.file_props.streamhosts = self.transport.remote_candidates
|
||||
# Calculate file hash in a new thread
|
||||
# if we haven't sent the hash already.
|
||||
if self.file_props.hash_ is None and self.file_props.algo and \
|
||||
not self.werequest:
|
||||
self.hashThread = threading.Thread(target=self.__send_hash)
|
||||
self.hashThread.start()
|
||||
not self.werequest:
|
||||
self.hash_thread = threading.Thread(target=self.__send_hash)
|
||||
self.hash_thread.start()
|
||||
for host in self.file_props.streamhosts:
|
||||
host['initiator'] = self.session.initiator
|
||||
host['target'] = self.session.responder
|
||||
|
@ -225,11 +237,13 @@ class JingleFileTransfer(JingleContent):
|
|||
fingerprint = 'client'
|
||||
if self.transport.type_ == TransportType.SOCKS5:
|
||||
gajim.socks5queue.connect_to_hosts(self.session.connection.name,
|
||||
self.file_props.sid, self.on_connect,
|
||||
self._on_connect_error, fingerprint=fingerprint,
|
||||
receiving=False)
|
||||
self.file_props.sid,
|
||||
self.on_connect,
|
||||
self._on_connect_error,
|
||||
fingerprint=fingerprint,
|
||||
receiving=False)
|
||||
raise nbxmpp.NodeProcessed
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
self.__state_changed(State.TRANSFERING)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
def __on_session_terminate(self, stanza, content, error, action):
|
||||
|
@ -249,78 +263,78 @@ class JingleFileTransfer(JingleContent):
|
|||
|
||||
def __on_transport_info(self, stanza, content, error, action):
|
||||
log.info("__on_transport_info")
|
||||
candError = content.getTag('transport').getTag('candidate-error')
|
||||
candUsed = content.getTag('transport').getTag('candidate-used')
|
||||
if (candError or candUsed) and \
|
||||
self.state >= STATE_CAND_SENT_AND_RECEIVED:
|
||||
cand_error = content.getTag('transport').getTag('candidate-error')
|
||||
cand_used = content.getTag('transport').getTag('candidate-used')
|
||||
if (cand_error or cand_used) and \
|
||||
self.state >= State.CAND_SENT_AND_RECEIVED:
|
||||
raise nbxmpp.NodeProcessed
|
||||
if candError:
|
||||
if cand_error:
|
||||
if not gajim.socks5queue.listener.connections:
|
||||
gajim.socks5queue.listener.disconnect()
|
||||
self.nominated_cand['peer-cand'] = False
|
||||
if self.state == STATE_CAND_SENT:
|
||||
if self.state == State.CAND_SENT:
|
||||
if not self.nominated_cand['our-cand'] and \
|
||||
not self.nominated_cand['peer-cand']:
|
||||
if not self.weinitiate:
|
||||
return
|
||||
self.__state_changed(STATE_TRANSPORT_REPLACE)
|
||||
self.__state_changed(State.TRANSPORT_REPLACE)
|
||||
else:
|
||||
response = stanza.buildReply('result')
|
||||
response.delChild(response.getQuery())
|
||||
self.session.connection.connection.send(response)
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
self.__state_changed(State.TRANSFERING)
|
||||
raise nbxmpp.NodeProcessed
|
||||
else:
|
||||
args = {'candError' : True}
|
||||
self.__state_changed(STATE_CAND_RECEIVED, args)
|
||||
args = {'cand_error' : True}
|
||||
self.__state_changed(State.CAND_RECEIVED, args)
|
||||
return
|
||||
if candUsed:
|
||||
streamhost_cid = candUsed.getAttr('cid')
|
||||
if cand_used:
|
||||
streamhost_cid = cand_used.getAttr('cid')
|
||||
streamhost_used = None
|
||||
for cand in self.transport.candidates:
|
||||
if cand['candidate_id'] == streamhost_cid:
|
||||
streamhost_used = cand
|
||||
break
|
||||
if streamhost_used == None or streamhost_used['type'] == 'proxy':
|
||||
if streamhost_used is None or streamhost_used['type'] == 'proxy':
|
||||
if gajim.socks5queue.listener and \
|
||||
not gajim.socks5queue.listener.connections:
|
||||
gajim.socks5queue.listener.disconnect()
|
||||
if content.getTag('transport').getTag('activated'):
|
||||
self.state = STATE_TRANSFERING
|
||||
self.state = State.TRANSFERING
|
||||
jid = gajim.get_jid_without_resource(self.session.ourjid)
|
||||
gajim.socks5queue.send_file(self.file_props,
|
||||
self.session.connection.name, 'client')
|
||||
self.session.connection.name, 'client')
|
||||
return
|
||||
args = {'content' : content,
|
||||
'sendCand' : False}
|
||||
if self.state == STATE_CAND_SENT:
|
||||
self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args)
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
args = {'content': content,
|
||||
'sendCand': False}
|
||||
if self.state == State.CAND_SENT:
|
||||
self.__state_changed(State.CAND_SENT_AND_RECEIVED, args)
|
||||
self.__state_changed(State.TRANSFERING)
|
||||
raise nbxmpp.NodeProcessed
|
||||
else:
|
||||
self.__state_changed(STATE_CAND_RECEIVED, args)
|
||||
self.__state_changed(State.CAND_RECEIVED, args)
|
||||
|
||||
def __on_iq_result(self, stanza, content, error, action):
|
||||
log.info("__on_iq_result")
|
||||
|
||||
if self.state == STATE_NOT_STARTED:
|
||||
self.__state_changed(STATE_INITIALIZED)
|
||||
elif self.state == STATE_CAND_SENT_AND_RECEIVED:
|
||||
if self.state == State.NOT_STARTED:
|
||||
self.__state_changed(State.INITIALIZED)
|
||||
elif self.state == State.CAND_SENT_AND_RECEIVED:
|
||||
if not self.nominated_cand['our-cand'] and \
|
||||
not self.nominated_cand['peer-cand']:
|
||||
if not self.weinitiate:
|
||||
return
|
||||
self.__state_changed(STATE_TRANSPORT_REPLACE)
|
||||
self.__state_changed(State.TRANSPORT_REPLACE)
|
||||
return
|
||||
# initiate transfer
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
self.__state_changed(State.TRANSFERING)
|
||||
|
||||
def __transport_setup(self, stanza=None, content=None, error=None,
|
||||
action=None):
|
||||
action=None):
|
||||
# Sets up a few transport specific things for the file transfer
|
||||
if self.transport.type_ == TransportType.IBB:
|
||||
# No action required, just set the state to transfering
|
||||
self.state = STATE_TRANSFERING
|
||||
self.state = State.TRANSFERING
|
||||
else:
|
||||
self._listen_host()
|
||||
|
||||
|
@ -334,19 +348,19 @@ class JingleFileTransfer(JingleContent):
|
|||
args = {'streamhost' : streamhost,
|
||||
'sendCand' : True}
|
||||
self.nominated_cand['our-cand'] = streamhost
|
||||
self.__sendCand(args)
|
||||
self.__send_candidate(args)
|
||||
|
||||
def _on_connect_error(self, sid):
|
||||
log.info('connect error, sid=' + sid)
|
||||
args = {'candError' : True,
|
||||
'sendCand' : True}
|
||||
self.__sendCand(args)
|
||||
self.__send_candidate(args)
|
||||
|
||||
def __sendCand(self, args):
|
||||
if self.state == STATE_CAND_RECEIVED:
|
||||
self.__state_changed(STATE_CAND_SENT_AND_RECEIVED, args)
|
||||
def __send_candidate(self, args):
|
||||
if self.state == State.CAND_RECEIVED:
|
||||
self.__state_changed(State.CAND_SENT_AND_RECEIVED, args)
|
||||
else:
|
||||
self.__state_changed(STATE_CAND_SENT, args)
|
||||
self.__state_changed(State.CAND_SENT, args)
|
||||
|
||||
def _store_socks5_sid(self, sid, hash_id):
|
||||
# callback from socsk5queue.start_listener
|
||||
|
@ -356,44 +370,40 @@ class JingleFileTransfer(JingleContent):
|
|||
receiver = self.file_props.receiver
|
||||
sender = self.file_props.sender
|
||||
sha_str = helpers.get_auth_sha(self.file_props.sid, sender,
|
||||
receiver)
|
||||
receiver)
|
||||
self.file_props.sha_str = sha_str
|
||||
port = gajim.config.get('file_transfers_port')
|
||||
fingerprint = None
|
||||
if self.use_security:
|
||||
fingerprint = 'server'
|
||||
if self.weinitiate:
|
||||
listener = gajim.socks5queue.start_listener(port, sha_str,
|
||||
self._store_socks5_sid, self.file_props,
|
||||
fingerprint=fingerprint, typ='sender')
|
||||
else:
|
||||
listener = gajim.socks5queue.start_listener(port, sha_str,
|
||||
self._store_socks5_sid, self.file_props,
|
||||
fingerprint=fingerprint, typ='receiver')
|
||||
listener = gajim.socks5queue.start_listener(port, sha_str,
|
||||
self._store_socks5_sid,
|
||||
self.file_props,
|
||||
fingerprint=fingerprint,
|
||||
typ='sender' if self.weinitiate else 'receiver')
|
||||
if not listener:
|
||||
# send error message, notify the user
|
||||
return
|
||||
|
||||
def isOurCandUsed(self):
|
||||
def is_our_candidate_used(self):
|
||||
'''
|
||||
If this method returns true then the candidate we nominated will be
|
||||
used, if false, the candidate nominated by peer will be used
|
||||
'''
|
||||
|
||||
if self.nominated_cand['peer-cand'] == False:
|
||||
if not self.nominated_cand['peer-cand']:
|
||||
return True
|
||||
if self.nominated_cand['our-cand'] == False:
|
||||
if not self.nominated_cand['our-cand']:
|
||||
return False
|
||||
peer_pr = int(self.nominated_cand['peer-cand']['priority'])
|
||||
our_pr = int(self.nominated_cand['our-cand']['priority'])
|
||||
if peer_pr != our_pr:
|
||||
return our_pr > peer_pr
|
||||
else:
|
||||
return self.weinitiate
|
||||
return self.weinitiate
|
||||
|
||||
def start_IBB_transfer(self):
|
||||
def start_ibb_transfer(self):
|
||||
if self.file_props.type_ == 's':
|
||||
self.__state_changed(STATE_TRANSFERING)
|
||||
self.__state_changed(State.TRANSFERING)
|
||||
|
||||
|
||||
def get_content(desc):
|
||||
|
|
|
@ -13,11 +13,14 @@
|
|||
## You should have received a copy of the GNU General Public License
|
||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from common import gajim
|
||||
import nbxmpp
|
||||
from common.jingle_transport import *
|
||||
from common import gajim
|
||||
from common.jingle_transport import TransportType
|
||||
from common.socks5 import Socks5ReceiverClient, Socks5SenderClient
|
||||
|
||||
import logging
|
||||
log = logging.getLogger('gajim.c.jingle_ftstates')
|
||||
|
||||
|
||||
class JingleFileTransferStates:
|
||||
'''
|
||||
|
@ -31,7 +34,7 @@ class JingleFileTransferStates:
|
|||
'''
|
||||
This method MUST be overriden by a subclass
|
||||
'''
|
||||
raise Exception('This is an abstract method!!')
|
||||
raise NotImplementedError('This is an abstract method!')
|
||||
|
||||
|
||||
class StateInitialized(JingleFileTransferStates):
|
||||
|
@ -50,8 +53,10 @@ class StateInitialized(JingleFileTransferStates):
|
|||
fingerprint = 'client'
|
||||
# Connect to the candidate host, on success call on_connect method
|
||||
gajim.socks5queue.connect_to_hosts(self.jft.session.connection.name,
|
||||
self.jft.file_props.sid, self.jft.on_connect,
|
||||
self.jft._on_connect_error, fingerprint=fingerprint)
|
||||
self.jft.file_props.sid,
|
||||
self.jft.on_connect,
|
||||
self.jft._on_connect_error,
|
||||
fingerprint=fingerprint)
|
||||
|
||||
|
||||
class StateCandSent(JingleFileTransferStates):
|
||||
|
@ -59,7 +64,7 @@ class StateCandSent(JingleFileTransferStates):
|
|||
This state sends our nominated candidate
|
||||
'''
|
||||
|
||||
def _sendCand(self, args):
|
||||
def _send_candidate(self, args):
|
||||
if 'candError' in args:
|
||||
self.jft.nominated_cand['our-cand'] = False
|
||||
self.jft.send_error_candidate()
|
||||
|
@ -80,16 +85,16 @@ class StateCandSent(JingleFileTransferStates):
|
|||
self.jft.session.send_transport_info(content)
|
||||
|
||||
def action(self, args=None):
|
||||
self._sendCand(args)
|
||||
self._send_candidate(args)
|
||||
|
||||
|
||||
class StateCandReceived(JingleFileTransferStates):
|
||||
class StateCandReceived(JingleFileTransferStates):
|
||||
'''
|
||||
This state happens when we receive a candidate.
|
||||
It takes the arguments: canError if we receive a candidate-error
|
||||
'''
|
||||
|
||||
def _recvCand(self, args):
|
||||
def _recv_candidate(self, args):
|
||||
if 'candError' in args:
|
||||
return
|
||||
content = args['content']
|
||||
|
@ -100,17 +105,17 @@ class StateCandReceived(JingleFileTransferStates):
|
|||
if cand['candidate_id'] == streamhost_cid:
|
||||
streamhost_used = cand
|
||||
break
|
||||
if streamhost_used == None:
|
||||
if streamhost_used is None:
|
||||
log.info("unknow streamhost")
|
||||
return
|
||||
# We save the candidate nominated by peer
|
||||
self.jft.nominated_cand['peer-cand'] = streamhost_used
|
||||
|
||||
def action(self, args=None):
|
||||
self._recvCand(args)
|
||||
self._recv_candidate(args)
|
||||
|
||||
|
||||
class StateCandSentAndRecv( StateCandSent, StateCandReceived):
|
||||
class StateCandSentAndRecv(StateCandSent, StateCandReceived):
|
||||
'''
|
||||
This state happens when we have received and sent the candidates.
|
||||
It takes the boolean argument: sendCand in order to decide whether
|
||||
|
@ -119,9 +124,9 @@ class StateCandSentAndRecv( StateCandSent, StateCandReceived):
|
|||
|
||||
def action(self, args=None):
|
||||
if args['sendCand']:
|
||||
self._sendCand(args)
|
||||
self._send_candidate(args)
|
||||
else:
|
||||
self._recvCand(args)
|
||||
self._recv_candidate(args)
|
||||
|
||||
|
||||
class StateTransportReplace(JingleFileTransferStates):
|
||||
|
@ -139,16 +144,16 @@ class StateTransfering(JingleFileTransferStates):
|
|||
we have.
|
||||
'''
|
||||
|
||||
def __start_IBB_transfer(self, con):
|
||||
def _start_ibb_transfer(self, con):
|
||||
self.jft.file_props.transport_sid = self.jft.transport.sid
|
||||
fp = open(self.jft.file_props.file_name, 'r')
|
||||
con.OpenStream( self.jft.file_props.sid, self.jft.session.peerjid, fp,
|
||||
blocksize=4096)
|
||||
con.OpenStream(self.jft.file_props.sid, self.jft.session.peerjid, fp,
|
||||
blocksize=4096)
|
||||
|
||||
def __start_SOCK5_transfer(self):
|
||||
def _start_sock5_transfer(self):
|
||||
# It tells wether we start the transfer as client or server
|
||||
mode = None
|
||||
if self.jft.isOurCandUsed():
|
||||
if self.jft.is_our_candidate_used():
|
||||
mode = 'client'
|
||||
streamhost_used = self.jft.nominated_cand['our-cand']
|
||||
gajim.socks5queue.remove_server(self.jft.file_props.sid)
|
||||
|
@ -191,34 +196,34 @@ class StateTransfering(JingleFileTransferStates):
|
|||
gajim.socks5queue.idx += 1
|
||||
idx = gajim.socks5queue.idx
|
||||
sockobj = Socks5SenderClient(gajim.idlequeue, idx,
|
||||
gajim.socks5queue, _sock=None,
|
||||
host=str(streamhost_used['host']),
|
||||
port=int(streamhost_used['port']), fingerprint=None,
|
||||
connected=False, file_props=self.jft.file_props)
|
||||
gajim.socks5queue, _sock=None,
|
||||
host=str(streamhost_used['host']),
|
||||
port=int(streamhost_used['port']),
|
||||
fingerprint=None, connected=False,
|
||||
file_props=self.jft.file_props)
|
||||
else:
|
||||
sockobj = Socks5ReceiverClient(gajim.idlequeue, streamhost_used,
|
||||
sid=self.jft.file_props.sid,
|
||||
file_props=self.jft.file_props, fingerprint=None)
|
||||
sid=self.jft.file_props.sid,
|
||||
file_props=self.jft.file_props,
|
||||
fingerprint=None)
|
||||
sockobj.proxy = True
|
||||
sockobj.streamhost = streamhost_used
|
||||
gajim.socks5queue.add_sockobj(self.jft.session.connection.name,
|
||||
sockobj)
|
||||
sockobj)
|
||||
streamhost_used['idx'] = sockobj.queue_idx
|
||||
# If we offered the nominated candidate used, we activate
|
||||
# the proxy
|
||||
if not self.jft.isOurCandUsed():
|
||||
if not self.jft.is_our_candidate_used():
|
||||
gajim.socks5queue.on_success[self.jft.file_props.sid] = \
|
||||
self.jft.transport._on_proxy_auth_ok
|
||||
# TODO: add on failure
|
||||
else:
|
||||
jid = gajim.get_jid_without_resource(self.jft.session.ourjid)
|
||||
gajim.socks5queue.send_file(self.jft.file_props,
|
||||
self.jft.session.connection.name, mode)
|
||||
self.jft.session.connection.name, mode)
|
||||
|
||||
def action(self, args=None):
|
||||
if self.jft.transport.type_ == TransportType.IBB:
|
||||
self.__start_IBB_transfer(self.jft.session.connection)
|
||||
self._start_ibb_transfer(self.jft.session.connection)
|
||||
elif self.jft.transport.type_ == TransportType.SOCKS5:
|
||||
self.__start_SOCK5_transfer()
|
||||
|
||||
|
||||
self._start_sock5_transfer()
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
Handles Jingle RTP sessions (XEP 0167)
|
||||
"""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
import nbxmpp
|
||||
import gi
|
||||
|
@ -35,7 +36,6 @@ from common.jingle_session import FailedApplication
|
|||
|
||||
from collections import deque
|
||||
|
||||
import logging
|
||||
log = logging.getLogger('gajim.c.jingle_rtp')
|
||||
|
||||
|
||||
|
@ -46,7 +46,8 @@ class JingleRTPContent(JingleContent):
|
|||
JingleContent.__init__(self, session, transport)
|
||||
self.media = media
|
||||
self._dtmf_running = False
|
||||
self.farstream_media = {'audio': Farstream.MediaType.AUDIO,
|
||||
self.farstream_media = {
|
||||
'audio': Farstream.MediaType.AUDIO,
|
||||
'video': Farstream.MediaType.VIDEO}[media]
|
||||
|
||||
self.pipeline = None
|
||||
|
@ -55,6 +56,12 @@ class JingleRTPContent(JingleContent):
|
|||
|
||||
self.candidates_ready = False # True when local candidates are prepared
|
||||
|
||||
# TODO
|
||||
self.conference = None
|
||||
self.funnel = None
|
||||
self.p2psession = None
|
||||
self.p2pstream = None
|
||||
|
||||
self.callbacks['session-initiate'] += [self.__on_remote_codecs]
|
||||
self.callbacks['content-add'] += [self.__on_remote_codecs]
|
||||
self.callbacks['description-info'] += [self.__on_remote_codecs]
|
||||
|
@ -90,32 +97,32 @@ class JingleRTPContent(JingleContent):
|
|||
if stun_server:
|
||||
try:
|
||||
ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC,
|
||||
socket.SOCK_STREAM)[0][4][0]
|
||||
socket.SOCK_STREAM)[0][4][0]
|
||||
except socket.gaierror as e:
|
||||
log.warning('Lookup of stun ip failed: %s' % str(e))
|
||||
log.warning('Lookup of stun ip failed: %s', str(e))
|
||||
else:
|
||||
params['stun-ip'] = ip
|
||||
|
||||
self.p2pstream = self.p2psession.new_stream(participant,
|
||||
Farstream.StreamDirection.BOTH)
|
||||
Farstream.StreamDirection.BOTH)
|
||||
self.p2pstream.connect('src-pad-added', on_src_pad_added)
|
||||
self.p2pstream.set_transmitter_ht('nice', params)
|
||||
|
||||
def is_ready(self):
|
||||
return (JingleContent.is_ready(self) and self.candidates_ready)
|
||||
return JingleContent.is_ready(self) and self.candidates_ready
|
||||
|
||||
def make_bin_from_config(self, config_key, pipeline, text):
|
||||
pipeline = pipeline % gajim.config.get(config_key)
|
||||
try:
|
||||
bin = Gst.parse_bin_from_description(pipeline, True)
|
||||
return bin
|
||||
gst_bin = Gst.parse_bin_from_description(pipeline, True)
|
||||
return gst_bin
|
||||
except GLib.GError as e:
|
||||
gajim.nec.push_incoming_event(InformationEvent(None,
|
||||
conn=self.session.connection, level='error',
|
||||
pri_txt=_('%s configuration error') % text.capitalize(),
|
||||
sec_txt=_("Couldn't setup %s. Check your configuration.\n\n"
|
||||
"Pipeline was:\n%s\n\nError was:\n%s") % (text, pipeline,
|
||||
str(e))))
|
||||
gajim.nec.push_incoming_event(
|
||||
InformationEvent(
|
||||
None, conn=self.session.connection, level='error',
|
||||
pri_txt=_('%s configuration error') % text.capitalize(),
|
||||
sec_txt=_('Couldn’t setup %s. Check your configuration.\n\n'
|
||||
'Pipeline was:\n%s\n\nError was:\n%s') % (text, pipeline, str(e))))
|
||||
raise JingleContentSetupException
|
||||
|
||||
def add_remote_candidates(self, candidates):
|
||||
|
@ -147,7 +154,7 @@ class JingleRTPContent(JingleContent):
|
|||
def _start_dtmf(self, event):
|
||||
if event in ('*', '#'):
|
||||
event = {'*': Farstream.DTMFEvent.STAR,
|
||||
'#': Farstream.DTMFEvent.POUND}[event]
|
||||
'#': Farstream.DTMFEvent.POUND}[event]
|
||||
else:
|
||||
event = int(event)
|
||||
self.p2psession.start_telephony_event(event, 2)
|
||||
|
@ -157,7 +164,8 @@ class JingleRTPContent(JingleContent):
|
|||
|
||||
def _fill_content(self, content):
|
||||
content.addChild(nbxmpp.NS_JINGLE_RTP + ' description',
|
||||
attrs={'media': self.media}, payload=list(self.iter_codecs()))
|
||||
attrs={'media': self.media},
|
||||
payload=list(self.iter_codecs()))
|
||||
|
||||
def _setup_funnel(self):
|
||||
self.funnel = Gst.ElementFactory.make('funnel', None)
|
||||
|
@ -174,7 +182,7 @@ class JingleRTPContent(JingleContent):
|
|||
def _on_gst_message(self, bus, message):
|
||||
if message.type == Gst.MessageType.ELEMENT:
|
||||
name = message.get_structure().get_name()
|
||||
log.debug('gst element message: %s: %s' % (name, message))
|
||||
log.debug('gst element message: %s: %s', name, message)
|
||||
if name == 'farstream-new-active-candidate-pair':
|
||||
pass
|
||||
elif name == 'farstream-recv-codecs-changed':
|
||||
|
@ -189,7 +197,7 @@ class JingleRTPContent(JingleContent):
|
|||
self.transport.remote_candidates)
|
||||
self.transport.remote_candidates = []
|
||||
self.p2pstream.set_property('direction',
|
||||
Farstream.StreamDirection.BOTH)
|
||||
Farstream.StreamDirection.BOTH)
|
||||
|
||||
elif name == 'farstream-local-candidates-prepared':
|
||||
self.candidates_ready = True
|
||||
|
@ -208,19 +216,21 @@ class JingleRTPContent(JingleContent):
|
|||
reason.setTag('failed-transport')
|
||||
self.session.remove_content(self.creator, self.name, reason)
|
||||
elif name == 'farstream-error':
|
||||
log.error('Farstream error #%d!\nMessage: %s' % (
|
||||
message.get_structure().get_value('error-no'),
|
||||
message.get_structure().get_value('error-msg')))
|
||||
log.error('Farstream error #%d!\nMessage: %s',
|
||||
message.get_structure().get_value('error-no'),
|
||||
message.get_structure().get_value('error-msg'))
|
||||
elif message.type == Gst.MessageType.ERROR:
|
||||
# TODO: Fix it to fallback to videotestsrc anytime an error occur,
|
||||
# or raise an error, Jingle way
|
||||
# or maybe one-sided stream?
|
||||
if not self.stream_failed_once:
|
||||
gajim.nec.push_incoming_event(InformationEvent(None,
|
||||
conn=self.session.connection, level='error',
|
||||
pri_txt=_('GStreamer error'), sec_txt=_('Error: %s\nDebug: '
|
||||
'%s' % (message.get_structure().get_value('gerror'),
|
||||
message.get_structure().get_value('debug')))))
|
||||
gajim.nec.push_incoming_event(
|
||||
InformationEvent(
|
||||
None, conn=self.session.connection, level='error',
|
||||
pri_txt=_('GStreamer error'),
|
||||
sec_txt=_('Error: %s\nDebug: %s' %
|
||||
(message.get_structure().get_value('gerror'),
|
||||
message.get_structure().get_value('debug')))))
|
||||
|
||||
sink_pad = self.p2psession.get_property('sink-pad')
|
||||
|
||||
|
@ -243,7 +253,8 @@ class JingleRTPContent(JingleContent):
|
|||
# Start playing again
|
||||
self.pipeline.set_state(Gst.State.PLAYING)
|
||||
|
||||
def get_fallback_src(self):
|
||||
@staticmethod
|
||||
def get_fallback_src():
|
||||
return Gst.ElementFactory.make('fakesrc', None)
|
||||
|
||||
def on_negotiated(self):
|
||||
|
@ -270,7 +281,7 @@ class JingleRTPContent(JingleContent):
|
|||
# ignore invalid payload-types
|
||||
continue
|
||||
c = Farstream.Codec.new(int(codec['id']), codec['name'],
|
||||
self.farstream_media, int(codec['clockrate']))
|
||||
self.farstream_media, int(codec['clockrate']))
|
||||
if 'channels' in codec:
|
||||
c.channels = int(codec['channels'])
|
||||
else:
|
||||
|
@ -288,14 +299,17 @@ class JingleRTPContent(JingleContent):
|
|||
def iter_codecs(self):
|
||||
codecs = self.p2psession.props.codecs_without_config
|
||||
for codec in codecs:
|
||||
attrs = {'name': codec.encoding_name,
|
||||
attrs = {
|
||||
'name': codec.encoding_name,
|
||||
'id': codec.id,
|
||||
'channels': codec.channels}
|
||||
'channels': codec.channels
|
||||
}
|
||||
if codec.clock_rate:
|
||||
attrs['clockrate'] = codec.clock_rate
|
||||
if codec.optional_params:
|
||||
payload = list(nbxmpp.Node('parameter', {'name': p.name,
|
||||
'value': p.value}) for p in codec.optional_params)
|
||||
payload = [nbxmpp.Node('parameter',
|
||||
{'name': p.name, 'value': p.value})
|
||||
for p in codec.optional_params]
|
||||
else:
|
||||
payload = []
|
||||
yield nbxmpp.Node('payload-type', attrs, payload)
|
||||
|
@ -343,19 +357,22 @@ class JingleAudio(JingleRTPContent):
|
|||
# place 16kHz before 8kHz, as buggy psi versions will take in
|
||||
# account only the first codec
|
||||
|
||||
codecs = [Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
|
||||
Farstream.MediaType.AUDIO, 16000),
|
||||
codecs = [
|
||||
Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
|
||||
Farstream.MediaType.AUDIO, 8000)]
|
||||
Farstream.MediaType.AUDIO, 16000),
|
||||
Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
|
||||
Farstream.MediaType.AUDIO, 8000)]
|
||||
self.p2psession.set_codec_preferences(codecs)
|
||||
|
||||
# the local parts
|
||||
# TODO: Add queues?
|
||||
self.src_bin = self.make_bin_from_config('audio_input_device',
|
||||
'%s ! audioconvert', _("audio input"))
|
||||
'%s ! audioconvert',
|
||||
_("audio input"))
|
||||
|
||||
self.sink = self.make_bin_from_config('audio_output_device',
|
||||
'audioconvert ! volume name=gajim_out_vol ! %s', _("audio output"))
|
||||
'audioconvert ! volume name=gajim_out_vol ! %s',
|
||||
_("audio output"))
|
||||
|
||||
self.mic_volume = self.src_bin.get_by_name('gajim_vol')
|
||||
self.out_volume = self.sink.get_by_name('gajim_out_vol')
|
||||
|
@ -411,15 +428,16 @@ class JingleVideo(JingleRTPContent):
|
|||
tee = ''
|
||||
|
||||
self.src_bin = self.make_bin_from_config('video_input_device',
|
||||
'%%s %s! %svideoscale ! %svideoconvert' % (tee, framerate,
|
||||
video_size), _("video input"))
|
||||
'%%s %s! %svideoscale ! %svideoconvert' %
|
||||
(tee, framerate, video_size),
|
||||
_("video input"))
|
||||
|
||||
self.pipeline.add(self.src_bin)
|
||||
self.pipeline.set_state(Gst.State.PLAYING)
|
||||
|
||||
self.sink = self.make_bin_from_config('video_output_device',
|
||||
'videoscale ! videoconvert ! %s',
|
||||
_("video output"))
|
||||
'videoscale ! videoconvert ! %s',
|
||||
_("video output"))
|
||||
|
||||
self.pipeline.add(self.sink)
|
||||
|
||||
|
|
|
@ -28,24 +28,28 @@ Handles Jingle sessions (XEP 0166)
|
|||
# - Tie-breaking
|
||||
# * timeout
|
||||
|
||||
from common import gajim
|
||||
import nbxmpp
|
||||
from common.jingle_transport import get_jingle_transport, JingleTransportIBB
|
||||
from common.jingle_content import get_jingle_content, JingleContentSetupException
|
||||
from common.jingle_content import JingleContent
|
||||
from common.jingle_ft import STATE_TRANSPORT_REPLACE
|
||||
from common.connection_handlers_events import *
|
||||
import logging
|
||||
from enum import Enum
|
||||
import nbxmpp
|
||||
from common import gajim
|
||||
from common.jingle_transport import get_jingle_transport, JingleTransportIBB
|
||||
from common.jingle_content import get_jingle_content, JingleContentSetupException, JingleContent
|
||||
from common.jingle_ft import State
|
||||
from common.connection_handlers_events import (
|
||||
FilesProp, JingleRequestReceivedEvent, JingleDisconnectedReceivedEvent,
|
||||
JingleTransferCancelledEvent, JingleConnectedReceivedEvent,
|
||||
JingleErrorReceivedEvent)
|
||||
|
||||
log = logging.getLogger("gajim.c.jingle_session")
|
||||
|
||||
# FIXME: Move it to JingleSession.States?
|
||||
class JingleStates(object):
|
||||
class JingleStates(Enum):
|
||||
"""
|
||||
States in which jingle session may exist
|
||||
"""
|
||||
ended = 0
|
||||
pending = 1
|
||||
active = 2
|
||||
ENDED = 0
|
||||
PENDING = 1
|
||||
ACTIVE = 2
|
||||
|
||||
class OutOfOrder(Exception):
|
||||
"""
|
||||
|
@ -61,17 +65,18 @@ class TieBreak(Exception):
|
|||
|
||||
class FailedApplication(Exception):
|
||||
"""
|
||||
Exception that should be raised in case responder supports none of the payload-types offered by the initiator
|
||||
Exception that should be raised in case responder supports none of the
|
||||
payload-types offered by the initiator
|
||||
"""
|
||||
|
||||
class JingleSession(object):
|
||||
class JingleSession:
|
||||
"""
|
||||
This represents one jingle session, that is, one or more content types
|
||||
negotiated between an initiator and a responder.
|
||||
"""
|
||||
|
||||
def __init__(self, con, weinitiate, jid, iq_id=None, sid=None,
|
||||
werequest=False):
|
||||
werequest=False):
|
||||
"""
|
||||
con -- connection object,
|
||||
weinitiate -- boolean, are we the initiator?
|
||||
|
@ -85,16 +90,16 @@ class JingleSession(object):
|
|||
self.ourjid = self.ourjid + '/' + con.server_resource
|
||||
self.peerjid = jid # jid we connect to
|
||||
# jid we use as the initiator
|
||||
self.initiator = weinitiate and self.ourjid or self.peerjid
|
||||
self.initiator = self.ourjid if weinitiate else self.peerjid
|
||||
# jid we use as the responder
|
||||
self.responder = weinitiate and self.peerjid or self.ourjid
|
||||
self.responder = self.peerjid if weinitiate else self.ourjid
|
||||
# are we an initiator?
|
||||
self.weinitiate = weinitiate
|
||||
# Are we requesting or offering a file?
|
||||
self.werequest = werequest
|
||||
self.request = False
|
||||
# what state is session in? (one from JingleStates)
|
||||
self.state = JingleStates.ended
|
||||
self.state = JingleStates.ENDED
|
||||
if not sid:
|
||||
sid = con.connection.getAnID()
|
||||
self.sid = sid # sessionid
|
||||
|
@ -106,37 +111,37 @@ class JingleSession(object):
|
|||
self.iq_ids = []
|
||||
self.accepted = True # is this session accepted by user
|
||||
# Tells whether this session is a file transfer or not
|
||||
self.session_type_FT = False
|
||||
self.session_type_ft = False
|
||||
# callbacks to call on proper contents
|
||||
# use .prepend() to add new callbacks, especially when you're going
|
||||
# to send error instead of ack
|
||||
self.callbacks = {
|
||||
'content-accept': [self.__ack, self.__on_content_accept,
|
||||
self.__broadcast],
|
||||
'content-add': [self.__ack,
|
||||
self.__on_content_add, self.__broadcast
|
||||
], #TODO
|
||||
'content-modify': [self.__ack], #TODO
|
||||
'content-reject': [self.__ack, self.__on_content_remove],
|
||||
'content-remove': [self.__ack, self.__on_content_remove],
|
||||
'description-info': [self.__ack, self.__broadcast], #TODO
|
||||
'security-info': [self.__ack], #TODO
|
||||
'session-accept': [self.__ack, self.__on_session_accept,
|
||||
self.__on_content_accept,
|
||||
self.__broadcast],
|
||||
'session-info': [self.__ack, self.__broadcast,
|
||||
self.__on_session_info ],
|
||||
'session-initiate': [self.__ack, self.__on_session_initiate,
|
||||
self.__broadcast],
|
||||
'session-terminate': [self.__ack,self.__on_session_terminate,
|
||||
self.__broadcast_all],
|
||||
'transport-info': [self.__ack, self.__broadcast],
|
||||
'transport-replace': [self.__ack, self.__broadcast,
|
||||
self.__on_transport_replace], #TODO
|
||||
'transport-accept': [self.__ack], #TODO
|
||||
'transport-reject': [self.__ack], #TODO
|
||||
'iq-result': [self.__broadcast],
|
||||
'iq-error': [self.__on_error],
|
||||
'content-accept': [self.__ack, self.__on_content_accept,
|
||||
self.__broadcast],
|
||||
'content-add': [self.__ack,
|
||||
self.__on_content_add, self.__broadcast
|
||||
], #TODO
|
||||
'content-modify': [self.__ack], #TODO
|
||||
'content-reject': [self.__ack, self.__on_content_remove],
|
||||
'content-remove': [self.__ack, self.__on_content_remove],
|
||||
'description-info': [self.__ack, self.__broadcast], #TODO
|
||||
'security-info': [self.__ack], #TODO
|
||||
'session-accept': [self.__ack, self.__on_session_accept,
|
||||
self.__on_content_accept,
|
||||
self.__broadcast],
|
||||
'session-info': [self.__ack, self.__broadcast,
|
||||
self.__on_session_info],
|
||||
'session-initiate': [self.__ack, self.__on_session_initiate,
|
||||
self.__broadcast],
|
||||
'session-terminate': [self.__ack, self.__on_session_terminate,
|
||||
self.__broadcast_all],
|
||||
'transport-info': [self.__ack, self.__broadcast],
|
||||
'transport-replace': [self.__ack, self.__broadcast,
|
||||
self.__on_transport_replace], #TODO
|
||||
'transport-accept': [self.__ack], #TODO
|
||||
'transport-reject': [self.__ack], #TODO
|
||||
'iq-result': [self.__broadcast],
|
||||
'iq-error': [self.__on_error],
|
||||
}
|
||||
|
||||
def collect_iq_id(self, iq_id):
|
||||
|
@ -174,7 +179,7 @@ class JingleSession(object):
|
|||
def reject_content(self, media, name=None):
|
||||
content = self.get_content(media, name)
|
||||
if content:
|
||||
if self.state == JingleStates.active:
|
||||
if self.state == JingleStates.ACTIVE:
|
||||
self.__content_reject(content)
|
||||
content.destroy()
|
||||
self.on_session_state_changed()
|
||||
|
@ -184,7 +189,7 @@ class JingleSession(object):
|
|||
Called when user stops or cancel session in UI
|
||||
"""
|
||||
reason = nbxmpp.Node('reason')
|
||||
if self.state == JingleStates.active:
|
||||
if self.state == JingleStates.ACTIVE:
|
||||
reason.addChild('success')
|
||||
else:
|
||||
reason.addChild('cancel')
|
||||
|
@ -232,11 +237,11 @@ class JingleSession(object):
|
|||
if not self.contents:
|
||||
self.end_session()
|
||||
|
||||
def modify_content(self, creator, name, transport = None):
|
||||
def modify_content(self, creator, name, transport=None):
|
||||
'''
|
||||
Currently used for transport replacement
|
||||
'''
|
||||
content = self.contents[(creator,name)]
|
||||
content = self.contents[(creator, name)]
|
||||
transport.set_sid(content.transport.sid)
|
||||
transport.set_file_props(content.transport.file_props)
|
||||
content.transport = transport
|
||||
|
@ -245,11 +250,11 @@ class JingleSession(object):
|
|||
content.accepted = True
|
||||
|
||||
def on_session_state_changed(self, content=None):
|
||||
if self.state == JingleStates.ended:
|
||||
if self.state == JingleStates.ENDED:
|
||||
# Session not yet started, only one action possible: session-initiate
|
||||
if self.is_ready() and self.weinitiate:
|
||||
self.__session_initiate()
|
||||
elif self.state == JingleStates.pending:
|
||||
elif self.state == JingleStates.PENDING:
|
||||
# We can either send a session-accept or a content-add
|
||||
if self.is_ready() and not self.weinitiate:
|
||||
self.__session_accept()
|
||||
|
@ -257,7 +262,7 @@ class JingleSession(object):
|
|||
self.__content_add(content)
|
||||
elif content and self.weinitiate:
|
||||
self.__content_accept(content)
|
||||
elif self.state == JingleStates.active:
|
||||
elif self.state == JingleStates.ACTIVE:
|
||||
# We can either send a content-add or a content-accept. However, if
|
||||
# we are sending a file we can only use session_initiate.
|
||||
if not content:
|
||||
|
@ -278,7 +283,7 @@ class JingleSession(object):
|
|||
Return True when all codecs and candidates are ready (for all contents)
|
||||
"""
|
||||
return (any((content.is_ready() for content in self.contents.values()))
|
||||
and self.accepted)
|
||||
and self.accepted)
|
||||
|
||||
def accept_session(self):
|
||||
"""
|
||||
|
@ -298,20 +303,20 @@ class JingleSession(object):
|
|||
pass
|
||||
|
||||
def send_content_accept(self, content):
|
||||
assert self.state != JingleStates.ended
|
||||
assert self.state != JingleStates.ENDED
|
||||
stanza, jingle = self.__make_jingle('content-accept')
|
||||
jingle.addChild(node=content)
|
||||
self.connection.connection.send(stanza)
|
||||
|
||||
def send_transport_info(self, content):
|
||||
assert self.state != JingleStates.ended
|
||||
assert self.state != JingleStates.ENDED
|
||||
stanza, jingle = self.__make_jingle('transport-info')
|
||||
jingle.addChild(node=content)
|
||||
self.connection.connection.send(stanza)
|
||||
self.collect_iq_id(stanza.getID())
|
||||
|
||||
def send_description_info(self, content):
|
||||
assert self.state != JingleStates.ended
|
||||
assert self.state != JingleStates.ENDED
|
||||
stanza, jingle = self.__make_jingle('description-info')
|
||||
jingle.addChild(node=content)
|
||||
self.connection.connection.send(stanza)
|
||||
|
@ -334,8 +339,8 @@ class JingleSession(object):
|
|||
self.__send_error(stanza, 'bad-request')
|
||||
return
|
||||
# FIXME: If we aren't initiated and it's not a session-initiate...
|
||||
if action not in ['session-initiate','session-terminate'] \
|
||||
and self.state == JingleStates.ended:
|
||||
if action not in ['session-initiate', 'session-terminate'] \
|
||||
and self.state == JingleStates.ENDED:
|
||||
self.__send_error(stanza, 'item-not-found', 'unknown-session')
|
||||
return
|
||||
else:
|
||||
|
@ -381,7 +386,7 @@ class JingleSession(object):
|
|||
transport = JingleTransportIBB()
|
||||
# For debug only, delete this and replace for a function
|
||||
# that will identify contents by its sid
|
||||
for creator, name in self.contents.keys():
|
||||
for creator, name in self.contents:
|
||||
self.modify_content(creator, name, transport)
|
||||
cont = self.contents[(creator, name)]
|
||||
cont.transport = transport
|
||||
|
@ -389,7 +394,7 @@ class JingleSession(object):
|
|||
self.__append_contents(jingle)
|
||||
self.__broadcast(stanza, jingle, None, 'transport-replace')
|
||||
self.connection.connection.send(stanza)
|
||||
self.state = JingleStates.pending
|
||||
self.state = JingleStates.PENDING
|
||||
|
||||
def __on_transport_replace(self, stanza, jingle, error, action):
|
||||
for content in jingle.iterTags('content'):
|
||||
|
@ -405,15 +410,15 @@ class JingleSession(object):
|
|||
elif transport_ns == nbxmpp.NS_JINGLE_IBB:
|
||||
transport = JingleTransportIBB()
|
||||
self.modify_content(creator, name, transport)
|
||||
self.state = JingleStates.pending
|
||||
self.contents[(creator,name)].state = STATE_TRANSPORT_REPLACE
|
||||
self.state = JingleStates.PENDING
|
||||
self.contents[(creator, name)].state = State.TRANSPORT_REPLACE
|
||||
self.__ack(stanza, jingle, error, action)
|
||||
self.__session_accept()
|
||||
self.contents[(creator,name)].start_IBB_transfer()
|
||||
self.contents[(creator, name)].start_IBB_transfer()
|
||||
else:
|
||||
stanza, jingle = self.__make_jingle('transport-reject')
|
||||
content = jingle.setTag('content', attrs={'creator': creator,
|
||||
'name': name})
|
||||
'name': name})
|
||||
content.setTag('transport', namespace=transport_ns)
|
||||
self.connection.connection.send(stanza)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
@ -421,9 +426,9 @@ class JingleSession(object):
|
|||
# FIXME: This ressource is unknown to us, what should we do?
|
||||
# For now, reject the transport
|
||||
stanza, jingle = self.__make_jingle('transport-reject')
|
||||
c = jingle.setTag('content', attrs={'creator': creator,
|
||||
'name': name})
|
||||
c.setTag('transport', namespace=transport_ns)
|
||||
content = jingle.setTag('content', attrs={'creator': creator,
|
||||
'name': name})
|
||||
content.setTag('transport', namespace=transport_ns)
|
||||
self.connection.connection.send(stanza)
|
||||
raise nbxmpp.NodeProcessed
|
||||
|
||||
|
@ -433,12 +438,12 @@ class JingleSession(object):
|
|||
if payload[0].getName() == 'ringing':
|
||||
# ignore ringing
|
||||
raise nbxmpp.NodeProcessed
|
||||
if self.state != JingleStates.active:
|
||||
if self.state != JingleStates.ACTIVE:
|
||||
raise OutOfOrder
|
||||
for p in payload:
|
||||
if p.getName() == 'checksum':
|
||||
hash_ = p.getTag('file').getTag(name='hash',
|
||||
namespace=nbxmpp.NS_HASHES)
|
||||
for child in payload:
|
||||
if child.getName() == 'checksum':
|
||||
hash_ = child.getTag('file').getTag(name='hash',
|
||||
namespace=nbxmpp.NS_HASHES)
|
||||
algo = hash_.getAttr('algo')
|
||||
if algo in nbxmpp.Hashes.supported:
|
||||
file_props = FilesProp.getFileProp(self.connection.name,
|
||||
|
@ -468,11 +473,12 @@ class JingleSession(object):
|
|||
|
||||
def __on_session_accept(self, stanza, jingle, error, action):
|
||||
# FIXME
|
||||
if self.state != JingleStates.pending:
|
||||
if self.state != JingleStates.PENDING:
|
||||
raise OutOfOrder
|
||||
self.state = JingleStates.active
|
||||
self.state = JingleStates.ACTIVE
|
||||
|
||||
def __on_content_accept(self, stanza, jingle, error, action):
|
||||
@staticmethod
|
||||
def __on_content_accept(stanza, jingle, error, action):
|
||||
"""
|
||||
Called when we get content-accept stanza or equivalent one (like
|
||||
session-accept)
|
||||
|
@ -484,7 +490,7 @@ class JingleSession(object):
|
|||
name = content['name']
|
||||
|
||||
def __on_content_add(self, stanza, jingle, error, action):
|
||||
if self.state == JingleStates.ended:
|
||||
if self.state == JingleStates.ENDED:
|
||||
raise OutOfOrder
|
||||
parse_result = self.__parse_contents(jingle)
|
||||
contents = parse_result[0]
|
||||
|
@ -496,14 +502,16 @@ class JingleSession(object):
|
|||
self.__content_reject(content)
|
||||
self.contents[(content.creator, content.name)].destroy()
|
||||
gajim.nec.push_incoming_event(JingleRequestReceivedEvent(None,
|
||||
conn=self.connection, jingle_session=self, contents=contents))
|
||||
conn=self.connection,
|
||||
jingle_session=self,
|
||||
contents=contents))
|
||||
|
||||
def __on_session_initiate(self, stanza, jingle, error, action):
|
||||
"""
|
||||
We got a jingle session request from other entity, therefore we are the
|
||||
receiver... Unpack the data, inform the user
|
||||
"""
|
||||
if self.state != JingleStates.ended:
|
||||
if self.state != JingleStates.ENDED:
|
||||
raise OutOfOrder
|
||||
self.initiator = jingle['initiator']
|
||||
self.responder = self.ourjid
|
||||
|
@ -533,18 +541,17 @@ class JingleSession(object):
|
|||
jingle.getTag('content').getTag('description').getTag('request')
|
||||
if request:
|
||||
self.request = True
|
||||
h = request.getTag('file').getTag('hash')
|
||||
h = h.getData() if h else None
|
||||
hash_tag = request.getTag('file').getTag('hash')
|
||||
hash_data = hash_tag.getData() if hash_tag else None
|
||||
n = request.getTag('file').getTag('name')
|
||||
n = n.getData() if n else None
|
||||
pjid = gajim.get_jid_without_resource(self.peerjid)
|
||||
file_info = self.connection.get_file_info(pjid, h, n,
|
||||
self.connection.name)
|
||||
file_info = self.connection.get_file_info(pjid, hash_data, n,
|
||||
self.connection.name)
|
||||
if not file_info:
|
||||
log.warning('The peer ' + pjid + \
|
||||
' is requesting a ' + \
|
||||
'file that we dont have or ' + \
|
||||
'it is not allowed to request')
|
||||
log.warning('The peer %s is requesting a ' \
|
||||
'file that we dont have or ' \
|
||||
'it is not allowed to request', pjid)
|
||||
self.decline_session()
|
||||
raise nbxmpp.NodeProcessed
|
||||
# If there's no content we understand...
|
||||
|
@ -555,10 +562,12 @@ class JingleSession(object):
|
|||
self.__ack(stanza, jingle, error, action)
|
||||
self._session_terminate(reason)
|
||||
raise nbxmpp.NodeProcessed
|
||||
self.state = JingleStates.pending
|
||||
self.state = JingleStates.PENDING
|
||||
# Send event about starting a session
|
||||
gajim.nec.push_incoming_event(JingleRequestReceivedEvent(None,
|
||||
conn=self.connection, jingle_session=self, contents=contents))
|
||||
conn=self.connection,
|
||||
jingle_session=self,
|
||||
contents=contents))
|
||||
|
||||
def __broadcast(self, stanza, jingle, error, action):
|
||||
"""
|
||||
|
@ -594,10 +603,12 @@ class JingleSession(object):
|
|||
else:
|
||||
# TODO
|
||||
text = reason
|
||||
if reason == 'cancel' and self.session_type_FT:
|
||||
if reason == 'cancel' and self.session_type_ft:
|
||||
gajim.nec.push_incoming_event(JingleTransferCancelledEvent(None,
|
||||
conn=self.connection, jingle_session=self, media=None,
|
||||
reason=text))
|
||||
conn=self.connection,
|
||||
jingle_session=self,
|
||||
media=None,
|
||||
reason=text))
|
||||
|
||||
def __broadcast_all(self, stanza, jingle, error, action):
|
||||
"""
|
||||
|
@ -621,7 +632,7 @@ class JingleSession(object):
|
|||
if transport:
|
||||
content = content_type(self, transport)
|
||||
self.add_content(element['name'],
|
||||
content, 'peer')
|
||||
content, 'peer')
|
||||
contents.append((content.media,))
|
||||
else:
|
||||
reasons.add('unsupported-transports')
|
||||
|
@ -634,7 +645,7 @@ class JingleSession(object):
|
|||
failure_reason = None
|
||||
# Store the first reason of failure
|
||||
for reason in ('failed-application', 'unsupported-transports',
|
||||
'unsupported-applications'):
|
||||
'unsupported-applications'):
|
||||
if reason in reasons:
|
||||
failure_reason = reason
|
||||
break
|
||||
|
@ -645,17 +656,21 @@ class JingleSession(object):
|
|||
text = '%s (%s)' % (error, text)
|
||||
if type_ != 'modify':
|
||||
gajim.nec.push_incoming_event(JingleErrorReceivedEvent(None,
|
||||
conn=self.connection, jingle_session=self,
|
||||
reason=text or error))
|
||||
conn=self.connection,
|
||||
jingle_session=self,
|
||||
reason=text or error))
|
||||
|
||||
def __reason_from_stanza(self, stanza):
|
||||
@staticmethod
|
||||
def __reason_from_stanza(stanza):
|
||||
# TODO: Move to GUI?
|
||||
reason = 'success'
|
||||
reasons = ['success', 'busy', 'cancel', 'connectivity-error',
|
||||
'decline', 'expired', 'failed-application', 'failed-transport',
|
||||
'general-error', 'gone', 'incompatible-parameters', 'media-error',
|
||||
'security-error', 'timeout', 'unsupported-applications',
|
||||
'unsupported-transports']
|
||||
reasons = [
|
||||
'success', 'busy', 'cancel', 'connectivity-error', 'decline',
|
||||
'expired', 'failed-application', 'failed-transport',
|
||||
'general-error', 'gone', 'incompatible-parameters', 'media-error',
|
||||
'security-error', 'timeout', 'unsupported-applications',
|
||||
'unsupported-transports'
|
||||
]
|
||||
tag = stanza.getTag('reason')
|
||||
text = ''
|
||||
if tag:
|
||||
|
@ -668,12 +683,14 @@ class JingleSession(object):
|
|||
|
||||
def __make_jingle(self, action, reason=None):
|
||||
stanza = nbxmpp.Iq(typ='set', to=nbxmpp.JID(self.peerjid),
|
||||
frm=self.ourjid)
|
||||
attrs = {'action': action,
|
||||
'sid': self.sid,
|
||||
'initiator' : self.initiator}
|
||||
frm=self.ourjid)
|
||||
attrs = {
|
||||
'action': action,
|
||||
'sid': self.sid,
|
||||
'initiator' : self.initiator
|
||||
}
|
||||
jingle = stanza.addChild('jingle', attrs=attrs,
|
||||
namespace=nbxmpp.NS_JINGLE)
|
||||
namespace=nbxmpp.NS_JINGLE)
|
||||
if reason is not None:
|
||||
jingle.addChild(node=reason)
|
||||
return stanza, jingle
|
||||
|
@ -690,13 +707,15 @@ class JingleSession(object):
|
|||
self.connection.connection.send(err_stanza)
|
||||
self.__dispatch_error(jingle_error or error, text, type_)
|
||||
|
||||
def __append_content(self, jingle, content):
|
||||
@staticmethod
|
||||
def __append_content(jingle, content):
|
||||
"""
|
||||
Append <content/> element to <jingle/> element, with (full=True) or
|
||||
without (full=False) <content/> children
|
||||
"""
|
||||
jingle.addChild('content',
|
||||
attrs={'name': content.name, 'creator': content.creator})
|
||||
attrs={'name': content.name,
|
||||
'creator': content.creator})
|
||||
|
||||
def __append_contents(self, jingle):
|
||||
"""
|
||||
|
@ -709,36 +728,36 @@ class JingleSession(object):
|
|||
self.__append_content(jingle, content)
|
||||
|
||||
def __session_initiate(self):
|
||||
assert self.state == JingleStates.ended
|
||||
assert self.state == JingleStates.ENDED
|
||||
stanza, jingle = self.__make_jingle('session-initiate')
|
||||
self.__append_contents(jingle)
|
||||
self.__broadcast(stanza, jingle, None, 'session-initiate-sent')
|
||||
self.connection.connection.send(stanza)
|
||||
self.collect_iq_id(stanza.getID())
|
||||
self.state = JingleStates.pending
|
||||
self.state = JingleStates.PENDING
|
||||
|
||||
def __session_accept(self):
|
||||
assert self.state == JingleStates.pending
|
||||
assert self.state == JingleStates.PENDING
|
||||
stanza, jingle = self.__make_jingle('session-accept')
|
||||
self.__append_contents(jingle)
|
||||
self.__broadcast(stanza, jingle, None, 'session-accept-sent')
|
||||
self.connection.connection.send(stanza)
|
||||
self.collect_iq_id(stanza.getID())
|
||||
self.state = JingleStates.active
|
||||
self.state = JingleStates.ACTIVE
|
||||
|
||||
def __session_info(self, payload=None):
|
||||
assert self.state != JingleStates.ended
|
||||
assert self.state != JingleStates.ENDED
|
||||
stanza, jingle = self.__make_jingle('session-info')
|
||||
if payload:
|
||||
jingle.addChild(node=payload)
|
||||
self.connection.connection.send(stanza)
|
||||
|
||||
def _JingleFileTransfer__session_info(self, p):
|
||||
def _JingleFileTransfer__session_info(self, payload):
|
||||
# For some strange reason when I call
|
||||
# self.session.__session_info(h) from the jingleFileTransfer object
|
||||
# self.session.__session_info(payload) from the jingleFileTransfer object
|
||||
# within a thread, this method gets called instead. Even though, it
|
||||
# isn't being called explicitly.
|
||||
self.__session_info(p)
|
||||
self.__session_info(payload)
|
||||
|
||||
def _session_terminate(self, reason=None):
|
||||
stanza, jingle = self.__make_jingle('session-terminate', reason=reason)
|
||||
|
@ -755,12 +774,14 @@ class JingleSession(object):
|
|||
text = reason
|
||||
self.connection.delete_jingle_session(self.sid)
|
||||
gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
|
||||
conn=self.connection, jingle_session=self, media=None,
|
||||
reason=text))
|
||||
conn=self.connection,
|
||||
jingle_session=self,
|
||||
media=None,
|
||||
reason=text))
|
||||
|
||||
def __content_add(self, content):
|
||||
# TODO: test
|
||||
assert self.state != JingleStates.ended
|
||||
assert self.state != JingleStates.ENDED
|
||||
stanza, jingle = self.__make_jingle('content-add')
|
||||
self.__append_content(jingle, content)
|
||||
self.__broadcast(stanza, jingle, None, 'content-add-sent')
|
||||
|
@ -769,7 +790,7 @@ class JingleSession(object):
|
|||
|
||||
def __content_accept(self, content):
|
||||
# TODO: test
|
||||
assert self.state != JingleStates.ended
|
||||
assert self.state != JingleStates.ENDED
|
||||
stanza, jingle = self.__make_jingle('content-accept')
|
||||
self.__append_content(jingle, content)
|
||||
self.__broadcast(stanza, jingle, None, 'content-accept-sent')
|
||||
|
@ -777,31 +798,35 @@ class JingleSession(object):
|
|||
self.collect_iq_id(id_)
|
||||
|
||||
def __content_reject(self, content):
|
||||
assert self.state != JingleStates.ended
|
||||
assert self.state != JingleStates.ENDED
|
||||
stanza, jingle = self.__make_jingle('content-reject')
|
||||
self.__append_content(jingle, content)
|
||||
self.connection.connection.send(stanza)
|
||||
# TODO: this will fail if content is not an RTP content
|
||||
gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
|
||||
conn=self.connection, jingle_session=self, media=content.media,
|
||||
reason='rejected'))
|
||||
conn=self.connection,
|
||||
jingle_session=self,
|
||||
media=content.media,
|
||||
reason='rejected'))
|
||||
|
||||
def __content_modify(self):
|
||||
assert self.state != JingleStates.ended
|
||||
assert self.state != JingleStates.ENDED
|
||||
|
||||
def __content_remove(self, content, reason=None):
|
||||
assert self.state != JingleStates.ended
|
||||
assert self.state != JingleStates.ENDED
|
||||
if self.connection.connection and self.connection.connected > 1:
|
||||
stanza, jingle = self.__make_jingle('content-remove', reason=reason)
|
||||
self.__append_content(jingle, content)
|
||||
self.connection.connection.send(stanza)
|
||||
# TODO: this will fail if content is not an RTP content
|
||||
gajim.nec.push_incoming_event(JingleDisconnectedReceivedEvent(None,
|
||||
conn=self.connection, jingle_session=self, media=content.media,
|
||||
reason='removed'))
|
||||
conn=self.connection,
|
||||
jingle_session=self,
|
||||
media=content.media,
|
||||
reason='removed'))
|
||||
|
||||
def content_negotiated(self, media):
|
||||
gajim.nec.push_incoming_event(JingleConnectedReceivedEvent(None,
|
||||
conn=self.connection, jingle_session=self, media=media))
|
||||
|
||||
|
||||
conn=self.connection,
|
||||
jingle_session=self,
|
||||
media=media))
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
Handles Jingle Transports (currently only ICE-UDP)
|
||||
"""
|
||||
|
||||
import nbxmpp
|
||||
import socket
|
||||
from common import gajim
|
||||
import logging
|
||||
import socket
|
||||
from enum import IntEnum
|
||||
import nbxmpp
|
||||
from common import gajim
|
||||
|
||||
log = logging.getLogger('gajim.c.jingle_transport')
|
||||
|
||||
|
@ -43,16 +43,24 @@ class TransportType(IntEnum):
|
|||
IBB = 3
|
||||
|
||||
|
||||
class JingleTransport(object):
|
||||
class JingleTransport:
|
||||
"""
|
||||
An abstraction of a transport in Jingle sessions
|
||||
"""
|
||||
|
||||
__slots__ = ['type_', 'candidates', 'remote_candidates', 'connection',
|
||||
'file_props', 'ourjid', 'sid']
|
||||
|
||||
def __init__(self, type_):
|
||||
self.type_ = type_
|
||||
self.candidates = []
|
||||
self.remote_candidates = []
|
||||
|
||||
self.connection = None
|
||||
self.file_props = None
|
||||
self.ourjid = None
|
||||
self.sid = None
|
||||
|
||||
def _iter_candidates(self):
|
||||
for candidate in self.candidates:
|
||||
yield self.make_candidate(candidate)
|
||||
|
@ -110,9 +118,7 @@ class JingleTransportSocks5(JingleTransport):
|
|||
|
||||
|
||||
def make_candidate(self, candidate):
|
||||
import logging
|
||||
log = logging.getLogger()
|
||||
log.info('candidate dict, %s' % candidate)
|
||||
log.info('candidate dict, %s', candidate)
|
||||
attrs = {
|
||||
'cid': candidate['candidate_id'],
|
||||
'host': candidate['host'],
|
||||
|
@ -124,8 +130,8 @@ class JingleTransportSocks5(JingleTransport):
|
|||
|
||||
return nbxmpp.Node('candidate', attrs=attrs)
|
||||
|
||||
def make_transport(self, candidates=None, add_candidates = True):
|
||||
if add_candidates:
|
||||
def make_transport(self, candidates=None, add_candidates=True):
|
||||
if add_candidates:
|
||||
self._add_local_ips_as_candidates()
|
||||
self._add_additional_candidates()
|
||||
self._add_proxy_candidates()
|
||||
|
@ -174,7 +180,7 @@ class JingleTransportSocks5(JingleTransport):
|
|||
|
||||
def _add_local_ips_as_candidates(self):
|
||||
if not gajim.config.get_per('accounts', self.connection.name,
|
||||
'ft_send_local_ips'):
|
||||
'ft_send_local_ips'):
|
||||
return
|
||||
if not self.connection:
|
||||
return
|
||||
|
@ -186,30 +192,34 @@ class JingleTransportSocks5(JingleTransport):
|
|||
hosts = set()
|
||||
local_ip_cand = []
|
||||
|
||||
c = {'host': self.connection.peerhost[0],
|
||||
'candidate_id': self.connection.connection.getAnID(),
|
||||
'port': port,
|
||||
'type': 'direct',
|
||||
'jid': self.ourjid,
|
||||
'priority': priority}
|
||||
candidate = {
|
||||
'host': self.connection.peerhost[0],
|
||||
'candidate_id': self.connection.connection.getAnID(),
|
||||
'port': port,
|
||||
'type': 'direct',
|
||||
'jid': self.ourjid,
|
||||
'priority': priority
|
||||
}
|
||||
hosts.add(self.connection.peerhost[0])
|
||||
local_ip_cand.append(c)
|
||||
local_ip_cand.append(candidate)
|
||||
|
||||
try:
|
||||
for addrinfo in socket.getaddrinfo(socket.gethostname(), None):
|
||||
addr = addrinfo[4][0]
|
||||
if not addr in hosts and not addr.startswith('127.') and \
|
||||
addr != '::1':
|
||||
c = {'host': addr,
|
||||
'candidate_id': self.connection.connection.getAnID(),
|
||||
'port': port,
|
||||
'type': 'direct',
|
||||
'jid': self.ourjid,
|
||||
'priority': priority,
|
||||
'initiator': self.file_props.sender,
|
||||
'target': self.file_props.receiver}
|
||||
candidate = {
|
||||
'host': addr,
|
||||
'candidate_id': self.connection.connection.getAnID(),
|
||||
'port': port,
|
||||
'type': 'direct',
|
||||
'jid': self.ourjid,
|
||||
'priority': priority,
|
||||
'initiator': self.file_props.sender,
|
||||
'target': self.file_props.receiver
|
||||
}
|
||||
hosts.add(addr)
|
||||
local_ip_cand.append(c)
|
||||
local_ip_cand.append(candidate)
|
||||
except socket.gaierror:
|
||||
pass # ignore address-related errors for getaddrinfo
|
||||
|
||||
|
@ -226,16 +236,18 @@ class JingleTransportSocks5(JingleTransport):
|
|||
|
||||
if ft_add_hosts:
|
||||
hosts = [e.strip() for e in ft_add_hosts.split(',')]
|
||||
for h in hosts:
|
||||
c = {'host': h,
|
||||
'candidate_id': self.connection.connection.getAnID(),
|
||||
'port': port,
|
||||
'type': 'direct',
|
||||
'jid': self.ourjid,
|
||||
'priority': priority,
|
||||
'initiator': self.file_props.sender,
|
||||
'target': self.file_props.receiver}
|
||||
additional_ip_cand.append(c)
|
||||
for host in hosts:
|
||||
candidate = {
|
||||
'host': host,
|
||||
'candidate_id': self.connection.connection.getAnID(),
|
||||
'port': port,
|
||||
'type': 'direct',
|
||||
'jid': self.ourjid,
|
||||
'priority': priority,
|
||||
'initiator': self.file_props.sender,
|
||||
'target': self.file_props.receiver
|
||||
}
|
||||
additional_ip_cand.append(candidate)
|
||||
|
||||
self._add_candidates(additional_ip_cand)
|
||||
|
||||
|
@ -252,21 +264,23 @@ class JingleTransportSocks5(JingleTransport):
|
|||
self.file_props.proxyhosts = proxyhosts
|
||||
|
||||
for proxyhost in proxyhosts:
|
||||
c = {'host': proxyhost['host'],
|
||||
'candidate_id': self.connection.connection.getAnID(),
|
||||
'port': int(proxyhost['port']),
|
||||
'type': 'proxy',
|
||||
'jid': proxyhost['jid'],
|
||||
'priority': priority,
|
||||
'initiator': self.file_props.sender,
|
||||
'target': self.file_props.receiver}
|
||||
proxy_cand.append(c)
|
||||
candidate = {
|
||||
'host': proxyhost['host'],
|
||||
'candidate_id': self.connection.connection.getAnID(),
|
||||
'port': int(proxyhost['port']),
|
||||
'type': 'proxy',
|
||||
'jid': proxyhost['jid'],
|
||||
'priority': priority,
|
||||
'initiator': self.file_props.sender,
|
||||
'target': self.file_props.receiver
|
||||
}
|
||||
proxy_cand.append(candidate)
|
||||
|
||||
self._add_candidates(proxy_cand)
|
||||
|
||||
def get_content(self):
|
||||
sesn = self.connection.get_jingle_session(self.ourjid,
|
||||
self.file_props.sid)
|
||||
self.file_props.sid)
|
||||
for content in sesn.contents.values():
|
||||
if content.transport == self:
|
||||
return content
|
||||
|
@ -277,7 +291,7 @@ class JingleTransportSocks5(JingleTransport):
|
|||
if not self.connection:
|
||||
return
|
||||
sesn = self.connection.get_jingle_session(self.ourjid,
|
||||
self.file_props.sid)
|
||||
self.file_props.sid)
|
||||
if sesn is None:
|
||||
return
|
||||
|
||||
|
@ -294,8 +308,8 @@ class JingleTransportSocks5(JingleTransport):
|
|||
|
||||
content = nbxmpp.Node('content')
|
||||
content.setAttr('creator', 'initiator')
|
||||
c = self.get_content()
|
||||
content.setAttr('name', c.name)
|
||||
content_object = self.get_content()
|
||||
content.setAttr('name', content_object.name)
|
||||
transport = nbxmpp.Node('transport')
|
||||
transport.setNamespace(nbxmpp.NS_JINGLE_BYTESTREAM)
|
||||
transport.setAttr('sid', proxy['sid'])
|
||||
|
@ -307,7 +321,7 @@ class JingleTransportSocks5(JingleTransport):
|
|||
else:
|
||||
for host in self.candidates:
|
||||
if host['host'] == proxy['host'] and host['jid'] == proxy['jid'] \
|
||||
and host['port'] == proxy['port']:
|
||||
and host['port'] == proxy['port']:
|
||||
cid = host['candidate_id']
|
||||
break
|
||||
if cid is None:
|
||||
|
@ -345,7 +359,7 @@ class JingleTransportIBB(JingleTransport):
|
|||
|
||||
try:
|
||||
from gi.repository import Farstream
|
||||
except Exception:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class JingleTransportICEUDP(JingleTransport):
|
||||
|
@ -353,11 +367,13 @@ class JingleTransportICEUDP(JingleTransport):
|
|||
JingleTransport.__init__(self, TransportType.ICEUDP)
|
||||
|
||||
def make_candidate(self, candidate):
|
||||
types = {Farstream.CandidateType.HOST: 'host',
|
||||
types = {
|
||||
Farstream.CandidateType.HOST: 'host',
|
||||
Farstream.CandidateType.SRFLX: 'srflx',
|
||||
Farstream.CandidateType.PRFLX: 'prflx',
|
||||
Farstream.CandidateType.RELAY: 'relay',
|
||||
Farstream.CandidateType.MULTICAST: 'multicast'}
|
||||
Farstream.CandidateType.MULTICAST: 'multicast'
|
||||
}
|
||||
attrs = {
|
||||
'component': candidate.component_id,
|
||||
'foundation': '1', # hack
|
||||
|
@ -402,23 +418,26 @@ class JingleTransportICEUDP(JingleTransport):
|
|||
# jingle
|
||||
proto = Farstream.NetworkProtocol.TCP
|
||||
priority = int(candidate['priority'])
|
||||
types = {'host': Farstream.CandidateType.HOST,
|
||||
types = {
|
||||
'host': Farstream.CandidateType.HOST,
|
||||
'srflx': Farstream.CandidateType.SRFLX,
|
||||
'prflx': Farstream.CandidateType.PRFLX,
|
||||
'relay': Farstream.CandidateType.RELAY,
|
||||
'multicast': Farstream.CandidateType.MULTICAST}
|
||||
'multicast': Farstream.CandidateType.MULTICAST
|
||||
}
|
||||
if 'type' in candidate and candidate['type'] in types:
|
||||
type_ = types[candidate['type']]
|
||||
else:
|
||||
log.warning('Unknown type %s' % candidate['type'])
|
||||
log.warning('Unknown type %s', candidate['type'])
|
||||
type_ = Farstream.CandidateType.HOST
|
||||
username = str(transport['ufrag'])
|
||||
password = str(transport['pwd'])
|
||||
ttl = 0
|
||||
|
||||
cand = Farstream.Candidate.new_full(foundation, component_id, ip,
|
||||
port, base_ip, base_port, proto, priority, type_, username,
|
||||
password, ttl)
|
||||
port, base_ip, base_port,
|
||||
proto, priority, type_,
|
||||
username, password, ttl)
|
||||
|
||||
candidates.append(cand)
|
||||
self.remote_candidates.extend(candidates)
|
||||
|
|
|
@ -16,13 +16,15 @@
|
|||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import os
|
||||
import nbxmpp
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
import nbxmpp
|
||||
from common import gajim
|
||||
|
||||
log = logging.getLogger('gajim.c.jingle_xtls')
|
||||
|
||||
|
||||
PYOPENSSL_PRESENT = False
|
||||
|
||||
# key-exchange id -> [callback, args], accept that session once key-exchange completes
|
||||
|
@ -53,7 +55,7 @@ DH_PARAMS = 'dh_params.pem'
|
|||
DEFAULT_DH_PARAMS = 'dh4096.pem'
|
||||
|
||||
def default_callback(connection, certificate, error_num, depth, return_code):
|
||||
log.info("certificate: %s" % certificate)
|
||||
log.info("certificate: %s", certificate)
|
||||
return return_code
|
||||
|
||||
def load_cert_file(cert_path, cert_store=None):
|
||||
|
@ -65,8 +67,8 @@ def load_cert_file(cert_path, cert_store=None):
|
|||
try:
|
||||
f = open(cert_path)
|
||||
except IOError as e:
|
||||
log.warning('Unable to open certificate file %s: %s' % (cert_path,
|
||||
str(e)))
|
||||
log.warning('Unable to open certificate file %s: %s', cert_path,
|
||||
str(e))
|
||||
return None
|
||||
lines = f.readlines()
|
||||
i = 0
|
||||
|
@ -84,11 +86,11 @@ def load_cert_file(cert_path, cert_store=None):
|
|||
f.close()
|
||||
return x509cert
|
||||
except OpenSSL.crypto.Error as exception_obj:
|
||||
log.warning('Unable to load a certificate from file %s: %s' %\
|
||||
(cert_path, exception_obj.args[0][0][2]))
|
||||
log.warning('Unable to load a certificate from file %s: %s',
|
||||
cert_path, exception_obj.args[0][0][2])
|
||||
except:
|
||||
log.warning('Unknown error while loading certificate from file '
|
||||
'%s' % cert_path)
|
||||
'%s', cert_path)
|
||||
begin = -1
|
||||
i += 1
|
||||
f.close()
|
||||
|
@ -105,7 +107,7 @@ def get_context(fingerprint, verify_cb=None, remote_jid=None):
|
|||
|
||||
if fingerprint == 'server': # for testing purposes only
|
||||
ctx.set_verify(SSL.VERIFY_NONE|SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||
verify_cb or default_callback)
|
||||
verify_cb or default_callback)
|
||||
elif fingerprint == 'client':
|
||||
ctx.set_verify(SSL.VERIFY_PEER, verify_cb or default_callback)
|
||||
|
||||
|
@ -121,23 +123,23 @@ def get_context(fingerprint, verify_cb=None, remote_jid=None):
|
|||
ctx.load_tmp_dh(dh_params_name.encode('utf-8'))
|
||||
except FileNotFoundError as err:
|
||||
default_dh_params_name = os.path.join(gajim.DATA_DIR,
|
||||
'other', DEFAULT_DH_PARAMS)
|
||||
'other', DEFAULT_DH_PARAMS)
|
||||
try:
|
||||
with open(default_dh_params_name, "r") as default_dh_params_file:
|
||||
ctx.load_tmp_dh(default_dh_params_name.encode('utf-8'))
|
||||
except FileNotFoundError as err:
|
||||
log.error('Unable to load default DH parameter file: %s , %s'
|
||||
% (default_dh_params_name, err))
|
||||
log.error('Unable to load default DH parameter file: %s, %s',
|
||||
default_dh_params_name, err)
|
||||
raise
|
||||
|
||||
if remote_jid:
|
||||
store = ctx.get_cert_store()
|
||||
path = os.path.join(os.path.expanduser(gajim.MY_PEER_CERTS_PATH),
|
||||
remote_jid) + '.cert'
|
||||
remote_jid) + '.cert'
|
||||
if os.path.exists(path):
|
||||
load_cert_file(path, cert_store=store)
|
||||
log.debug('certificate file ' + path + ' loaded fingerprint ' + \
|
||||
fingerprint)
|
||||
log.debug('certificate file %s loaded fingerprint %s',
|
||||
path, fingerprint)
|
||||
return ctx
|
||||
|
||||
def read_cert(certpath):
|
||||
|
@ -212,16 +214,16 @@ def send_cert_request(con, to_jid):
|
|||
|
||||
# the following code is partly due to pyopenssl examples
|
||||
|
||||
def createKeyPair(type, bits):
|
||||
def createKeyPair(type_, bits):
|
||||
"""
|
||||
Create a public/private key pair.
|
||||
|
||||
Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA
|
||||
Arguments: type_ - Key type, must be one of TYPE_RSA and TYPE_DSA
|
||||
bits - Number of bits to use in the key
|
||||
Returns: The public/private key pair in a PKey object
|
||||
"""
|
||||
pkey = crypto.PKey()
|
||||
pkey.generate_key(type, bits)
|
||||
pkey.generate_key(type_, bits)
|
||||
return pkey
|
||||
|
||||
def createCertRequest(pkey, digest="sha256", **name):
|
||||
|
@ -244,7 +246,7 @@ def createCertRequest(pkey, digest="sha256", **name):
|
|||
req = crypto.X509Req()
|
||||
subj = req.get_subject()
|
||||
|
||||
for (key,value) in name.items():
|
||||
for (key, value) in name.items():
|
||||
setattr(subj, key, value)
|
||||
|
||||
req.set_pubkey(pkey)
|
||||
|
|
Loading…
Reference in New Issue