make jingle implementation send file hash when using ft5

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/DownloadableFile.java         |   6 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java      | 100 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java |   6 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java |   4 
src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java  |  19 
5 files changed, 93 insertions(+), 42 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/DownloadableFile.java 🔗

@@ -9,7 +9,7 @@ public class DownloadableFile extends File {
 	private static final long serialVersionUID = 2247012619505115863L;
 
 	private long expectedSize = 0;
-	private String sha1sum;
+	private byte[] sha1sum;
 	private byte[] aeskey;
 
 	private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
@@ -42,11 +42,11 @@ public class DownloadableFile extends File {
 		this.expectedSize = size;
 	}
 
-	public String getSha1Sum() {
+	public byte[] getSha1Sum() {
 		return this.sha1sum;
 	}
 
-	public void setSha1Sum(String sum) {
+	public void setSha1Sum(byte[] sum) {
 		this.sha1sum = sum;
 	}
 

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

@@ -1,5 +1,6 @@
 package eu.siacs.conversations.xmpp.jingle;
 
+import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
 
@@ -7,6 +8,7 @@ import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
@@ -29,6 +31,7 @@ import eu.siacs.conversations.parser.IqParser;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.services.AbstractConnectionManager;
 import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 import eu.siacs.conversations.xmpp.jid.Jid;
@@ -92,12 +95,24 @@ public class JingleConnection implements Transferable {
 			}
 		}
 	};
+	private byte[] expectedHash = new byte[0];
 
-	final OnFileTransmissionStatusChanged onFileTransmissionSatusChanged = new OnFileTransmissionStatusChanged() {
+	private boolean responding() {
+		return responder.equals(account.getJid());
+	}
+
+	private boolean initiating() {
+		return initiator.equals(account.getJid());
+	}
+
+	final OnFileTransmissionStatusChanged onFileTransmissionStatusChanged = new OnFileTransmissionStatusChanged() {
 
 		@Override
 		public void onFileTransmitted(DownloadableFile file) {
-			if (responder.equals(account.getJid())) {
+			if (responding()) {
+				if (expectedHash.length > 0 && !Arrays.equals(expectedHash,file.getSha1Sum())) {
+					Log.d(Config.LOGTAG,account.getJid().toBareJid()+": hashes did not match");
+				}
 				sendSuccess();
 				mXmppConnectionService.getFileBackend().updateFileParams(message);
 				mXmppConnectionService.databaseBackend.createMessage(message);
@@ -111,6 +126,9 @@ public class JingleConnection implements Transferable {
 					}
 				}
 			} 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);
 				}
@@ -118,7 +136,7 @@ public class JingleConnection implements Transferable {
 					file.delete();
 				}
 			}
-			Log.d(Config.LOGTAG,"successfully transmitted file:" + file.getAbsolutePath()+" ("+file.getSha1Sum()+")");
+			Log.d(Config.LOGTAG,"successfully transmitted file:" + file.getAbsolutePath()+" ("+ CryptoHelper.bytesToHex(file.getSha1Sum())+")");
 			if (message.getEncryption() != Message.ENCRYPTION_PGP) {
 				mXmppConnectionService.getFileBackend().updateMediaScanner(file);
 			}
@@ -145,9 +163,9 @@ public class JingleConnection implements Transferable {
 		public void success() {
 			if (initiator.equals(account.getJid())) {
 				Log.d(Config.LOGTAG, "we were initiating. sending file");
-				transport.send(file, onFileTransmissionSatusChanged);
+				transport.send(file, onFileTransmissionStatusChanged);
 			} else {
-				transport.receive(file, onFileTransmissionSatusChanged);
+				transport.receive(file, onFileTransmissionStatusChanged);
 				Log.d(Config.LOGTAG, "we were responding. receiving file");
 			}
 		}
@@ -193,6 +211,18 @@ public class JingleConnection implements Transferable {
 			}
 		} else if (packet.isAction("session-accept")) {
 			returnResult = receiveAccept(packet);
+		} else if (packet.isAction("session-info")) {
+			returnResult = true;
+			Element checksum = packet.getChecksum();
+			Element file = checksum == null ? null : checksum.findChild("file");
+			Element hash = file == null ? null : file.findChild("hash","urn:xmpp:hashes:2");
+			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];
+				}
+			}
 		} else if (packet.isAction("transport-info")) {
 			returnResult = receiveTransportInfo(packet);
 		} else if (packet.isAction("transport-replace")) {
@@ -499,6 +529,12 @@ public class JingleConnection implements Transferable {
 		}
 	}
 
+	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) {
@@ -653,7 +689,7 @@ public class JingleConnection implements Transferable {
 		if (connection == null) {
 			Log.d(Config.LOGTAG, "could not find suitable candidate");
 			this.disconnectSocks5Connections();
-			if (this.initiator.equals(account.getJid())) {
+			if (initiating()) {
 				this.sendFallbackToIbb();
 			}
 		} else {
@@ -697,12 +733,12 @@ public class JingleConnection implements Transferable {
 									+ " was a proxy. waiting for other party to activate");
 				}
 			} else {
-				if (initiator.equals(account.getJid())) {
+				if (initiating()) {
 					Log.d(Config.LOGTAG, "we were initiating. sending file");
-					connection.send(file, onFileTransmissionSatusChanged);
+					connection.send(file, onFileTransmissionStatusChanged);
 				} else {
 					Log.d(Config.LOGTAG, "we were responding. receiving file");
-					connection.receive(file, onFileTransmissionSatusChanged);
+					connection.receive(file, onFileTransmissionStatusChanged);
 				}
 			}
 		}
@@ -727,7 +763,7 @@ public class JingleConnection implements Transferable {
 					} else if (connection.getCandidate().getPriority() == currentConnection
 							.getCandidate().getPriority()) {
 						// Log.d(Config.LOGTAG,"found two candidates with same priority");
-						if (initiator.equals(account.getJid())) {
+						if (initiating()) {
 							if (currentConnection.getCandidate().isOurs()) {
 								connection = currentConnection;
 							}
@@ -777,7 +813,7 @@ public class JingleConnection implements Transferable {
 
 		@Override
 		public void established() {
-			JingleConnection.this.transport.send(file, onFileTransmissionSatusChanged);
+			JingleConnection.this.transport.send(file, onFileTransmissionStatusChanged);
 		}
 	};
 
@@ -801,7 +837,7 @@ public class JingleConnection implements Transferable {
 		answer.setContent(content);
 
 
-		if (initiator.equals(account.getJid())) {
+		if (initiating()) {
 			this.sendJinglePacket(answer, new OnIqPacketReceived() {
 				@Override
 				public void onIqPacketReceived(Account account, IqPacket packet) {
@@ -812,7 +848,7 @@ public class JingleConnection implements Transferable {
 				}
 			});
 		} else {
-			this.transport.receive(file, onFileTransmissionSatusChanged);
+			this.transport.receive(file, onFileTransmissionStatusChanged);
 			this.sendJinglePacket(answer);
 		}
 		return true;
@@ -831,10 +867,10 @@ public class JingleConnection implements Transferable {
 			this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
 
 			//might be receive instead if we are not initiating
-			if (initiator.equals(account.getJid())) {
+			if (initiating()) {
 				this.transport.connect(onIbbTransportConnected);
 			} else {
-				this.transport.receive(file,onFileTransmissionSatusChanged);
+				this.transport.receive(file, onFileTransmissionStatusChanged);
 			}
 			return true;
 		} else {
@@ -843,14 +879,18 @@ public class JingleConnection implements Transferable {
 	}
 
 	private void receiveSuccess() {
-		this.mJingleStatus = JINGLE_STATUS_FINISHED;
-		this.mXmppConnectionService.markMessage(this.message,Message.STATUS_SEND_RECEIVED);
-		this.disconnectSocks5Connections();
-		if (this.transport != null && this.transport instanceof JingleInbandTransport) {
-			this.transport.disconnect();
+		if (initiating()) {
+			this.mJingleStatus = JINGLE_STATUS_FINISHED;
+			this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_RECEIVED);
+			this.disconnectSocks5Connections();
+			if (this.transport != null && this.transport instanceof JingleInbandTransport) {
+				this.transport.disconnect();
+			}
+			this.message.setTransferable(null);
+			this.mJingleConnectionManager.finishConnection(this);
+		} else {
+			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": received session-terminate/success while responding");
 		}
-		this.message.setTransferable(null);
-		this.mJingleConnectionManager.finishConnection(this);
 	}
 
 	public void cancel() {
@@ -860,7 +900,7 @@ public class JingleConnection implements Transferable {
 		}
 		this.sendCancel();
 		this.mJingleConnectionManager.finishConnection(this);
-		if (this.responder.equals(account.getJid())) {
+		if (responding()) {
 			this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
 			if (this.file!=null) {
 				file.delete();
@@ -886,7 +926,7 @@ public class JingleConnection implements Transferable {
 		FileBackend.close(mFileInputStream);
 		FileBackend.close(mFileOutputStream);
 		if (this.message != null) {
-			if (this.responder.equals(account.getJid())) {
+			if (responding()) {
 				this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
 				if (this.file!=null) {
 					file.delete();
@@ -992,14 +1032,6 @@ public class JingleConnection implements Transferable {
 		this.sendJinglePacket(packet);
 	}
 
-	public Jid getInitiator() {
-		return this.initiator;
-	}
-
-	public Jid getResponder() {
-		return this.responder;
-	}
-
 	public int getJingleStatus() {
 		return this.mJingleStatus;
 	}
@@ -1051,9 +1083,9 @@ public class JingleConnection implements Transferable {
 	}
 
 	interface OnProxyActivated {
-		public void success();
+		void success();
 
-		public void failed();
+		void failed();
 	}
 
 	public boolean hasTransportId(String sid) {

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java 🔗

@@ -166,7 +166,7 @@ public class JingleInbandTransport extends JingleTransport {
 			int count = fileInputStream.read(buffer);
 			if (count == -1) {
 				sendClose();
-				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
+				file.setSha1Sum(digest.digest());
 				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
 				fileInputStream.close();
 				return;
@@ -193,7 +193,7 @@ public class JingleInbandTransport extends JingleTransport {
 				connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
 			} else {
 				sendClose();
-				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
+				file.setSha1Sum(digest.digest());
 				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
 				fileInputStream.close();
 			}
@@ -214,7 +214,7 @@ public class JingleInbandTransport extends JingleTransport {
 			this.fileOutputStream.write(buffer);
 			this.digest.update(buffer);
 			if (this.remainingSize <= 0) {
-				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
+				file.setSha1Sum(digest.digest());
 				fileOutputStream.flush();
 				fileOutputStream.close();
 				this.onFileTransmissionStatusChanged.onFileTransmitted(file);

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java 🔗

@@ -112,7 +112,7 @@ public class JingleSocks5Transport extends JingleTransport {
 						connection.updateProgress((int) ((((double) transmitted) / size) * 100));
 					}
 					outputStream.flush();
-					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
+					file.setSha1Sum(digest.digest());
 					if (callback != null) {
 						callback.onFileTransmitted(file);
 					}
@@ -168,7 +168,7 @@ public class JingleSocks5Transport extends JingleTransport {
 					}
 					fileOutputStream.flush();
 					fileOutputStream.close();
-					file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
+					file.setSha1Sum(digest.digest());
 					callback.onFileTransmitted(file);
 				} catch (Exception e) {
 					Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": "+e.getMessage());

src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/JinglePacket.java 🔗

@@ -1,5 +1,7 @@
 package eu.siacs.conversations.xmpp.jingle.stanzas;
 
+import android.util.Base64;
+
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.jid.Jid;
 import eu.siacs.conversations.xmpp.stanzas.IqPacket;
@@ -7,6 +9,7 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 public class JinglePacket extends IqPacket {
 	Content content = null;
 	Reason reason = null;
+	Element checksum = null;
 	Element jingle = new Element("jingle");
 
 	@Override
@@ -24,6 +27,7 @@ public class JinglePacket extends IqPacket {
 				this.reason.setChildren(reasonElement.getChildren());
 				this.reason.setAttributes(reasonElement.getAttributes());
 			}
+			this.checksum = child.findChild("checksum");
 			this.jingle.setAttributes(child.getAttributes());
 		}
 		return child;
@@ -50,6 +54,10 @@ public class JinglePacket extends IqPacket {
 		return this.reason;
 	}
 
+	public Element getChecksum() {
+		return this.checksum;
+	}
+
 	private void build() {
 		this.children.clear();
 		this.jingle.clearChildren();
@@ -60,6 +68,9 @@ public class JinglePacket extends IqPacket {
 		if (this.reason != null) {
 			jingle.addChild(this.reason);
 		}
+		if (this.checksum != null) {
+			jingle.addChild(checksum);
+		}
 		this.children.add(jingle);
 		this.setAttribute("type", "set");
 	}
@@ -93,4 +104,12 @@ public class JinglePacket extends IqPacket {
 	public boolean isAction(String action) {
 		return action.equalsIgnoreCase(this.getAction());
 	}
+
+	public void addChecksum(byte[] sha1Sum, String namespace) {
+		this.checksum = new Element("checksum",namespace);
+		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(sha1Sum,Base64.NO_WRAP));
+	}
 }