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