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