2014-04-08 21:15:55 +00:00
package eu.siacs.conversations.xmpp.jingle ;
2014-04-07 18:05:45 +00:00
2017-05-03 09:03:04 +00:00
import android.util.Base64 ;
2015-07-20 12:26:29 +00:00
import android.util.Log ;
2020-04-01 16:35:36 +00:00
import com.google.common.base.Preconditions ;
2020-04-02 08:59:25 +00:00
import com.google.common.collect.Collections2 ;
2020-06-07 10:47:03 +00:00
import com.google.common.collect.FluentIterable ;
import com.google.common.collect.Iterables ;
2020-04-01 16:35:36 +00:00
2020-04-01 08:45:03 +00:00
import java.io.File ;
2018-10-03 16:14:41 +00:00
import java.io.FileInputStream ;
2015-08-11 14:50:00 +00:00
import java.io.FileNotFoundException ;
2017-10-26 16:42:10 +00:00
import java.io.IOException ;
2015-07-31 23:19:16 +00:00
import java.io.InputStream ;
import java.io.OutputStream ;
2014-04-07 18:05:45 +00:00
import java.util.ArrayList ;
2017-05-03 09:03:04 +00:00
import java.util.Arrays ;
2020-04-02 08:59:25 +00:00
import java.util.Collection ;
2019-06-16 20:57:10 +00:00
import java.util.Collections ;
2014-04-13 09:32:45 +00:00
import java.util.Iterator ;
2014-04-07 18:05:45 +00:00
import java.util.List ;
2014-04-13 09:32:45 +00:00
import java.util.Map.Entry ;
2014-08-19 13:06:50 +00:00
import java.util.concurrent.ConcurrentHashMap ;
2014-04-07 18:05:45 +00:00
2014-08-31 14:28:21 +00:00
import eu.siacs.conversations.Config ;
2015-07-31 23:19:16 +00:00
import eu.siacs.conversations.crypto.axolotl.AxolotlService ;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage ;
2014-04-07 18:05:45 +00:00
import eu.siacs.conversations.entities.Account ;
2014-04-13 09:32:45 +00:00
import eu.siacs.conversations.entities.Conversation ;
2014-10-14 10:02:48 +00:00
import eu.siacs.conversations.entities.DownloadableFile ;
2014-04-07 18:05:45 +00:00
import eu.siacs.conversations.entities.Message ;
2016-06-29 15:16:34 +00:00
import eu.siacs.conversations.entities.Presence ;
2016-07-04 17:29:46 +00:00
import eu.siacs.conversations.entities.ServiceDiscoveryResult ;
2015-07-20 12:26:29 +00:00
import eu.siacs.conversations.entities.Transferable ;
import eu.siacs.conversations.entities.TransferablePlaceholder ;
2016-10-26 10:26:04 +00:00
import eu.siacs.conversations.parser.IqParser ;
2015-07-31 23:19:16 +00:00
import eu.siacs.conversations.persistance.FileBackend ;
import eu.siacs.conversations.services.AbstractConnectionManager ;
2017-05-03 09:03:04 +00:00
import eu.siacs.conversations.utils.CryptoHelper ;
2014-04-07 18:05:45 +00:00
import eu.siacs.conversations.xml.Element ;
2019-06-16 20:57:10 +00:00
import eu.siacs.conversations.xml.Namespace ;
2014-04-08 21:15:55 +00:00
import eu.siacs.conversations.xmpp.OnIqPacketReceived ;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content ;
2020-04-01 16:35:36 +00:00
import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription ;
2020-04-02 08:59:25 +00:00
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo ;
import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo ;
2014-04-08 21:15:55 +00:00
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket ;
2014-04-13 09:32:45 +00:00
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason ;
2020-04-02 08:59:25 +00:00
import eu.siacs.conversations.xmpp.jingle.stanzas.S5BTransportInfo ;
2014-04-08 21:15:55 +00:00
import eu.siacs.conversations.xmpp.stanzas.IqPacket ;
2020-05-15 15:06:16 +00:00
import eu.siacs.conversations.xmpp.Jid ;
2014-04-07 18:05:45 +00:00
2020-04-01 08:45:03 +00:00
public class JingleFileTransferConnection extends AbstractJingleConnection implements Transferable {
2014-04-07 18:05:45 +00:00
2020-04-01 08:45:03 +00:00
private static final int JINGLE_STATUS_TRANSMITTING = 5 ;
2019-09-04 14:14:01 +00:00
private static final String JET_OMEMO_CIPHER = " urn:xmpp:ciphers:aes-128-gcm-nopadding " ;
2019-06-16 20:57:10 +00:00
private static final int JINGLE_STATUS_INITIATED = 0 ;
private static final int JINGLE_STATUS_ACCEPTED = 1 ;
private static final int JINGLE_STATUS_FINISHED = 4 ;
private static final int JINGLE_STATUS_FAILED = 99 ;
private static final int JINGLE_STATUS_OFFERED = - 1 ;
2020-04-02 08:59:25 +00:00
private static final int MAX_IBB_BLOCK_SIZE = 8192 ;
private int ibbBlockSize = MAX_IBB_BLOCK_SIZE ;
2019-06-16 20:57:10 +00:00
2020-04-01 08:45:03 +00:00
private int mJingleStatus = JINGLE_STATUS_OFFERED ; //migrate to enum
2019-06-16 20:57:10 +00:00
private int mStatus = Transferable . STATUS_UNKNOWN ;
private Message message ;
private Jid responder ;
private List < JingleCandidate > candidates = new ArrayList < > ( ) ;
private ConcurrentHashMap < String , JingleSocks5Transport > connections = new ConcurrentHashMap < > ( ) ;
private String transportId ;
2020-04-01 16:35:36 +00:00
private FileTransferDescription description ;
2019-06-16 20:57:10 +00:00
private DownloadableFile file = null ;
2019-11-08 21:27:14 +00:00
private boolean proxyActivationFailed = false ;
2019-06-16 20:57:10 +00:00
private String contentName ;
2020-04-01 11:25:52 +00:00
private Content . Creator contentCreator ;
2020-05-11 09:09:18 +00:00
private Content . Senders contentSenders ;
2020-04-02 08:59:25 +00:00
private Class < ? extends GenericTransportInfo > initialTransport ;
2019-09-04 14:14:01 +00:00
private boolean remoteSupportsOmemoJet ;
2019-06-16 20:57:10 +00:00
private int mProgress = 0 ;
private boolean receivedCandidate = false ;
private boolean sentCandidate = false ;
private boolean acceptedAutomatically = false ;
private boolean cancelled = false ;
private XmppAxolotlMessage mXmppAxolotlMessage ;
private JingleTransport transport = null ;
private OutputStream mFileOutputStream ;
private InputStream mFileInputStream ;
private OnIqPacketReceived responseListener = ( account , packet ) - > {
if ( packet . getType ( ) ! = IqPacket . TYPE . RESULT ) {
2019-12-05 13:03:54 +00:00
if ( mJingleStatus ! = JINGLE_STATUS_FAILED & & mJingleStatus ! = JINGLE_STATUS_FINISHED ) {
fail ( IqParser . extractErrorMessage ( packet ) ) ;
} else {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , " ignoring late delivery of jingle packet to jingle session with status= " + mJingleStatus + " : " + packet . toString ( ) ) ;
2019-12-05 13:03:54 +00:00
}
2019-06-16 20:57:10 +00:00
}
} ;
private byte [ ] expectedHash = new byte [ 0 ] ;
private final OnFileTransmissionStatusChanged onFileTransmissionStatusChanged = new OnFileTransmissionStatusChanged ( ) {
@Override
public void onFileTransmitted ( DownloadableFile file ) {
if ( responding ( ) ) {
2020-04-01 11:25:52 +00:00
if ( expectedHash . length > 0 ) {
if ( Arrays . equals ( expectedHash , file . getSha1Sum ( ) ) ) {
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received file matched the expected hash " ) ;
} else {
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : hashes did not match " ) ;
}
} else {
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : other party did not include file hash in file transfer " ) ;
2019-06-16 20:57:10 +00:00
}
sendSuccess ( ) ;
2020-04-01 08:45:03 +00:00
xmppConnectionService . getFileBackend ( ) . updateFileParams ( message ) ;
xmppConnectionService . databaseBackend . createMessage ( message ) ;
xmppConnectionService . markMessage ( message , Message . STATUS_RECEIVED ) ;
2019-06-16 20:57:10 +00:00
if ( acceptedAutomatically ) {
message . markUnread ( ) ;
if ( message . getEncryption ( ) = = Message . ENCRYPTION_PGP ) {
2020-04-01 08:45:03 +00:00
id . account . getPgpDecryptionService ( ) . decrypt ( message , true ) ;
2019-06-16 20:57:10 +00:00
} else {
2020-04-01 08:45:03 +00:00
xmppConnectionService . getFileBackend ( ) . updateMediaScanner ( file , ( ) - > JingleFileTransferConnection . this . xmppConnectionService . getNotificationService ( ) . push ( message ) ) ;
2019-06-16 20:57:10 +00:00
}
Log . d ( Config . LOGTAG , " successfully transmitted file: " + file . getAbsolutePath ( ) + " ( " + CryptoHelper . bytesToHex ( file . getSha1Sum ( ) ) + " ) " ) ;
return ;
2019-08-16 13:29:58 +00:00
} else if ( message . getEncryption ( ) = = Message . ENCRYPTION_PGP ) {
2020-04-01 08:45:03 +00:00
id . account . getPgpDecryptionService ( ) . decrypt ( message , true ) ;
2019-06-16 20:57:10 +00:00
}
} else {
2020-04-01 16:35:36 +00:00
if ( description . getVersion ( ) = = FileTransferDescription . Version . FT_5 ) { //older Conversations will break when receiving a session-info
2019-06-16 20:57:10 +00:00
sendHash ( ) ;
}
if ( message . getEncryption ( ) = = Message . ENCRYPTION_PGP ) {
2020-04-01 08:45:03 +00:00
id . account . getPgpDecryptionService ( ) . decrypt ( message , false ) ;
2019-06-16 20:57:10 +00:00
}
if ( message . getEncryption ( ) = = Message . ENCRYPTION_PGP | | message . getEncryption ( ) = = Message . ENCRYPTION_DECRYPTED ) {
file . delete ( ) ;
}
}
Log . d ( Config . LOGTAG , " successfully transmitted file: " + file . getAbsolutePath ( ) + " ( " + CryptoHelper . bytesToHex ( file . getSha1Sum ( ) ) + " ) " ) ;
if ( message . getEncryption ( ) ! = Message . ENCRYPTION_PGP ) {
2020-04-01 08:45:03 +00:00
xmppConnectionService . getFileBackend ( ) . updateMediaScanner ( file ) ;
2019-06-16 20:57:10 +00:00
}
}
@Override
public void onFileTransferAborted ( ) {
2020-04-07 19:26:51 +00:00
JingleFileTransferConnection . this . sendSessionTerminate ( Reason . CONNECTIVITY_ERROR ) ;
2020-04-01 08:45:03 +00:00
JingleFileTransferConnection . this . fail ( ) ;
2019-06-16 20:57:10 +00:00
}
} ;
private OnTransportConnected onIbbTransportConnected = new OnTransportConnected ( ) {
@Override
public void failed ( ) {
Log . d ( Config . LOGTAG , " ibb open failed " ) ;
}
@Override
public void established ( ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : ibb transport connected. sending file " ) ;
2019-06-16 20:57:10 +00:00
mJingleStatus = JINGLE_STATUS_TRANSMITTING ;
2020-04-01 08:45:03 +00:00
JingleFileTransferConnection . this . transport . send ( file , onFileTransmissionStatusChanged ) ;
2019-06-16 20:57:10 +00:00
}
} ;
private OnProxyActivated onProxyActivated = new OnProxyActivated ( ) {
@Override
public void success ( ) {
2020-04-03 06:16:55 +00:00
if ( isInitiator ( ) ) {
2019-06-16 20:57:10 +00:00
Log . d ( Config . LOGTAG , " we were initiating. sending file " ) ;
transport . send ( file , onFileTransmissionStatusChanged ) ;
} else {
transport . receive ( file , onFileTransmissionStatusChanged ) ;
Log . d ( Config . LOGTAG , " we were responding. receiving file " ) ;
}
}
@Override
public void failed ( ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : proxy activation failed " ) ;
2019-11-08 21:27:14 +00:00
proxyActivationFailed = true ;
2020-04-03 06:16:55 +00:00
if ( isInitiator ( ) ) {
2019-09-01 19:41:28 +00:00
sendFallbackToIbb ( ) ;
}
2019-06-16 20:57:10 +00:00
}
} ;
2020-04-03 06:16:55 +00:00
public JingleFileTransferConnection ( JingleConnectionManager jingleConnectionManager , Id id , Jid initiator ) {
super ( jingleConnectionManager , id , initiator ) ;
2020-04-01 08:45:03 +00:00
}
private static long parseLong ( final Element element , final long l ) {
final String input = element = = null ? null : element . getContent ( ) ;
if ( input = = null ) {
return l ;
}
try {
return Long . parseLong ( input ) ;
} catch ( Exception e ) {
return l ;
}
2019-06-16 20:57:10 +00:00
}
2020-04-03 06:16:55 +00:00
//TODO get rid and use isInitiator() instead
2019-06-16 20:57:10 +00:00
private boolean responding ( ) {
2020-04-01 08:45:03 +00:00
return responder ! = null & & responder . equals ( id . account . getJid ( ) ) ;
2019-06-16 20:57:10 +00:00
}
InputStream getFileInputStream ( ) {
return this . mFileInputStream ;
}
OutputStream getFileOutputStream ( ) throws IOException {
if ( this . file = = null ) {
Log . d ( Config . LOGTAG , " file object was not assigned " ) ;
return null ;
}
2020-04-01 08:45:03 +00:00
final File parent = this . file . getParentFile ( ) ;
if ( parent ! = null & & parent . mkdirs ( ) ) {
2020-04-01 11:25:52 +00:00
Log . d ( Config . LOGTAG , " created parent directories for file " + file . getAbsolutePath ( ) ) ;
2020-04-01 08:45:03 +00:00
}
if ( this . file . createNewFile ( ) ) {
2020-04-01 11:25:52 +00:00
Log . d ( Config . LOGTAG , " created output file " + file . getAbsolutePath ( ) ) ;
2020-04-01 08:45:03 +00:00
}
2019-12-20 18:07:31 +00:00
this . mFileOutputStream = AbstractConnectionManager . createOutputStream ( this . file , false , true ) ;
2019-06-16 20:57:10 +00:00
return this . mFileOutputStream ;
}
2020-04-01 08:45:03 +00:00
@Override
void deliverPacket ( final JinglePacket packet ) {
2020-04-01 11:25:52 +00:00
final JinglePacket . Action action = packet . getAction ( ) ;
//TODO switch case
2020-04-01 16:35:36 +00:00
if ( action = = JinglePacket . Action . SESSION_INITIATE ) {
init ( packet ) ;
} else if ( action = = JinglePacket . Action . SESSION_TERMINATE ) {
2020-04-12 07:59:32 +00:00
final Reason reason = packet . getReason ( ) . reason ;
2020-04-07 19:26:51 +00:00
switch ( reason ) {
case CANCEL :
2019-10-05 19:58:21 +00:00
this . cancelled = true ;
2019-06-16 20:57:10 +00:00
this . fail ( ) ;
2020-04-07 19:26:51 +00:00
break ;
case SUCCESS :
2019-06-16 20:57:10 +00:00
this . receiveSuccess ( ) ;
2020-04-07 19:26:51 +00:00
break ;
default :
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received session-terminate with reason " + reason ) ;
this . fail ( ) ;
break ;
2019-06-16 20:57:10 +00:00
}
2020-04-01 11:25:52 +00:00
} else if ( action = = JinglePacket . Action . SESSION_ACCEPT ) {
2019-10-05 19:29:36 +00:00
receiveAccept ( packet ) ;
2020-04-01 11:25:52 +00:00
} else if ( action = = JinglePacket . Action . SESSION_INFO ) {
final Element checksum = packet . getJingleChild ( " checksum " ) ;
2019-10-05 19:29:36 +00:00
final Element file = checksum = = null ? null : checksum . findChild ( " file " ) ;
final Element hash = file = = null ? null : file . findChild ( " hash " , " urn:xmpp:hashes:2 " ) ;
2019-06-16 20:57:10 +00:00
if ( hash ! = null & & " sha-1 " . equalsIgnoreCase ( hash . getAttribute ( " algo " ) ) ) {
try {
this . expectedHash = Base64 . decode ( hash . getContent ( ) , Base64 . DEFAULT ) ;
} catch ( Exception e ) {
this . expectedHash = new byte [ 0 ] ;
}
}
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2020-04-01 11:25:52 +00:00
} else if ( action = = JinglePacket . Action . TRANSPORT_INFO ) {
2019-10-05 19:29:36 +00:00
receiveTransportInfo ( packet ) ;
2020-04-01 11:25:52 +00:00
} else if ( action = = JinglePacket . Action . TRANSPORT_REPLACE ) {
2020-04-02 08:59:25 +00:00
final Content content = packet . getJingleContent ( ) ;
final GenericTransportInfo transportInfo = content = = null ? null : content . getTransport ( ) ;
if ( transportInfo instanceof IbbTransportInfo ) {
receiveFallbackToIbb ( packet , ( IbbTransportInfo ) transportInfo ) ;
2019-06-16 20:57:10 +00:00
} else {
2019-10-05 19:29:36 +00:00
Log . d ( Config . LOGTAG , " trying to fallback to something unknown " + packet . toString ( ) ) ;
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
}
2020-04-01 11:25:52 +00:00
} else if ( action = = JinglePacket . Action . TRANSPORT_ACCEPT ) {
2019-10-05 19:29:36 +00:00
receiveTransportAccept ( packet ) ;
2019-06-16 20:57:10 +00:00
} else {
2019-10-05 19:29:36 +00:00
Log . d ( Config . LOGTAG , " packet arrived in connection. action was " + packet . getAction ( ) ) ;
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
}
2019-10-05 19:29:36 +00:00
}
2020-04-13 16:30:12 +00:00
@Override
void notifyRebound ( ) {
if ( getJingleStatus ( ) = = JINGLE_STATUS_TRANSMITTING ) {
abort ( Reason . CONNECTIVITY_ERROR ) ;
}
}
2019-10-05 19:29:36 +00:00
private void respondToIq ( final IqPacket packet , final boolean result ) {
final IqPacket response ;
if ( result ) {
2019-06-16 20:57:10 +00:00
response = packet . generateResponse ( IqPacket . TYPE . RESULT ) ;
} else {
response = packet . generateResponse ( IqPacket . TYPE . ERROR ) ;
2019-10-01 09:31:15 +00:00
final Element error = response . addChild ( " error " ) . setAttribute ( " type " , " cancel " ) ;
error . addChild ( " not-acceptable " , " urn:ietf:params:xml:ns:xmpp-stanzas " ) ;
2019-06-16 20:57:10 +00:00
}
2020-04-01 08:45:03 +00:00
xmppConnectionService . sendIqPacket ( id . account , response , null ) ;
2019-06-16 20:57:10 +00:00
}
2019-10-05 19:29:36 +00:00
private void respondToIqWithOutOfOrder ( final IqPacket packet ) {
final IqPacket response = packet . generateResponse ( IqPacket . TYPE . ERROR ) ;
final Element error = response . addChild ( " error " ) . setAttribute ( " type " , " wait " ) ;
error . addChild ( " unexpected-request " , " urn:ietf:params:xml:ns:xmpp-stanzas " ) ;
error . addChild ( " out-of-order " , " urn:xmpp:jingle:errors:1 " ) ;
2020-04-01 08:45:03 +00:00
xmppConnectionService . sendIqPacket ( id . account , response , null ) ;
2019-10-05 19:29:36 +00:00
}
2019-06-16 20:57:10 +00:00
public void init ( final Message message ) {
2020-04-01 16:35:36 +00:00
Preconditions . checkArgument ( message . isFileOrImage ( ) ) ;
2019-06-16 20:57:10 +00:00
if ( message . getEncryption ( ) = = Message . ENCRYPTION_AXOLOTL ) {
Conversation conversation = ( Conversation ) message . getConversation ( ) ;
conversation . getAccount ( ) . getAxolotlService ( ) . prepareKeyTransportMessage ( conversation , xmppAxolotlMessage - > {
if ( xmppAxolotlMessage ! = null ) {
init ( message , xmppAxolotlMessage ) ;
} else {
fail ( ) ;
}
} ) ;
} else {
init ( message , null ) ;
}
}
2020-04-01 16:35:36 +00:00
private void init ( final Message message , final XmppAxolotlMessage xmppAxolotlMessage ) {
2019-06-16 20:57:10 +00:00
this . mXmppAxolotlMessage = xmppAxolotlMessage ;
2020-04-01 11:25:52 +00:00
this . contentCreator = Content . Creator . INITIATOR ;
2020-05-11 09:09:18 +00:00
this . contentSenders = Content . Senders . INITIATOR ;
2020-04-01 08:45:03 +00:00
this . contentName = JingleConnectionManager . nextRandomId ( ) ;
2019-06-16 20:57:10 +00:00
this . message = message ;
2019-09-04 14:14:01 +00:00
final List < String > remoteFeatures = getRemoteFeatures ( ) ;
2020-04-01 16:35:36 +00:00
final FileTransferDescription . Version remoteVersion = getAvailableFileTransferVersion ( remoteFeatures ) ;
2020-04-02 08:59:25 +00:00
this . initialTransport = remoteFeatures . contains ( Namespace . JINGLE_TRANSPORTS_S5B ) ? S5BTransportInfo . class : IbbTransportInfo . class ;
2019-09-04 14:14:01 +00:00
this . remoteSupportsOmemoJet = remoteFeatures . contains ( Namespace . JINGLE_ENCRYPTED_TRANSPORT_OMEMO ) ;
2019-06-16 20:57:10 +00:00
this . message . setTransferable ( this ) ;
this . mStatus = Transferable . STATUS_UPLOADING ;
2020-04-02 14:29:33 +00:00
this . responder = this . id . with ;
2020-04-01 08:45:03 +00:00
this . transportId = JingleConnectionManager . nextRandomId ( ) ;
2020-04-01 16:35:36 +00:00
this . setupDescription ( remoteVersion ) ;
2020-04-02 08:59:25 +00:00
if ( this . initialTransport = = IbbTransportInfo . class ) {
2019-06-16 20:57:10 +00:00
this . sendInitRequest ( ) ;
} else {
2019-09-01 18:42:07 +00:00
gatherAndConnectDirectCandidates ( ) ;
2020-04-03 06:16:55 +00:00
this . jingleConnectionManager . getPrimaryCandidate ( id . account , isInitiator ( ) , ( success , candidate ) - > {
2019-06-16 20:57:10 +00:00
if ( success ) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport ( this , candidate ) ;
connections . put ( candidate . getCid ( ) , socksConnection ) ;
socksConnection . connect ( new OnTransportConnected ( ) {
@Override
public void failed ( ) {
2019-09-29 11:32:45 +00:00
Log . d ( Config . LOGTAG , String . format ( " connection to our own proxy65 candidate failed (%s:%d) " , candidate . getHost ( ) , candidate . getPort ( ) ) ) ;
2019-06-16 20:57:10 +00:00
sendInitRequest ( ) ;
}
@Override
public void established ( ) {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , " successfully connected to our own proxy65 candidate " ) ;
2019-06-16 20:57:10 +00:00
mergeCandidate ( candidate ) ;
sendInitRequest ( ) ;
}
} ) ;
mergeCandidate ( candidate ) ;
} else {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , " no proxy65 candidate of our own was found " ) ;
2019-06-16 20:57:10 +00:00
sendInitRequest ( ) ;
}
} ) ;
}
}
2019-09-01 18:42:07 +00:00
private void gatherAndConnectDirectCandidates ( ) {
final List < JingleCandidate > directCandidates ;
if ( Config . USE_DIRECT_JINGLE_CANDIDATES ) {
2020-04-01 08:45:03 +00:00
if ( id . account . isOnion ( ) | | xmppConnectionService . useTorToConnect ( ) ) {
2019-09-01 18:42:07 +00:00
directCandidates = Collections . emptyList ( ) ;
} else {
2020-04-01 08:45:03 +00:00
directCandidates = DirectConnectionUtils . getLocalCandidates ( id . account . getJid ( ) ) ;
2019-09-01 18:42:07 +00:00
}
} else {
directCandidates = Collections . emptyList ( ) ;
}
for ( JingleCandidate directCandidate : directCandidates ) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport ( this , directCandidate ) ;
connections . put ( directCandidate . getCid ( ) , socksConnection ) ;
candidates . add ( directCandidate ) ;
}
}
2020-04-01 16:35:36 +00:00
private FileTransferDescription . Version getAvailableFileTransferVersion ( List < String > remoteFeatures ) {
if ( remoteFeatures . contains ( FileTransferDescription . Version . FT_5 . getNamespace ( ) ) ) {
return FileTransferDescription . Version . FT_5 ;
} else if ( remoteFeatures . contains ( FileTransferDescription . Version . FT_4 . getNamespace ( ) ) ) {
return FileTransferDescription . Version . FT_4 ;
} else {
return FileTransferDescription . Version . FT_3 ;
2019-06-16 20:57:10 +00:00
}
}
private List < String > getRemoteFeatures ( ) {
2020-04-02 14:29:33 +00:00
final Jid jid = this . id . with ;
2019-06-16 20:57:10 +00:00
String resource = jid ! = null ? jid . getResource ( ) : null ;
if ( resource ! = null ) {
2020-05-30 08:57:22 +00:00
Presence presence = this . id . account . getRoster ( ) . getContact ( jid ) . getPresences ( ) . get ( resource ) ;
2019-06-16 20:57:10 +00:00
ServiceDiscoveryResult result = presence ! = null ? presence . getServiceDiscoveryResult ( ) : null ;
return result = = null ? Collections . emptyList ( ) : result . getFeatures ( ) ;
} else {
return Collections . emptyList ( ) ;
}
}
2020-04-01 16:35:36 +00:00
private void init ( JinglePacket packet ) { //should move to deliverPacket
2020-04-02 14:29:33 +00:00
//TODO if not 'OFFERED' reply with out-of-order
2019-06-16 20:57:10 +00:00
this . mJingleStatus = JINGLE_STATUS_INITIATED ;
2020-04-02 14:29:33 +00:00
final Conversation conversation = this . xmppConnectionService . findOrCreateConversation ( id . account , id . with . asBareJid ( ) , false , false ) ;
2019-06-16 20:57:10 +00:00
this . message = new Message ( conversation , " " , Message . ENCRYPTION_NONE ) ;
this . message . setStatus ( Message . STATUS_RECEIVED ) ;
this . mStatus = Transferable . STATUS_OFFER ;
this . message . setTransferable ( this ) ;
2020-04-02 14:29:33 +00:00
this . message . setCounterpart ( this . id . with ) ;
2020-04-01 08:45:03 +00:00
this . responder = this . id . account . getJid ( ) ;
final Content content = packet . getJingleContent ( ) ;
2020-04-02 08:59:25 +00:00
final GenericTransportInfo transportInfo = content . getTransport ( ) ;
2020-04-01 11:25:52 +00:00
this . contentCreator = content . getCreator ( ) ;
2020-05-11 09:09:18 +00:00
Content . Senders senders ;
try {
senders = content . getSenders ( ) ;
} catch ( final Exception e ) {
2020-06-07 10:47:03 +00:00
senders = Content . Senders . INITIATOR ;
2020-05-11 09:09:18 +00:00
}
this . contentSenders = senders ;
2019-06-16 20:57:10 +00:00
this . contentName = content . getAttribute ( " name " ) ;
2019-06-17 07:51:49 +00:00
2020-04-02 08:59:25 +00:00
if ( transportInfo instanceof S5BTransportInfo ) {
final S5BTransportInfo s5BTransportInfo = ( S5BTransportInfo ) transportInfo ;
this . transportId = s5BTransportInfo . getTransportId ( ) ;
this . initialTransport = s5BTransportInfo . getClass ( ) ;
this . mergeCandidates ( s5BTransportInfo . getCandidates ( ) ) ;
} else if ( transportInfo instanceof IbbTransportInfo ) {
final IbbTransportInfo ibbTransportInfo = ( IbbTransportInfo ) transportInfo ;
this . initialTransport = ibbTransportInfo . getClass ( ) ;
this . transportId = ibbTransportInfo . getTransportId ( ) ;
final int remoteBlockSize = ibbTransportInfo . getBlockSize ( ) ;
if ( remoteBlockSize < = 0 ) {
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : remote party requested invalid ibb block size " ) ;
2019-10-05 19:58:21 +00:00
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
this . fail ( ) ;
}
2020-04-02 08:59:25 +00:00
this . ibbBlockSize = Math . min ( MAX_IBB_BLOCK_SIZE , ibbTransportInfo . getBlockSize ( ) ) ;
} else {
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : remote tried to use unknown transport " + transportInfo . getNamespace ( ) ) ;
respondToIq ( packet , false ) ;
this . fail ( ) ;
return ;
2019-06-16 20:57:10 +00:00
}
2020-04-01 16:35:36 +00:00
this . description = ( FileTransferDescription ) content . getDescription ( ) ;
final Element fileOffer = this . description . getFileOffer ( ) ;
2019-09-06 11:11:37 +00:00
2019-06-16 20:57:10 +00:00
if ( fileOffer ! = null ) {
2019-09-06 11:11:37 +00:00
boolean remoteIsUsingJet = false ;
2019-06-16 20:57:10 +00:00
Element encrypted = fileOffer . findChild ( " encrypted " , AxolotlService . PEP_PREFIX ) ;
2019-09-04 14:14:01 +00:00
if ( encrypted = = null ) {
final Element security = content . findChild ( " security " , Namespace . JINGLE_ENCRYPTED_TRANSPORT ) ;
if ( security ! = null & & AxolotlService . PEP_PREFIX . equals ( security . getAttribute ( " type " ) ) ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received jingle file offer with JET " ) ;
2019-09-04 14:14:01 +00:00
encrypted = security . findChild ( " encrypted " , AxolotlService . PEP_PREFIX ) ;
2019-09-06 11:11:37 +00:00
remoteIsUsingJet = true ;
2019-09-04 14:14:01 +00:00
}
}
2019-06-16 20:57:10 +00:00
if ( encrypted ! = null ) {
this . mXmppAxolotlMessage = XmppAxolotlMessage . fromElement ( encrypted , packet . getFrom ( ) . asBareJid ( ) ) ;
}
Element fileSize = fileOffer . findChild ( " size " ) ;
2019-08-16 12:09:42 +00:00
final String path = fileOffer . findChildContent ( " name " ) ;
if ( path ! = null ) {
AbstractConnectionManager . Extension extension = AbstractConnectionManager . Extension . of ( path ) ;
if ( VALID_IMAGE_EXTENSIONS . contains ( extension . main ) ) {
2019-06-16 20:57:10 +00:00
message . setType ( Message . TYPE_IMAGE ) ;
2019-08-16 12:09:42 +00:00
message . setRelativeFilePath ( message . getUuid ( ) + " . " + extension . main ) ;
} else if ( VALID_CRYPTO_EXTENSIONS . contains ( extension . main ) ) {
if ( VALID_IMAGE_EXTENSIONS . contains ( extension . secondary ) ) {
message . setType ( Message . TYPE_IMAGE ) ;
2019-08-16 13:29:58 +00:00
message . setRelativeFilePath ( message . getUuid ( ) + " . " + extension . secondary ) ;
2019-08-16 12:09:42 +00:00
} else {
message . setType ( Message . TYPE_FILE ) ;
message . setRelativeFilePath ( message . getUuid ( ) + ( extension . secondary ! = null ? ( " . " + extension . secondary ) : " " ) ) ;
2019-06-16 20:57:10 +00:00
}
2019-08-16 12:09:42 +00:00
message . setEncryption ( Message . ENCRYPTION_PGP ) ;
2019-06-16 20:57:10 +00:00
} else {
message . setType ( Message . TYPE_FILE ) ;
2019-08-16 12:09:42 +00:00
message . setRelativeFilePath ( message . getUuid ( ) + ( extension . main ! = null ? ( " . " + extension . main ) : " " ) ) ;
2019-06-16 20:57:10 +00:00
}
2019-08-16 12:09:42 +00:00
long size = parseLong ( fileSize , 0 ) ;
2019-06-16 20:57:10 +00:00
message . setBody ( Long . toString ( size ) ) ;
conversation . add ( message ) ;
2020-04-01 08:45:03 +00:00
jingleConnectionManager . updateConversationUi ( true ) ;
this . file = this . xmppConnectionService . getFileBackend ( ) . getFile ( message , false ) ;
2019-06-16 20:57:10 +00:00
if ( mXmppAxolotlMessage ! = null ) {
2020-04-01 08:45:03 +00:00
XmppAxolotlMessage . XmppAxolotlKeyTransportMessage transportMessage = id . account . getAxolotlService ( ) . processReceivingKeyTransportMessage ( mXmppAxolotlMessage , false ) ;
2019-06-16 20:57:10 +00:00
if ( transportMessage ! = null ) {
message . setEncryption ( Message . ENCRYPTION_AXOLOTL ) ;
this . file . setKey ( transportMessage . getKey ( ) ) ;
this . file . setIv ( transportMessage . getIv ( ) ) ;
message . setFingerprint ( transportMessage . getFingerprint ( ) ) ;
} else {
Log . d ( Config . LOGTAG , " could not process KeyTransportMessage " ) ;
}
}
message . resetFileParams ( ) ;
2019-09-06 11:11:37 +00:00
//legacy OMEMO encrypted file transfers reported the file size after encryption
//JET reports the plain text size. however lower levels of our receiving code still
//expect the cipher text size. so we just + 16 bytes (auth tag size) here
this . file . setExpectedSize ( size + ( remoteIsUsingJet ? 16 : 0 ) ) ;
2019-10-05 19:58:21 +00:00
respondToIq ( packet , true ) ;
2020-04-02 14:29:33 +00:00
if ( id . account . getRoster ( ) . getContact ( id . with ) . showInContactList ( )
2020-04-01 08:45:03 +00:00
& & jingleConnectionManager . hasStoragePermission ( )
& & size < this . jingleConnectionManager . getAutoAcceptFileSize ( )
& & xmppConnectionService . isDataSaverDisabled ( ) ) {
2020-04-02 14:29:33 +00:00
Log . d ( Config . LOGTAG , " auto accepting file from " + id . with ) ;
2019-06-16 20:57:10 +00:00
this . acceptedAutomatically = true ;
this . sendAccept ( ) ;
} else {
message . markUnread ( ) ;
Log . d ( Config . LOGTAG ,
" not auto accepting new file offer with size: "
+ size
+ " allowed size: "
2020-04-01 08:45:03 +00:00
+ this . jingleConnectionManager
2019-06-16 20:57:10 +00:00
. getAutoAcceptFileSize ( ) ) ;
2020-04-01 08:45:03 +00:00
this . xmppConnectionService . getNotificationService ( ) . push ( message ) ;
2019-06-16 20:57:10 +00:00
}
Log . d ( Config . LOGTAG , " receiving file: expecting size of " + this . file . getExpectedSize ( ) ) ;
2019-10-05 19:58:21 +00:00
return ;
2019-06-16 20:57:10 +00:00
}
2019-10-05 19:58:21 +00:00
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
}
}
2020-04-01 16:35:36 +00:00
private void setupDescription ( final FileTransferDescription . Version version ) {
this . file = this . xmppConnectionService . getFileBackend ( ) . getFile ( message , false ) ;
final FileTransferDescription description ;
if ( message . getEncryption ( ) = = Message . ENCRYPTION_AXOLOTL ) {
this . file . setKey ( mXmppAxolotlMessage . getInnerKey ( ) ) ;
this . file . setIv ( mXmppAxolotlMessage . getIV ( ) ) ;
//legacy OMEMO encrypted file transfer reported file size of the encrypted file
//JET uses the file size of the plain text file. The difference is only 16 bytes (auth tag)
this . file . setExpectedSize ( file . getSize ( ) + ( this . remoteSupportsOmemoJet ? 0 : 16 ) ) ;
if ( remoteSupportsOmemoJet ) {
description = FileTransferDescription . of ( this . file , version , null ) ;
} else {
description = FileTransferDescription . of ( this . file , version , this . mXmppAxolotlMessage ) ;
}
} else {
this . file . setExpectedSize ( file . getSize ( ) ) ;
description = FileTransferDescription . of ( this . file , version , null ) ;
}
this . description = description ;
}
2019-06-16 20:57:10 +00:00
private void sendInitRequest ( ) {
2020-04-01 11:25:52 +00:00
final JinglePacket packet = this . bootstrapPacket ( JinglePacket . Action . SESSION_INITIATE ) ;
final Content content = new Content ( this . contentCreator , this . contentName ) ;
2020-05-11 09:09:18 +00:00
content . setSenders ( this . contentSenders ) ;
2020-04-01 16:35:36 +00:00
if ( message . getEncryption ( ) = = Message . ENCRYPTION_AXOLOTL & & remoteSupportsOmemoJet ) {
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : remote announced support for JET " ) ;
final Element security = new Element ( " security " , Namespace . JINGLE_ENCRYPTED_TRANSPORT ) ;
security . setAttribute ( " name " , this . contentName ) ;
security . setAttribute ( " cipher " , JET_OMEMO_CIPHER ) ;
security . setAttribute ( " type " , AxolotlService . PEP_PREFIX ) ;
security . addChild ( mXmppAxolotlMessage . toElement ( ) ) ;
content . addChild ( security ) ;
}
content . setDescription ( this . description ) ;
message . resetFileParams ( ) ;
try {
this . mFileInputStream = new FileInputStream ( file ) ;
} catch ( FileNotFoundException e ) {
fail ( e . getMessage ( ) ) ;
return ;
}
2020-04-02 08:59:25 +00:00
if ( this . initialTransport = = IbbTransportInfo . class ) {
content . setTransport ( new IbbTransportInfo ( this . transportId , this . ibbBlockSize ) ) ;
2020-04-01 16:35:36 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : sending IBB offer " ) ;
} else {
2020-04-02 08:59:25 +00:00
final Collection < JingleCandidate > candidates = getOurCandidates ( ) ;
content . setTransport ( new S5BTransportInfo ( this . transportId , candidates ) ) ;
2020-04-01 16:35:36 +00:00
Log . d ( Config . LOGTAG , String . format ( " %s: sending S5B offer with %d candidates " , id . account . getJid ( ) . asBareJid ( ) , candidates . size ( ) ) ) ;
}
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2020-04-01 16:35:36 +00:00
this . sendJinglePacket ( packet , ( account , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : other party received offer " ) ;
if ( mJingleStatus = = JINGLE_STATUS_OFFERED ) {
mJingleStatus = JINGLE_STATUS_INITIATED ;
xmppConnectionService . markMessage ( message , Message . STATUS_OFFERED ) ;
2019-09-04 14:14:01 +00:00
} else {
2020-04-01 16:35:36 +00:00
Log . d ( Config . LOGTAG , " received ack for offer when status was " + mJingleStatus ) ;
2019-09-04 14:14:01 +00:00
}
2019-06-16 20:57:10 +00:00
} else {
2020-04-01 16:35:36 +00:00
fail ( IqParser . extractErrorMessage ( response ) ) ;
2019-06-16 20:57:10 +00:00
}
2020-04-01 16:35:36 +00:00
} ) ;
2019-06-16 20:57:10 +00:00
}
private void sendHash ( ) {
2020-04-01 16:35:36 +00:00
final Element checksum = new Element ( " checksum " , description . getVersion ( ) . getNamespace ( ) ) ;
2020-04-01 11:25:52 +00:00
checksum . setAttribute ( " creator " , " initiator " ) ;
checksum . setAttribute ( " name " , " a-file-offer " ) ;
Element hash = checksum . addChild ( " file " ) . addChild ( " hash " , " urn:xmpp:hashes:2 " ) ;
hash . setAttribute ( " algo " , " sha-1 " ) . setContent ( Base64 . encodeToString ( file . getSha1Sum ( ) , Base64 . NO_WRAP ) ) ;
final JinglePacket packet = this . bootstrapPacket ( JinglePacket . Action . SESSION_INFO ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleChild ( checksum ) ;
2019-06-16 20:57:10 +00:00
this . sendJinglePacket ( packet ) ;
}
2020-04-02 08:59:25 +00:00
public Collection < JingleCandidate > getOurCandidates ( ) {
return Collections2 . filter ( this . candidates , c - > c ! = null & & c . isOurs ( ) ) ;
2019-06-16 20:57:10 +00:00
}
private void sendAccept ( ) {
mJingleStatus = JINGLE_STATUS_ACCEPTED ;
this . mStatus = Transferable . STATUS_DOWNLOADING ;
2020-04-01 08:45:03 +00:00
this . jingleConnectionManager . updateConversationUi ( true ) ;
2020-04-02 08:59:25 +00:00
if ( initialTransport = = S5BTransportInfo . class ) {
2019-06-16 20:57:10 +00:00
sendAcceptSocks ( ) ;
} else {
sendAcceptIbb ( ) ;
}
}
private void sendAcceptSocks ( ) {
2019-09-01 18:42:07 +00:00
gatherAndConnectDirectCandidates ( ) ;
2020-04-03 06:16:55 +00:00
this . jingleConnectionManager . getPrimaryCandidate ( this . id . account , isInitiator ( ) , ( success , candidate ) - > {
2020-04-01 11:25:52 +00:00
final JinglePacket packet = bootstrapPacket ( JinglePacket . Action . SESSION_ACCEPT ) ;
2019-06-16 20:57:10 +00:00
final Content content = new Content ( contentCreator , contentName ) ;
2020-05-11 09:09:18 +00:00
content . setSenders ( this . contentSenders ) ;
2020-04-01 16:35:36 +00:00
content . setDescription ( this . description ) ;
2019-06-16 20:57:10 +00:00
if ( success & & candidate ! = null & & ! equalCandidateExists ( candidate ) ) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport ( this , candidate ) ;
connections . put ( candidate . getCid ( ) , socksConnection ) ;
socksConnection . connect ( new OnTransportConnected ( ) {
@Override
public void failed ( ) {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , " connection to our own proxy65 candidate failed " ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new S5BTransportInfo ( transportId , getOurCandidates ( ) ) ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2019-06-16 20:57:10 +00:00
sendJinglePacket ( packet ) ;
connectNextCandidate ( ) ;
}
@Override
public void established ( ) {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , " connected to proxy65 candidate " ) ;
2019-06-16 20:57:10 +00:00
mergeCandidate ( candidate ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new S5BTransportInfo ( transportId , getOurCandidates ( ) ) ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2019-06-16 20:57:10 +00:00
sendJinglePacket ( packet ) ;
connectNextCandidate ( ) ;
}
} ) ;
} else {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , " did not find a proxy65 candidate for ourselves " ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new S5BTransportInfo ( transportId , getOurCandidates ( ) ) ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2019-06-16 20:57:10 +00:00
sendJinglePacket ( packet ) ;
connectNextCandidate ( ) ;
}
} ) ;
}
private void sendAcceptIbb ( ) {
2019-12-05 13:03:54 +00:00
this . transport = new JingleInBandTransport ( this , this . transportId , this . ibbBlockSize ) ;
2020-04-01 11:25:52 +00:00
final JinglePacket packet = bootstrapPacket ( JinglePacket . Action . SESSION_ACCEPT ) ;
2019-06-16 20:57:10 +00:00
final Content content = new Content ( contentCreator , contentName ) ;
2020-05-11 09:09:18 +00:00
content . setSenders ( this . contentSenders ) ;
2020-04-01 16:35:36 +00:00
content . setDescription ( this . description ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new IbbTransportInfo ( this . transportId , this . ibbBlockSize ) ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2019-06-16 20:57:10 +00:00
this . transport . receive ( file , onFileTransmissionStatusChanged ) ;
this . sendJinglePacket ( packet ) ;
}
2020-04-01 11:25:52 +00:00
private JinglePacket bootstrapPacket ( JinglePacket . Action action ) {
final JinglePacket packet = new JinglePacket ( action , this . id . sessionId ) ;
2020-04-02 14:29:33 +00:00
packet . setTo ( id . with ) ;
2019-06-16 20:57:10 +00:00
return packet ;
}
private void sendJinglePacket ( JinglePacket packet ) {
2020-04-01 08:45:03 +00:00
xmppConnectionService . sendIqPacket ( id . account , packet , responseListener ) ;
2019-06-16 20:57:10 +00:00
}
private void sendJinglePacket ( JinglePacket packet , OnIqPacketReceived callback ) {
2020-04-01 08:45:03 +00:00
xmppConnectionService . sendIqPacket ( id . account , packet , callback ) ;
2019-06-16 20:57:10 +00:00
}
2019-10-05 19:29:36 +00:00
private void receiveAccept ( JinglePacket packet ) {
2019-11-08 21:27:14 +00:00
if ( responding ( ) ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received out of order session-accept (we were responding) " ) ;
2019-11-08 21:27:14 +00:00
respondToIqWithOutOfOrder ( packet ) ;
return ;
}
2019-06-17 07:51:49 +00:00
if ( this . mJingleStatus ! = JINGLE_STATUS_INITIATED ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received out of order session-accept " ) ;
2019-10-05 19:29:36 +00:00
respondToIqWithOutOfOrder ( packet ) ;
return ;
2019-06-17 07:51:49 +00:00
}
2019-06-16 20:57:10 +00:00
this . mJingleStatus = JINGLE_STATUS_ACCEPTED ;
2020-04-01 08:45:03 +00:00
xmppConnectionService . markMessage ( message , Message . STATUS_UNSEND ) ;
2020-04-02 08:59:25 +00:00
final Content content = packet . getJingleContent ( ) ;
final GenericTransportInfo transportInfo = content . getTransport ( ) ;
//TODO we want to fail if transportInfo doesn’ t match our intialTransport and/or our id
if ( transportInfo instanceof S5BTransportInfo ) {
final S5BTransportInfo s5BTransportInfo = ( S5BTransportInfo ) transportInfo ;
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2020-04-02 08:59:25 +00:00
//TODO calling merge is probably a bug because that might eliminate candidates of the other party and lead to us not sending accept/deny
//TODO: we probably just want to call add
mergeCandidates ( s5BTransportInfo . getCandidates ( ) ) ;
2019-06-16 20:57:10 +00:00
this . connectNextCandidate ( ) ;
2020-04-02 08:59:25 +00:00
} else if ( transportInfo instanceof IbbTransportInfo ) {
final IbbTransportInfo ibbTransportInfo = ( IbbTransportInfo ) transportInfo ;
final int remoteBlockSize = ibbTransportInfo . getBlockSize ( ) ;
if ( remoteBlockSize > 0 ) {
this . ibbBlockSize = Math . min ( ibbBlockSize , remoteBlockSize ) ;
2019-06-16 20:57:10 +00:00
}
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-12-05 13:03:54 +00:00
this . transport = new JingleInBandTransport ( this , this . transportId , this . ibbBlockSize ) ;
2019-06-16 20:57:10 +00:00
this . transport . connect ( onIbbTransportConnected ) ;
2019-06-17 07:51:49 +00:00
} else {
2019-10-05 19:29:36 +00:00
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
}
}
2019-10-05 19:29:36 +00:00
private void receiveTransportInfo ( JinglePacket packet ) {
final Content content = packet . getJingleContent ( ) ;
2020-04-02 08:59:25 +00:00
final GenericTransportInfo transportInfo = content . getTransport ( ) ;
if ( transportInfo instanceof S5BTransportInfo ) {
final S5BTransportInfo s5BTransportInfo = ( S5BTransportInfo ) transportInfo ;
if ( s5BTransportInfo . hasChild ( " activated " ) ) {
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
if ( ( this . transport ! = null ) & & ( this . transport instanceof JingleSocks5Transport ) ) {
onProxyActivated . success ( ) ;
} else {
2020-04-02 08:59:25 +00:00
String cid = s5BTransportInfo . findChild ( " activated " ) . getAttribute ( " cid " ) ;
2019-06-16 20:57:10 +00:00
Log . d ( Config . LOGTAG , " received proxy activated ( " + cid
+ " )prior to choosing our own transport " ) ;
JingleSocks5Transport connection = this . connections . get ( cid ) ;
if ( connection ! = null ) {
connection . setActivated ( true ) ;
} else {
Log . d ( Config . LOGTAG , " activated connection not found " ) ;
2020-04-07 19:26:51 +00:00
sendSessionTerminate ( Reason . FAILED_TRANSPORT ) ;
2019-06-16 20:57:10 +00:00
this . fail ( ) ;
}
}
2020-04-02 08:59:25 +00:00
} else if ( s5BTransportInfo . hasChild ( " proxy-error " ) ) {
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
onProxyActivated . failed ( ) ;
2020-04-02 08:59:25 +00:00
} else if ( s5BTransportInfo . hasChild ( " candidate-error " ) ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received candidate error " ) ;
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
this . receivedCandidate = true ;
if ( mJingleStatus = = JINGLE_STATUS_ACCEPTED & & this . sentCandidate ) {
this . connect ( ) ;
}
2020-04-02 08:59:25 +00:00
} else if ( s5BTransportInfo . hasChild ( " candidate-used " ) ) {
String cid = s5BTransportInfo . findChild ( " candidate-used " ) . getAttribute ( " cid " ) ;
2019-06-16 20:57:10 +00:00
if ( cid ! = null ) {
Log . d ( Config . LOGTAG , " candidate used by counterpart: " + cid ) ;
JingleCandidate candidate = getCandidate ( cid ) ;
if ( candidate = = null ) {
Log . d ( Config . LOGTAG , " could not find candidate with cid= " + cid ) ;
2019-10-05 19:29:36 +00:00
respondToIq ( packet , false ) ;
return ;
2019-06-16 20:57:10 +00:00
}
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
candidate . flagAsUsedByCounterpart ( ) ;
this . receivedCandidate = true ;
if ( mJingleStatus = = JINGLE_STATUS_ACCEPTED & & this . sentCandidate ) {
this . connect ( ) ;
} else {
Log . d ( Config . LOGTAG , " ignoring because file is already in transmission or we haven't sent our candidate yet status= " + mJingleStatus + " sentCandidate= " + sentCandidate ) ;
}
} else {
2019-10-05 19:29:36 +00:00
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
}
} else {
2019-10-05 19:29:36 +00:00
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
}
} else {
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
}
}
private void connect ( ) {
final JingleSocks5Transport connection = chooseConnection ( ) ;
this . transport = connection ;
if ( connection = = null ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : could not find suitable candidate " ) ;
2019-06-16 20:57:10 +00:00
this . disconnectSocks5Connections ( ) ;
2020-04-03 06:16:55 +00:00
if ( isInitiator ( ) ) {
2019-06-16 20:57:10 +00:00
this . sendFallbackToIbb ( ) ;
}
} else {
2020-06-07 10:47:03 +00:00
//TODO at this point we can already close other connections to free some resources
2019-09-04 14:14:01 +00:00
final JingleCandidate candidate = connection . getCandidate ( ) ;
2020-06-07 10:47:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : elected candidate " + candidate . toString ( ) ) ;
2019-06-16 20:57:10 +00:00
this . mJingleStatus = JINGLE_STATUS_TRANSMITTING ;
if ( connection . needsActivation ( ) ) {
if ( connection . getCandidate ( ) . isOurs ( ) ) {
final String sid ;
2020-04-01 16:35:36 +00:00
if ( description . getVersion ( ) = = FileTransferDescription . Version . FT_3 ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : use session ID instead of transport ID to activate proxy " ) ;
sid = id . sessionId ;
2019-06-16 20:57:10 +00:00
} else {
sid = getTransportId ( ) ;
}
Log . d ( Config . LOGTAG , " candidate "
+ connection . getCandidate ( ) . getCid ( )
+ " was our proxy. going to activate " ) ;
IqPacket activation = new IqPacket ( IqPacket . TYPE . SET ) ;
activation . setTo ( connection . getCandidate ( ) . getJid ( ) ) ;
activation . query ( " http://jabber.org/protocol/bytestreams " )
. setAttribute ( " sid " , sid ) ;
activation . query ( ) . addChild ( " activate " )
2020-04-02 14:29:33 +00:00
. setContent ( this . id . with . toEscapedString ( ) ) ;
2020-04-01 08:45:03 +00:00
xmppConnectionService . sendIqPacket ( this . id . account , activation , ( account , response ) - > {
2019-06-16 20:57:10 +00:00
if ( response . getType ( ) ! = IqPacket . TYPE . RESULT ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : " + response . toString ( ) ) ;
2019-09-01 19:41:28 +00:00
sendProxyError ( ) ;
2019-06-16 20:57:10 +00:00
onProxyActivated . failed ( ) ;
} else {
sendProxyActivated ( connection . getCandidate ( ) . getCid ( ) ) ;
2019-09-01 19:41:28 +00:00
onProxyActivated . success ( ) ;
2019-06-16 20:57:10 +00:00
}
} ) ;
} else {
Log . d ( Config . LOGTAG ,
" candidate "
+ connection . getCandidate ( ) . getCid ( )
+ " was a proxy. waiting for other party to activate " ) ;
}
} else {
2020-04-03 06:16:55 +00:00
if ( isInitiator ( ) ) {
2019-06-16 20:57:10 +00:00
Log . d ( Config . LOGTAG , " we were initiating. sending file " ) ;
connection . send ( file , onFileTransmissionStatusChanged ) ;
} else {
Log . d ( Config . LOGTAG , " we were responding. receiving file " ) ;
connection . receive ( file , onFileTransmissionStatusChanged ) ;
}
}
}
}
private JingleSocks5Transport chooseConnection ( ) {
2020-06-07 10:47:03 +00:00
final List < JingleSocks5Transport > establishedConnections = FluentIterable . from ( connections . entrySet ( ) )
. transform ( Entry : : getValue )
. filter ( c - > ( c ! = null & & c . isEstablished ( ) & & ( c . getCandidate ( ) . isUsedByCounterpart ( ) | | ! c . getCandidate ( ) . isOurs ( ) ) ) )
. toSortedList ( ( a , b ) - > {
final int compare = Integer . compare ( b . getCandidate ( ) . getPriority ( ) , a . getCandidate ( ) . getPriority ( ) ) ;
if ( compare = = 0 ) {
2020-04-03 06:16:55 +00:00
if ( isInitiator ( ) ) {
2020-06-07 10:47:03 +00:00
//pick the one we sent a candidate-used for (meaning not ours)
return a . getCandidate ( ) . isOurs ( ) ? 1 : - 1 ;
2019-06-16 20:57:10 +00:00
} else {
2020-06-07 10:47:03 +00:00
//pick the one they sent a candidate-used for (meaning ours)
return a . getCandidate ( ) . isOurs ( ) ? - 1 : 1 ;
2019-06-16 20:57:10 +00:00
}
}
2020-06-07 10:47:03 +00:00
return compare ;
} ) ;
return Iterables . getFirst ( establishedConnections , null ) ;
2019-06-16 20:57:10 +00:00
}
private void sendSuccess ( ) {
2020-04-07 19:26:51 +00:00
sendSessionTerminate ( Reason . SUCCESS ) ;
2019-06-16 20:57:10 +00:00
this . disconnectSocks5Connections ( ) ;
this . mJingleStatus = JINGLE_STATUS_FINISHED ;
this . message . setStatus ( Message . STATUS_RECEIVED ) ;
this . message . setTransferable ( null ) ;
2020-04-01 08:45:03 +00:00
this . xmppConnectionService . updateMessage ( message , false ) ;
this . jingleConnectionManager . finishConnection ( this ) ;
2019-06-16 20:57:10 +00:00
}
private void sendFallbackToIbb ( ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : sending fallback to ibb " ) ;
2020-05-11 09:09:18 +00:00
final JinglePacket packet = this . bootstrapPacket ( JinglePacket . Action . TRANSPORT_REPLACE ) ;
final Content content = new Content ( this . contentCreator , this . contentName ) ;
content . setSenders ( this . contentSenders ) ;
2020-04-01 11:25:52 +00:00
this . transportId = JingleConnectionManager . nextRandomId ( ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new IbbTransportInfo ( this . transportId , this . ibbBlockSize ) ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2019-06-16 20:57:10 +00:00
this . sendJinglePacket ( packet ) ;
}
2020-04-02 08:59:25 +00:00
private void receiveFallbackToIbb ( final JinglePacket packet , final IbbTransportInfo transportInfo ) {
2020-04-03 06:16:55 +00:00
if ( isInitiator ( ) ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received out of order transport-replace (we were initiating) " ) ;
2019-11-08 21:27:14 +00:00
respondToIqWithOutOfOrder ( packet ) ;
return ;
}
2019-11-09 08:31:35 +00:00
final boolean validState = mJingleStatus = = JINGLE_STATUS_ACCEPTED | | ( proxyActivationFailed & & mJingleStatus = = JINGLE_STATUS_TRANSMITTING ) ;
if ( ! validState ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received out of order transport-replace " ) ;
2019-11-08 21:27:14 +00:00
respondToIqWithOutOfOrder ( packet ) ;
return ;
}
2019-11-09 08:31:35 +00:00
this . proxyActivationFailed = false ; //fallback received; now we no longer need to accept another one;
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : receiving fallback to ibb " ) ;
2020-04-02 08:59:25 +00:00
final int remoteBlockSize = transportInfo . getBlockSize ( ) ;
if ( remoteBlockSize > 0 ) {
this . ibbBlockSize = Math . min ( MAX_IBB_BLOCK_SIZE , remoteBlockSize ) ;
} else {
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : unable to parse block size in transport-replace " ) ;
2019-06-16 20:57:10 +00:00
}
2020-04-02 08:59:25 +00:00
this . transportId = transportInfo . getTransportId ( ) ; //TODO: handle the case where this is null by the remote party
2019-12-05 13:03:54 +00:00
this . transport = new JingleInBandTransport ( this , this . transportId , this . ibbBlockSize ) ;
2019-06-16 20:57:10 +00:00
2020-04-01 11:25:52 +00:00
final JinglePacket answer = bootstrapPacket ( JinglePacket . Action . TRANSPORT_ACCEPT ) ;
2019-06-16 20:57:10 +00:00
final Content content = new Content ( contentCreator , contentName ) ;
2020-05-11 09:09:18 +00:00
content . setSenders ( this . contentSenders ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new IbbTransportInfo ( this . transportId , this . ibbBlockSize ) ) ;
2020-04-05 08:20:34 +00:00
answer . addJingleContent ( content ) ;
2019-06-16 20:57:10 +00:00
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
2020-04-03 06:16:55 +00:00
if ( isInitiator ( ) ) {
2019-06-16 20:57:10 +00:00
this . sendJinglePacket ( answer , ( account , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " recipient ACKed our transport-accept. creating ibb " ) ;
2019-06-16 20:57:10 +00:00
transport . connect ( onIbbTransportConnected ) ;
}
} ) ;
} else {
this . transport . receive ( file , onFileTransmissionStatusChanged ) ;
this . sendJinglePacket ( answer ) ;
}
}
2019-10-05 19:29:36 +00:00
private void receiveTransportAccept ( JinglePacket packet ) {
2019-11-08 21:27:14 +00:00
if ( responding ( ) ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received out of order transport-accept (we were responding) " ) ;
2019-11-08 21:27:14 +00:00
respondToIqWithOutOfOrder ( packet ) ;
return ;
}
2019-11-09 08:31:35 +00:00
final boolean validState = mJingleStatus = = JINGLE_STATUS_ACCEPTED | | ( proxyActivationFailed & & mJingleStatus = = JINGLE_STATUS_TRANSMITTING ) ;
if ( ! validState ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received out of order transport-accept " ) ;
2019-11-08 21:27:14 +00:00
respondToIqWithOutOfOrder ( packet ) ;
return ;
}
2019-11-09 08:31:35 +00:00
this . proxyActivationFailed = false ; //fallback accepted; now we no longer need to accept another one;
2020-04-02 08:59:25 +00:00
final Content content = packet . getJingleContent ( ) ;
final GenericTransportInfo transportInfo = content = = null ? null : content . getTransport ( ) ;
if ( transportInfo instanceof IbbTransportInfo ) {
final IbbTransportInfo ibbTransportInfo = ( IbbTransportInfo ) transportInfo ;
final int remoteBlockSize = ibbTransportInfo . getBlockSize ( ) ;
if ( remoteBlockSize > 0 ) {
this . ibbBlockSize = Math . min ( MAX_IBB_BLOCK_SIZE , remoteBlockSize ) ;
2019-06-16 20:57:10 +00:00
}
2020-04-02 08:59:25 +00:00
final String sid = ibbTransportInfo . getTransportId ( ) ;
2019-12-05 13:03:54 +00:00
this . transport = new JingleInBandTransport ( this , this . transportId , this . ibbBlockSize ) ;
2019-06-16 20:57:10 +00:00
2019-10-01 09:31:15 +00:00
if ( sid = = null | | ! sid . equals ( this . transportId ) ) {
2020-04-01 08:45:03 +00:00
Log . w ( Config . LOGTAG , String . format ( " %s: sid in transport-accept (%s) did not match our sid (%s) " , id . account . getJid ( ) . asBareJid ( ) , sid , transportId ) ) ;
2019-10-01 09:31:15 +00:00
}
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
//might be receive instead if we are not initiating
2020-04-03 06:16:55 +00:00
if ( isInitiator ( ) ) {
2019-06-16 20:57:10 +00:00
this . transport . connect ( onIbbTransportConnected ) ;
}
} else {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received invalid transport-accept " ) ;
2019-10-05 19:29:36 +00:00
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
}
}
private void receiveSuccess ( ) {
2020-04-03 06:16:55 +00:00
if ( isInitiator ( ) ) {
2019-06-16 20:57:10 +00:00
this . mJingleStatus = JINGLE_STATUS_FINISHED ;
2020-04-01 08:45:03 +00:00
this . xmppConnectionService . markMessage ( this . message , Message . STATUS_SEND_RECEIVED ) ;
2019-06-16 20:57:10 +00:00
this . disconnectSocks5Connections ( ) ;
2019-12-05 13:03:54 +00:00
if ( this . transport instanceof JingleInBandTransport ) {
2019-06-16 20:57:10 +00:00
this . transport . disconnect ( ) ;
}
this . message . setTransferable ( null ) ;
2020-04-01 08:45:03 +00:00
this . jingleConnectionManager . finishConnection ( this ) ;
2019-06-16 20:57:10 +00:00
} else {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : received session-terminate/success while responding " ) ;
2019-06-16 20:57:10 +00:00
}
}
@Override
public void cancel ( ) {
this . cancelled = true ;
2020-04-07 19:26:51 +00:00
abort ( Reason . CANCEL ) ;
2019-06-16 20:57:10 +00:00
}
2020-04-07 19:26:51 +00:00
void abort ( final Reason reason ) {
2019-06-16 20:57:10 +00:00
this . disconnectSocks5Connections ( ) ;
2019-12-05 13:03:54 +00:00
if ( this . transport instanceof JingleInBandTransport ) {
2019-06-16 20:57:10 +00:00
this . transport . disconnect ( ) ;
}
2019-10-05 19:58:21 +00:00
sendSessionTerminate ( reason ) ;
2020-04-01 08:45:03 +00:00
this . jingleConnectionManager . finishConnection ( this ) ;
2019-06-16 20:57:10 +00:00
if ( responding ( ) ) {
2019-10-05 19:58:21 +00:00
this . message . setTransferable ( new TransferablePlaceholder ( cancelled ? Transferable . STATUS_CANCELLED : Transferable . STATUS_FAILED ) ) ;
2019-06-16 20:57:10 +00:00
if ( this . file ! = null ) {
file . delete ( ) ;
}
2020-04-01 08:45:03 +00:00
this . jingleConnectionManager . updateConversationUi ( true ) ;
2019-06-16 20:57:10 +00:00
} else {
2020-04-01 08:45:03 +00:00
this . xmppConnectionService . markMessage ( this . message , Message . STATUS_SEND_FAILED , cancelled ? Message . ERROR_MESSAGE_CANCELLED : null ) ;
2019-06-16 20:57:10 +00:00
this . message . setTransferable ( null ) ;
}
}
private void fail ( ) {
fail ( null ) ;
}
private void fail ( String errorMessage ) {
this . mJingleStatus = JINGLE_STATUS_FAILED ;
this . disconnectSocks5Connections ( ) ;
2019-12-05 13:03:54 +00:00
if ( this . transport instanceof JingleInBandTransport ) {
2019-06-16 20:57:10 +00:00
this . transport . disconnect ( ) ;
}
FileBackend . close ( mFileInputStream ) ;
FileBackend . close ( mFileOutputStream ) ;
if ( this . message ! = null ) {
if ( responding ( ) ) {
2019-10-05 19:58:21 +00:00
this . message . setTransferable ( new TransferablePlaceholder ( cancelled ? Transferable . STATUS_CANCELLED : Transferable . STATUS_FAILED ) ) ;
2019-06-16 20:57:10 +00:00
if ( this . file ! = null ) {
file . delete ( ) ;
}
2020-04-01 08:45:03 +00:00
this . jingleConnectionManager . updateConversationUi ( true ) ;
2019-06-16 20:57:10 +00:00
} else {
2020-04-01 08:45:03 +00:00
this . xmppConnectionService . markMessage ( this . message ,
2019-06-16 20:57:10 +00:00
Message . STATUS_SEND_FAILED ,
cancelled ? Message . ERROR_MESSAGE_CANCELLED : errorMessage ) ;
this . message . setTransferable ( null ) ;
}
}
2020-04-01 08:45:03 +00:00
this . jingleConnectionManager . finishConnection ( this ) ;
2019-06-16 20:57:10 +00:00
}
2020-04-07 19:26:51 +00:00
private void sendSessionTerminate ( Reason reason ) {
2020-04-01 11:25:52 +00:00
final JinglePacket packet = bootstrapPacket ( JinglePacket . Action . SESSION_TERMINATE ) ;
2020-04-09 18:35:44 +00:00
packet . setReason ( reason , null ) ;
2019-06-16 20:57:10 +00:00
this . sendJinglePacket ( packet ) ;
}
private void connectNextCandidate ( ) {
for ( JingleCandidate candidate : this . candidates ) {
if ( ( ! connections . containsKey ( candidate . getCid ( ) ) & & ( ! candidate
. isOurs ( ) ) ) ) {
this . connectWithCandidate ( candidate ) ;
return ;
}
}
this . sendCandidateError ( ) ;
}
private void connectWithCandidate ( final JingleCandidate candidate ) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport (
this , candidate ) ;
connections . put ( candidate . getCid ( ) , socksConnection ) ;
socksConnection . connect ( new OnTransportConnected ( ) {
@Override
public void failed ( ) {
Log . d ( Config . LOGTAG ,
" connection failed with " + candidate . getHost ( ) + " : "
+ candidate . getPort ( ) ) ;
connectNextCandidate ( ) ;
}
@Override
public void established ( ) {
Log . d ( Config . LOGTAG ,
" established connection with " + candidate . getHost ( )
+ " : " + candidate . getPort ( ) ) ;
sendCandidateUsed ( candidate . getCid ( ) ) ;
}
} ) ;
}
private void disconnectSocks5Connections ( ) {
Iterator < Entry < String , JingleSocks5Transport > > it = this . connections
. entrySet ( ) . iterator ( ) ;
while ( it . hasNext ( ) ) {
Entry < String , JingleSocks5Transport > pairs = it . next ( ) ;
pairs . getValue ( ) . disconnect ( ) ;
it . remove ( ) ;
}
}
private void sendProxyActivated ( String cid ) {
2020-04-01 11:25:52 +00:00
final JinglePacket packet = bootstrapPacket ( JinglePacket . Action . TRANSPORT_INFO ) ;
2019-09-01 19:41:28 +00:00
final Content content = new Content ( this . contentCreator , this . contentName ) ;
2020-05-11 09:09:18 +00:00
content . setSenders ( this . contentSenders ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new S5BTransportInfo ( this . transportId , new Element ( " activated " ) . setAttribute ( " cid " , cid ) ) ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2019-06-16 20:57:10 +00:00
this . sendJinglePacket ( packet ) ;
}
2019-09-01 19:41:28 +00:00
private void sendProxyError ( ) {
2020-04-01 11:25:52 +00:00
final JinglePacket packet = bootstrapPacket ( JinglePacket . Action . TRANSPORT_INFO ) ;
2019-09-01 19:41:28 +00:00
final Content content = new Content ( this . contentCreator , this . contentName ) ;
2020-05-11 09:09:18 +00:00
content . setSenders ( this . contentSenders ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new S5BTransportInfo ( this . transportId , new Element ( " proxy-error " ) ) ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2019-09-01 19:41:28 +00:00
this . sendJinglePacket ( packet ) ;
}
2019-06-16 20:57:10 +00:00
private void sendCandidateUsed ( final String cid ) {
2020-04-01 11:25:52 +00:00
JinglePacket packet = bootstrapPacket ( JinglePacket . Action . TRANSPORT_INFO ) ;
2020-04-02 08:59:25 +00:00
final Content content = new Content ( this . contentCreator , this . contentName ) ;
2020-05-11 09:09:18 +00:00
content . setSenders ( this . contentSenders ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new S5BTransportInfo ( this . transportId , new Element ( " candidate-used " ) . setAttribute ( " cid " , cid ) ) ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2019-06-16 20:57:10 +00:00
this . sentCandidate = true ;
if ( ( receivedCandidate ) & & ( mJingleStatus = = JINGLE_STATUS_ACCEPTED ) ) {
connect ( ) ;
}
this . sendJinglePacket ( packet ) ;
}
private void sendCandidateError ( ) {
2020-04-01 08:45:03 +00:00
Log . d ( Config . LOGTAG , id . account . getJid ( ) . asBareJid ( ) + " : sending candidate error " ) ;
2020-04-01 11:25:52 +00:00
JinglePacket packet = bootstrapPacket ( JinglePacket . Action . TRANSPORT_INFO ) ;
2019-06-16 20:57:10 +00:00
Content content = new Content ( this . contentCreator , this . contentName ) ;
2020-05-11 09:09:18 +00:00
content . setSenders ( this . contentSenders ) ;
2020-04-02 08:59:25 +00:00
content . setTransport ( new S5BTransportInfo ( this . transportId , new Element ( " candidate-error " ) ) ) ;
2020-04-05 08:20:34 +00:00
packet . addJingleContent ( content ) ;
2019-06-16 20:57:10 +00:00
this . sentCandidate = true ;
this . sendJinglePacket ( packet ) ;
if ( receivedCandidate & & mJingleStatus = = JINGLE_STATUS_ACCEPTED ) {
connect ( ) ;
}
}
public int getJingleStatus ( ) {
return this . mJingleStatus ;
}
private boolean equalCandidateExists ( JingleCandidate candidate ) {
for ( JingleCandidate c : this . candidates ) {
if ( c . equalValues ( candidate ) ) {
return true ;
}
}
return false ;
}
private void mergeCandidate ( JingleCandidate candidate ) {
for ( JingleCandidate c : this . candidates ) {
if ( c . equals ( candidate ) ) {
return ;
}
}
this . candidates . add ( candidate ) ;
}
private void mergeCandidates ( List < JingleCandidate > candidates ) {
2019-09-01 09:34:36 +00:00
Collections . sort ( candidates , ( a , b ) - > Integer . compare ( b . getPriority ( ) , a . getPriority ( ) ) ) ;
2019-06-16 20:57:10 +00:00
for ( JingleCandidate c : candidates ) {
mergeCandidate ( c ) ;
}
}
private JingleCandidate getCandidate ( String cid ) {
for ( JingleCandidate c : this . candidates ) {
if ( c . getCid ( ) . equals ( cid ) ) {
return c ;
}
}
return null ;
}
void updateProgress ( int i ) {
this . mProgress = i ;
2020-04-01 08:45:03 +00:00
jingleConnectionManager . updateConversationUi ( false ) ;
2019-06-16 20:57:10 +00:00
}
public String getTransportId ( ) {
return this . transportId ;
}
2020-04-01 16:35:36 +00:00
public FileTransferDescription . Version getFtVersion ( ) {
return this . description . getVersion ( ) ;
2019-06-16 20:57:10 +00:00
}
public JingleTransport getTransport ( ) {
return this . transport ;
}
public boolean start ( ) {
2020-04-01 08:45:03 +00:00
if ( id . account . getStatus ( ) = = Account . State . ONLINE ) {
2019-06-16 20:57:10 +00:00
if ( mJingleStatus = = JINGLE_STATUS_INITIATED ) {
2019-06-17 07:51:49 +00:00
new Thread ( this : : sendAccept ) . start ( ) ;
2019-06-16 20:57:10 +00:00
}
return true ;
} else {
return false ;
}
}
@Override
public int getStatus ( ) {
return this . mStatus ;
}
@Override
public long getFileSize ( ) {
if ( this . file ! = null ) {
return this . file . getExpectedSize ( ) ;
} else {
return 0 ;
}
}
@Override
public int getProgress ( ) {
return this . mProgress ;
}
2020-04-01 11:25:52 +00:00
AbstractConnectionManager getConnectionManager ( ) {
2020-04-01 08:45:03 +00:00
return this . jingleConnectionManager ;
2019-06-16 20:57:10 +00:00
}
interface OnProxyActivated {
void success ( ) ;
void failed ( ) ;
}
2014-04-07 18:05:45 +00:00
}