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