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