transmitting files between two conversations works. no error handling and no ui on the receiving end

Daniel Gultsch created

Change summary

src/eu/siacs/conversations/persistance/FileBackend.java             |  4 
src/eu/siacs/conversations/services/XmppConnectionService.java      |  2 
src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java        | 61 
src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java | 13 
src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java         | 73 
src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java         | 19 
6 files changed, 136 insertions(+), 36 deletions(-)

Detailed changes

src/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -10,7 +10,6 @@ import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
-import android.util.Log;
 import android.util.LruCache;
 
 import eu.siacs.conversations.entities.Conversation;
@@ -81,7 +80,7 @@ public class FileBackend {
 			Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
 			boolean success = scalledBitmap.compress(Bitmap.CompressFormat.WEBP,75,os);
 			if (!success) {
-				Log.d("xmppService", "couldnt compress");
+				//Log.d("xmppService", "couldnt compress");
 			}
 			os.close();
 			return file;
@@ -104,7 +103,6 @@ public class FileBackend {
 	public Bitmap getThumbnailFromMessage(Message message, int size) {
 		Bitmap thumbnail = thumbnailCache.get(message.getUuid());
 		if (thumbnail==null) {
-			Log.d("xmppService","creating new thumbnail" + message.getUuid());
 			Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
 					.getAbsolutePath());
 			thumbnail = resize(fullsize, size);

src/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -1059,12 +1059,10 @@ public class XmppConnectionService extends Service {
 			OnConversationListChangedListener listener) {
 		this.convChangedListener = listener;
 		this.convChangedListenerCount++;
-		Log.d(LOGTAG,"registered on conv changed in backend ("+convChangedListenerCount+")");
 	}
 
 	public void removeOnConversationListChangedListener() {
 		this.convChangedListenerCount--;
-		Log.d(LOGTAG,"someone on conv changed listener removed listener ("+convChangedListenerCount+")");
 		if (this.convChangedListenerCount==0) {
 			this.convChangedListener = null;
 		}

src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java 🔗

@@ -29,6 +29,7 @@ public class JingleConnection {
 	public static final int STATUS_TERMINATED = 2;
 	public static final int STATUS_CANCELED = 3;
 	public static final int STATUS_FINISHED = 4;
+	public static final int STATUS_TRANSMITTING = 5;
 	public static final int STATUS_FAILED = 99;
 	
 	private int status = -1;
@@ -64,7 +65,7 @@ public class JingleConnection {
 	}
 	
 	public String getAccountJid() {
-		return this.account.getJid();
+		return this.account.getFullJid();
 	}
 	
 	public String getCounterPart() {
@@ -113,6 +114,7 @@ public class JingleConnection {
 	}
 	
 	public void init(Account account, JinglePacket packet) {
+		this.status = STATUS_INITIATED;
 		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);
@@ -140,6 +142,7 @@ public class JingleConnection {
 		} else {
 			Log.d("xmppService","no file offer was attached. aborting");
 		}
+		Log.d("xmppService","session Id "+getSessionId());
 	}
 	
 	private void sendInitRequest() {
@@ -172,13 +175,12 @@ public class JingleConnection {
 				JinglePacket packet = bootstrapPacket();
 				packet.setAction("session-accept");
 				packet.setContent(content);
-				Log.d("xmppService","sending session accept: "+packet.toString());
 				account.getXmppConnection().sendIqPacket(packet, new OnIqPacketReceived() {
 					
 					@Override
 					public void onIqPacketReceived(Account account, IqPacket packet) {
 						if (packet.getType() != IqPacket.TYPE_ERROR) {
-							Log.d("xmppService","opsing side has acked our session-accept");
+							status = STATUS_ACCEPTED;
 							connectWithCandidates();
 						}
 					}
@@ -209,20 +211,31 @@ public class JingleConnection {
 
 	private void transportInfo(JinglePacket packet) {
 		Content content = packet.getJingleContent();
-		Log.d("xmppService","transport info : "+content.toString());
 		String cid = content.getUsedCandidate();
+		IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
 		if (cid!=null) {
 			Log.d("xmppService","candidate used by counterpart:"+cid);
 			this.candidatesUsedByCounterpart.add(cid);
 			if (this.connections.containsKey(cid)) {
-				this.connect(this.connections.get(cid));
+				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");
 			}
 		}
-		IqPacket response = packet.generateRespone(IqPacket.TYPE_RESULT);
 		account.getXmppConnection().sendIqPacket(response, null);
 	}
 
 	private void connect(final SocksConnection connection) {
+		this.status = STATUS_TRANSMITTING;
 		final OnFileTransmitted callback = new OnFileTransmitted() {
 			
 			@Override
@@ -230,12 +243,12 @@ public class JingleConnection {
 				Log.d("xmppService","sucessfully transmitted file. sha1:"+file.getSha1Sum());
 			}
 		};
-		if (connection.isProxy()) {
+		if ((connection.isProxy()&&(connection.getCid().equals(mJingleConnectionManager.getPrimaryCandidateId(account))))) {
+			Log.d("xmppService","candidate "+connection.getCid()+" was our proxy and needs activation");
 			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());
-			Log.d("xmppService","connection is proxy. need to activate "+activation.toString());
 			this.account.getXmppConnection().sendIqPacket(activation, new OnIqPacketReceived() {
 				
 				@Override
@@ -245,9 +258,9 @@ public class JingleConnection {
 						Log.d("xmppService","we were initiating. sending file");
 						connection.send(file,callback);
 					} else {
+						connection.receive(file,callback);
 						Log.d("xmppService","we were responding. receiving file");
 					}
-					
 				}
 			});
 		} else {
@@ -256,6 +269,7 @@ public class JingleConnection {
 				connection.send(file,callback);
 			} else {
 				Log.d("xmppService","we were responding. receiving file");
+				connection.receive(file,callback);
 			}
 		}
 	}
@@ -273,14 +287,9 @@ public class JingleConnection {
 	}
 	
 	private void connectWithCandidates() {
-		for(Element canditate : this.candidates) {
-			
-			String host = canditate.getAttribute("host");
-			int port = Integer.parseInt(canditate.getAttribute("port"));
-			String type = canditate.getAttribute("type");
-			String jid = canditate.getAttribute("jid");
-			SocksConnection socksConnection = new SocksConnection(this, host, jid, port,type);
-			connections.put(canditate.getAttribute("cid"), socksConnection);
+		for(Element candidate : this.candidates) {
+			final SocksConnection socksConnection = new SocksConnection(this,candidate);
+			connections.put(socksConnection.getCid(), socksConnection);
 			socksConnection.connect(new OnSocksConnection() {
 				
 				@Override
@@ -290,7 +299,15 @@ public class JingleConnection {
 				
 				@Override
 				public void established() {
-					Log.d("xmppService","established socks5");
+					if (candidatesUsedByCounterpart.contains(socksConnection.getCid())) {
+						if (status!=STATUS_TRANSMITTING) {
+							connect(socksConnection);
+						} else {
+							Log.d("xmppService","ignoring cuz already transmitting");
+						}
+					} else {
+						sendCandidateUsed(socksConnection.getCid());
+					}
 				}
 			});
 		}
@@ -306,7 +323,13 @@ public class JingleConnection {
 	}
 	
 	private void sendCandidateUsed(String cid) {
-		
+		JinglePacket packet = bootstrapPacket();
+		packet.setAction("transport-info");
+		Content content = new Content();
+		content.setUsedCandidate(this.content.getTransportId(), cid);
+		packet.setContent(content);
+		Log.d("xmppService","send using candidate: "+packet.toString());
+		this.account.getXmppConnection().sendIqPacket(packet, responseListener);
 	}
 
 	public String getInitiator() {

src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java 🔗

@@ -35,13 +35,16 @@ public class JingleConnectionManager {
 		if (packet.isAction("session-initiate")) {
 			JingleConnection connection = new JingleConnection(this);
 			connection.init(account,packet);
+			connections.add(connection);
 		} else {
 			for (JingleConnection connection : connections) {
-				if (connection.getAccountJid().equals(account.getJid()) && connection
+				if (connection.getAccountJid().equals(account.getFullJid()) && connection
 						.getSessionId().equals(packet.getSessionId()) && connection
 						.getCounterPart().equals(packet.getFrom())) {
 					connection.deliverPacket(packet);
 					return;
+				} else {
+					Log.d("xmppService","no match sid:"+connection.getSessionId()+"="+packet.getSessionId()+" counterpart:"+connection.getCounterPart()+"="+packet.getFrom()+" account:"+connection.getAccountJid()+"="+packet.getTo());
 				}
 			}
 			Log.d("xmppService","delivering packet failed "+packet.toString());
@@ -118,6 +121,14 @@ public class JingleConnectionManager {
 					this.primaryCandidates.get(account.getJid()));
 		}
 	}
+	
+	public String getPrimaryCandidateId(Account account) {
+		if (this.primaryCandidates.containsKey(account.getJid())) {
+			return this.primaryCandidates.get(account.getJid()).getAttribute("cid");
+		} else {
+			return null;
+		}
+	}
 
 	public String nextRandomId() {
 		return new BigInteger(50, random).toString(32);

src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java 🔗

@@ -2,6 +2,7 @@ package eu.siacs.conversations.xmpp.jingle;
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -12,27 +13,29 @@ import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
 
 import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.xml.Element;
 
 import android.util.Log;
+import android.widget.Button;
 
 public class SocksConnection {
-
-	private JingleConnection jingleConnection;
 	private Socket socket;
 	private String host;
 	private String jid;
+	private String cid;
 	private int port;
 	private boolean isProxy = false;
 	private String destination;
 	private OutputStream outputStream;
+	private InputStream inputStream;
 	private boolean isEstablished = false;
 
-	public SocksConnection(JingleConnection jingleConnection, String host,
-			String jid, int port, String type) {
-		this.jingleConnection = jingleConnection;
-		this.host = host;
-		this.jid = jid;
-		this.port = port;
+	public SocksConnection(JingleConnection jingleConnection, Element candidate) {
+		this.cid = candidate.getAttribute("cid");
+		this.host = candidate.getAttribute("host");
+		this.port = Integer.parseInt(candidate.getAttribute("port"));
+		String type = candidate.getAttribute("type");
+		this.jid = candidate.getAttribute("jid");
 		this.isProxy = "proxy".equalsIgnoreCase(type);
 		try {
 			MessageDigest mDigest = MessageDigest.getInstance("SHA-1");
@@ -55,19 +58,19 @@ public class SocksConnection {
 			public void run() {
 				try {
 					socket = new Socket(host, port);
-					InputStream is = socket.getInputStream();
+					inputStream = socket.getInputStream();
 					outputStream = socket.getOutputStream();
 					byte[] login = { 0x05, 0x01, 0x00 };
 					byte[] expectedReply = { 0x05, 0x00 };
 					byte[] reply = new byte[2];
 					outputStream.write(login);
-					is.read(reply);
+					inputStream.read(reply);
 					if (Arrays.equals(reply, expectedReply)) {
 						String connect = "" + '\u0005' + '\u0001' + '\u0000' + '\u0003'
 								+ '\u0028' + destination + '\u0000' + '\u0000';
 						outputStream.write(connect.getBytes());
 						byte[] result = new byte[2];
-						is.read(result);
+						inputStream.read(result);
 						int status = result[1];
 						if (status == 0) {
 							Log.d("xmppService", "established connection with "+host + ":" + port
@@ -135,6 +138,50 @@ public class SocksConnection {
 		}).start();
 		
 	}
+	
+	public void receive(final JingleFile file, final OnFileTransmitted callback) {
+		new Thread(new Runnable() {
+			
+			@Override
+			public void run() {
+				try {
+					MessageDigest digest = MessageDigest.getInstance("SHA-1");
+					digest.reset();
+					inputStream.skip(45);
+					file.getParentFile().mkdirs();
+					file.createNewFile();
+					FileOutputStream fileOutputStream = new FileOutputStream(file);
+					long remainingSize = file.getExpectedSize();
+					byte[] buffer = new byte[8192];
+					int count = buffer.length;
+					while(remainingSize > 0) {
+						Log.d("xmppService","remaning size:"+remainingSize);
+						if (remainingSize<=count) {
+							count = (int) remainingSize;
+						}
+						count = inputStream.read(buffer, 0, count);
+						fileOutputStream.write(buffer, 0, count);
+						digest.update(buffer, 0, count);
+						remainingSize-=count;
+					}
+					fileOutputStream.flush();
+					fileOutputStream.close();
+					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
+					Log.d("xmppService","transmitted filename was: "+file.getAbsolutePath());
+					callback.onFileTransmitted(file);
+				} catch (FileNotFoundException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (IOException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				} catch (NoSuchAlgorithmException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}).start();
+	}
 
 	public boolean isProxy() {
 		return this.isProxy;
@@ -143,6 +190,10 @@ public class SocksConnection {
 	public String getJid() {
 		return this.jid;
 	}
+	
+	public String getCid() {
+		return this.cid;
+	}
 
 	public void disconnect() {
 		if (this.socket!=null) {

src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java 🔗

@@ -56,6 +56,14 @@ public class Content extends Element {
 		}
 	}
 	
+	public String getTransportId() {
+		Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+		if (transport==null) {
+			return null;
+		}
+		return transport.getAttribute("sid");
+	}
+	
 	public String getUsedCandidate() {
 		Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
 		if (transport==null) {
@@ -68,6 +76,17 @@ public class Content extends Element {
 			return usedCandidate.getAttribute("cid");
 		}
 	}
+	
+	public void setUsedCandidate(String transportId, String cid) {
+		Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+		if (transport==null) {
+			transport = this.addChild("transport", "urn:xmpp:jingle:transports:s5b:1");
+		}
+		transport.setAttribute("sid", transportId);
+		transport.clearChildren();
+		Element usedCandidate = transport.addChild("candidate-used");
+		usedCandidate.setAttribute("cid",cid);
+	}
 
 	public void addCandidate(Element candidate) {
 		Element transport = this.findChild("transport", "urn:xmpp:jingle:transports:s5b:1");