diff --git a/src/common/jingle_content.py b/src/common/jingle_content.py index e2a5c9a28..586a86acb 100644 --- a/src/common/jingle_content.py +++ b/src/common/jingle_content.py @@ -16,6 +16,7 @@ Handles Jingle contents (XEP 0166) """ import xmpp +from jingle_transport import JingleTransportIBB contents = {} @@ -69,7 +70,7 @@ class JingleContent(object): 'session-initiate': [self.__on_transport_info], 'session-terminate': [], 'transport-info': [self.__on_transport_info], - 'transport-replace': [], + 'transport-replace': [self.__on_transport_replace], 'transport-accept': [], 'transport-reject': [], 'iq-result': [], @@ -109,6 +110,10 @@ class JingleContent(object): for callback in self.callbacks[action]: callback(stanza, content, error, action) + def __on_transport_replace(self, stanza, content, error, action): + + content.addChild(node=self.transport.make_transport()) + def __on_transport_info(self, stanza, content, error, action): """ Got a new transport candidate diff --git a/src/common/jingle_ft.py b/src/common/jingle_ft.py index e385202c6..bbb1a39f7 100644 --- a/src/common/jingle_ft.py +++ b/src/common/jingle_ft.py @@ -23,6 +23,8 @@ import gajim import xmpp from jingle_content import contents, JingleContent from jingle_transport import JingleTransportICEUDP, JingleTransportSocks5 +from jingle_transport import JingleTransportIBB +from jingle_session import JingleStates from common import helpers from common.socks5 import Socks5Receiver from common.connection_handlers_events import FileRequestReceivedEvent @@ -35,6 +37,7 @@ STATE_INITIALIZED = 1 STATE_ACCEPTED = 2 STATE_TRANSPORT_INFO = 3 STATE_PROXY_ACTIVATED = 4 +STATE_TRANSPORT_REPLACE = 5 class JingleFileTransfer(JingleContent): def __init__(self, session, transport=None, file_props=None, @@ -98,6 +101,19 @@ class JingleFileTransfer(JingleContent): security = content.getTag('security') if not security: # responder can not verify our fingerprint self.use_security = False + + if self.state == STATE_TRANSPORT_REPLACE: + # We ack the session accept + response = stanza.buildReply('result') + con = self.session.connection + con.connection.send(response) + # We send the file + con.files_props[self.file_props['sid']] = self.file_props + fp = open(self.file_props['file-name'], 'r') + con.OpenStream( self.transport.sid, self.session.peerjid, + fp, blocksize=4096) + raise xmpp.NodeProcessed + def __on_session_terminate(self, stanza, content, error, action): log.info("__on_session_terminate") @@ -197,7 +213,7 @@ class JingleFileTransfer(JingleContent): elif self.weinitiate and self.state == STATE_INITIALIZED: # proxy activated self.state = STATE_PROXY_ACTIVATED - + def send_candidate_used(self, streamhost): """ send candidate-used stanza diff --git a/src/common/jingle_session.py b/src/common/jingle_session.py index bddb17b1e..be435d025 100644 --- a/src/common/jingle_session.py +++ b/src/common/jingle_session.py @@ -28,8 +28,10 @@ Handles Jingle sessions (XEP 0166) import gajim #Get rid of that? import xmpp -from jingle_transport import get_jingle_transport +from jingle_transport import get_jingle_transport, JingleTransportIBB from jingle_content import get_jingle_content, JingleContentSetupException +from jingle_content import JingleContent +from jingle_ft import STATE_TRANSPORT_REPLACE from common.connection_handlers_events import * import logging log = logging.getLogger("gajim.c.jingle_session") @@ -213,11 +215,19 @@ class JingleSession(object): if not self.contents: self.end_session() - def modify_content(self, creator, name, *someother): - """ - We do not need this now - """ - pass + def modify_content(self, creator, name, transport = None): + ''' + Currently used for transport replacement + ''' + + content = self.contents[(creator,name)] + transport.set_sid(content.transport.sid) + transport.set_file_props(content.transport.file_props) + content.transport = transport + # The content will have to be resend now that it is modified + content.sent = False + content.accepted = True + def on_session_state_changed(self, content=None): if self.state == JingleStates.ended: @@ -343,18 +353,43 @@ class JingleSession(object): error_name = child.getName() self.__dispatch_error(error_name, text, error.getAttr('type')) # FIXME: Not sure when we would want to do that... + def transport_replace(self): + 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(): + self.modify_content(creator, name, transport) + cont = self.contents[(creator, name)] + cont.transport = transport + cont.state = STATE_TRANSPORT_REPLACE + + stanza, jingle = self.__make_jingle('transport-replace') + self.__append_contents(jingle) + self.__broadcast(stanza, jingle, None, 'transport-replace') + self.connection.connection.send(stanza) + #self.collect_iq_id(stanza.getID()) + def __on_transport_replace(self, stanza, jingle, error, action): for content in jingle.iterTags('content'): creator = content['creator'] name = content['name'] if (creator, name) in self.contents: transport_ns = content.getTag('transport').getNamespace() - if transport_ns == xmpp.JINGLE_ICE_UDP: + if transport_ns == xmpp.NS_JINGLE_ICE_UDP: # FIXME: We don't manage anything else than ICE-UDP now... # What was the previous transport?!? # Anyway, content's transport is not modifiable yet pass + elif transport_ns == xmpp.NS_JINGLE_IBB: + + transport = JingleTransportIBB() + self.modify_content(creator, name, transport) + #self.state = JingleStates.pending + self.contents[(creator,name)].state = STATE_TRANSPORT_REPLACE + self.__ack(stanza, jingle, error, action) + self.__session_accept() + else: stanza, jingle = self.__make_jingle('transport-reject') content = jingle.setTag('content', attrs={'creator': creator, @@ -400,6 +435,7 @@ class JingleSession(object): if self.state != JingleStates.pending: raise OutOfOrder self.state = JingleStates.active + def __on_content_accept(self, stanza, jingle, error, action): """ @@ -562,12 +598,19 @@ class JingleSession(object): return (contents, contents_rejected, failure_reason) def __dispatch_error(self, error=None, text=None, type_=None): + + if type_ == 'cancel' and error == 'item-not-found': + # We coudln't connect with sock5stream, we fallback to IBB + self.transport_replace() + return if text: 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)) + + def __reason_from_stanza(self, stanza): # TODO: Move to GUI? @@ -595,6 +638,8 @@ class JingleSession(object): attrs['initiator'] = self.initiator elif action == 'session-accept': attrs['responder'] = self.responder + elif action == 'transport-replace': + attrs['initiator'] = self.initiator jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE) if reason is not None: jingle.addChild(node=reason) diff --git a/src/common/jingle_transport.py b/src/common/jingle_transport.py index ff47a7c23..3c582b773 100644 --- a/src/common/jingle_transport.py +++ b/src/common/jingle_transport.py @@ -165,7 +165,7 @@ class JingleTransportSocks5(JingleTransport): for addr in socket.getaddrinfo(socket.gethostname(), None): if not addr[4][0] in local_ip_cand and not addr[4][0].startswith('127'): c = {'host': addr[4][0]} - c['candidate_id'] = conn.connection.getAnID() + c['candidate_id'] = self.connection.connection.getAnID() c['port'] = port c['type'] = 'direct' c['jid'] = self.ourjid @@ -272,6 +272,39 @@ class JingleTransportSocks5(JingleTransport): return sesn.send_transport_info(content) + +class JingleTransportIBB(JingleTransport): + + def __init__(self, node=None, block_sz=None): + + JingleTransport.__init__(self, TransportType.streaming) + + if block_sz: + self.block_sz = block_sz + else: + self.block_sz = '4096' + + self.connection = None + self.sid = None + if node and node.getAttr('sid'): + self.sid = node.getAttr('sid') + + + def set_sid(self, sid): + self.sid = sid + + def make_transport(self): + + transport = JingleTransport.make_transport(self) + transport.setNamespace(xmpp.NS_JINGLE_IBB) + transport.setAttr('block-size', self.block_sz) + transport.setAttr('sid', self.sid) + return transport + + def set_file_props(self, file_props): + self.file_props = file_props + + import farsight class JingleTransportICEUDP(JingleTransport): @@ -347,3 +380,4 @@ class JingleTransportICEUDP(JingleTransport): transports[xmpp.NS_JINGLE_ICE_UDP] = JingleTransportICEUDP transports[xmpp.NS_JINGLE_BYTESTREAM] = JingleTransportSocks5 +transports[xmpp.NS_JINGLE_IBB] = JingleTransportIBB diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index df4b57a82..f32863674 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -91,6 +91,7 @@ NS_JINGLE_XTLS='urn:xmpp:jingle:security:xtls:0' # XTLS: EX NS_JINGLE_RAW_UDP = 'urn:xmpp:jingle:transports:raw-udp:1' # XEP-0177 NS_JINGLE_ICE_UDP = 'urn:xmpp:jingle:transports:ice-udp:1' # XEP-0176 NS_JINGLE_BYTESTREAM ='urn:xmpp:jingle:transports:s5b:1' # XEP-0260 +NS_JINGLE_IBB = 'urn:xmpp:jingle:transports:ibb:1' # XEP-0261 NS_LAST = 'jabber:iq:last' NS_LOCATION = 'http://jabber.org/protocol/geoloc' # XEP-0080 NS_MESSAGE = 'message' # Jabberd2