1package eu.siacs.conversations.xmpp.jingle;
2
3import java.io.FileNotFoundException;
4import java.io.IOException;
5import java.io.InputStream;
6import java.io.OutputStream;
7import java.security.MessageDigest;
8import java.security.NoSuchAlgorithmException;
9import java.util.Arrays;
10
11import android.util.Base64;
12import eu.siacs.conversations.entities.Account;
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 JingleFile 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(JingleFile 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 = getOutputStream(file);
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(JingleFile file, OnFileTransmissionStatusChanged callback) {
104 this.onFileTransmissionStatusChanged = callback;
105 this.file = file;
106 try {
107 this.digest = MessageDigest.getInstance("SHA-1");
108 this.digest.reset();
109 fileInputStream = this.getInputStream(file);
110 if (fileInputStream == null) {
111 callback.onFileTransferAborted();
112 return;
113 }
114 this.sendNextBlock();
115 } catch (FileNotFoundException e) {
116 callback.onFileTransferAborted();
117 } catch (NoSuchAlgorithmException e) {
118 callback.onFileTransferAborted();
119 }
120 }
121
122 private void sendNextBlock() {
123 byte[] buffer = new byte[this.bufferSize];
124 try {
125 int count = fileInputStream.read(buffer);
126 if (count == -1) {
127 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
128 fileInputStream.close();
129 this.onFileTransmissionStatusChanged.onFileTransmitted(file);
130 } else {
131 this.digest.update(buffer);
132 String base64 = Base64.encodeToString(buffer, Base64.NO_WRAP);
133 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
134 iq.setTo(this.counterpart);
135 Element data = iq.addChild("data",
136 "http://jabber.org/protocol/ibb");
137 data.setAttribute("seq", Integer.toString(this.seq));
138 data.setAttribute("block-size",
139 Integer.toString(this.blockSize));
140 data.setAttribute("sid", this.sessionId);
141 data.setContent(base64);
142 this.account.getXmppConnection().sendIqPacket(iq,
143 this.onAckReceived);
144 this.seq++;
145 }
146 } catch (IOException e) {
147 this.onFileTransmissionStatusChanged.onFileTransferAborted();
148 }
149 }
150
151 private void receiveNextBlock(String data) {
152 try {
153 byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
154 if (this.remainingSize < buffer.length) {
155 buffer = Arrays
156 .copyOfRange(buffer, 0, (int) this.remainingSize);
157 }
158 this.remainingSize -= buffer.length;
159
160 this.fileOutputStream.write(buffer);
161
162 this.digest.update(buffer);
163 if (this.remainingSize <= 0) {
164 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
165 fileOutputStream.flush();
166 fileOutputStream.close();
167 this.onFileTransmissionStatusChanged.onFileTransmitted(file);
168 }
169 } catch (IOException e) {
170 this.onFileTransmissionStatusChanged.onFileTransferAborted();
171 }
172 }
173
174 public void deliverPayload(IqPacket packet, Element payload) {
175 if (payload.getName().equals("open")) {
176 if (!established) {
177 established = true;
178 this.account.getXmppConnection().sendIqPacket(
179 packet.generateRespone(IqPacket.TYPE_RESULT), null);
180 } else {
181 this.account.getXmppConnection().sendIqPacket(
182 packet.generateRespone(IqPacket.TYPE_ERROR), null);
183 }
184 } else if (payload.getName().equals("data")) {
185 this.receiveNextBlock(payload.getContent());
186 this.account.getXmppConnection().sendIqPacket(
187 packet.generateRespone(IqPacket.TYPE_RESULT), null);
188 } else {
189 // TODO some sort of exception
190 }
191 }
192}