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        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}