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 eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 26import rocks.xmpp.addr.Jid;
 27
 28public class JingleConnectionManager extends AbstractConnectionManager {
 29    private Map<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>();
 30
 31    private HashMap<Jid, JingleCandidate> primaryCandidates = new HashMap<>();
 32
 33    public JingleConnectionManager(XmppConnectionService service) {
 34        super(service);
 35    }
 36
 37    public void deliverPacket(final Account account, final JinglePacket packet) {
 38        final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet);
 39        final AbstractJingleConnection existingJingleConnection = connections.get(id);
 40        if (existingJingleConnection != null) {
 41            existingJingleConnection.deliverPacket(packet);
 42        } else if (packet.getAction() == JinglePacket.Action.SESSION_INITIATE) {
 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);
 48            } else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace)) {
 49                connection = new JingleRtpConnection(this, id);
 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);
 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 AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(message);
111        final JingleFileTransferConnection connection = new JingleFileTransferConnection(this, id);
112        mXmppConnectionService.markMessage(message, Message.STATUS_WAITING);
113        connection.init(message);
114        this.connections.put(id, connection);
115    }
116
117    void finishConnection(final AbstractJingleConnection connection) {
118        this.connections.remove(connection.getId());
119    }
120
121    void getPrimaryCandidate(final Account account, final boolean initiator, final OnPrimaryCandidateFound listener) {
122        if (Config.DISABLE_PROXY_LOOKUP) {
123            listener.onPrimaryCandidateFound(false, null);
124            return;
125        }
126        if (!this.primaryCandidates.containsKey(account.getJid().asBareJid())) {
127            final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Namespace.BYTE_STREAMS);
128            if (proxy != null) {
129                IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
130                iq.setTo(proxy);
131                iq.query(Namespace.BYTE_STREAMS);
132                account.getXmppConnection().sendIqPacket(iq, new OnIqPacketReceived() {
133
134                    @Override
135                    public void onIqPacketReceived(Account account, IqPacket packet) {
136                        final Element streamhost = packet.query().findChild("streamhost", Namespace.BYTE_STREAMS);
137                        final String host = streamhost == null ? null : streamhost.getAttribute("host");
138                        final String port = streamhost == null ? null : streamhost.getAttribute("port");
139                        if (host != null && port != null) {
140                            try {
141                                JingleCandidate candidate = new JingleCandidate(nextRandomId(), true);
142                                candidate.setHost(host);
143                                candidate.setPort(Integer.parseInt(port));
144                                candidate.setType(JingleCandidate.TYPE_PROXY);
145                                candidate.setJid(proxy);
146                                candidate.setPriority(655360 + (initiator ? 30 : 0));
147                                primaryCandidates.put(account.getJid().asBareJid(), candidate);
148                                listener.onPrimaryCandidateFound(true, candidate);
149                            } catch (final NumberFormatException e) {
150                                listener.onPrimaryCandidateFound(false, null);
151                            }
152                        } else {
153                            listener.onPrimaryCandidateFound(false, null);
154                        }
155                    }
156                });
157            } else {
158                listener.onPrimaryCandidateFound(false, null);
159            }
160
161        } else {
162            listener.onPrimaryCandidateFound(true,
163                    this.primaryCandidates.get(account.getJid().asBareJid()));
164        }
165    }
166
167    static String nextRandomId() {
168        return UUID.randomUUID().toString();
169    }
170
171    public void deliverIbbPacket(Account account, IqPacket packet) {
172        final String sid;
173        final Element payload;
174        if (packet.hasChild("open", Namespace.IBB)) {
175            payload = packet.findChild("open", Namespace.IBB);
176            sid = payload.getAttribute("sid");
177        } else if (packet.hasChild("data", Namespace.IBB)) {
178            payload = packet.findChild("data", Namespace.IBB);
179            sid = payload.getAttribute("sid");
180        } else if (packet.hasChild("close", Namespace.IBB)) {
181            payload = packet.findChild("close", Namespace.IBB);
182            sid = payload.getAttribute("sid");
183        } else {
184            payload = null;
185            sid = null;
186        }
187        if (sid != null) {
188            for (final AbstractJingleConnection connection : this.connections.values()) {
189                if (connection instanceof JingleFileTransferConnection) {
190                    final JingleFileTransferConnection fileTransfer = (JingleFileTransferConnection) connection;
191                    final JingleTransport transport = fileTransfer.getTransport();
192                    if (transport instanceof JingleInBandTransport) {
193                        final JingleInBandTransport inBandTransport = (JingleInBandTransport) transport;
194                        if (inBandTransport.matches(account, sid)) {
195                            inBandTransport.deliverPayload(packet, payload);
196                        }
197                        return;
198                    }
199                }
200            }
201        }
202        Log.d(Config.LOGTAG, "unable to deliver ibb packet: " + packet.toString());
203        account.getXmppConnection().sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
204    }
205
206    public void cancelInTransmission() {
207        for (AbstractJingleConnection connection : this.connections.values()) {
208            /*if (connection.getJingleStatus() == JingleFileTransferConnection.JINGLE_STATUS_TRANSMITTING) {
209                connection.abort("connectivity-error");
210            }*/
211        }
212    }
213}