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