JingleInbandTransport.java

  1package eu.siacs.conversations.xmpp.jingle;
  2
  3import android.util.Base64;
  4import android.util.Log;
  5
  6import java.io.IOException;
  7import java.io.InputStream;
  8import java.io.OutputStream;
  9import java.security.MessageDigest;
 10import java.security.NoSuchAlgorithmException;
 11import java.util.Arrays;
 12
 13import eu.siacs.conversations.Config;
 14import eu.siacs.conversations.entities.Account;
 15import eu.siacs.conversations.entities.DownloadableFile;
 16import eu.siacs.conversations.persistance.FileBackend;
 17import eu.siacs.conversations.utils.CryptoHelper;
 18import eu.siacs.conversations.xml.Element;
 19import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 20import eu.siacs.conversations.xmpp.jid.Jid;
 21import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 22
 23public class JingleInbandTransport extends JingleTransport {
 24
 25	private Account account;
 26	private Jid counterpart;
 27	private int blockSize;
 28	private int seq = 0;
 29	private String sessionId;
 30
 31	private boolean established = false;
 32
 33	private boolean connected = true;
 34
 35	private DownloadableFile file;
 36	private JingleConnection connection;
 37
 38	private InputStream fileInputStream = null;
 39	private OutputStream fileOutputStream = null;
 40	private long remainingSize = 0;
 41	private long fileSize = 0;
 42	private MessageDigest digest;
 43
 44	private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged;
 45
 46	private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() {
 47		@Override
 48		public void onIqPacketReceived(Account account, IqPacket packet) {
 49			if (connected && packet.getType() == IqPacket.TYPE.RESULT) {
 50				sendNextBlock();
 51			}
 52		}
 53	};
 54
 55	public JingleInbandTransport(final JingleConnection connection, final String sid, final int blocksize) {
 56		this.connection = connection;
 57		this.account = connection.getAccount();
 58		this.counterpart = connection.getCounterPart();
 59		this.blockSize = blocksize;
 60		this.sessionId = sid;
 61	}
 62
 63	public void connect(final OnTransportConnected callback) {
 64		IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
 65		iq.setTo(this.counterpart);
 66		Element open = iq.addChild("open", "http://jabber.org/protocol/ibb");
 67		open.setAttribute("sid", this.sessionId);
 68		open.setAttribute("stanza", "iq");
 69		open.setAttribute("block-size", Integer.toString(this.blockSize));
 70		this.connected = true;
 71		this.account.getXmppConnection().sendIqPacket(iq,
 72				new OnIqPacketReceived() {
 73
 74					@Override
 75					public void onIqPacketReceived(Account account,
 76							IqPacket packet) {
 77						if (packet.getType() != IqPacket.TYPE.RESULT) {
 78							callback.failed();
 79						} else {
 80							callback.established();
 81						}
 82					}
 83				});
 84	}
 85
 86	@Override
 87	public void receive(DownloadableFile file,
 88			OnFileTransmissionStatusChanged callback) {
 89		this.onFileTransmissionStatusChanged = callback;
 90		this.file = file;
 91		try {
 92			this.digest = MessageDigest.getInstance("SHA-1");
 93			digest.reset();
 94			file.getParentFile().mkdirs();
 95			file.createNewFile();
 96			this.fileOutputStream = connection.getFileOutputStream();
 97			if (this.fileOutputStream == null) {
 98				Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
 99				callback.onFileTransferAborted();
100				return;
101			}
102			this.remainingSize = this.fileSize = file.getExpectedSize();
103		} catch (final NoSuchAlgorithmException | IOException e) {
104			Log.d(Config.LOGTAG,account.getJid().toBareJid()+" "+e.getMessage());
105			callback.onFileTransferAborted();
106		}
107    }
108
109	@Override
110	public void send(DownloadableFile file,
111			OnFileTransmissionStatusChanged callback) {
112		this.onFileTransmissionStatusChanged = callback;
113		this.file = file;
114		try {
115			this.remainingSize = this.file.getExpectedSize();
116			this.fileSize = this.remainingSize;
117			this.digest = MessageDigest.getInstance("SHA-1");
118			this.digest.reset();
119			fileInputStream = connection.getFileInputStream();
120			if (fileInputStream == null) {
121				Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
122				callback.onFileTransferAborted();
123				return;
124			}
125			if (this.connected) {
126				this.sendNextBlock();
127			}
128		} catch (NoSuchAlgorithmException e) {
129			callback.onFileTransferAborted();
130			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
131		}
132	}
133
134	@Override
135	public void disconnect() {
136		this.connected = false;
137		if (this.fileOutputStream != null) {
138			try {
139				this.fileOutputStream.close();
140			} catch (IOException e) {
141
142			}
143		}
144		if (this.fileInputStream != null) {
145			try {
146				this.fileInputStream.close();
147			} catch (IOException e) {
148
149			}
150		}
151	}
152
153	private void sendNextBlock() {
154		byte[] buffer = new byte[this.blockSize];
155		try {
156			int count = fileInputStream.read(buffer);
157			if (count == -1) {
158				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
159				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
160				fileInputStream.close();
161				return;
162			} else if (count != buffer.length) {
163				int rem = fileInputStream.read(buffer,count,buffer.length-count);
164				if (rem > 0) {
165					count += rem;
166				}
167			}
168			this.remainingSize -= count;
169			this.digest.update(buffer,0,count);
170			String base64 = Base64.encodeToString(buffer,0,count, Base64.NO_WRAP);
171			IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
172			iq.setTo(this.counterpart);
173			Element data = iq.addChild("data", "http://jabber.org/protocol/ibb");
174			data.setAttribute("seq", Integer.toString(this.seq));
175			data.setAttribute("block-size", Integer.toString(this.blockSize));
176			data.setAttribute("sid", this.sessionId);
177			data.setContent(base64);
178			this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived);
179			this.account.getXmppConnection().r(); //don't fill up stanza queue too much
180			this.seq++;
181			if (this.remainingSize > 0) {
182				connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
183			} else {
184				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
185				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
186				fileInputStream.close();
187			}
188		} catch (IOException e) {
189			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
190			FileBackend.close(fileInputStream);
191			this.onFileTransmissionStatusChanged.onFileTransferAborted();
192		}
193	}
194
195	private void receiveNextBlock(String data) {
196		try {
197			byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
198			if (this.remainingSize < buffer.length) {
199				buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize);
200			}
201			this.remainingSize -= buffer.length;
202			this.fileOutputStream.write(buffer);
203			this.digest.update(buffer);
204			if (this.remainingSize <= 0) {
205				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
206				fileOutputStream.flush();
207				fileOutputStream.close();
208				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
209			} else {
210				connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
211			}
212		} catch (Exception e) {
213			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
214			FileBackend.close(fileOutputStream);
215			this.onFileTransmissionStatusChanged.onFileTransferAborted();
216		}
217	}
218
219	public void deliverPayload(IqPacket packet, Element payload) {
220		if (payload.getName().equals("open")) {
221			if (!established) {
222				established = true;
223				connected = true;
224				this.receiveNextBlock("");
225				this.account.getXmppConnection().sendIqPacket(
226						packet.generateResponse(IqPacket.TYPE.RESULT), null);
227			} else {
228				this.account.getXmppConnection().sendIqPacket(
229						packet.generateResponse(IqPacket.TYPE.ERROR), null);
230			}
231		} else if (connected && payload.getName().equals("data")) {
232			this.receiveNextBlock(payload.getContent());
233			this.account.getXmppConnection().sendIqPacket(
234					packet.generateResponse(IqPacket.TYPE.RESULT), null);
235		} else {
236			// TODO some sort of exception
237		}
238	}
239}