aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInBandTransport.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/pixart/messenger/xmpp/jingle/JingleInBandTransport.java')
-rw-r--r--src/main/java/de/pixart/messenger/xmpp/jingle/JingleInBandTransport.java259
1 files changed, 259 insertions, 0 deletions
diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInBandTransport.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInBandTransport.java
new file mode 100644
index 000000000..5de666402
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInBandTransport.java
@@ -0,0 +1,259 @@
+package de.pixart.messenger.xmpp.jingle;
+
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.DownloadableFile;
+import de.pixart.messenger.persistance.FileBackend;
+import de.pixart.messenger.services.AbstractConnectionManager;
+import de.pixart.messenger.xml.Element;
+import de.pixart.messenger.xmpp.OnIqPacketReceived;
+import de.pixart.messenger.xmpp.stanzas.IqPacket;
+import rocks.xmpp.addr.Jid;
+
+public class JingleInBandTransport extends JingleTransport {
+
+ private final Account account;
+ private final Jid counterpart;
+ private final int blockSize;
+ private int seq = 0;
+ private final String sessionId;
+
+ private boolean established = false;
+
+ private boolean connected = true;
+
+ private DownloadableFile file;
+ private final JingleConnection connection;
+
+ private InputStream fileInputStream = null;
+ private InputStream innerInputStream = null;
+ private OutputStream fileOutputStream = null;
+ private long remainingSize = 0;
+ private long fileSize = 0;
+ private MessageDigest digest;
+
+ private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged;
+
+ private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (!connected) {
+ return;
+ }
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ if (remainingSize > 0) {
+ sendNextBlock();
+ }
+ } else if (packet.getType() == IqPacket.TYPE.ERROR) {
+ onFileTransmissionStatusChanged.onFileTransferAborted();
+ }
+ }
+ };
+
+ JingleInBandTransport(final JingleConnection connection, final String sid, final int blockSize) {
+ this.connection = connection;
+ this.account = connection.getAccount();
+ this.counterpart = connection.getCounterPart();
+ this.blockSize = blockSize;
+ this.sessionId = sid;
+ }
+
+ private void sendClose() {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sending ibb close");
+ IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
+ iq.setTo(this.counterpart);
+ Element close = iq.addChild("close", "http://jabber.org/protocol/ibb");
+ close.setAttribute("sid", this.sessionId);
+ this.account.getXmppConnection().sendIqPacket(iq, null);
+ }
+
+ public void connect(final OnTransportConnected callback) {
+ IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
+ iq.setTo(this.counterpart);
+ Element open = iq.addChild("open", "http://jabber.org/protocol/ibb");
+ open.setAttribute("sid", this.sessionId);
+ open.setAttribute("stanza", "iq");
+ open.setAttribute("block-size", Integer.toString(this.blockSize));
+ this.connected = true;
+ this.account.getXmppConnection().sendIqPacket(iq, (account, packet) -> {
+ if (packet.getType() != IqPacket.TYPE.RESULT) {
+ callback.failed();
+ } else {
+ callback.established();
+ }
+ });
+ }
+
+ @Override
+ public void receive(DownloadableFile file, OnFileTransmissionStatusChanged callback) {
+ this.onFileTransmissionStatusChanged = callback;
+ this.file = file;
+ try {
+ this.digest = MessageDigest.getInstance("SHA-1");
+ digest.reset();
+ this.fileOutputStream = connection.getFileOutputStream();
+ if (this.fileOutputStream == null) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not create output stream");
+ callback.onFileTransferAborted();
+ return;
+ }
+ this.remainingSize = this.fileSize = file.getExpectedSize();
+ } catch (final NoSuchAlgorithmException | IOException e) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + " " + e.getMessage());
+ callback.onFileTransferAborted();
+ }
+ }
+
+ @Override
+ public void send(DownloadableFile file, OnFileTransmissionStatusChanged callback) {
+ this.onFileTransmissionStatusChanged = callback;
+ this.file = file;
+ try {
+ this.remainingSize = this.file.getExpectedSize();
+ this.fileSize = this.remainingSize;
+ this.digest = MessageDigest.getInstance("SHA-1");
+ this.digest.reset();
+ fileInputStream = connection.getFileInputStream();
+ if (fileInputStream == null) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could no create input stream");
+ callback.onFileTransferAborted();
+ return;
+ }
+ innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream);
+ if (this.connected) {
+ this.sendNextBlock();
+ }
+ } catch (Exception e) {
+ callback.onFileTransferAborted();
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ this.connected = false;
+ FileBackend.close(fileOutputStream);
+ FileBackend.close(fileInputStream);
+ }
+
+ private void sendNextBlock() {
+ byte[] buffer = new byte[this.blockSize];
+ try {
+ int count = innerInputStream.read(buffer);
+ if (count == -1) {
+ sendClose();
+ file.setSha1Sum(digest.digest());
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sendNextBlock() count was -1");
+ this.onFileTransmissionStatusChanged.onFileTransmitted(file);
+ fileInputStream.close();
+ return;
+ } else if (count != buffer.length) {
+ int rem = innerInputStream.read(buffer, count, buffer.length - count);
+ if (rem > 0) {
+ count += rem;
+ }
+ }
+ this.remainingSize -= count;
+ this.digest.update(buffer, 0, count);
+ String base64 = Base64.encodeToString(buffer, 0, count, Base64.NO_WRAP);
+ IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
+ iq.setTo(this.counterpart);
+ Element data = iq.addChild("data", "http://jabber.org/protocol/ibb");
+ data.setAttribute("seq", Integer.toString(this.seq));
+ data.setAttribute("block-size", Integer.toString(this.blockSize));
+ data.setAttribute("sid", this.sessionId);
+ data.setContent(base64);
+ this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived);
+ this.account.getXmppConnection().r(); //don't fill up stanza queue too much
+ this.seq++;
+ connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
+ if (this.remainingSize <= 0) {
+ file.setSha1Sum(digest.digest());
+ this.onFileTransmissionStatusChanged.onFileTransmitted(file);
+ sendClose();
+ fileInputStream.close();
+ }
+ } catch (IOException e) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": io exception during sendNextBlock() " + e.getMessage());
+ FileBackend.close(fileInputStream);
+ this.onFileTransmissionStatusChanged.onFileTransferAborted();
+ }
+ }
+
+ private void receiveNextBlock(String data) {
+ try {
+ byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
+ if (this.remainingSize < buffer.length) {
+ buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize);
+ }
+ this.remainingSize -= buffer.length;
+ this.fileOutputStream.write(buffer);
+ this.digest.update(buffer);
+ if (this.remainingSize <= 0) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received last block. waiting for close");
+ } else {
+ connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
+ }
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
+ FileBackend.close(fileOutputStream);
+ this.onFileTransmissionStatusChanged.onFileTransferAborted();
+ }
+ }
+
+ private void done() {
+ try {
+ file.setSha1Sum(digest.digest());
+ fileOutputStream.flush();
+ fileOutputStream.close();
+ this.onFileTransmissionStatusChanged.onFileTransmitted(file);
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
+ FileBackend.close(fileOutputStream);
+ this.onFileTransmissionStatusChanged.onFileTransferAborted();
+ }
+ }
+
+ void deliverPayload(IqPacket packet, Element payload) {
+ if (payload.getName().equals("open")) {
+ if (!established) {
+ established = true;
+ connected = true;
+ this.receiveNextBlock("");
+ this.account.getXmppConnection().sendIqPacket(
+ packet.generateResponse(IqPacket.TYPE.RESULT), null);
+ } else {
+ this.account.getXmppConnection().sendIqPacket(
+ packet.generateResponse(IqPacket.TYPE.ERROR), null);
+ }
+ } else if (connected && payload.getName().equals("data")) {
+ this.receiveNextBlock(payload.getContent());
+ this.account.getXmppConnection().sendIqPacket(
+ packet.generateResponse(IqPacket.TYPE.RESULT), null);
+ } else if (connected && payload.getName().equals("close")) {
+ this.connected = false;
+ this.account.getXmppConnection().sendIqPacket(
+ packet.generateResponse(IqPacket.TYPE.RESULT), null);
+ if (this.remainingSize <= 0) {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ibb close. done");
+ done();
+ } else {
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ibb close with " + this.remainingSize + " remaining");
+ FileBackend.close(fileOutputStream);
+ this.onFileTransmissionStatusChanged.onFileTransferAborted();
+ }
+ } else {
+ this.account.getXmppConnection().sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
+ }
+ }
+} \ No newline at end of file