From 084ab51b1d7625cdf1cca4cc6a31715acffe5ce7 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 13 Apr 2014 21:10:36 +0200 Subject: [PATCH] transmitting files between two conversations works. no error handling and no ui on the receiving end --- .../persistance/FileBackend.java | 4 +- .../services/XmppConnectionService.java | 2 - .../xmpp/jingle/JingleConnection.java | 61 +++++++++++----- .../xmpp/jingle/JingleConnectionManager.java | 13 +++- .../xmpp/jingle/SocksConnection.java | 73 ++++++++++++++++--- .../xmpp/jingle/stanzas/Content.java | 19 +++++ 6 files changed, 136 insertions(+), 36 deletions(-) diff --git a/src/eu/siacs/conversations/persistance/FileBackend.java b/src/eu/siacs/conversations/persistance/FileBackend.java index 59373e74..f7f986f2 100644 --- a/src/eu/siacs/conversations/persistance/FileBackend.java +++ b/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); diff --git a/src/eu/siacs/conversations/services/XmppConnectionService.java b/src/eu/siacs/conversations/services/XmppConnectionService.java index 63ab0897..e5974c2d 100644 --- a/src/eu/siacs/conversations/services/XmppConnectionService.java +++ b/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; } diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index a97c3ac3..4551a1ca 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/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() { diff --git a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 7df27d4d..972489d0 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/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); diff --git a/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java b/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java index d4cb432a..bf7c87ad 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/SocksConnection.java +++ b/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) { diff --git a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index c9015d39..79e04610 100644 --- a/src/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/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");