1package eu.siacs.conversations.xmpp.jingle.stanzas;
2
3import android.util.Log;
4
5import androidx.annotation.NonNull;
6
7import com.google.common.base.Preconditions;
8import com.google.common.base.Strings;
9import com.google.common.collect.ImmutableSet;
10
11import eu.siacs.conversations.Config;
12import eu.siacs.conversations.xml.Element;
13import eu.siacs.conversations.xml.Namespace;
14import eu.siacs.conversations.xmpp.jingle.SessionDescription;
15
16import java.util.Locale;
17import java.util.Set;
18
19public class Content extends Element {
20
21 public Content(final Creator creator, final Senders senders, final String name) {
22 super("content", Namespace.JINGLE);
23 this.setAttribute("creator", creator.toString());
24 this.setAttribute("name", name);
25 this.setSenders(senders);
26 }
27
28 private Content() {
29 super("content", Namespace.JINGLE);
30 }
31
32 public static Content upgrade(final Element element) {
33 Preconditions.checkArgument("content".equals(element.getName()));
34 final Content content = new Content();
35 content.setAttributes(element.getAttributes());
36 content.setChildren(element.getChildren());
37 return content;
38 }
39
40 public String getContentName() {
41 return this.getAttribute("name");
42 }
43
44 public Creator getCreator() {
45 return Creator.of(getAttribute("creator"));
46 }
47
48 public Senders getSenders() {
49 final String attribute = getAttribute("senders");
50 if (Strings.isNullOrEmpty(attribute)) {
51 return Senders.BOTH;
52 }
53 return Senders.of(getAttribute("senders"));
54 }
55
56 public void setSenders(final Senders senders) {
57 if (senders != null && senders != Senders.BOTH) {
58 this.setAttribute("senders", senders.toString());
59 }
60 }
61
62 public GenericDescription getDescription() {
63 final Element description = this.findChild("description");
64 if (description == null) {
65 return null;
66 }
67 final String namespace = description.getNamespace();
68 if (Namespace.JINGLE_APPS_FILE_TRANSFER.equals(namespace)) {
69 return FileTransferDescription.upgrade(description);
70 } else if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
71 return RtpDescription.upgrade(description);
72 } else {
73 return GenericDescription.upgrade(description);
74 }
75 }
76
77 public void setDescription(final GenericDescription description) {
78 Preconditions.checkNotNull(description);
79 this.addChild(description);
80 }
81
82 public String getDescriptionNamespace() {
83 final Element description = this.findChild("description");
84 return description == null ? null : description.getNamespace();
85 }
86
87 public GenericTransportInfo getTransport() {
88 final Element transport = this.findChild("transport");
89 final String namespace = transport == null ? null : transport.getNamespace();
90 if (Namespace.JINGLE_TRANSPORTS_IBB.equals(namespace)) {
91 return IbbTransportInfo.upgrade(transport);
92 } else if (Namespace.JINGLE_TRANSPORTS_S5B.equals(namespace)) {
93 return SocksByteStreamsTransportInfo.upgrade(transport);
94 } else if (Namespace.JINGLE_TRANSPORT_ICE_UDP.equals(namespace)) {
95 return IceUdpTransportInfo.upgrade(transport);
96 } else if (Namespace.JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL.equals(namespace)) {
97 return WebRTCDataChannelTransportInfo.upgrade(transport);
98 } else if (transport != null) {
99 return GenericTransportInfo.upgrade(transport);
100 } else {
101 return null;
102 }
103 }
104
105 public void setTransport(GenericTransportInfo transportInfo) {
106 this.addChild(transportInfo);
107 }
108
109 public enum Creator {
110 INITIATOR,
111 RESPONDER;
112
113 public static Creator of(final String value) {
114 return Creator.valueOf(value.toUpperCase(Locale.ROOT));
115 }
116
117 @Override
118 @NonNull
119 public String toString() {
120 return super.toString().toLowerCase(Locale.ROOT);
121 }
122 }
123
124 public enum Senders {
125 BOTH,
126 INITIATOR,
127 NONE,
128 RESPONDER;
129
130 public static Senders of(final String value) {
131 return Senders.valueOf(value.toUpperCase(Locale.ROOT));
132 }
133
134 public static Senders of(final SessionDescription.Media media, final boolean initiator) {
135 final Set<String> attributes = media.attributes.keySet();
136 if (attributes.contains("sendrecv")) {
137 return BOTH;
138 } else if (attributes.contains("inactive")) {
139 return NONE;
140 } else if (attributes.contains("sendonly")) {
141 return initiator ? INITIATOR : RESPONDER;
142 } else if (attributes.contains("recvonly")) {
143 return initiator ? RESPONDER : INITIATOR;
144 }
145 Log.w(Config.LOGTAG, "assuming default value for senders");
146 // If none of the attributes "sendonly", "recvonly", "inactive", and "sendrecv" is
147 // present, "sendrecv" SHOULD be assumed as the default
148 // https://www.rfc-editor.org/rfc/rfc4566
149 return BOTH;
150 }
151
152 public static Set<Senders> receiveOnly(final boolean initiator) {
153 return ImmutableSet.of(initiator ? RESPONDER : INITIATOR);
154 }
155
156 @Override
157 @NonNull
158 public String toString() {
159 return super.toString().toLowerCase(Locale.ROOT);
160 }
161
162 public String asMediaAttribute(final boolean initiator) {
163 final boolean responder = !initiator;
164 if (this == Content.Senders.BOTH) {
165 return "sendrecv";
166 } else if (this == Content.Senders.NONE) {
167 return "inactive";
168 } else if ((initiator && this == Content.Senders.INITIATOR)
169 || (responder && this == Content.Senders.RESPONDER)) {
170 return "sendonly";
171 } else if ((initiator && this == Content.Senders.RESPONDER)
172 || (responder && this == Content.Senders.INITIATOR)) {
173 return "recvonly";
174 } else {
175 throw new IllegalStateException(
176 String.format(
177 "illegal combination of initiator=%s and %s", initiator, this));
178 }
179 }
180 }
181}