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.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				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			if (this.file.getKey() != null) {
116				this.remainingSize = (this.file.getSize() / 16 + 1) * 16;
117			} else {
118				this.remainingSize = this.file.getSize();
119			}
120			this.fileSize = this.remainingSize;
121			this.digest = MessageDigest.getInstance("SHA-1");
122			this.digest.reset();
123			fileInputStream = this.file.createInputStream();
124			if (fileInputStream == null) {
125				Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
126				callback.onFileTransferAborted();
127				return;
128			}
129			if (this.connected) {
130				this.sendNextBlock();
131			}
132		} catch (NoSuchAlgorithmException e) {
133			callback.onFileTransferAborted();
134			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
135		}
136	}
137
138	@Override
139	public void disconnect() {
140		this.connected = false;
141		if (this.fileOutputStream != null) {
142			try {
143				this.fileOutputStream.close();
144			} catch (IOException e) {
145
146			}
147		}
148		if (this.fileInputStream != null) {
149			try {
150				this.fileInputStream.close();
151			} catch (IOException e) {
152
153			}
154		}
155	}
156
157	private void sendNextBlock() {
158		byte[] buffer = new byte[this.blockSize];
159		try {
160			int count = fileInputStream.read(buffer);
161			if (count == -1) {
162				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
163				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
164				fileInputStream.close();
165				return;
166			} else if (count != buffer.length) {
167				int rem = fileInputStream.read(buffer,count,buffer.length-count);
168				if (rem > 0) {
169					count += rem;
170				}
171			}
172			this.remainingSize -= count;
173			this.digest.update(buffer,0,count);
174			String base64 = Base64.encodeToString(buffer,0,count, Base64.NO_WRAP);
175			IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
176			iq.setTo(this.counterpart);
177			Element data = iq.addChild("data", "http://jabber.org/protocol/ibb");
178			data.setAttribute("seq", Integer.toString(this.seq));
179			data.setAttribute("block-size", Integer.toString(this.blockSize));
180			data.setAttribute("sid", this.sessionId);
181			data.setContent(base64);
182			this.account.getXmppConnection().sendIqPacket(iq, this.onAckReceived);
183			this.seq++;
184			if (this.remainingSize > 0) {
185				connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
186			} else {
187				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
188				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
189				fileInputStream.close();
190			}
191		} catch (IOException e) {
192			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
193			FileBackend.close(fileInputStream);
194			this.onFileTransmissionStatusChanged.onFileTransferAborted();
195		}
196	}
197
198	private void receiveNextBlock(String data) {
199		try {
200			byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
201			if (this.remainingSize < buffer.length) {
202				buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize);
203			}
204			this.remainingSize -= buffer.length;
205			this.fileOutputStream.write(buffer);
206			this.digest.update(buffer);
207			if (this.remainingSize <= 0) {
208				file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
209				fileOutputStream.flush();
210				fileOutputStream.close();
211				this.onFileTransmissionStatusChanged.onFileTransmitted(file);
212			} else {
213				connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
214			}
215		} catch (IOException e) {
216			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": "+e.getMessage());
217			FileBackend.close(fileOutputStream);
218			this.onFileTransmissionStatusChanged.onFileTransferAborted();
219		}
220	}
221
222	public void deliverPayload(IqPacket packet, Element payload) {
223		if (payload.getName().equals("open")) {
224			if (!established) {
225				established = true;
226				connected = true;
227				this.receiveNextBlock("");
228				this.account.getXmppConnection().sendIqPacket(
229						packet.generateResponse(IqPacket.TYPE.RESULT), null);
230			} else {
231				this.account.getXmppConnection().sendIqPacket(
232						packet.generateResponse(IqPacket.TYPE.ERROR), null);
233			}
234		} else if (connected && payload.getName().equals("data")) {
235			this.receiveNextBlock(payload.getContent());
236			this.account.getXmppConnection().sendIqPacket(
237					packet.generateResponse(IqPacket.TYPE.RESULT), null);
238		} else {
239			// TODO some sort of exception
240		}
241	}
242}