FileTransferContentMap.java

  1package eu.siacs.conversations.xmpp.jingle;
  2
  3import com.google.common.collect.ImmutableMap;
  4import com.google.common.collect.Iterables;
  5import com.google.common.collect.Maps;
  6
  7import eu.siacs.conversations.xml.Element;
  8import eu.siacs.conversations.xml.Namespace;
  9import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
 10import eu.siacs.conversations.xmpp.jingle.stanzas.FileTransferDescription;
 11import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
 12import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
 13import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
 14import eu.siacs.conversations.xmpp.jingle.stanzas.IbbTransportInfo;
 15import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
 16import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
 17import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
 18import eu.siacs.conversations.xmpp.jingle.stanzas.SocksByteStreamsTransportInfo;
 19import eu.siacs.conversations.xmpp.jingle.stanzas.WebRTCDataChannelTransportInfo;
 20import eu.siacs.conversations.xmpp.jingle.transports.Transport;
 21
 22import java.util.Arrays;
 23import java.util.Collections;
 24import java.util.List;
 25import java.util.Map;
 26
 27public class FileTransferContentMap
 28        extends AbstractContentMap<FileTransferDescription, GenericTransportInfo> {
 29
 30    private static final List<Class<? extends GenericTransportInfo>> SUPPORTED_TRANSPORTS =
 31            Arrays.asList(
 32                    SocksByteStreamsTransportInfo.class,
 33                    IbbTransportInfo.class,
 34                    WebRTCDataChannelTransportInfo.class);
 35
 36    protected FileTransferContentMap(
 37            final Group group, final Map<String, DescriptionTransport<FileTransferDescription, GenericTransportInfo>>
 38                    contents) {
 39        super(group, contents);
 40    }
 41
 42    public static FileTransferContentMap of(final JinglePacket jinglePacket) {
 43        final Map<String, DescriptionTransport<FileTransferDescription, GenericTransportInfo>>
 44                contents = of(jinglePacket.getJingleContents());
 45        return new FileTransferContentMap(jinglePacket.getGroup(), contents);
 46    }
 47
 48    public static DescriptionTransport<FileTransferDescription, GenericTransportInfo> of(
 49            final Content content) {
 50        final GenericDescription description = content.getDescription();
 51        final GenericTransportInfo transportInfo = content.getTransport();
 52        final Content.Senders senders = content.getSenders();
 53        final FileTransferDescription fileTransferDescription;
 54        if (description == null) {
 55            fileTransferDescription = null;
 56        } else if (description instanceof FileTransferDescription ftDescription) {
 57            fileTransferDescription = ftDescription;
 58        } else {
 59            throw new UnsupportedApplicationException(
 60                    "Content does not contain file transfer description");
 61        }
 62        if (!SUPPORTED_TRANSPORTS.contains(transportInfo.getClass())) {
 63            throw new UnsupportedTransportException("Content does not have supported transport");
 64        }
 65        return new DescriptionTransport<>(senders, fileTransferDescription, transportInfo);
 66    }
 67
 68    private static Map<String, DescriptionTransport<FileTransferDescription, GenericTransportInfo>>
 69            of(final Map<String, Content> contents) {
 70        return ImmutableMap.copyOf(
 71                Maps.transformValues(contents, content -> content == null ? null : of(content)));
 72    }
 73
 74    public static FileTransferContentMap of(
 75            final FileTransferDescription.File file, final Transport.InitialTransportInfo initialTransportInfo) {
 76        // TODO copy groups
 77        final var transportInfo = initialTransportInfo.transportInfo;
 78        return new FileTransferContentMap(initialTransportInfo.group,
 79                Map.of(
 80                        initialTransportInfo.contentName,
 81                        new DescriptionTransport<>(
 82                                Content.Senders.INITIATOR,
 83                                FileTransferDescription.of(file),
 84                                transportInfo)));
 85    }
 86
 87    public FileTransferDescription.File requireOnlyFile() {
 88        if (this.contents.size() != 1) {
 89            throw new IllegalStateException("Only one file at a time is supported");
 90        }
 91        final var dt = Iterables.getOnlyElement(this.contents.values());
 92        return dt.description.getFile();
 93    }
 94
 95    public FileTransferDescription requireOnlyFileTransferDescription() {
 96        if (this.contents.size() != 1) {
 97            throw new IllegalStateException("Only one file at a time is supported");
 98        }
 99        final var dt = Iterables.getOnlyElement(this.contents.values());
100        return dt.description;
101    }
102
103    public GenericTransportInfo requireOnlyTransportInfo() {
104        if (this.contents.size() != 1) {
105            throw new IllegalStateException(
106                    "We expect exactly one content with one transport info");
107        }
108        final var dt = Iterables.getOnlyElement(this.contents.values());
109        return dt.transport;
110    }
111
112    public FileTransferContentMap withTransport(final Transport.TransportInfo transportWrapper) {
113        final var transportInfo = transportWrapper.transportInfo;
114        return new FileTransferContentMap(transportWrapper.group,
115                ImmutableMap.copyOf(
116                        Maps.transformValues(
117                                contents,
118                                content -> {
119                                    if (content == null) {
120                                        return null;
121                                    }
122                                    return new DescriptionTransport<>(
123                                            content.senders, content.description, transportInfo);
124                                })));
125    }
126
127    public FileTransferContentMap candidateUsed(final String streamId, final String cid) {
128        return new FileTransferContentMap(null,
129                ImmutableMap.copyOf(
130                        Maps.transformValues(
131                                contents,
132                                content -> {
133                                    if (content == null) {
134                                        return null;
135                                    }
136                                    final var transportInfo =
137                                            new SocksByteStreamsTransportInfo(
138                                                    streamId, Collections.emptyList());
139                                    final Element candidateUsed =
140                                            transportInfo.addChild(
141                                                    "candidate-used",
142                                                    Namespace.JINGLE_TRANSPORTS_S5B);
143                                    candidateUsed.setAttribute("cid", cid);
144                                    return new DescriptionTransport<>(
145                                            content.senders, null, transportInfo);
146                                })));
147    }
148
149    public FileTransferContentMap candidateError(final String streamId) {
150        return new FileTransferContentMap(null,
151                ImmutableMap.copyOf(
152                        Maps.transformValues(
153                                contents,
154                                content -> {
155                                    if (content == null) {
156                                        return null;
157                                    }
158                                    final var transportInfo =
159                                            new SocksByteStreamsTransportInfo(
160                                                    streamId, Collections.emptyList());
161                                    transportInfo.addChild(
162                                            "candidate-error", Namespace.JINGLE_TRANSPORTS_S5B);
163                                    return new DescriptionTransport<>(
164                                            content.senders, null, transportInfo);
165                                })));
166    }
167
168    public FileTransferContentMap proxyActivated(final String streamId, final String cid) {
169        return new FileTransferContentMap(null,
170                ImmutableMap.copyOf(
171                        Maps.transformValues(
172                                contents,
173                                content -> {
174                                    if (content == null) {
175                                        return null;
176                                    }
177                                    final var transportInfo =
178                                            new SocksByteStreamsTransportInfo(
179                                                    streamId, Collections.emptyList());
180                                    final Element candidateUsed =
181                                            transportInfo.addChild(
182                                                    "activated", Namespace.JINGLE_TRANSPORTS_S5B);
183                                    candidateUsed.setAttribute("cid", cid);
184                                    return new DescriptionTransport<>(
185                                            content.senders, null, transportInfo);
186                                })));
187    }
188
189    FileTransferContentMap transportInfo() {
190        return new FileTransferContentMap(this.group,
191                Maps.transformValues(
192                        contents,
193                        dt -> new DescriptionTransport<>(dt.senders, null, dt.transport)));
194    }
195
196    FileTransferContentMap transportInfo(
197            final String contentName, final IceUdpTransportInfo.Candidate candidate) {
198        final DescriptionTransport<FileTransferDescription, GenericTransportInfo> descriptionTransport =
199                contents.get(contentName);
200        if (descriptionTransport == null) {
201            throw new IllegalArgumentException(
202                    "Unable to find transport info for content name " + contentName);
203        }
204        final WebRTCDataChannelTransportInfo transportInfo;
205        if (descriptionTransport.transport instanceof WebRTCDataChannelTransportInfo webRTCDataChannelTransportInfo) {
206            transportInfo = webRTCDataChannelTransportInfo;
207        } else {
208            throw new IllegalStateException("TransportInfo is not WebRTCDataChannel");
209        }
210        final WebRTCDataChannelTransportInfo newTransportInfo = transportInfo.cloneWrapper();
211        newTransportInfo.addCandidate(candidate);
212        return new FileTransferContentMap(
213                null,
214                ImmutableMap.of(
215                        contentName,
216                        new DescriptionTransport<>(
217                                descriptionTransport.senders, null, newTransportInfo)));
218    }
219}