JingleInbandTransport.java

  1package eu.siacs.conversations.xmpp.jingle;
  2
  3import java.io.IOException;
  4import java.io.InputStream;
  5import java.io.OutputStream;
  6import java.security.MessageDigest;
  7import java.security.NoSuchAlgorithmException;
  8import java.util.Arrays;
  9
 10import android.util.Base64;
 11
 12import eu.siacs.conversations.entities.Account;
 13import eu.siacs.conversations.entities.DownloadableFile;
 14import eu.siacs.conversations.persistance.FileBackend;
 15import eu.siacs.conversations.utils.CryptoHelper;
 16import eu.siacs.conversations.xml.Element;
 17import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 18import eu.siacs.conversations.xmpp.jid.Jid;
 19import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 20
 21public class JingleInbandTransport extends JingleTransport {
 22
 23	private Account account;
 24	private Jid counterpart;
 25	private int blockSize;
 26	private int bufferSize;
 27	private int seq = 0;
 28	private String sessionId;
 29
 30	private boolean established = false;
 31
 32	private boolean connected = true;
 33
 34	private DownloadableFile file;
 35	private JingleConnection connection;
 36
 37	private InputStream fileInputStream = null;
 38	private OutputStream fileOutputStream = null;
 39	private long remainingSize = 0;
 40	private long fileSize = 0;
 41	private MessageDigest digest;
 42
 43	private OnFileTransmissionStatusChanged onFileTransmissionStatusChanged;
 44
 45	private OnIqPacketReceived onAckReceived = new OnIqPacketReceived() {
 46		@Override
 47		public void onIqPacketReceived(Account account, IqPacket packet) {
 48			if (connected && packet.getType() == IqPacket.TYPE.RESULT) {
 49				sendNextBlock();
 50			}
 51		}
 52	};
 53
 54	public JingleInbandTransport(final JingleConnection connection, final String sid, final int blocksize) {
 55		this.connection = connection;
 56		this.account = connection.getAccount();
 57		this.counterpart = connection.getCounterPart();
 58		this.blockSize = blocksize;
 59		this.bufferSize = blocksize / 4;
 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.ERROR) {
 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 = file.createOutputStream();
 97			if (this.fileOutputStream == null) {
 98				callback.onFileTransferAborted();
 99				return;
100			}
101			this.remainingSize = this.fileSize = file.getExpectedSize();
102		} catch (final NoSuchAlgorithmException | IOException e) {
103			callback.onFileTransferAborted();
104		}
105    }
106
107	@Override
108	public void send(DownloadableFile file,
109			OnFileTransmissionStatusChanged callback) {
110		this.onFileTransmissionStatusChanged = callback;
111		this.file = file;
112		try {
113			this.remainingSize = this.file.getSize();
114			this.fileSize = this.remainingSize;
115			this.digest = MessageDigest.getInstance("SHA-1");
116			this.digest.reset();
117			fileInputStream = this.file.createInputStream();
118			if (fileInputStream == null) {
119				callback.onFileTransferAborted();
120				return;
121			}
122			if (this.connected) {
123				this.sendNextBlock();
124			}
125		} catch (NoSuchAlgorithmException e) {
126			callback.onFileTransferAborted();
127		}
128	}
129
130	@Override
131	public void disconnect() {
132		this.connected = false;
133		if (this.fileOutputStream != null) {
134			try {
135				this.fileOutputStream.close();
136			} catch (IOException e) {
137
138			}
139		}
140		if (this.fileInputStream != null) {
141			try {
142				this.fileInputStream.close();
143			} catch (IOException e) {
144
145			}
146		}
147	}
148
149	private void sendNextBlock() {
150		byte[] buffer = new byte[this.bufferSize];
151		try {
152			int count = fileInputStream.read(buffer);
153			if (count == -1) {
154				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
155				fileInputStream.close();
156				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
157			} else {
158				this.remainingSize -= count;
159				this.digest.update(buffer);
160				String base64 = Base64.encodeToString(buffer, Base64.NO_WRAP);
161				IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
162				iq.setTo(this.counterpart);
163				Element data = iq.addChild("data",
164						"http://jabber.org/protocol/ibb");
165				data.setAttribute("seq", Integer.toString(this.seq));
166				data.setAttribute("block-size",
167						Integer.toString(this.blockSize));
168				data.setAttribute("sid", this.sessionId);
169				data.setContent(base64);
170				this.account.getXmppConnection().sendIqPacket(iq,
171						this.onAckReceived);
172				this.seq++;
173				connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
174			}
175		} catch (IOException e) {
176			FileBackend.close(fileInputStream);
177			this.onFileTransmissionStatusChanged.onFileTransferAborted();
178		}
179	}
180
181	private void receiveNextBlock(String data) {
182		try {
183			byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
184			if (this.remainingSize < buffer.length) {
185				buffer = Arrays
186						.copyOfRange(buffer, 0, (int) this.remainingSize);
187			}
188			this.remainingSize -= buffer.length;
189
190
191			this.fileOutputStream.write(buffer);
192
193			this.digest.update(buffer);
194			if (this.remainingSize <= 0) {
195				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
196				fileOutputStream.flush();
197				fileOutputStream.close();
198				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
199			} else {
200				connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
201			}
202		} catch (IOException e) {
203			FileBackend.close(fileOutputStream);
204			this.onFileTransmissionStatusChanged.onFileTransferAborted();
205		}
206	}
207
208	public void deliverPayload(IqPacket packet, Element payload) {
209		if (payload.getName().equals("open")) {
210			if (!established) {
211				established = true;
212				connected = true;
213				this.receiveNextBlock("");
214				this.account.getXmppConnection().sendIqPacket(
215						packet.generateResponse(IqPacket.TYPE.RESULT), null);
216			} else {
217				this.account.getXmppConnection().sendIqPacket(
218						packet.generateResponse(IqPacket.TYPE.ERROR), null);
219			}
220		} else if (connected && payload.getName().equals("data")) {
221			this.receiveNextBlock(payload.getContent());
222			this.account.getXmppConnection().sendIqPacket(
223					packet.generateResponse(IqPacket.TYPE.RESULT), null);
224		} else {
225			// TODO some sort of exception
226		}
227	}
228}