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 OnFileTransmitted onFileTransmitted;
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, OnFileTransmitted callback) {
81 this.onFileTransmitted = 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 this.remainingSize = file.getExpectedSize();
90 } catch (NoSuchAlgorithmException e) {
91 e.printStackTrace();
92 } catch (IOException e) {
93 e.printStackTrace();
94 }
95 }
96
97 @Override
98 public void send(JingleFile file, OnFileTransmitted callback) {
99 this.onFileTransmitted = callback;
100 this.file = file;
101 try {
102 this.digest = MessageDigest.getInstance("SHA-1");
103 this.digest.reset();
104 fileInputStream = this.getInputStream(file);
105 this.sendNextBlock();
106 } catch (FileNotFoundException e) {
107 e.printStackTrace();
108 } catch (NoSuchAlgorithmException e) {
109 e.printStackTrace();
110 }
111 }
112
113 private void sendNextBlock() {
114 byte[] buffer = new byte[this.bufferSize];
115 try {
116 int count = fileInputStream.read(buffer);
117 if (count == -1) {
118 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
119 fileInputStream.close();
120 this.onFileTransmitted.onFileTransmitted(file);
121 } else {
122 this.digest.update(buffer);
123 String base64 = Base64.encodeToString(buffer, Base64.NO_WRAP);
124 IqPacket iq = new IqPacket(IqPacket.TYPE_SET);
125 iq.setTo(this.counterpart);
126 Element data = iq.addChild("data",
127 "http://jabber.org/protocol/ibb");
128 data.setAttribute("seq", "" + this.seq);
129 data.setAttribute("block-size", "" + this.blockSize);
130 data.setAttribute("sid", this.sessionId);
131 data.setContent(base64);
132 this.account.getXmppConnection().sendIqPacket(iq,
133 this.onAckReceived);
134 this.seq++;
135 }
136 } catch (IOException e) {
137 // TODO Auto-generated catch block
138 e.printStackTrace();
139 }
140 }
141
142 private void receiveNextBlock(String data) {
143 try {
144 byte[] buffer = Base64.decode(data, Base64.NO_WRAP);
145 if (this.remainingSize < buffer.length) {
146 buffer = Arrays.copyOfRange(buffer, 0, (int) this.remainingSize);
147 }
148 this.remainingSize -= buffer.length;
149
150 this.fileOutputStream.write(buffer);
151
152 this.digest.update(buffer);
153 if (this.remainingSize <= 0) {
154 file.setSha1Sum(CryptoHelper.bytesToHex(digest.digest()));
155 fileOutputStream.flush();
156 fileOutputStream.close();
157 this.onFileTransmitted.onFileTransmitted(file);
158 }
159 } catch (IOException e) {
160 e.printStackTrace();
161 }
162 }
163
164 public void deliverPayload(IqPacket packet, Element payload) {
165 if (payload.getName().equals("open")) {
166 if (!established) {
167 established = true;
168 this.account.getXmppConnection().sendIqPacket(
169 packet.generateRespone(IqPacket.TYPE_RESULT), null);
170 } else {
171 this.account.getXmppConnection().sendIqPacket(
172 packet.generateRespone(IqPacket.TYPE_ERROR), null);
173 }
174 } else if (payload.getName().equals("data")) {
175 this.receiveNextBlock(payload.getContent());
176 this.account.getXmppConnection().sendIqPacket(
177 packet.generateRespone(IqPacket.TYPE_RESULT), null);
178 } else {
179 // TODO some sort of exception
180 }
181 }
182}