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