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