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 if (Namespace.JINGLE_APP_RTP.equals(descriptionNamespace)) {
45 connection = new JingleRtpConnection(this, id);
46 } else {
47 //TODO return feature-not-implemented
48 return;
49 }
50 connections.put(id, connection);
51 connection.deliverPacket(packet);
52 } else {
53 final AbstractJingleConnection abstractJingleConnection = connections.get(id);
54 if (abstractJingleConnection != null) {
55 abstractJingleConnection.deliverPacket(packet);
56 } else {
57 Log.d(Config.LOGTAG, "unable to route jingle packet: " + packet);
58 IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
59 Element error = response.addChild("error");
60 error.setAttribute("type", "cancel");
61 error.addChild("item-not-found",
62 "urn:ietf:params:xml:ns:xmpp-stanzas");
63 error.addChild("unknown-session", "urn:xmpp:jingle:errors:1");
64 account.getXmppConnection().sendIqPacket(response, null);
65 }
66 }
67 }
68
69 public void startJingleFileTransfer(final Message message) {
70 Preconditions.checkArgument(message.isFileOrImage(), "Message is not of type file or image");
71 final Transferable old = message.getTransferable();
72 if (old != null) {
73 old.cancel();
74 }
75 final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(message);
76 final JingleFileTransferConnection connection = new JingleFileTransferConnection(this, id);
77 mXmppConnectionService.markMessage(message, Message.STATUS_WAITING);
78 connection.init(message);
79 this.connections.put(id, connection);
80 }
81
82 void finishConnection(final AbstractJingleConnection connection) {
83 this.connections.remove(connection.getId());
84 }
85
86 void getPrimaryCandidate(final Account account, final boolean initiator, final OnPrimaryCandidateFound listener) {
87 if (Config.DISABLE_PROXY_LOOKUP) {
88 listener.onPrimaryCandidateFound(false, null);
89 return;
90 }
91 if (!this.primaryCandidates.containsKey(account.getJid().asBareJid())) {
92 final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Namespace.BYTE_STREAMS);
93 if (proxy != null) {
94 IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
95 iq.setTo(proxy);
96 iq.query(Namespace.BYTE_STREAMS);
97 account.getXmppConnection().sendIqPacket(iq, new OnIqPacketReceived() {
98
99 @Override
100 public void onIqPacketReceived(Account account, IqPacket packet) {
101 final Element streamhost = packet.query().findChild("streamhost", Namespace.BYTE_STREAMS);
102 final String host = streamhost == null ? null : streamhost.getAttribute("host");
103 final String port = streamhost == null ? null : streamhost.getAttribute("port");
104 if (host != null && port != null) {
105 try {
106 JingleCandidate candidate = new JingleCandidate(nextRandomId(), true);
107 candidate.setHost(host);
108 candidate.setPort(Integer.parseInt(port));
109 candidate.setType(JingleCandidate.TYPE_PROXY);
110 candidate.setJid(proxy);
111 candidate.setPriority(655360 + (initiator ? 30 : 0));
112 primaryCandidates.put(account.getJid().asBareJid(), candidate);
113 listener.onPrimaryCandidateFound(true, candidate);
114 } catch (final NumberFormatException e) {
115 listener.onPrimaryCandidateFound(false, null);
116 }
117 } else {
118 listener.onPrimaryCandidateFound(false, null);
119 }
120 }
121 });
122 } else {
123 listener.onPrimaryCandidateFound(false, null);
124 }
125
126 } else {
127 listener.onPrimaryCandidateFound(true,
128 this.primaryCandidates.get(account.getJid().asBareJid()));
129 }
130 }
131
132 static String nextRandomId() {
133 return UUID.randomUUID().toString();
134 }
135
136 public void deliverIbbPacket(Account account, IqPacket packet) {
137 final String sid;
138 final Element payload;
139 if (packet.hasChild("open", Namespace.IBB)) {
140 payload = packet.findChild("open", Namespace.IBB);
141 sid = payload.getAttribute("sid");
142 } else if (packet.hasChild("data", Namespace.IBB)) {
143 payload = packet.findChild("data", Namespace.IBB);
144 sid = payload.getAttribute("sid");
145 } else if (packet.hasChild("close", Namespace.IBB)) {
146 payload = packet.findChild("close", Namespace.IBB);
147 sid = payload.getAttribute("sid");
148 } else {
149 payload = null;
150 sid = null;
151 }
152 if (sid != null) {
153 for (final AbstractJingleConnection connection : this.connections.values()) {
154 if (connection instanceof JingleFileTransferConnection) {
155 final JingleFileTransferConnection fileTransfer = (JingleFileTransferConnection) connection;
156 final JingleTransport transport = fileTransfer.getTransport();
157 if (transport instanceof JingleInBandTransport) {
158 final JingleInBandTransport inBandTransport = (JingleInBandTransport) transport;
159 if (inBandTransport.matches(account, sid)) {
160 inBandTransport.deliverPayload(packet, payload);
161 }
162 return;
163 }
164 }
165 }
166 }
167 Log.d(Config.LOGTAG, "unable to deliver ibb packet: " + packet.toString());
168 account.getXmppConnection().sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
169 }
170
171 public void cancelInTransmission() {
172 for (AbstractJingleConnection connection : this.connections.values()) {
173 /*if (connection.getJingleStatus() == JingleFileTransferConnection.JINGLE_STATUS_TRANSMITTING) {
174 connection.abort("connectivity-error");
175 }*/
176 }
177 }
178}