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 //todo throw unsupported application
124 throw new IllegalArgumentException("Content does not contain RtpDescription");
125 }
126 if (transportInfo instanceof IceUdpTransportInfo) {
127 iceUdpTransportInfo = (IceUdpTransportInfo) transportInfo;
128 } else {
129 //TODO throw UNSUPPORTED_TRANSPORT exception
130 throw new IllegalArgumentException("Content does not contain ICE-UDP transport");
131 }
132 return new DescriptionTransport(rtpDescription, iceUdpTransportInfo);
133 }
134
135 public static DescriptionTransport of(final SessionDescription sessionDescription, final SessionDescription.Media media) {
136 final RtpDescription rtpDescription = RtpDescription.of(media);
137 final IceUdpTransportInfo transportInfo = IceUdpTransportInfo.of(sessionDescription, media);
138 return new DescriptionTransport(rtpDescription, transportInfo);
139 }
140
141 public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
142 return ImmutableMap.copyOf(Maps.transformValues(contents, new Function<Content, DescriptionTransport>() {
143 @NullableDecl
144 @Override
145 public DescriptionTransport apply(@NullableDecl Content content) {
146 return content == null ? null : of(content);
147 }
148 }));
149 }
150 }
151}