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