RtpContentMap.java

  1package eu.siacs.conversations.xmpp.jingle;
  2
  3import android.util.Log;
  4
  5import com.google.common.base.Function;
  6import com.google.common.base.Preconditions;
  7import com.google.common.base.Strings;
  8import com.google.common.collect.Collections2;
  9import com.google.common.collect.ImmutableList;
 10import com.google.common.collect.ImmutableMap;
 11import com.google.common.collect.Iterables;
 12import com.google.common.collect.Maps;
 13import com.google.common.collect.Sets;
 14
 15import org.checkerframework.checker.nullness.compatqual.NullableDecl;
 16
 17import java.util.Collection;
 18import java.util.List;
 19import java.util.Map;
 20import java.util.Set;
 21
 22import eu.siacs.conversations.Config;
 23import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
 24import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
 25import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
 26import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
 27import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
 28import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
 29import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
 30import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
 31
 32public class RtpContentMap {
 33
 34    public final Group group;
 35    public final Map<String, DescriptionTransport> contents;
 36
 37    public RtpContentMap(Group group, Map<String, DescriptionTransport> contents) {
 38        this.group = group;
 39        this.contents = contents;
 40    }
 41
 42    public static RtpContentMap of(final JinglePacket jinglePacket) {
 43        final Map<String, DescriptionTransport> contents = DescriptionTransport.of(jinglePacket.getJingleContents());
 44        if (isOmemoVerified(contents)) {
 45            return new OmemoVerifiedRtpContentMap(jinglePacket.getGroup(), contents);
 46        } else {
 47            return new RtpContentMap(jinglePacket.getGroup(), contents);
 48        }
 49    }
 50
 51    private static boolean isOmemoVerified(Map<String, DescriptionTransport> contents) {
 52        final Collection<DescriptionTransport> values = contents.values();
 53        if (values.size() == 0) {
 54            return false;
 55        }
 56        for (final DescriptionTransport descriptionTransport : values) {
 57            if (descriptionTransport.transport instanceof OmemoVerifiedIceUdpTransportInfo) {
 58                continue;
 59            }
 60            return false;
 61        }
 62        return true;
 63    }
 64
 65    public static RtpContentMap of(final SessionDescription sessionDescription) {
 66        final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder = new ImmutableMap.Builder<>();
 67        for (SessionDescription.Media media : sessionDescription.media) {
 68            final String id = Iterables.getFirst(media.attributes.get("mid"), null);
 69            Preconditions.checkNotNull(id, "media has no mid");
 70            contentMapBuilder.put(id, DescriptionTransport.of(sessionDescription, media));
 71        }
 72        final String groupAttribute = Iterables.getFirst(sessionDescription.attributes.get("group"), null);
 73        final Group group = groupAttribute == null ? null : Group.ofSdpString(groupAttribute);
 74        return new RtpContentMap(group, contentMapBuilder.build());
 75    }
 76
 77    public Set<Media> getMedia() {
 78        return Sets.newHashSet(Collections2.transform(contents.values(), input -> {
 79            final RtpDescription rtpDescription = input == null ? null : input.description;
 80            return rtpDescription == null ? Media.UNKNOWN : input.description.getMedia();
 81        }));
 82    }
 83
 84    public List<String> getNames() {
 85        return ImmutableList.copyOf(contents.keySet());
 86    }
 87
 88    void requireContentDescriptions() {
 89        if (this.contents.size() == 0) {
 90            throw new IllegalStateException("No contents available");
 91        }
 92        for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
 93            if (entry.getValue().description == null) {
 94                throw new IllegalStateException(String.format("%s is lacking content description", entry.getKey()));
 95            }
 96        }
 97    }
 98
 99    void requireDTLSFingerprint() {
100        if (this.contents.size() == 0) {
101            throw new IllegalStateException("No contents available");
102        }
103        for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
104            final IceUdpTransportInfo transport = entry.getValue().transport;
105            final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
106            if (fingerprint == null || Strings.isNullOrEmpty(fingerprint.getContent()) || Strings.isNullOrEmpty(fingerprint.getHash())) {
107                throw new SecurityException(String.format("Use of DTLS-SRTP (XEP-0320) is required for content %s", entry.getKey()));
108            }
109            if (Strings.isNullOrEmpty(fingerprint.getSetup())) {
110                throw new SecurityException(String.format("Use of DTLS-SRTP (XEP-0320) is required for content %s but missing setup attribute", entry.getKey()));
111            }
112        }
113    }
114
115    JinglePacket toJinglePacket(final JinglePacket.Action action, final String sessionId) {
116        final JinglePacket jinglePacket = new JinglePacket(action, sessionId);
117        if (this.group != null) {
118            jinglePacket.addGroup(this.group);
119        }
120        for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
121            final Content content = new Content(Content.Creator.INITIATOR, entry.getKey());
122            if (entry.getValue().description != null) {
123                content.addChild(entry.getValue().description);
124            }
125            content.addChild(entry.getValue().transport);
126            jinglePacket.addJingleContent(content);
127        }
128        return jinglePacket;
129    }
130
131    RtpContentMap transportInfo(final String contentName, final IceUdpTransportInfo.Candidate candidate) {
132        final RtpContentMap.DescriptionTransport descriptionTransport = contents.get(contentName);
133        final IceUdpTransportInfo transportInfo = descriptionTransport == null ? null : descriptionTransport.transport;
134        if (transportInfo == null) {
135            throw new IllegalArgumentException("Unable to find transport info for content name " + contentName);
136        }
137        final IceUdpTransportInfo newTransportInfo = transportInfo.cloneWrapper();
138        newTransportInfo.addChild(candidate);
139        return new RtpContentMap(null, ImmutableMap.of(contentName, new DescriptionTransport(null, newTransportInfo)));
140
141    }
142
143    public static class DescriptionTransport {
144        public final RtpDescription description;
145        public final IceUdpTransportInfo transport;
146
147        public DescriptionTransport(final RtpDescription description, final IceUdpTransportInfo transport) {
148            this.description = description;
149            this.transport = transport;
150        }
151
152        public static DescriptionTransport of(final Content content) {
153            final GenericDescription description = content.getDescription();
154            final GenericTransportInfo transportInfo = content.getTransport();
155            final RtpDescription rtpDescription;
156            final IceUdpTransportInfo iceUdpTransportInfo;
157            if (description == null) {
158                rtpDescription = null;
159            } else if (description instanceof RtpDescription) {
160                rtpDescription = (RtpDescription) description;
161            } else {
162                throw new UnsupportedApplicationException("Content does not contain rtp description");
163            }
164            if (transportInfo instanceof IceUdpTransportInfo) {
165                iceUdpTransportInfo = (IceUdpTransportInfo) transportInfo;
166            } else {
167                throw new UnsupportedTransportException("Content does not contain ICE-UDP transport");
168            }
169            return new DescriptionTransport(
170                    rtpDescription,
171                    OmemoVerifiedIceUdpTransportInfo.upgrade(iceUdpTransportInfo)
172            );
173        }
174
175        public static DescriptionTransport of(final SessionDescription sessionDescription, final SessionDescription.Media media) {
176            final RtpDescription rtpDescription = RtpDescription.of(sessionDescription, media);
177            final IceUdpTransportInfo transportInfo = IceUdpTransportInfo.of(sessionDescription, media);
178            return new DescriptionTransport(rtpDescription, transportInfo);
179        }
180
181        public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
182            return ImmutableMap.copyOf(Maps.transformValues(contents, new Function<Content, DescriptionTransport>() {
183                @NullableDecl
184                @Override
185                public DescriptionTransport apply(@NullableDecl Content content) {
186                    return content == null ? null : of(content);
187                }
188            }));
189        }
190    }
191
192    public static class UnsupportedApplicationException extends IllegalArgumentException {
193        UnsupportedApplicationException(String message) {
194            super(message);
195        }
196    }
197
198    public static class UnsupportedTransportException extends IllegalArgumentException {
199        UnsupportedTransportException(String message) {
200            super(message);
201        }
202    }
203}