JinglePacket.java

  1package eu.siacs.conversations.xmpp.jingle.stanzas;
  2
  3import android.util.Log;
  4
  5import androidx.annotation.NonNull;
  6
  7import com.google.common.base.CaseFormat;
  8import com.google.common.base.Preconditions;
  9import com.google.common.base.Strings;
 10import com.google.common.collect.ImmutableMap;
 11
 12import eu.siacs.conversations.Config;
 13import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 14import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
 15import eu.siacs.conversations.xml.Element;
 16import eu.siacs.conversations.xml.Namespace;
 17import eu.siacs.conversations.xmpp.Jid;
 18import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 19
 20import java.util.Map;
 21
 22public class JinglePacket extends IqPacket {
 23
 24    private JinglePacket() {
 25        super();
 26    }
 27
 28    public JinglePacket(final Action action, final String sessionId) {
 29        super(TYPE.SET);
 30        final Element jingle = addChild("jingle", Namespace.JINGLE);
 31        jingle.setAttribute("sid", sessionId);
 32        jingle.setAttribute("action", action.toString());
 33    }
 34
 35    public static JinglePacket upgrade(final IqPacket iqPacket) {
 36        Preconditions.checkArgument(iqPacket.hasChild("jingle", Namespace.JINGLE));
 37        Preconditions.checkArgument(iqPacket.getType() == TYPE.SET);
 38        final JinglePacket jinglePacket = new JinglePacket();
 39        jinglePacket.setAttributes(iqPacket.getAttributes());
 40        jinglePacket.setChildren(iqPacket.getChildren());
 41        return jinglePacket;
 42    }
 43
 44    // TODO deprecate this somehow and make file transfer fail if there are multiple (or something)
 45    public Content getJingleContent() {
 46        final Element content = getJingleChild("content");
 47        return content == null ? null : Content.upgrade(content);
 48    }
 49
 50    public Group getGroup() {
 51        final Element jingle = findChild("jingle", Namespace.JINGLE);
 52        final Element group = jingle.findChild("group", Namespace.JINGLE_APPS_GROUPING);
 53        return group == null ? null : Group.upgrade(group);
 54    }
 55
 56    public void addGroup(final Group group) {
 57        this.addJingleChild(group);
 58    }
 59
 60    public Map<String, Content> getJingleContents() {
 61        final Element jingle = findChild("jingle", Namespace.JINGLE);
 62        ImmutableMap.Builder<String, Content> builder = new ImmutableMap.Builder<>();
 63        for (final Element child : jingle.getChildren()) {
 64            if ("content".equals(child.getName())) {
 65                final Content content = Content.upgrade(child);
 66                builder.put(content.getContentName(), content);
 67            }
 68        }
 69        return builder.build();
 70    }
 71
 72    public void addJingleContent(final Content content) { // take content interface
 73        addJingleChild(content);
 74    }
 75
 76    public ReasonWrapper getReason() {
 77        final Element reasonElement = getJingleChild("reason");
 78        if (reasonElement == null) {
 79            return new ReasonWrapper(Reason.UNKNOWN, null);
 80        }
 81        String text = null;
 82        Reason reason = Reason.UNKNOWN;
 83        for (Element child : reasonElement.getChildren()) {
 84            if ("text".equals(child.getName())) {
 85                text = child.getContent();
 86            } else {
 87                reason = Reason.of(child.getName());
 88            }
 89        }
 90        return new ReasonWrapper(reason, text);
 91    }
 92
 93    public void setReason(final Reason reason, final String text) {
 94        final Element jingle = findChild("jingle", Namespace.JINGLE);
 95        final Element reasonElement = jingle.addChild("reason");
 96        reasonElement.addChild(reason.toString());
 97        if (!Strings.isNullOrEmpty(text)) {
 98            reasonElement.addChild("text").setContent(text);
 99        }
100    }
101
102    // RECOMMENDED for session-initiate, NOT RECOMMENDED otherwise
103    public void setInitiator(final Jid initiator) {
104        Preconditions.checkArgument(initiator.isFullJid(), "initiator should be a full JID");
105        findChild("jingle", Namespace.JINGLE).setAttribute("initiator", initiator);
106    }
107
108    // RECOMMENDED for session-accept, NOT RECOMMENDED otherwise
109    public void setResponder(Jid responder) {
110        Preconditions.checkArgument(responder.isFullJid(), "responder should be a full JID");
111        findChild("jingle", Namespace.JINGLE).setAttribute("responder", responder);
112    }
113
114    public Element getJingleChild(final String name) {
115        final Element jingle = findChild("jingle", Namespace.JINGLE);
116        return jingle == null ? null : jingle.findChild(name);
117    }
118
119    public void addJingleChild(final Element child) {
120        final Element jingle = findChild("jingle", Namespace.JINGLE);
121        jingle.addChild(child);
122    }
123
124    public void setSecurity(final String name, final XmppAxolotlMessage xmppAxolotlMessage) {
125        final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
126        security.setAttribute("name", name);
127        security.setAttribute("cipher", "urn:xmpp:ciphers:aes-128-gcm-nopadding");
128        security.setAttribute("type", AxolotlService.PEP_PREFIX);
129        security.addChild(xmppAxolotlMessage.toElement());
130        addJingleChild(security);
131    }
132
133    public XmppAxolotlMessage getSecurity(final String nameNeedle) {
134        final Element jingle = findChild("jingle", Namespace.JINGLE);
135        if (jingle == null) {
136            return null;
137        }
138        for (final Element child : jingle.getChildren()) {
139            if ("security".equals(child.getName())
140                    && Namespace.JINGLE_ENCRYPTED_TRANSPORT.equals(child.getNamespace())) {
141                final String name = child.getAttribute("name");
142                final String type = child.getAttribute("type");
143                final String cipher = child.getAttribute("cipher");
144                if (nameNeedle.equals(name)
145                        && AxolotlService.PEP_PREFIX.equals(type)
146                        && "urn:xmpp:ciphers:aes-128-gcm-nopadding".equals(cipher)) {
147                    final var encrypted = child.findChild("encrypted", AxolotlService.PEP_PREFIX);
148                    if (encrypted != null) {
149                        return XmppAxolotlMessage.fromElement(encrypted, getFrom().asBareJid());
150                    }
151                }
152            }
153        }
154        return null;
155    }
156
157    public String getSessionId() {
158        return findChild("jingle", Namespace.JINGLE).getAttribute("sid");
159    }
160
161    public Action getAction() {
162        return Action.of(findChild("jingle", Namespace.JINGLE).getAttribute("action"));
163    }
164
165    public enum Action {
166        CONTENT_ACCEPT,
167        CONTENT_ADD,
168        CONTENT_MODIFY,
169        CONTENT_REJECT,
170        CONTENT_REMOVE,
171        DESCRIPTION_INFO,
172        SECURITY_INFO,
173        SESSION_ACCEPT,
174        SESSION_INFO,
175        SESSION_INITIATE,
176        SESSION_TERMINATE,
177        TRANSPORT_ACCEPT,
178        TRANSPORT_INFO,
179        TRANSPORT_REJECT,
180        TRANSPORT_REPLACE;
181
182        public static Action of(final String value) {
183            // TODO handle invalid
184            return Action.valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, value));
185        }
186
187        @Override
188        @NonNull
189        public String toString() {
190            return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, super.toString());
191        }
192    }
193
194    public static class ReasonWrapper {
195        public final Reason reason;
196        public final String text;
197
198        public ReasonWrapper(Reason reason, String text) {
199            this.reason = reason;
200            this.text = text;
201        }
202    }
203}