1package eu.siacs.conversations.xmpp.jingle.stanzas;
2
3import android.util.Log;
4
5import androidx.annotation.NonNull;
6
7import com.google.common.base.CaseFormat;
8import com.google.common.base.Preconditions;
9import com.google.common.base.Strings;
10import com.google.common.collect.ImmutableMap;
11
12import eu.siacs.conversations.Config;
13import eu.siacs.conversations.crypto.axolotl.AxolotlService;
14import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
15import eu.siacs.conversations.xml.Element;
16import eu.siacs.conversations.xml.Namespace;
17import eu.siacs.conversations.xmpp.Jid;
18import eu.siacs.conversations.xmpp.stanzas.IqPacket;
19
20import java.util.Map;
21
22public class JinglePacket extends IqPacket {
23
24 private JinglePacket() {
25 super();
26 }
27
28 public JinglePacket(final Action action, final String sessionId) {
29 super(TYPE.SET);
30 final Element jingle = addChild("jingle", Namespace.JINGLE);
31 jingle.setAttribute("sid", sessionId);
32 jingle.setAttribute("action", action.toString());
33 }
34
35 public static JinglePacket upgrade(final IqPacket iqPacket) {
36 Preconditions.checkArgument(iqPacket.hasChild("jingle", Namespace.JINGLE));
37 Preconditions.checkArgument(iqPacket.getType() == TYPE.SET);
38 final JinglePacket jinglePacket = new JinglePacket();
39 jinglePacket.setAttributes(iqPacket.getAttributes());
40 jinglePacket.setChildren(iqPacket.getChildren());
41 return jinglePacket;
42 }
43
44 // TODO deprecate this somehow and make file transfer fail if there are multiple (or something)
45 public Content getJingleContent() {
46 final Element content = getJingleChild("content");
47 return content == null ? null : Content.upgrade(content);
48 }
49
50 public Group getGroup() {
51 final Element jingle = findChild("jingle", Namespace.JINGLE);
52 final Element group = jingle.findChild("group", Namespace.JINGLE_APPS_GROUPING);
53 return group == null ? null : Group.upgrade(group);
54 }
55
56 public void addGroup(final Group group) {
57 this.addJingleChild(group);
58 }
59
60 public Map<String, Content> getJingleContents() {
61 final Element jingle = findChild("jingle", Namespace.JINGLE);
62 ImmutableMap.Builder<String, Content> builder = new ImmutableMap.Builder<>();
63 for (final Element child : jingle.getChildren()) {
64 if ("content".equals(child.getName())) {
65 final Content content = Content.upgrade(child);
66 builder.put(content.getContentName(), content);
67 }
68 }
69 return builder.build();
70 }
71
72 public void addJingleContent(final Content content) { // take content interface
73 addJingleChild(content);
74 }
75
76 public ReasonWrapper getReason() {
77 final Element reasonElement = getJingleChild("reason");
78 if (reasonElement == null) {
79 return new ReasonWrapper(Reason.UNKNOWN, null);
80 }
81 String text = null;
82 Reason reason = Reason.UNKNOWN;
83 for (Element child : reasonElement.getChildren()) {
84 if ("text".equals(child.getName())) {
85 text = child.getContent();
86 } else {
87 reason = Reason.of(child.getName());
88 }
89 }
90 return new ReasonWrapper(reason, text);
91 }
92
93 public void setReason(final Reason reason, final String text) {
94 final Element jingle = findChild("jingle", Namespace.JINGLE);
95 final Element reasonElement = jingle.addChild("reason");
96 reasonElement.addChild(reason.toString());
97 if (!Strings.isNullOrEmpty(text)) {
98 reasonElement.addChild("text").setContent(text);
99 }
100 }
101
102 // RECOMMENDED for session-initiate, NOT RECOMMENDED otherwise
103 public void setInitiator(final Jid initiator) {
104 Preconditions.checkArgument(initiator.isFullJid(), "initiator should be a full JID");
105 findChild("jingle", Namespace.JINGLE).setAttribute("initiator", initiator);
106 }
107
108 // RECOMMENDED for session-accept, NOT RECOMMENDED otherwise
109 public void setResponder(Jid responder) {
110 Preconditions.checkArgument(responder.isFullJid(), "responder should be a full JID");
111 findChild("jingle", Namespace.JINGLE).setAttribute("responder", responder);
112 }
113
114 public Element getJingleChild(final String name) {
115 final Element jingle = findChild("jingle", Namespace.JINGLE);
116 return jingle == null ? null : jingle.findChild(name);
117 }
118
119 public void addJingleChild(final Element child) {
120 final Element jingle = findChild("jingle", Namespace.JINGLE);
121 jingle.addChild(child);
122 }
123
124 public void setSecurity(final String name, final XmppAxolotlMessage xmppAxolotlMessage) {
125 final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
126 security.setAttribute("name", name);
127 security.setAttribute("cipher", "urn:xmpp:ciphers:aes-128-gcm-nopadding");
128 security.setAttribute("type", AxolotlService.PEP_PREFIX);
129 security.addChild(xmppAxolotlMessage.toElement());
130 addJingleChild(security);
131 }
132
133 public XmppAxolotlMessage getSecurity(final String nameNeedle) {
134 final Element jingle = findChild("jingle", Namespace.JINGLE);
135 if (jingle == null) {
136 return null;
137 }
138 for (final Element child : jingle.getChildren()) {
139 if ("security".equals(child.getName())
140 && Namespace.JINGLE_ENCRYPTED_TRANSPORT.equals(child.getNamespace())) {
141 final String name = child.getAttribute("name");
142 final String type = child.getAttribute("type");
143 final String cipher = child.getAttribute("cipher");
144 if (nameNeedle.equals(name)
145 && AxolotlService.PEP_PREFIX.equals(type)
146 && "urn:xmpp:ciphers:aes-128-gcm-nopadding".equals(cipher)) {
147 final var encrypted = child.findChild("encrypted", AxolotlService.PEP_PREFIX);
148 if (encrypted != null) {
149 return XmppAxolotlMessage.fromElement(encrypted, getFrom().asBareJid());
150 }
151 }
152 }
153 }
154 return null;
155 }
156
157 public String getSessionId() {
158 return findChild("jingle", Namespace.JINGLE).getAttribute("sid");
159 }
160
161 public Action getAction() {
162 return Action.of(findChild("jingle", Namespace.JINGLE).getAttribute("action"));
163 }
164
165 public enum Action {
166 CONTENT_ACCEPT,
167 CONTENT_ADD,
168 CONTENT_MODIFY,
169 CONTENT_REJECT,
170 CONTENT_REMOVE,
171 DESCRIPTION_INFO,
172 SECURITY_INFO,
173 SESSION_ACCEPT,
174 SESSION_INFO,
175 SESSION_INITIATE,
176 SESSION_TERMINATE,
177 TRANSPORT_ACCEPT,
178 TRANSPORT_INFO,
179 TRANSPORT_REJECT,
180 TRANSPORT_REPLACE;
181
182 public static Action of(final String value) {
183 // TODO handle invalid
184 return Action.valueOf(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, value));
185 }
186
187 @Override
188 @NonNull
189 public String toString() {
190 return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, super.toString());
191 }
192 }
193
194 public static class ReasonWrapper {
195 public final Reason reason;
196 public final String text;
197
198 public ReasonWrapper(Reason reason, String text) {
199 this.reason = reason;
200 this.text = text;
201 }
202 }
203}