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}