1package eu.siacs.conversations.xmpp.jingle;
2
3import android.util.Log;
4
5import com.google.common.base.Preconditions;
6
7import java.util.HashMap;
8import java.util.Map;
9import java.util.UUID;
10import java.util.concurrent.ConcurrentHashMap;
11
12import eu.siacs.conversations.Config;
13import eu.siacs.conversations.entities.Account;
14import eu.siacs.conversations.entities.Message;
15import eu.siacs.conversations.entities.Transferable;
16import eu.siacs.conversations.services.AbstractConnectionManager;
17import eu.siacs.conversations.services.XmppConnectionService;
18import eu.siacs.conversations.xml.Element;
19import eu.siacs.conversations.xml.Namespace;
20import eu.siacs.conversations.xmpp.OnIqPacketReceived;
21import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
22import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription;
23import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
24import eu.siacs.conversations.xmpp.stanzas.IqPacket;
25import rocks.xmpp.addr.Jid;
26
27public class JingleConnectionManager extends AbstractConnectionManager {
28 private Map<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>();
29
30 private HashMap<Jid, JingleCandidate> primaryCandidates = new HashMap<>();
31
32 public JingleConnectionManager(XmppConnectionService service) {
33 super(service);
34 }
35
36 public void deliverPacket(final Account account, final JinglePacket packet) {
37 final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet);
38 if (packet.getAction() == JinglePacket.Action.SESSION_INITIATE) { //TODO check that id doesn't exist yet
39 final Content content = packet.getJingleContent();
40 final String descriptionNamespace = content == null ? null : content.getDescriptionNamespace();
41 final AbstractJingleConnection connection;
42 if (FileTransferDescription.NAMESPACES.contains(descriptionNamespace)) {
43 connection = new JingleFileTransferConnection(this, id);
44 } else {
45 //TODO return feature-not-implemented
46 return;
47 }
48 connections.put(id, connection);
49 connection.deliverPacket(packet);
50 } else {
51 final AbstractJingleConnection abstractJingleConnection = connections.get(id);
52 if (abstractJingleConnection != null) {
53 abstractJingleConnection.deliverPacket(packet);
54 } else {
55 Log.d(Config.LOGTAG, "unable to route jingle packet: " + packet);
56 IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
57 Element error = response.addChild("error");
58 error.setAttribute("type", "cancel");
59 error.addChild("item-not-found",
60 "urn:ietf:params:xml:ns:xmpp-stanzas");
61 error.addChild("unknown-session", "urn:xmpp:jingle:errors:1");
62 account.getXmppConnection().sendIqPacket(response, null);
63 }
64 }
65 }
66
67 public void startJingleFileTransfer(final Message message) {
68 Preconditions.checkArgument(message.isFileOrImage(), "Message is not of type file or image");
69 final Transferable old = message.getTransferable();
70 if (old != null) {
71 old.cancel();
72 }
73 final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(message);
74 final JingleFileTransferConnection connection = new JingleFileTransferConnection(this, id);
75 mXmppConnectionService.markMessage(message, Message.STATUS_WAITING);
76 connection.init(message);
77 this.connections.put(id, connection);
78 }
79
80 void finishConnection(final AbstractJingleConnection connection) {
81 this.connections.remove(connection.getId());
82 }
83
84 void getPrimaryCandidate(final Account account, final boolean initiator, final OnPrimaryCandidateFound listener) {
85 if (Config.DISABLE_PROXY_LOOKUP) {
86 listener.onPrimaryCandidateFound(false, null);
87 return;
88 }
89 if (!this.primaryCandidates.containsKey(account.getJid().asBareJid())) {
90 final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Namespace.BYTE_STREAMS);
91 if (proxy != null) {
92 IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
93 iq.setTo(proxy);
94 iq.query(Namespace.BYTE_STREAMS);
95 account.getXmppConnection().sendIqPacket(iq, new OnIqPacketReceived() {
96
97 @Override
98 public void onIqPacketReceived(Account account, IqPacket packet) {
99 final Element streamhost = packet.query().findChild("streamhost", Namespace.BYTE_STREAMS);
100 final String host = streamhost == null ? null : streamhost.getAttribute("host");
101 final String port = streamhost == null ? null : streamhost.getAttribute("port");
102 if (host != null && port != null) {
103 try {
104 JingleCandidate candidate = new JingleCandidate(nextRandomId(), true);
105 candidate.setHost(host);
106 candidate.setPort(Integer.parseInt(port));
107 candidate.setType(JingleCandidate.TYPE_PROXY);
108 candidate.setJid(proxy);
109 candidate.setPriority(655360 + (initiator ? 30 : 0));
110 primaryCandidates.put(account.getJid().asBareJid(), candidate);
111 listener.onPrimaryCandidateFound(true, candidate);
112 } catch (final NumberFormatException e) {
113 listener.onPrimaryCandidateFound(false, null);
114 }
115 } else {
116 listener.onPrimaryCandidateFound(false, null);
117 }
118 }
119 });
120 } else {
121 listener.onPrimaryCandidateFound(false, null);
122 }
123
124 } else {
125 listener.onPrimaryCandidateFound(true,
126 this.primaryCandidates.get(account.getJid().asBareJid()));
127 }
128 }
129
130 static String nextRandomId() {
131 return UUID.randomUUID().toString();
132 }
133
134 public void deliverIbbPacket(Account account, IqPacket packet) {
135 final String sid;
136 final Element payload;
137 if (packet.hasChild("open", Namespace.IBB)) {
138 payload = packet.findChild("open", Namespace.IBB);
139 sid = payload.getAttribute("sid");
140 } else if (packet.hasChild("data", Namespace.IBB)) {
141 payload = packet.findChild("data", Namespace.IBB);
142 sid = payload.getAttribute("sid");
143 } else if (packet.hasChild("close", Namespace.IBB)) {
144 payload = packet.findChild("close", Namespace.IBB);
145 sid = payload.getAttribute("sid");
146 } else {
147 payload = null;
148 sid = null;
149 }
150 if (sid != null) {
151 for (final AbstractJingleConnection connection : this.connections.values()) {
152 if (connection instanceof JingleFileTransferConnection) {
153 final JingleFileTransferConnection fileTransfer = (JingleFileTransferConnection) connection;
154 final JingleTransport transport = fileTransfer.getTransport();
155 if (transport instanceof JingleInBandTransport) {
156 final JingleInBandTransport inBandTransport = (JingleInBandTransport) transport;
157 if (inBandTransport.matches(account, sid)) {
158 inBandTransport.deliverPayload(packet, payload);
159 }
160 return;
161 }
162 }
163 }
164 }
165 Log.d(Config.LOGTAG, "unable to deliver ibb packet: " + packet.toString());
166 account.getXmppConnection().sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
167 }
168
169 public void cancelInTransmission() {
170 for (AbstractJingleConnection connection : this.connections.values()) {
171 /*if (connection.getJingleStatus() == JingleFileTransferConnection.JINGLE_STATUS_TRANSMITTING) {
172 connection.abort("connectivity-error");
173 }*/
174 }
175 }
176}