JingleConnectionManager.java

  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        final AbstractJingleConnection existingJingleConnection = connections.get(id);
 39        if (existingJingleConnection != null) {
 40            existingJingleConnection.deliverPacket(packet);
 41        } else if (packet.getAction() == JinglePacket.Action.SESSION_INITIATE) {
 42            final Jid from = packet.getFrom();
 43            final Content content = packet.getJingleContent();
 44            final String descriptionNamespace = content == null ? null : content.getDescriptionNamespace();
 45            final AbstractJingleConnection connection;
 46            if (FileTransferDescription.NAMESPACES.contains(descriptionNamespace)) {
 47                connection = new JingleFileTransferConnection(this, id, from);
 48            } else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace)) {
 49                connection = new JingleRtpConnection(this, id, from);
 50            } else {
 51                //TODO return feature-not-implemented
 52                return;
 53            }
 54            connections.put(id, connection);
 55            connection.deliverPacket(packet);
 56        } else {
 57            Log.d(Config.LOGTAG, "unable to route jingle packet: " + packet);
 58            final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
 59            final Element error = response.addChild("error");
 60            error.setAttribute("type", "cancel");
 61            error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
 62            error.addChild("unknown-session", "urn:xmpp:jingle:errors:1");
 63            account.getXmppConnection().sendIqPacket(response, null);
 64        }
 65    }
 66
 67    public void deliverMessage(final Account account, final Jid to, final Jid from, final Element message) {
 68        Preconditions.checkArgument(Namespace.JINGLE_MESSAGE.equals(message.getNamespace()));
 69        final String sessionId = message.getAttribute("id");
 70        if (sessionId == null) {
 71            return;
 72        }
 73        final Jid with;
 74        if (account.getJid().asBareJid().equals(from.asBareJid())) {
 75            with = to;
 76        } else {
 77            with = from;
 78        }
 79        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received jingle message from " + from + " with=" + with + " " + message);
 80        final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with, sessionId);
 81        final AbstractJingleConnection existingJingleConnection = connections.get(id);
 82        if (existingJingleConnection != null) {
 83            if (existingJingleConnection instanceof JingleRtpConnection) {
 84                ((JingleRtpConnection) existingJingleConnection).deliveryMessage(to, from, message);
 85            } else {
 86                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + existingJingleConnection.getClass().getName() + " does not support jingle messages");
 87            }
 88        } else if ("propose".equals(message.getName())) {
 89            final Element description = message.findChild("description");
 90            final String namespace = description == null ? null : description.getNamespace();
 91            if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
 92                final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, with);
 93                this.connections.put(id, rtpConnection);
 94                rtpConnection.deliveryMessage(to, from, message);
 95            } else {
 96                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to react to proposed " + namespace + " session");
 97            }
 98        } else {
 99            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved out of order jingle message");
100        }
101
102    }
103
104    public void startJingleFileTransfer(final Message message) {
105        Preconditions.checkArgument(message.isFileOrImage(), "Message is not of type file or image");
106        final Transferable old = message.getTransferable();
107        if (old != null) {
108            old.cancel();
109        }
110        final Account account = message.getConversation().getAccount();
111        final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(message);
112        final JingleFileTransferConnection connection = new JingleFileTransferConnection(this, id, account.getJid());
113        mXmppConnectionService.markMessage(message, Message.STATUS_WAITING);
114        this.connections.put(id, connection);
115        connection.init(message);
116    }
117
118    void finishConnection(final AbstractJingleConnection connection) {
119        this.connections.remove(connection.getId());
120    }
121
122    void getPrimaryCandidate(final Account account, final boolean initiator, final OnPrimaryCandidateFound listener) {
123        if (Config.DISABLE_PROXY_LOOKUP) {
124            listener.onPrimaryCandidateFound(false, null);
125            return;
126        }
127        if (!this.primaryCandidates.containsKey(account.getJid().asBareJid())) {
128            final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Namespace.BYTE_STREAMS);
129            if (proxy != null) {
130                IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
131                iq.setTo(proxy);
132                iq.query(Namespace.BYTE_STREAMS);
133                account.getXmppConnection().sendIqPacket(iq, new OnIqPacketReceived() {
134
135                    @Override
136                    public void onIqPacketReceived(Account account, IqPacket packet) {
137                        final Element streamhost = packet.query().findChild("streamhost", Namespace.BYTE_STREAMS);
138                        final String host = streamhost == null ? null : streamhost.getAttribute("host");
139                        final String port = streamhost == null ? null : streamhost.getAttribute("port");
140                        if (host != null && port != null) {
141                            try {
142                                JingleCandidate candidate = new JingleCandidate(nextRandomId(), true);
143                                candidate.setHost(host);
144                                candidate.setPort(Integer.parseInt(port));
145                                candidate.setType(JingleCandidate.TYPE_PROXY);
146                                candidate.setJid(proxy);
147                                candidate.setPriority(655360 + (initiator ? 30 : 0));
148                                primaryCandidates.put(account.getJid().asBareJid(), candidate);
149                                listener.onPrimaryCandidateFound(true, candidate);
150                            } catch (final NumberFormatException e) {
151                                listener.onPrimaryCandidateFound(false, null);
152                            }
153                        } else {
154                            listener.onPrimaryCandidateFound(false, null);
155                        }
156                    }
157                });
158            } else {
159                listener.onPrimaryCandidateFound(false, null);
160            }
161
162        } else {
163            listener.onPrimaryCandidateFound(true,
164                    this.primaryCandidates.get(account.getJid().asBareJid()));
165        }
166    }
167
168    static String nextRandomId() {
169        return UUID.randomUUID().toString();
170    }
171
172    public void deliverIbbPacket(Account account, IqPacket packet) {
173        final String sid;
174        final Element payload;
175        if (packet.hasChild("open", Namespace.IBB)) {
176            payload = packet.findChild("open", Namespace.IBB);
177            sid = payload.getAttribute("sid");
178        } else if (packet.hasChild("data", Namespace.IBB)) {
179            payload = packet.findChild("data", Namespace.IBB);
180            sid = payload.getAttribute("sid");
181        } else if (packet.hasChild("close", Namespace.IBB)) {
182            payload = packet.findChild("close", Namespace.IBB);
183            sid = payload.getAttribute("sid");
184        } else {
185            payload = null;
186            sid = null;
187        }
188        if (sid != null) {
189            for (final AbstractJingleConnection connection : this.connections.values()) {
190                if (connection instanceof JingleFileTransferConnection) {
191                    final JingleFileTransferConnection fileTransfer = (JingleFileTransferConnection) connection;
192                    final JingleTransport transport = fileTransfer.getTransport();
193                    if (transport instanceof JingleInBandTransport) {
194                        final JingleInBandTransport inBandTransport = (JingleInBandTransport) transport;
195                        if (inBandTransport.matches(account, sid)) {
196                            inBandTransport.deliverPayload(packet, payload);
197                        }
198                        return;
199                    }
200                }
201            }
202        }
203        Log.d(Config.LOGTAG, "unable to deliver ibb packet: " + packet.toString());
204        account.getXmppConnection().sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
205    }
206
207    public void cancelInTransmission() {
208        for (AbstractJingleConnection connection : this.connections.values()) {
209            /*if (connection.getJingleStatus() == JingleFileTransferConnection.JINGLE_STATUS_TRANSMITTING) {
210                connection.abort("connectivity-error");
211            }*/
212        }
213    }
214}