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