2014-04-08 21:15:55 +00:00
package eu.siacs.conversations.xmpp.jingle ;
2014-04-07 18:05:45 +00:00
import java.util.ArrayList ;
2014-04-11 19:13:09 +00:00
import java.util.HashMap ;
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-04-07 18:05:45 +00:00
import android.util.Log ;
import eu.siacs.conversations.entities.Account ;
2014-04-13 09:32:45 +00:00
import eu.siacs.conversations.entities.Conversation ;
2014-04-07 18:05:45 +00:00
import eu.siacs.conversations.entities.Message ;
import eu.siacs.conversations.services.XmppConnectionService ;
import eu.siacs.conversations.xml.Element ;
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 ;
2014-04-07 18:05:45 +00:00
public class JingleConnection {
private JingleConnectionManager mJingleConnectionManager ;
private XmppConnectionService mXmppConnectionService ;
2014-04-10 12:12:08 +00:00
public static final int STATUS_INITIATED = 0 ;
public static final int STATUS_ACCEPTED = 1 ;
2014-04-11 19:13:09 +00:00
public static final int STATUS_TERMINATED = 2 ;
2014-04-13 09:32:45 +00:00
public static final int STATUS_CANCELED = 3 ;
public static final int STATUS_FINISHED = 4 ;
2014-04-13 19:10:36 +00:00
public static final int STATUS_TRANSMITTING = 5 ;
2014-04-10 12:12:08 +00:00
public static final int STATUS_FAILED = 99 ;
private int status = - 1 ;
2014-04-08 21:15:55 +00:00
private Message message ;
2014-04-07 18:05:45 +00:00
private String sessionId ;
private Account account ;
2014-04-08 21:15:55 +00:00
private String initiator ;
private String responder ;
2014-04-11 19:13:09 +00:00
private List < Element > candidates = new ArrayList < Element > ( ) ;
2014-04-13 16:09:40 +00:00
private List < String > candidatesUsedByCounterpart = new ArrayList < String > ( ) ;
2014-04-11 19:13:09 +00:00
private HashMap < String , SocksConnection > connections = new HashMap < String , SocksConnection > ( ) ;
2014-04-13 16:09:40 +00:00
private Content content = new Content ( ) ;
2014-04-13 09:32:45 +00:00
private JingleFile file = null ;
2014-04-07 18:05:45 +00:00
2014-04-08 21:15:55 +00:00
private OnIqPacketReceived responseListener = new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
2014-04-10 12:12:08 +00:00
if ( packet . getType ( ) = = IqPacket . TYPE_ERROR ) {
2014-04-11 19:13:09 +00:00
mXmppConnectionService . markMessage ( message , Message . STATUS_SEND_FAILED ) ;
2014-04-10 12:12:08 +00:00
status = STATUS_FAILED ;
}
2014-04-08 21:15:55 +00:00
}
} ;
public JingleConnection ( JingleConnectionManager mJingleConnectionManager ) {
2014-04-07 18:05:45 +00:00
this . mJingleConnectionManager = mJingleConnectionManager ;
this . mXmppConnectionService = mJingleConnectionManager . getXmppConnectionService ( ) ;
}
public String getSessionId ( ) {
return this . sessionId ;
}
2014-04-10 12:12:08 +00:00
public String getAccountJid ( ) {
2014-04-13 19:10:36 +00:00
return this . account . getFullJid ( ) ;
2014-04-10 12:12:08 +00:00
}
public String getCounterPart ( ) {
return this . message . getCounterpart ( ) ;
}
public void deliverPacket ( JinglePacket packet ) {
2014-04-11 19:13:09 +00:00
if ( packet . isAction ( " session-terminate " ) ) {
2014-04-13 09:32:45 +00:00
Reason reason = packet . getReason ( ) ;
if ( reason . hasChild ( " cancel " ) ) {
this . cancel ( ) ;
} else if ( reason . hasChild ( " success " ) ) {
this . finish ( ) ;
2014-04-11 19:13:09 +00:00
}
} else if ( packet . isAction ( " session-accept " ) ) {
accept ( packet ) ;
2014-04-11 20:49:26 +00:00
} else if ( packet . isAction ( " transport-info " ) ) {
transportInfo ( packet ) ;
2014-04-11 19:13:09 +00:00
} else {
Log . d ( " xmppService " , " packet arrived in connection. action was " + packet . getAction ( ) ) ;
2014-04-10 12:12:08 +00:00
}
}
2014-04-07 18:05:45 +00:00
public void init ( Message message ) {
2014-04-08 21:15:55 +00:00
this . message = message ;
this . account = message . getConversation ( ) . getAccount ( ) ;
this . initiator = this . account . getFullJid ( ) ;
2014-04-11 19:13:09 +00:00
this . responder = this . message . getCounterpart ( ) ;
2014-04-13 09:32:45 +00:00
this . sessionId = this . mJingleConnectionManager . nextRandomId ( ) ;
2014-04-11 19:13:09 +00:00
if ( this . candidates . size ( ) > 0 ) {
2014-04-08 21:15:55 +00:00
this . sendInitRequest ( ) ;
} else {
2014-04-11 19:13:09 +00:00
this . mJingleConnectionManager . getPrimaryCandidate ( account , new OnPrimaryCandidateFound ( ) {
2014-04-08 21:15:55 +00:00
@Override
2014-04-13 16:09:40 +00:00
public void onPrimaryCandidateFound ( boolean success , Element candidate ) {
2014-04-08 21:15:55 +00:00
if ( success ) {
2014-04-13 16:09:40 +00:00
mergeCandidate ( candidate ) ;
2014-04-08 21:15:55 +00:00
}
sendInitRequest ( ) ;
}
} ) ;
}
}
2014-04-13 09:32:45 +00:00
public void init ( Account account , JinglePacket packet ) {
2014-04-13 19:10:36 +00:00
this . status = STATUS_INITIATED ;
2014-04-13 09:32:45 +00:00
Conversation conversation = this . mXmppConnectionService . findOrCreateConversation ( account , packet . getFrom ( ) . split ( " / " ) [ 0 ] , false ) ;
this . message = new Message ( conversation , " receiving image file " , Message . ENCRYPTION_NONE ) ;
this . message . setType ( Message . TYPE_IMAGE ) ;
this . message . setStatus ( Message . STATUS_RECIEVING ) ;
2014-04-13 16:09:40 +00:00
String [ ] fromParts = packet . getFrom ( ) . split ( " / " ) ;
this . message . setPresence ( fromParts [ 1 ] ) ;
2014-04-13 09:32:45 +00:00
this . account = account ;
this . initiator = packet . getFrom ( ) ;
this . responder = this . account . getFullJid ( ) ;
this . sessionId = packet . getSessionId ( ) ;
2014-04-13 16:09:40 +00:00
this . content = packet . getJingleContent ( ) ;
this . mergeCandidates ( this . content . getCanditates ( ) ) ;
Element fileOffer = packet . getJingleContent ( ) . getFileOffer ( ) ;
if ( fileOffer ! = null ) {
this . file = this . mXmppConnectionService . getFileBackend ( ) . getJingleFile ( message ) ;
Element fileSize = fileOffer . findChild ( " size " ) ;
Element fileName = fileOffer . findChild ( " name " ) ;
this . file . setExpectedSize ( Long . parseLong ( fileSize . getContent ( ) ) ) ;
if ( this . file . getExpectedSize ( ) > = this . mJingleConnectionManager . getAutoAcceptFileSize ( ) ) {
Log . d ( " xmppService " , " auto accepting file from " + packet . getFrom ( ) ) ;
this . sendAccept ( ) ;
} else {
Log . d ( " xmppService " , " not auto accepting new file offer with size: " + this . file . getExpectedSize ( ) + " allowed size: " + this . mJingleConnectionManager . getAutoAcceptFileSize ( ) ) ;
}
} else {
Log . d ( " xmppService " , " no file offer was attached. aborting " ) ;
}
2014-04-13 09:32:45 +00:00
}
2014-04-08 21:15:55 +00:00
private void sendInitRequest ( ) {
2014-04-07 18:05:45 +00:00
JinglePacket packet = this . bootstrapPacket ( ) ;
packet . setAction ( " session-initiate " ) ;
2014-04-13 16:09:40 +00:00
this . content = new Content ( ) ;
2014-04-07 18:05:45 +00:00
if ( message . getType ( ) = = Message . TYPE_IMAGE ) {
content . setAttribute ( " creator " , " initiator " ) ;
content . setAttribute ( " name " , " a-file-offer " ) ;
2014-04-13 16:09:40 +00:00
this . file = this . mXmppConnectionService . getFileBackend ( ) . getJingleFile ( message ) ;
content . setFileOffer ( this . file ) ;
2014-04-11 19:13:09 +00:00
content . setCandidates ( this . mJingleConnectionManager . nextRandomId ( ) , this . candidates ) ;
2014-04-07 18:05:45 +00:00
packet . setContent ( content ) ;
Log . d ( " xmppService " , packet . toString ( ) ) ;
2014-04-08 21:15:55 +00:00
account . getXmppConnection ( ) . sendIqPacket ( packet , this . responseListener ) ;
2014-04-10 12:12:08 +00:00
this . status = STATUS_INITIATED ;
2014-04-07 18:05:45 +00:00
}
}
2014-04-13 16:09:40 +00:00
private void sendAccept ( ) {
this . mJingleConnectionManager . getPrimaryCandidate ( this . account , new OnPrimaryCandidateFound ( ) {
@Override
public void onPrimaryCandidateFound ( boolean success , Element candidate ) {
if ( success ) {
2014-04-14 18:35:11 +00:00
if ( ! equalCandidateExists ( candidate ) ) {
mergeCandidate ( candidate ) ;
2014-04-13 16:09:40 +00:00
content . addCandidate ( candidate ) ;
}
}
JinglePacket packet = bootstrapPacket ( ) ;
packet . setAction ( " session-accept " ) ;
packet . setContent ( content ) ;
account . getXmppConnection ( ) . sendIqPacket ( packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
if ( packet . getType ( ) ! = IqPacket . TYPE_ERROR ) {
2014-04-13 19:10:36 +00:00
status = STATUS_ACCEPTED ;
2014-04-13 16:09:40 +00:00
connectWithCandidates ( ) ;
}
}
} ) ;
}
} ) ;
}
2014-04-07 18:05:45 +00:00
private JinglePacket bootstrapPacket ( ) {
JinglePacket packet = new JinglePacket ( ) ;
packet . setFrom ( account . getFullJid ( ) ) ;
2014-04-10 12:12:08 +00:00
packet . setTo ( this . message . getCounterpart ( ) ) ; //fixme, not right in all cases;
2014-04-07 18:05:45 +00:00
packet . setSessionId ( this . sessionId ) ;
2014-04-13 16:09:40 +00:00
packet . setInitiator ( this . initiator ) ;
2014-04-07 18:05:45 +00:00
return packet ;
}
2014-04-11 19:13:09 +00:00
private void accept ( JinglePacket packet ) {
Log . d ( " xmppService " , " session-accept: " + packet . toString ( ) ) ;
Content content = packet . getJingleContent ( ) ;
2014-04-14 18:35:11 +00:00
mergeCandidates ( content . getCanditates ( ) ) ;
2014-04-11 19:13:09 +00:00
this . status = STATUS_ACCEPTED ;
this . connectWithCandidates ( ) ;
IqPacket response = packet . generateRespone ( IqPacket . TYPE_RESULT ) ;
account . getXmppConnection ( ) . sendIqPacket ( response , null ) ;
}
2014-04-13 16:09:40 +00:00
2014-04-11 20:49:26 +00:00
private void transportInfo ( JinglePacket packet ) {
Content content = packet . getJingleContent ( ) ;
String cid = content . getUsedCandidate ( ) ;
2014-04-13 19:10:36 +00:00
IqPacket response = packet . generateRespone ( IqPacket . TYPE_RESULT ) ;
2014-04-11 20:49:26 +00:00
if ( cid ! = null ) {
2014-04-13 16:09:40 +00:00
Log . d ( " xmppService " , " candidate used by counterpart: " + cid ) ;
this . candidatesUsedByCounterpart . add ( cid ) ;
if ( this . connections . containsKey ( cid ) ) {
2014-04-13 19:10:36 +00:00
SocksConnection connection = this . connections . get ( cid ) ;
if ( connection . isEstablished ( ) ) {
if ( status ! = STATUS_TRANSMITTING ) {
this . connect ( connection ) ;
} else {
Log . d ( " xmppService " , " ignoring canditate used because we are already transmitting " ) ;
}
} else {
Log . d ( " xmppService " , " not yet connected. check when callback comes back " ) ;
}
} else {
Log . d ( " xmppService " , " candidate not yet in list of connections " ) ;
2014-04-13 16:09:40 +00:00
}
}
account . getXmppConnection ( ) . sendIqPacket ( response , null ) ;
}
private void connect ( final SocksConnection connection ) {
2014-04-13 19:10:36 +00:00
this . status = STATUS_TRANSMITTING ;
2014-04-13 16:09:40 +00:00
final OnFileTransmitted callback = new OnFileTransmitted ( ) {
@Override
public void onFileTransmitted ( JingleFile file ) {
Log . d ( " xmppService " , " sucessfully transmitted file. sha1: " + file . getSha1Sum ( ) ) ;
}
} ;
2014-04-13 19:10:36 +00:00
if ( ( connection . isProxy ( ) & & ( connection . getCid ( ) . equals ( mJingleConnectionManager . getPrimaryCandidateId ( account ) ) ) ) ) {
Log . d ( " xmppService " , " candidate " + connection . getCid ( ) + " was our proxy and needs activation " ) ;
2014-04-13 16:09:40 +00:00
IqPacket activation = new IqPacket ( IqPacket . TYPE_SET ) ;
activation . setTo ( connection . getJid ( ) ) ;
activation . query ( " http://jabber.org/protocol/bytestreams " ) . setAttribute ( " sid " , this . getSessionId ( ) ) ;
activation . query ( ) . addChild ( " activate " ) . setContent ( this . getResponder ( ) ) ;
this . account . getXmppConnection ( ) . sendIqPacket ( activation , new OnIqPacketReceived ( ) {
2014-04-13 09:32:45 +00:00
@Override
2014-04-13 16:09:40 +00:00
public void onIqPacketReceived ( Account account , IqPacket packet ) {
Log . d ( " xmppService " , " activation result: " + packet . toString ( ) ) ;
if ( initiator . equals ( account . getFullJid ( ) ) ) {
Log . d ( " xmppService " , " we were initiating. sending file " ) ;
2014-04-13 09:32:45 +00:00
connection . send ( file , callback ) ;
2014-04-13 16:09:40 +00:00
} else {
2014-04-13 19:10:36 +00:00
connection . receive ( file , callback ) ;
2014-04-13 16:09:40 +00:00
Log . d ( " xmppService " , " we were responding. receiving file " ) ;
2014-04-11 20:49:26 +00:00
}
2014-04-13 16:09:40 +00:00
}
} ) ;
} else {
if ( initiator . equals ( account . getFullJid ( ) ) ) {
Log . d ( " xmppService " , " we were initiating. sending file " ) ;
2014-04-13 09:32:45 +00:00
connection . send ( file , callback ) ;
2014-04-13 16:09:40 +00:00
} else {
Log . d ( " xmppService " , " we were responding. receiving file " ) ;
2014-04-13 19:10:36 +00:00
connection . receive ( file , callback ) ;
2014-04-11 20:49:26 +00:00
}
}
}
2014-04-13 09:32:45 +00:00
private void finish ( ) {
this . status = STATUS_FINISHED ;
this . mXmppConnectionService . markMessage ( this . message , Message . STATUS_SEND ) ;
this . disconnect ( ) ;
}
public void cancel ( ) {
this . disconnect ( ) ;
this . status = STATUS_CANCELED ;
this . mXmppConnectionService . markMessage ( this . message , Message . STATUS_SEND_REJECTED ) ;
}
2014-04-11 19:13:09 +00:00
private void connectWithCandidates ( ) {
2014-04-13 19:10:36 +00:00
for ( Element candidate : this . candidates ) {
final SocksConnection socksConnection = new SocksConnection ( this , candidate ) ;
connections . put ( socksConnection . getCid ( ) , socksConnection ) ;
2014-04-13 16:09:40 +00:00
socksConnection . connect ( new OnSocksConnection ( ) {
@Override
public void failed ( ) {
Log . d ( " xmppService " , " socks5 failed " ) ;
}
@Override
public void established ( ) {
2014-04-13 19:10:36 +00:00
if ( candidatesUsedByCounterpart . contains ( socksConnection . getCid ( ) ) ) {
if ( status ! = STATUS_TRANSMITTING ) {
connect ( socksConnection ) ;
} else {
Log . d ( " xmppService " , " ignoring cuz already transmitting " ) ;
}
} else {
sendCandidateUsed ( socksConnection . getCid ( ) ) ;
}
2014-04-13 16:09:40 +00:00
}
} ) ;
2014-04-11 19:13:09 +00:00
}
}
2014-04-13 09:32:45 +00:00
private void disconnect ( ) {
Iterator < Entry < String , SocksConnection > > it = this . connections . entrySet ( ) . iterator ( ) ;
while ( it . hasNext ( ) ) {
Entry < String , SocksConnection > pairs = it . next ( ) ;
pairs . getValue ( ) . disconnect ( ) ;
it . remove ( ) ;
}
}
2014-04-14 18:35:11 +00:00
private void sendCandidateUsed ( final String cid ) {
2014-04-13 19:10:36 +00:00
JinglePacket packet = bootstrapPacket ( ) ;
packet . setAction ( " transport-info " ) ;
Content content = new Content ( ) ;
content . setUsedCandidate ( this . content . getTransportId ( ) , cid ) ;
packet . setContent ( content ) ;
2014-04-14 18:35:11 +00:00
Log . d ( " xmppService " , " send using candidate: " + cid ) ;
this . account . getXmppConnection ( ) . sendIqPacket ( packet , new OnIqPacketReceived ( ) {
@Override
public void onIqPacketReceived ( Account account , IqPacket packet ) {
Log . d ( " xmppService " , " got ack for our candidate used " ) ;
if ( status ! = STATUS_TRANSMITTING ) {
connect ( connections . get ( cid ) ) ;
} else {
Log . d ( " xmppService " , " ignoring cuz already transmitting " ) ;
}
}
} ) ;
2014-04-11 19:13:09 +00:00
}
2014-04-07 18:05:45 +00:00
2014-04-11 19:13:09 +00:00
public String getInitiator ( ) {
return this . initiator ;
}
public String getResponder ( ) {
return this . responder ;
}
2014-04-13 09:32:45 +00:00
public int getStatus ( ) {
return this . status ;
}
2014-04-13 16:09:40 +00:00
2014-04-14 18:35:11 +00:00
private boolean equalCandidateExists ( Element candidate ) {
2014-04-13 16:09:40 +00:00
for ( Element c : this . candidates ) {
if ( c . getAttribute ( " host " ) . equals ( candidate . getAttribute ( " host " ) ) & & ( c . getAttribute ( " port " ) . equals ( candidate . getAttribute ( " port " ) ) ) ) {
2014-04-14 18:35:11 +00:00
return true ;
}
}
return false ;
}
private void mergeCandidate ( Element candidate ) {
for ( Element c : this . candidates ) {
if ( c . getAttribute ( " cid " ) . equals ( candidate . getAttribute ( " cid " ) ) ) {
return ;
2014-04-13 16:09:40 +00:00
}
}
this . candidates . add ( candidate ) ;
}
2014-04-14 18:35:11 +00:00
private void mergeCandidates ( List < Element > candidates ) {
for ( Element c : candidates ) {
mergeCandidate ( c ) ;
2014-04-13 16:09:40 +00:00
}
}
2014-04-07 18:05:45 +00:00
}