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 ;
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 ;
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 ;
2014-04-07 18:05:45 +00:00
import eu.siacs.conversations.services.XmppConnectionService ;
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 ;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket ;
2014-04-13 09:32:45 +00:00
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason ;
2014-04-08 21:15:55 +00:00
import eu.siacs.conversations.xmpp.stanzas.IqPacket ;
2018-03-05 17:30:40 +00:00
import rocks.xmpp.addr.Jid ;
2014-04-07 18:05:45 +00:00
2015-07-10 13:11:03 +00:00
public class JingleConnection implements Transferable {
2014-04-07 18:05:45 +00:00
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 ;
static final int JINGLE_STATUS_TRANSMITTING = 5 ;
private static final int JINGLE_STATUS_FAILED = 99 ;
private static final int JINGLE_STATUS_OFFERED = - 1 ;
private JingleConnectionManager mJingleConnectionManager ;
private XmppConnectionService mXmppConnectionService ;
private Content . Version ftVersion = Content . Version . FT_3 ;
private int ibbBlockSize = 8192 ;
private int mJingleStatus = JINGLE_STATUS_OFFERED ;
private int mStatus = Transferable . STATUS_UNKNOWN ;
private Message message ;
private String sessionId ;
private Account account ;
private Jid initiator ;
private Jid responder ;
private List < JingleCandidate > candidates = new ArrayList < > ( ) ;
private ConcurrentHashMap < String , JingleSocks5Transport > connections = new ConcurrentHashMap < > ( ) ;
private String transportId ;
private Element fileOffer ;
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 ;
private String contentCreator ;
private Transport 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 {
Log . d ( Config . LOGTAG , " ignoring late delivery of jingle packet to jingle session with status= " + mJingleStatus + " : " + packet . toString ( ) ) ;
}
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 ( ) ) {
if ( expectedHash . length > 0 & & ! Arrays . equals ( expectedHash , file . getSha1Sum ( ) ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : hashes did not match " ) ;
}
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : file transmitted(). we are responding " ) ;
sendSuccess ( ) ;
mXmppConnectionService . getFileBackend ( ) . updateFileParams ( message ) ;
mXmppConnectionService . databaseBackend . createMessage ( message ) ;
mXmppConnectionService . markMessage ( message , Message . STATUS_RECEIVED ) ;
if ( acceptedAutomatically ) {
message . markUnread ( ) ;
if ( message . getEncryption ( ) = = Message . ENCRYPTION_PGP ) {
account . getPgpDecryptionService ( ) . decrypt ( message , true ) ;
} else {
mXmppConnectionService . getFileBackend ( ) . updateMediaScanner ( file , ( ) - > JingleConnection . this . mXmppConnectionService . getNotificationService ( ) . push ( message ) ) ;
}
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 ) {
account . getPgpDecryptionService ( ) . decrypt ( message , true ) ;
2019-06-16 20:57:10 +00:00
}
} else {
if ( ftVersion = = Content . Version . FT_5 ) { //older Conversations will break when receiving a session-info
sendHash ( ) ;
}
if ( message . getEncryption ( ) = = Message . ENCRYPTION_PGP ) {
account . getPgpDecryptionService ( ) . decrypt ( message , false ) ;
}
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 ) {
mXmppConnectionService . getFileBackend ( ) . updateMediaScanner ( file ) ;
}
}
@Override
public void onFileTransferAborted ( ) {
2019-10-05 19:58:21 +00:00
JingleConnection . this . sendSessionTerminate ( " connectivity-error " ) ;
2019-06-16 20:57:10 +00:00
JingleConnection . this . fail ( ) ;
}
} ;
private OnTransportConnected onIbbTransportConnected = new OnTransportConnected ( ) {
@Override
public void failed ( ) {
Log . d ( Config . LOGTAG , " ibb open failed " ) ;
}
@Override
public void established ( ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : ibb transport connected. sending file " ) ;
mJingleStatus = JINGLE_STATUS_TRANSMITTING ;
JingleConnection . this . transport . send ( file , onFileTransmissionStatusChanged ) ;
}
} ;
private OnProxyActivated onProxyActivated = new OnProxyActivated ( ) {
@Override
public void success ( ) {
if ( initiator . equals ( account . getJid ( ) ) ) {
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 ( ) {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : proxy activation failed " ) ;
2019-11-08 21:27:14 +00:00
proxyActivationFailed = true ;
2019-09-01 19:41:28 +00:00
if ( initiating ( ) ) {
sendFallbackToIbb ( ) ;
}
2019-06-16 20:57:10 +00:00
}
} ;
public JingleConnection ( JingleConnectionManager mJingleConnectionManager ) {
this . mJingleConnectionManager = mJingleConnectionManager ;
this . mXmppConnectionService = mJingleConnectionManager
. getXmppConnectionService ( ) ;
}
private boolean responding ( ) {
return responder ! = null & & responder . equals ( account . getJid ( ) ) ;
}
private boolean initiating ( ) {
return initiator . equals ( account . getJid ( ) ) ;
}
InputStream getFileInputStream ( ) {
return this . mFileInputStream ;
}
OutputStream getFileOutputStream ( ) throws IOException {
if ( this . file = = null ) {
Log . d ( Config . LOGTAG , " file object was not assigned " ) ;
return null ;
}
this . file . getParentFile ( ) . mkdirs ( ) ;
this . file . createNewFile ( ) ;
this . mFileOutputStream = AbstractConnectionManager . createOutputStream ( this . file ) ;
return this . mFileOutputStream ;
}
public String getSessionId ( ) {
return this . sessionId ;
}
public Account getAccount ( ) {
return this . account ;
}
public Jid getCounterPart ( ) {
return this . message . getCounterpart ( ) ;
}
2019-10-05 19:29:36 +00:00
void deliverPacket ( JinglePacket packet ) {
2019-06-16 20:57:10 +00:00
if ( packet . isAction ( " session-terminate " ) ) {
Reason reason = packet . getReason ( ) ;
if ( reason ! = null ) {
if ( reason . hasChild ( " cancel " ) ) {
2019-10-05 19:58:21 +00:00
this . cancelled = true ;
2019-06-16 20:57:10 +00:00
this . fail ( ) ;
} else if ( reason . hasChild ( " success " ) ) {
this . receiveSuccess ( ) ;
} else {
2019-10-05 20:14:38 +00:00
final List < Element > children = reason . getChildren ( ) ;
if ( children . size ( ) = = 1 ) {
this . fail ( children . get ( 0 ) . getName ( ) ) ;
} else {
this . fail ( ) ;
}
2019-06-16 20:57:10 +00:00
}
} else {
this . fail ( ) ;
}
} else if ( packet . isAction ( " session-accept " ) ) {
2019-10-05 19:29:36 +00:00
receiveAccept ( packet ) ;
2019-06-16 20:57:10 +00:00
} else if ( packet . isAction ( " session-info " ) ) {
2019-10-05 19:29:36 +00:00
final Element checksum = packet . getChecksum ( ) ;
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 ) ;
2019-06-16 20:57:10 +00:00
} else if ( packet . isAction ( " transport-info " ) ) {
2019-10-05 19:29:36 +00:00
receiveTransportInfo ( packet ) ;
2019-06-16 20:57:10 +00:00
} else if ( packet . isAction ( " transport-replace " ) ) {
if ( packet . getJingleContent ( ) . hasIbbTransport ( ) ) {
2019-10-05 19:29:36 +00:00
receiveFallbackToIbb ( packet ) ;
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
}
} else if ( packet . isAction ( " 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
}
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
}
mXmppConnectionService . sendIqPacket ( account , response , null ) ;
}
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 " ) ;
mXmppConnectionService . sendIqPacket ( account , response , null ) ;
}
2019-06-16 20:57:10 +00:00
public void init ( final Message message ) {
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 ) ;
}
}
private void init ( Message message , XmppAxolotlMessage xmppAxolotlMessage ) {
this . mXmppAxolotlMessage = xmppAxolotlMessage ;
this . contentCreator = " initiator " ;
this . contentName = this . mJingleConnectionManager . nextRandomId ( ) ;
this . message = message ;
this . account = message . getConversation ( ) . getAccount ( ) ;
2019-09-04 14:14:01 +00:00
final List < String > remoteFeatures = getRemoteFeatures ( ) ;
upgradeNamespace ( remoteFeatures ) ;
this . initialTransport = remoteFeatures . contains ( Namespace . JINGLE_TRANSPORTS_S5B ) ? Transport . SOCKS : Transport . IBB ;
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 ;
this . initiator = this . account . getJid ( ) ;
this . responder = this . message . getCounterpart ( ) ;
this . sessionId = this . mJingleConnectionManager . nextRandomId ( ) ;
this . transportId = this . mJingleConnectionManager . nextRandomId ( ) ;
if ( this . initialTransport = = Transport . IBB ) {
this . sendInitRequest ( ) ;
} else {
2019-09-01 18:42:07 +00:00
gatherAndConnectDirectCandidates ( ) ;
2019-09-02 06:51:50 +00:00
this . mJingleConnectionManager . getPrimaryCandidate ( account , initiating ( ) , ( 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 ) {
if ( account . isOnion ( ) | | mXmppConnectionService . useTorToConnect ( ) ) {
directCandidates = Collections . emptyList ( ) ;
} else {
directCandidates = DirectConnectionUtils . getLocalCandidates ( account . getJid ( ) ) ;
}
} else {
directCandidates = Collections . emptyList ( ) ;
}
for ( JingleCandidate directCandidate : directCandidates ) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport ( this , directCandidate ) ;
connections . put ( directCandidate . getCid ( ) , socksConnection ) ;
candidates . add ( directCandidate ) ;
}
}
2019-09-04 14:14:01 +00:00
private void upgradeNamespace ( List < String > remoteFeatures ) {
if ( remoteFeatures . contains ( Content . Version . FT_5 . getNamespace ( ) ) ) {
2019-06-16 20:57:10 +00:00
this . ftVersion = Content . Version . FT_5 ;
2019-09-04 14:14:01 +00:00
} else if ( remoteFeatures . contains ( Content . Version . FT_4 . getNamespace ( ) ) ) {
2019-06-16 20:57:10 +00:00
this . ftVersion = Content . Version . FT_4 ;
}
}
private List < String > getRemoteFeatures ( ) {
Jid jid = this . message . getCounterpart ( ) ;
String resource = jid ! = null ? jid . getResource ( ) : null ;
if ( resource ! = null ) {
Presence presence = this . account . getRoster ( ) . getContact ( jid ) . getPresences ( ) . getPresences ( ) . get ( resource ) ;
ServiceDiscoveryResult result = presence ! = null ? presence . getServiceDiscoveryResult ( ) : null ;
return result = = null ? Collections . emptyList ( ) : result . getFeatures ( ) ;
} else {
return Collections . emptyList ( ) ;
}
}
public void init ( Account account , JinglePacket packet ) {
this . mJingleStatus = JINGLE_STATUS_INITIATED ;
Conversation conversation = this . mXmppConnectionService
. findOrCreateConversation ( account ,
packet . getFrom ( ) . asBareJid ( ) , false , false ) ;
this . message = new Message ( conversation , " " , Message . ENCRYPTION_NONE ) ;
this . message . setStatus ( Message . STATUS_RECEIVED ) ;
this . mStatus = Transferable . STATUS_OFFER ;
this . message . setTransferable ( this ) ;
2014-11-06 19:10:03 +00:00
final Jid from = packet . getFrom ( ) ;
2019-06-16 20:57:10 +00:00
this . message . setCounterpart ( from ) ;
this . account = account ;
this . initiator = packet . getFrom ( ) ;
this . responder = this . account . getJid ( ) ;
this . sessionId = packet . getSessionId ( ) ;
Content content = packet . getJingleContent ( ) ;
this . contentCreator = content . getAttribute ( " creator " ) ;
this . initialTransport = content . hasSocks5Transport ( ) ? Transport . SOCKS : Transport . IBB ;
this . contentName = content . getAttribute ( " name " ) ;
this . transportId = content . getTransportId ( ) ;
2019-06-17 07:51:49 +00:00
2019-06-16 20:57:10 +00:00
if ( this . initialTransport = = Transport . SOCKS ) {
this . mergeCandidates ( JingleCandidate . parse ( content . socks5transport ( ) . getChildren ( ) ) ) ;
} else if ( this . initialTransport = = Transport . IBB ) {
final String receivedBlockSize = content . ibbTransport ( ) . getAttribute ( " block-size " ) ;
if ( receivedBlockSize ! = null ) {
try {
this . ibbBlockSize = Math . min ( Integer . parseInt ( receivedBlockSize ) , this . ibbBlockSize ) ;
} catch ( NumberFormatException e ) {
Log . d ( Config . LOGTAG , " number format exception " + e . getMessage ( ) ) ;
2019-10-05 19:58:21 +00:00
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
this . fail ( ) ;
return ;
}
} else {
2019-06-17 07:51:49 +00:00
Log . d ( Config . LOGTAG , " received block size was null " ) ;
2019-10-05 19:58:21 +00:00
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
this . fail ( ) ;
return ;
}
}
this . ftVersion = content . getVersion ( ) ;
if ( ftVersion = = null ) {
2019-10-05 19:58:21 +00:00
respondToIq ( packet , false ) ;
2019-06-16 20:57:10 +00:00
this . fail ( ) ;
return ;
}
this . fileOffer = content . getFileOffer ( this . ftVersion ) ;
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 " ) ) ) {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , 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 ) ;
mJingleConnectionManager . updateConversationUi ( true ) ;
this . file = this . mXmppConnectionService . getFileBackend ( ) . getFile ( message , false ) ;
if ( mXmppAxolotlMessage ! = null ) {
XmppAxolotlMessage . XmppAxolotlKeyTransportMessage transportMessage = account . getAxolotlService ( ) . processReceivingKeyTransportMessage ( mXmppAxolotlMessage , false ) ;
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 ) ;
2019-11-09 21:58:02 +00:00
if ( account . getRoster ( ) . getContact ( from ) . showInContactList ( )
& & mJingleConnectionManager . hasStoragePermission ( )
2019-06-16 20:57:10 +00:00
& & size < this . mJingleConnectionManager . getAutoAcceptFileSize ( )
& & mXmppConnectionService . isDataSaverDisabled ( ) ) {
2019-11-09 21:58:02 +00:00
Log . d ( Config . LOGTAG , " auto accepting file from " + from ) ;
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: "
+ this . mJingleConnectionManager
. getAutoAcceptFileSize ( ) ) ;
this . mXmppConnectionService . getNotificationService ( ) . push ( message ) ;
}
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
}
}
2019-08-16 12:09:42 +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
private void sendInitRequest ( ) {
JinglePacket packet = this . bootstrapPacket ( " session-initiate " ) ;
Content content = new Content ( this . contentCreator , this . contentName ) ;
if ( message . isFileOrImage ( ) ) {
content . setTransportId ( this . transportId ) ;
this . file = this . mXmppConnectionService . getFileBackend ( ) . getFile ( message , false ) ;
if ( message . getEncryption ( ) = = Message . ENCRYPTION_AXOLOTL ) {
this . file . setKey ( mXmppAxolotlMessage . getInnerKey ( ) ) ;
this . file . setIv ( mXmppAxolotlMessage . getIV ( ) ) ;
2019-09-06 11:11:37 +00:00
//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 ) ) ;
2019-09-04 14:14:01 +00:00
final Element file = content . setFileOffer ( this . file , false , this . ftVersion ) ;
if ( remoteSupportsOmemoJet ) {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : remote announced support for JET " ) ;
2019-09-04 14:14:01 +00:00
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 ) ;
} else {
file . addChild ( mXmppAxolotlMessage . toElement ( ) ) ;
}
2019-06-16 20:57:10 +00:00
} else {
this . file . setExpectedSize ( file . getSize ( ) ) ;
content . setFileOffer ( this . file , false , this . ftVersion ) ;
}
message . resetFileParams ( ) ;
try {
this . mFileInputStream = new FileInputStream ( file ) ;
} catch ( FileNotFoundException e ) {
2019-10-05 19:58:21 +00:00
fail ( e . getMessage ( ) ) ;
2019-06-16 20:57:10 +00:00
return ;
}
content . setTransportId ( this . transportId ) ;
if ( this . initialTransport = = Transport . IBB ) {
content . ibbTransport ( ) . setAttribute ( " block-size " , Integer . toString ( this . ibbBlockSize ) ) ;
2019-10-03 07:37:01 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : sending IBB offer " ) ;
2019-06-16 20:57:10 +00:00
} else {
2019-10-03 07:37:01 +00:00
final List < Element > candidates = getCandidatesAsElements ( ) ;
Log . d ( Config . LOGTAG , String . format ( " %s: sending S5B offer with %d candidates " , account . getJid ( ) . asBareJid ( ) , candidates . size ( ) ) ) ;
content . socks5transport ( ) . setChildren ( candidates ) ;
2019-06-16 20:57:10 +00:00
}
packet . setContent ( content ) ;
this . sendJinglePacket ( packet , ( account , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : other party received offer " ) ;
if ( mJingleStatus = = JINGLE_STATUS_OFFERED ) {
mJingleStatus = JINGLE_STATUS_INITIATED ;
mXmppConnectionService . markMessage ( message , Message . STATUS_OFFERED ) ;
} else {
Log . d ( Config . LOGTAG , " received ack for offer when status was " + mJingleStatus ) ;
}
} else {
fail ( IqParser . extractErrorMessage ( response ) ) ;
}
} ) ;
}
}
private void sendHash ( ) {
JinglePacket packet = this . bootstrapPacket ( " session-info " ) ;
packet . addChecksum ( file . getSha1Sum ( ) , ftVersion . getNamespace ( ) ) ;
this . sendJinglePacket ( packet ) ;
}
private List < Element > getCandidatesAsElements ( ) {
List < Element > elements = new ArrayList < > ( ) ;
for ( JingleCandidate c : this . candidates ) {
if ( c . isOurs ( ) ) {
elements . add ( c . toElement ( ) ) ;
}
}
return elements ;
}
private void sendAccept ( ) {
mJingleStatus = JINGLE_STATUS_ACCEPTED ;
this . mStatus = Transferable . STATUS_DOWNLOADING ;
this . mJingleConnectionManager . updateConversationUi ( true ) ;
if ( initialTransport = = Transport . SOCKS ) {
sendAcceptSocks ( ) ;
} else {
sendAcceptIbb ( ) ;
}
}
private void sendAcceptSocks ( ) {
2019-09-01 18:42:07 +00:00
gatherAndConnectDirectCandidates ( ) ;
2019-09-02 06:51:50 +00:00
this . mJingleConnectionManager . getPrimaryCandidate ( this . account , initiating ( ) , ( success , candidate ) - > {
2019-06-16 20:57:10 +00:00
final JinglePacket packet = bootstrapPacket ( " session-accept " ) ;
final Content content = new Content ( contentCreator , contentName ) ;
content . setFileOffer ( fileOffer , ftVersion ) ;
content . setTransportId ( transportId ) ;
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 " ) ;
2019-06-16 20:57:10 +00:00
content . socks5transport ( ) . setChildren ( getCandidatesAsElements ( ) ) ;
packet . setContent ( content ) ;
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 ) ;
content . socks5transport ( ) . setChildren ( getCandidatesAsElements ( ) ) ;
packet . setContent ( content ) ;
sendJinglePacket ( packet ) ;
connectNextCandidate ( ) ;
}
} ) ;
} else {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , " did not find a proxy65 candidate for ourselves " ) ;
2019-06-16 20:57:10 +00:00
content . socks5transport ( ) . setChildren ( getCandidatesAsElements ( ) ) ;
packet . setContent ( content ) ;
sendJinglePacket ( packet ) ;
connectNextCandidate ( ) ;
}
} ) ;
}
private void sendAcceptIbb ( ) {
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
final JinglePacket packet = bootstrapPacket ( " session-accept " ) ;
final Content content = new Content ( contentCreator , contentName ) ;
content . setFileOffer ( fileOffer , ftVersion ) ;
content . setTransportId ( transportId ) ;
content . ibbTransport ( ) . setAttribute ( " block-size " , this . ibbBlockSize ) ;
packet . setContent ( content ) ;
this . transport . receive ( file , onFileTransmissionStatusChanged ) ;
this . sendJinglePacket ( packet ) ;
}
private JinglePacket bootstrapPacket ( String action ) {
JinglePacket packet = new JinglePacket ( ) ;
packet . setAction ( action ) ;
packet . setFrom ( account . getJid ( ) ) ;
packet . setTo ( this . message . getCounterpart ( ) ) ;
packet . setSessionId ( this . sessionId ) ;
packet . setInitiator ( this . initiator ) ;
return packet ;
}
private void sendJinglePacket ( JinglePacket packet ) {
mXmppConnectionService . sendIqPacket ( account , packet , responseListener ) ;
}
private void sendJinglePacket ( JinglePacket packet , OnIqPacketReceived callback ) {
mXmppConnectionService . sendIqPacket ( account , packet , callback ) ;
}
2019-10-05 19:29:36 +00:00
private void receiveAccept ( JinglePacket packet ) {
2019-11-08 21:27:14 +00:00
if ( responding ( ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : received out of order session-accept (we were responding) " ) ;
respondToIqWithOutOfOrder ( packet ) ;
return ;
}
2019-06-17 07:51:49 +00:00
if ( this . mJingleStatus ! = JINGLE_STATUS_INITIATED ) {
2019-09-01 09:34:36 +00:00
Log . d ( Config . LOGTAG , 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 ;
mXmppConnectionService . markMessage ( message , Message . STATUS_UNSEND ) ;
Content content = packet . getJingleContent ( ) ;
if ( content . hasSocks5Transport ( ) ) {
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
mergeCandidates ( JingleCandidate . parse ( content . socks5transport ( ) . getChildren ( ) ) ) ;
this . connectNextCandidate ( ) ;
} else if ( content . hasIbbTransport ( ) ) {
String receivedBlockSize = packet . getJingleContent ( ) . ibbTransport ( ) . getAttribute ( " block-size " ) ;
if ( receivedBlockSize ! = null ) {
2019-08-22 12:47:30 +00:00
try {
int bs = Integer . parseInt ( receivedBlockSize ) ;
if ( bs > this . ibbBlockSize ) {
this . ibbBlockSize = bs ;
}
} catch ( Exception e ) {
2019-09-01 09:34:36 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : unable to parse block size in session-accept " ) ;
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 ( ) ;
2019-06-16 20:57:10 +00:00
if ( content . hasSocks5Transport ( ) ) {
if ( content . socks5transport ( ) . 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 {
String cid = content . socks5transport ( ) . findChild ( " activated " ) . getAttribute ( " cid " ) ;
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 " ) ;
2019-10-05 19:58:21 +00:00
sendSessionTerminate ( " failed-transport " ) ;
2019-06-16 20:57:10 +00:00
this . fail ( ) ;
}
}
} else if ( content . socks5transport ( ) . hasChild ( " proxy-error " ) ) {
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
onProxyActivated . failed ( ) ;
} else if ( content . socks5transport ( ) . hasChild ( " candidate-error " ) ) {
2019-09-01 13:06:59 +00:00
Log . d ( Config . LOGTAG , 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 ( ) ;
}
} else if ( content . socks5transport ( ) . hasChild ( " candidate-used " ) ) {
String cid = content . socks5transport ( ) . findChild ( " candidate-used " ) . getAttribute ( " cid " ) ;
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 ) {
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : could not find suitable candidate " ) ;
2019-06-16 20:57:10 +00:00
this . disconnectSocks5Connections ( ) ;
if ( initiating ( ) ) {
this . sendFallbackToIbb ( ) ;
}
} else {
2019-09-04 14:14:01 +00:00
final JingleCandidate candidate = connection . getCandidate ( ) ;
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : elected candidate " + candidate . getHost ( ) + " : " + candidate . getPort ( ) ) ;
2019-06-16 20:57:10 +00:00
this . mJingleStatus = JINGLE_STATUS_TRANSMITTING ;
if ( connection . needsActivation ( ) ) {
if ( connection . getCandidate ( ) . isOurs ( ) ) {
final String sid ;
if ( ftVersion = = Content . Version . FT_3 ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : use session ID instead of transport ID to activate proxy " ) ;
sid = getSessionId ( ) ;
} 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 " )
. setContent ( this . getCounterPart ( ) . toString ( ) ) ;
mXmppConnectionService . sendIqPacket ( account , activation , ( account , response ) - > {
if ( response . getType ( ) ! = IqPacket . TYPE . RESULT ) {
2019-09-01 13:06:59 +00:00
Log . d ( Config . LOGTAG , 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 {
if ( initiating ( ) ) {
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 ( ) {
JingleSocks5Transport connection = null ;
for ( Entry < String , JingleSocks5Transport > cursor : connections
. entrySet ( ) ) {
JingleSocks5Transport currentConnection = cursor . getValue ( ) ;
// Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString());
if ( currentConnection . isEstablished ( )
& & ( currentConnection . getCandidate ( ) . isUsedByCounterpart ( ) | | ( ! currentConnection
. getCandidate ( ) . isOurs ( ) ) ) ) {
// Log.d(Config.LOGTAG,"is usable");
if ( connection = = null ) {
connection = currentConnection ;
} else {
if ( connection . getCandidate ( ) . getPriority ( ) < currentConnection
. getCandidate ( ) . getPriority ( ) ) {
connection = currentConnection ;
} else if ( connection . getCandidate ( ) . getPriority ( ) = = currentConnection
. getCandidate ( ) . getPriority ( ) ) {
// Log.d(Config.LOGTAG,"found two candidates with same priority");
if ( initiating ( ) ) {
if ( currentConnection . getCandidate ( ) . isOurs ( ) ) {
connection = currentConnection ;
}
} else {
if ( ! currentConnection . getCandidate ( ) . isOurs ( ) ) {
connection = currentConnection ;
}
}
}
}
}
}
return connection ;
}
private void sendSuccess ( ) {
2019-10-05 19:58:21 +00:00
sendSessionTerminate ( " 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 ) ;
this . mXmppConnectionService . updateMessage ( message , false ) ;
this . mJingleConnectionManager . finishConnection ( this ) ;
}
private void sendFallbackToIbb ( ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : sending fallback to ibb " ) ;
JinglePacket packet = this . bootstrapPacket ( " transport-replace " ) ;
Content content = new Content ( this . contentCreator , this . contentName ) ;
this . transportId = this . mJingleConnectionManager . nextRandomId ( ) ;
content . setTransportId ( this . transportId ) ;
content . ibbTransport ( ) . setAttribute ( " block-size " ,
Integer . toString ( this . ibbBlockSize ) ) ;
packet . setContent ( content ) ;
this . sendJinglePacket ( packet ) ;
}
2019-10-05 19:29:36 +00:00
private void receiveFallbackToIbb ( JinglePacket packet ) {
2019-11-08 21:27:14 +00:00
if ( initiating ( ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : received out of order transport-replace (we were initiating) " ) ;
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 ) {
2019-11-08 21:27:14 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : received out of order transport-replace " ) ;
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;
2019-09-05 11:17:45 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : receiving fallback to ibb " ) ;
2019-08-11 14:54:00 +00:00
final String receivedBlockSize = packet . getJingleContent ( ) . ibbTransport ( ) . getAttribute ( " block-size " ) ;
2019-06-16 20:57:10 +00:00
if ( receivedBlockSize ! = null ) {
2019-08-22 12:47:30 +00:00
try {
final int bs = Integer . parseInt ( receivedBlockSize ) ;
if ( bs < this . ibbBlockSize ) {
this . ibbBlockSize = bs ;
}
} catch ( NumberFormatException e ) {
2019-09-01 09:34:36 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : unable to parse block size in transport-replace " ) ;
2019-06-16 20:57:10 +00:00
}
}
this . transportId = packet . getJingleContent ( ) . 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-08-11 14:54:00 +00:00
final JinglePacket answer = bootstrapPacket ( " transport-accept " ) ;
2019-06-16 20:57:10 +00:00
final Content content = new Content ( contentCreator , contentName ) ;
content . ibbTransport ( ) . setAttribute ( " block-size " , this . ibbBlockSize ) ;
2019-08-22 12:47:30 +00:00
content . ibbTransport ( ) . setAttribute ( " sid " , this . transportId ) ;
2019-06-16 20:57:10 +00:00
answer . setContent ( content ) ;
2019-10-05 19:29:36 +00:00
respondToIq ( packet , true ) ;
2019-06-16 20:57:10 +00:00
if ( initiating ( ) ) {
this . sendJinglePacket ( answer , ( account , response ) - > {
if ( response . getType ( ) = = IqPacket . TYPE . RESULT ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " recipient ACKed our transport-accept. creating ibb " ) ;
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 ( ) ) {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : received out of order transport-accept (we were responding) " ) ;
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 ) {
2019-11-08 21:27:14 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : received out of order transport-accept " ) ;
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;
2019-06-16 20:57:10 +00:00
if ( packet . getJingleContent ( ) . hasIbbTransport ( ) ) {
2019-10-01 09:31:15 +00:00
final Element ibbTransport = packet . getJingleContent ( ) . ibbTransport ( ) ;
final String receivedBlockSize = ibbTransport . getAttribute ( " block-size " ) ;
final String sid = ibbTransport . getAttribute ( " sid " ) ;
2019-06-16 20:57:10 +00:00
if ( receivedBlockSize ! = null ) {
2019-08-22 12:47:30 +00:00
try {
int bs = Integer . parseInt ( receivedBlockSize ) ;
if ( bs < this . ibbBlockSize ) {
this . ibbBlockSize = bs ;
}
} catch ( NumberFormatException e ) {
2019-09-01 09:34:36 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : unable to parse block size in transport-accept " ) ;
2019-06-16 20:57:10 +00:00
}
}
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 ) ) {
2019-10-03 07:37:01 +00:00
Log . w ( Config . LOGTAG , String . format ( " %s: sid in transport-accept (%s) did not match our sid (%s) " , 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
if ( initiating ( ) ) {
this . transport . connect ( onIbbTransportConnected ) ;
}
} else {
2019-10-01 09:31:15 +00:00
Log . d ( Config . LOGTAG , 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 ( ) {
if ( initiating ( ) ) {
this . mJingleStatus = JINGLE_STATUS_FINISHED ;
this . mXmppConnectionService . markMessage ( this . message , Message . STATUS_SEND_RECEIVED ) ;
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 ) ;
this . mJingleConnectionManager . finishConnection ( this ) ;
} else {
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : received session-terminate/success while responding " ) ;
}
}
@Override
public void cancel ( ) {
this . cancelled = true ;
2019-10-05 19:58:21 +00:00
abort ( " cancel " ) ;
2019-06-16 20:57:10 +00:00
}
2019-10-05 19:58:21 +00:00
void abort ( final String 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 ) ;
2019-06-16 20:57:10 +00:00
this . mJingleConnectionManager . finishConnection ( this ) ;
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 ( ) ;
}
this . mJingleConnectionManager . updateConversationUi ( true ) ;
} else {
this . mXmppConnectionService . markMessage ( this . message , Message . STATUS_SEND_FAILED , cancelled ? Message . ERROR_MESSAGE_CANCELLED : null ) ;
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 ( ) ;
}
this . mJingleConnectionManager . updateConversationUi ( true ) ;
} else {
this . mXmppConnectionService . markMessage ( this . message ,
Message . STATUS_SEND_FAILED ,
cancelled ? Message . ERROR_MESSAGE_CANCELLED : errorMessage ) ;
this . message . setTransferable ( null ) ;
}
}
this . mJingleConnectionManager . finishConnection ( this ) ;
}
2019-10-05 19:58:21 +00:00
private void sendSessionTerminate ( String reason ) {
final JinglePacket packet = bootstrapPacket ( " session-terminate " ) ;
final Reason r = new Reason ( ) ;
r . addChild ( reason ) ;
packet . setReason ( r ) ;
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 ) {
2019-09-01 19:41:28 +00:00
final JinglePacket packet = bootstrapPacket ( " transport-info " ) ;
final Content content = new Content ( this . contentCreator , this . contentName ) ;
2019-06-16 20:57:10 +00:00
content . setTransportId ( this . transportId ) ;
content . socks5transport ( ) . addChild ( " activated " ) . setAttribute ( " cid " , cid ) ;
packet . setContent ( content ) ;
this . sendJinglePacket ( packet ) ;
}
2019-09-01 19:41:28 +00:00
private void sendProxyError ( ) {
final JinglePacket packet = bootstrapPacket ( " transport-info " ) ;
final Content content = new Content ( this . contentCreator , this . contentName ) ;
content . setTransportId ( this . transportId ) ;
content . socks5transport ( ) . addChild ( " proxy-error " ) ;
packet . setContent ( content ) ;
this . sendJinglePacket ( packet ) ;
}
2019-06-16 20:57:10 +00:00
private void sendCandidateUsed ( final String cid ) {
JinglePacket packet = bootstrapPacket ( " transport-info " ) ;
Content content = new Content ( this . contentCreator , this . contentName ) ;
content . setTransportId ( this . transportId ) ;
content . socks5transport ( ) . addChild ( " candidate-used " ) . setAttribute ( " cid " , cid ) ;
packet . setContent ( content ) ;
this . sentCandidate = true ;
if ( ( receivedCandidate ) & & ( mJingleStatus = = JINGLE_STATUS_ACCEPTED ) ) {
connect ( ) ;
}
this . sendJinglePacket ( packet ) ;
}
private void sendCandidateError ( ) {
2019-09-01 13:06:59 +00:00
Log . d ( Config . LOGTAG , account . getJid ( ) . asBareJid ( ) + " : sending candidate error " ) ;
2019-06-16 20:57:10 +00:00
JinglePacket packet = bootstrapPacket ( " transport-info " ) ;
Content content = new Content ( this . contentCreator , this . contentName ) ;
content . setTransportId ( this . transportId ) ;
content . socks5transport ( ) . addChild ( " candidate-error " ) ;
packet . setContent ( content ) ;
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 ;
mJingleConnectionManager . updateConversationUi ( false ) ;
}
public String getTransportId ( ) {
return this . transportId ;
}
public Content . Version getFtVersion ( ) {
return this . ftVersion ;
}
public boolean hasTransportId ( String sid ) {
return sid . equals ( this . transportId ) ;
}
public JingleTransport getTransport ( ) {
return this . transport ;
}
public boolean start ( ) {
if ( account . getStatus ( ) = = Account . State . ONLINE ) {
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 ;
}
public AbstractConnectionManager getConnectionManager ( ) {
return this . mJingleConnectionManager ;
}
interface OnProxyActivated {
void success ( ) ;
void failed ( ) ;
}
2014-04-07 18:05:45 +00:00
}