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