Content.java

  1package eu.siacs.conversations.xmpp.jingle.stanzas;
  2
  3import android.util.Log;
  4
  5import androidx.annotation.NonNull;
  6
  7import com.google.common.base.Preconditions;
  8import com.google.common.base.Strings;
  9import com.google.common.collect.ImmutableSet;
 10
 11import eu.siacs.conversations.Config;
 12import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 13import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
 14import eu.siacs.conversations.xml.Element;
 15import eu.siacs.conversations.xml.Namespace;
 16import eu.siacs.conversations.xmpp.Jid;
 17import eu.siacs.conversations.xmpp.jingle.SessionDescription;
 18
 19import java.util.Locale;
 20import java.util.Set;
 21
 22public class Content extends Element {
 23
 24    public Content(final Creator creator, final Senders senders, final String name) {
 25        super("content", Namespace.JINGLE);
 26        this.setAttribute("creator", creator.toString());
 27        this.setAttribute("name", name);
 28        this.setSenders(senders);
 29    }
 30
 31    private Content() {
 32        super("content", Namespace.JINGLE);
 33    }
 34
 35    public static Content upgrade(final Element element) {
 36        Preconditions.checkArgument("content".equals(element.getName()));
 37        final Content content = new Content();
 38        content.setAttributes(element.getAttributes());
 39        content.setChildren(element.getChildren());
 40        return content;
 41    }
 42
 43    public String getContentName() {
 44        return this.getAttribute("name");
 45    }
 46
 47    public Creator getCreator() {
 48        return Creator.of(getAttribute("creator"));
 49    }
 50
 51    public Senders getSenders() {
 52        final String attribute = getAttribute("senders");
 53        if (Strings.isNullOrEmpty(attribute)) {
 54            return Senders.BOTH;
 55        }
 56        return Senders.of(getAttribute("senders"));
 57    }
 58
 59    public void setSenders(final Senders senders) {
 60        if (senders != null && senders != Senders.BOTH) {
 61            this.setAttribute("senders", senders.toString());
 62        }
 63    }
 64
 65    public GenericDescription getDescription() {
 66        final Element description = this.findChild("description");
 67        if (description == null) {
 68            return null;
 69        }
 70        final String namespace = description.getNamespace();
 71        if (Namespace.JINGLE_APPS_FILE_TRANSFER.equals(namespace)) {
 72            return FileTransferDescription.upgrade(description);
 73        } else if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
 74            return RtpDescription.upgrade(description);
 75        } else {
 76            return GenericDescription.upgrade(description);
 77        }
 78    }
 79
 80    public void setDescription(final GenericDescription description) {
 81        Preconditions.checkNotNull(description);
 82        this.addChild(description);
 83    }
 84
 85    public String getDescriptionNamespace() {
 86        final Element description = this.findChild("description");
 87        return description == null ? null : description.getNamespace();
 88    }
 89
 90    public GenericTransportInfo getTransport() {
 91        final Element transport = this.findChild("transport");
 92        final String namespace = transport == null ? null : transport.getNamespace();
 93        if (Namespace.JINGLE_TRANSPORTS_IBB.equals(namespace)) {
 94            return IbbTransportInfo.upgrade(transport);
 95        } else if (Namespace.JINGLE_TRANSPORTS_S5B.equals(namespace)) {
 96            return SocksByteStreamsTransportInfo.upgrade(transport);
 97        } else if (Namespace.JINGLE_TRANSPORT_ICE_UDP.equals(namespace)) {
 98            return IceUdpTransportInfo.upgrade(transport);
 99        } else if (Namespace.JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL.equals(namespace)) {
100            return WebRTCDataChannelTransportInfo.upgrade(transport);
101        } else if (transport != null) {
102            return GenericTransportInfo.upgrade(transport);
103        } else {
104            return null;
105        }
106    }
107
108    public void setSecurity(final XmppAxolotlMessage xmppAxolotlMessage) {
109        final String contentName = this.getContentName();
110        final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
111        security.setAttribute("name", contentName);
112        security.setAttribute("cipher", "urn:xmpp:ciphers:aes-128-gcm-nopadding");
113        security.setAttribute("type", AxolotlService.PEP_PREFIX);
114        security.addChild(xmppAxolotlMessage.toElement());
115        this.addChild(security);
116    }
117
118    public XmppAxolotlMessage getSecurity(final Jid from) {
119        final String contentName = this.getContentName();
120        for (final Element child : getChildren()) {
121            if ("security".equals(child.getName())
122                    && Namespace.JINGLE_ENCRYPTED_TRANSPORT.equals(child.getNamespace())) {
123                final String name = child.getAttribute("name");
124                final String type = child.getAttribute("type");
125                final String cipher = child.getAttribute("cipher");
126                if (contentName.equals(name)
127                        && AxolotlService.PEP_PREFIX.equals(type)
128                        && "urn:xmpp:ciphers:aes-128-gcm-nopadding".equals(cipher)) {
129                    final var encrypted = child.findChild("encrypted", AxolotlService.PEP_PREFIX);
130                    if (encrypted != null) {
131                        return XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
132                    }
133                }
134            }
135        }
136        return null;
137    }
138
139    public void setTransport(GenericTransportInfo transportInfo) {
140        this.addChild(transportInfo);
141    }
142
143    public enum Creator {
144        INITIATOR,
145        RESPONDER;
146
147        public static Creator of(final String value) {
148            return Creator.valueOf(value.toUpperCase(Locale.ROOT));
149        }
150
151        @Override
152        @NonNull
153        public String toString() {
154            return super.toString().toLowerCase(Locale.ROOT);
155        }
156    }
157
158    public enum Senders {
159        BOTH,
160        INITIATOR,
161        NONE,
162        RESPONDER;
163
164        public static Senders of(final String value) {
165            return Senders.valueOf(value.toUpperCase(Locale.ROOT));
166        }
167
168        public static Senders of(final SessionDescription.Media media, final boolean initiator) {
169            final Set<String> attributes = media.attributes.keySet();
170            if (attributes.contains("sendrecv")) {
171                return BOTH;
172            } else if (attributes.contains("inactive")) {
173                return NONE;
174            } else if (attributes.contains("sendonly")) {
175                return initiator ? INITIATOR : RESPONDER;
176            } else if (attributes.contains("recvonly")) {
177                return initiator ? RESPONDER : INITIATOR;
178            }
179            Log.w(Config.LOGTAG, "assuming default value for senders");
180            // If none of the attributes "sendonly", "recvonly", "inactive", and "sendrecv" is
181            // present, "sendrecv" SHOULD be assumed as the default
182            // https://www.rfc-editor.org/rfc/rfc4566
183            return BOTH;
184        }
185
186        public static Set<Senders> receiveOnly(final boolean initiator) {
187            return ImmutableSet.of(initiator ? RESPONDER : INITIATOR);
188        }
189
190        @Override
191        @NonNull
192        public String toString() {
193            return super.toString().toLowerCase(Locale.ROOT);
194        }
195
196        public String asMediaAttribute(final boolean initiator) {
197            final boolean responder = !initiator;
198            if (this == Content.Senders.BOTH) {
199                return "sendrecv";
200            } else if (this == Content.Senders.NONE) {
201                return "inactive";
202            } else if ((initiator && this == Content.Senders.INITIATOR)
203                    || (responder && this == Content.Senders.RESPONDER)) {
204                return "sendonly";
205            } else if ((initiator && this == Content.Senders.RESPONDER)
206                    || (responder && this == Content.Senders.INITIATOR)) {
207                return "recvonly";
208            } else {
209                throw new IllegalStateException(
210                        String.format(
211                                "illegal combination of initiator=%s and %s", initiator, this));
212            }
213        }
214    }
215}