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", "" + 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, OnFileTransmissionStatusChanged callback) {
81 this.onFileTransmissionStatusChanged = callback;
82 this.file = file;
83 try {
84 this.digest = MessageDigest.getInstance("SHA-1");
85 digest.reset();
86 file.getParentFile().mkdirs();
87 file.createNewFile();
88 this.fileOutputStream = getOutputStream(file);
89 if (this.fileOutputStream==null) {
90 callback.onFileTransferAborted();
91 return;
92 }
93 this.remainingSize = file.getExpectedSize();
94 } catch (NoSuchAlgorithmException e) {
95 callback.onFileTransferAborted();
96 } catch (IOException e) {
97 callback.onFileTransferAborted();
98 }
99 }
100
101 @Override
102 public void send(JingleFile file, OnFileTransmissionStatusChanged callback) {
103 this.onFileTransmissionStatusChanged = callback;
104 this.file = file;
105 try {
106 this.digest = MessageDigest.getInstance("SHA-1");
107 this.digest.reset();
108 fileInputStream = this.getInputStream(file);
109 if (fileInputStream==null) {
110 callback.onFileTransferAborted();
111 return;
112 }
113 this.sendNextBlock();
114 } catch (FileNotFoundException e) {
115 callback.onFileTransferAborted();
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", "" + this.seq);
137 data.setAttribute("block-size", "" + this.blockSize);
138 data.setAttribute("sid", this.sessionId);
139 data.setContent(base64);
140 this.account.getXmppConnection().sendIqPacket(iq,
141 this.onAckReceived);
142 this.seq++;
143 }
144 } catch (IOException e) {
145 this.onFileTransmissionStatusChanged.onFileTransferAborted();
146 }
147 }
148
149 private void receiveNextBlock(String data) {
150 try {
151 byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
152 if (this.remainingSize < buffer.length) {
153 buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize);
154 }
155 this.remainingSize -= buffer.length;
156
157 this.fileOutputStream.write(buffer);
158
159 this.digest.update(buffer);
160 if (this.remainingSize <= 0) {
161 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
162 fileOutputStream.flush();
163 fileOutputStream.close();
164 this.onFileTransmissionStatusChanged.onFileTransmitted(file);
165 }
166 } catch (IOException e) {
167 this.onFileTransmissionStatusChanged.onFileTransferAborted();
168 }
169 }
170
171 public void deliverPayload(IqPacket packet, Element payload) {
172 if (payload.getName().equals("open")) {
173 if (!established) {
174 established = true;
175 this.account.getXmppConnection().sendIqPacket(
176 packet.generateRespone(IqPacket.TYPE_RESULT), null);
177 } else {
178 this.account.getXmppConnection().sendIqPacket(
179 packet.generateRespone(IqPacket.TYPE_ERROR), null);
180 }
181 } else if (payload.getName().equals("data")) {
182 this.receiveNextBlock(payload.getContent());
183 this.account.getXmppConnection().sendIqPacket(
184 packet.generateRespone(IqPacket.TYPE_RESULT), null);
185 } else {
186 // TODO some sort of exception
187 }
188 }
189}