001/**
002 *
003 * Copyright 2017 Paul Schaub
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.jingle.transports.jingle_s5b;
018
019import java.util.ArrayList;
020import java.util.Iterator;
021import java.util.List;
022import java.util.WeakHashMap;
023import java.util.logging.Level;
024import java.util.logging.Logger;
025
026import org.jivesoftware.smack.SmackException;
027import org.jivesoftware.smack.XMPPConnection;
028import org.jivesoftware.smack.XMPPException;
029import org.jivesoftware.smack.packet.IQ;
030import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager;
031import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy;
032import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
033import org.jivesoftware.smackx.jingle.JingleSession;
034import org.jivesoftware.smackx.jingle.element.Jingle;
035import org.jivesoftware.smackx.jingle.element.JingleAction;
036import org.jivesoftware.smackx.jingle.element.JingleContent;
037import org.jivesoftware.smackx.jingle.provider.JingleContentProviderManager;
038import org.jivesoftware.smackx.jingle.transports.JingleTransportManager;
039import org.jivesoftware.smackx.jingle.transports.JingleTransportSession;
040import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport;
041import org.jivesoftware.smackx.jingle.transports.jingle_s5b.provider.JingleS5BTransportProvider;
042
043import org.jxmpp.jid.FullJid;
044import org.jxmpp.jid.Jid;
045
046/**
047 * Manager for Jingle SOCKS5 Bytestream transports (XEP-0261).
048 */
049public final class JingleS5BTransportManager extends JingleTransportManager<JingleS5BTransport> {
050
051    private static final Logger LOGGER = Logger.getLogger(JingleS5BTransportManager.class.getName());
052
053    private static final WeakHashMap<XMPPConnection, JingleS5BTransportManager> INSTANCES = new WeakHashMap<>();
054
055    private List<Bytestream.StreamHost> localStreamHosts = null;
056    private List<Bytestream.StreamHost> availableStreamHosts = null;
057
058    private static boolean useLocalCandidates = true;
059    private static boolean useExternalCandidates = true;
060
061    private JingleS5BTransportManager(XMPPConnection connection) {
062        super(connection);
063        JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleS5BTransportProvider());
064    }
065
066    public static JingleS5BTransportManager getInstanceFor(XMPPConnection connection) {
067        JingleS5BTransportManager manager = INSTANCES.get(connection);
068        if (manager == null) {
069            manager = new JingleS5BTransportManager(connection);
070            INSTANCES.put(connection, manager);
071        }
072        return manager;
073    }
074
075    @Override
076    public String getNamespace() {
077        return JingleS5BTransport.NAMESPACE_V1;
078    }
079
080    @Override
081    public JingleTransportSession<JingleS5BTransport> transportSession(JingleSession jingleSession) {
082        return new JingleS5BTransportSession(jingleSession);
083    }
084
085    private List<Bytestream.StreamHost> queryAvailableStreamHosts() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
086        Socks5BytestreamManager s5m = Socks5BytestreamManager.getBytestreamManager(getConnection());
087        List<Jid> proxies = s5m.determineProxies();
088        return determineStreamHostInfo(proxies);
089    }
090
091    private List<Bytestream.StreamHost> queryLocalStreamHosts() {
092        return Socks5BytestreamManager.getBytestreamManager(getConnection())
093                .getLocalStreamHost();
094    }
095
096    public List<Bytestream.StreamHost> getAvailableStreamHosts() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
097        if (availableStreamHosts == null) {
098            availableStreamHosts = queryAvailableStreamHosts();
099        }
100        return availableStreamHosts;
101    }
102
103    public List<Bytestream.StreamHost> getLocalStreamHosts() {
104        if (localStreamHosts == null) {
105            localStreamHosts = queryLocalStreamHosts();
106        }
107        return localStreamHosts;
108    }
109
110    public List<Bytestream.StreamHost> determineStreamHostInfo(List<Jid> proxies) {
111        XMPPConnection connection = getConnection();
112        List<Bytestream.StreamHost> streamHosts = new ArrayList<>();
113
114        Iterator<Jid> iterator = proxies.iterator();
115        while (iterator.hasNext()) {
116            Jid proxy = iterator.next();
117            Bytestream request = new Bytestream();
118            request.setType(IQ.Type.get);
119            request.setTo(proxy);
120            try {
121                Bytestream response = connection.createStanzaCollectorAndSend(request).nextResultOrThrow();
122                streamHosts.addAll(response.getStreamHosts());
123            }
124            catch (Exception e) {
125                iterator.remove();
126            }
127        }
128
129        return streamHosts;
130    }
131
132
133    @Override
134    public void authenticated(XMPPConnection connection, boolean resumed) {
135        if (!resumed) try {
136            Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy();
137            if (!socks5Proxy.isRunning()) {
138                socks5Proxy.start();
139            }
140            localStreamHosts = queryLocalStreamHosts();
141            availableStreamHosts = queryAvailableStreamHosts();
142        } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
143            LOGGER.log(Level.WARNING, "Could not query available StreamHosts: " + e, e);
144        }
145    }
146
147    public Jingle createCandidateUsed(FullJid recipient, FullJid initiator, String sessionId, JingleContent.Senders contentSenders,
148                                      JingleContent.Creator contentCreator, String contentName, String streamId,
149                                      String candidateId) {
150        Jingle.Builder jb = Jingle.getBuilder();
151        jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info);
152
153        JingleContent.Builder cb = JingleContent.getBuilder();
154        cb.setName(contentName).setCreator(contentCreator).setSenders(contentSenders);
155
156        JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder();
157        tb.setCandidateUsed(candidateId).setStreamId(streamId);
158
159        Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build();
160        jingle.setFrom(getConnection().getUser().asFullJidOrThrow());
161        jingle.setTo(recipient);
162
163        return jingle;
164    }
165
166    public Jingle createCandidateError(FullJid remote, FullJid initiator, String sessionId, JingleContent.Senders senders, JingleContent.Creator creator, String name, String streamId) {
167        Jingle.Builder jb = Jingle.getBuilder();
168        jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info);
169
170        JingleContent.Builder cb = JingleContent.getBuilder();
171        cb.setName(name).setCreator(creator).setSenders(senders);
172
173        JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder();
174        tb.setCandidateError().setStreamId(streamId);
175
176        Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build();
177        jingle.setFrom(getConnection().getUser().asFullJidOrThrow());
178        jingle.setTo(remote);
179
180        return jingle;
181    }
182
183    public Jingle createProxyError(FullJid remote, FullJid initiator, String sessionId,
184                                   JingleContent.Senders senders, JingleContent.Creator creator,
185                                   String name, String streamId) {
186        Jingle.Builder jb = Jingle.getBuilder();
187        jb.setSessionId(sessionId).setAction(JingleAction.transport_info).setInitiator(initiator);
188
189        JingleContent.Builder cb = JingleContent.getBuilder();
190        cb.setSenders(senders).setCreator(creator).setName(name);
191
192        JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder();
193        tb.setStreamId(sessionId).setProxyError().setStreamId(streamId);
194
195        Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build();
196        jingle.setTo(remote);
197        jingle.setFrom(getConnection().getUser().asFullJidOrThrow());
198        return jingle;
199    }
200
201    public Jingle createCandidateActivated(FullJid remote, FullJid initiator, String sessionId,
202                                           JingleContent.Senders senders, JingleContent.Creator creator,
203                                           String name, String streamId, String candidateId) {
204        Jingle.Builder jb = Jingle.getBuilder();
205        jb.setInitiator(initiator).setSessionId(sessionId).setAction(JingleAction.transport_info);
206
207        JingleContent.Builder cb = JingleContent.getBuilder();
208        cb.setName(name).setCreator(creator).setSenders(senders);
209
210        JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder();
211        tb.setStreamId(streamId).setCandidateActivated(candidateId);
212
213        Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build();
214        jingle.setFrom(getConnection().getUser().asFullJidOrThrow());
215        jingle.setTo(remote);
216        return jingle;
217    }
218
219    public static void setUseLocalCandidates(boolean localCandidates) {
220        JingleS5BTransportManager.useLocalCandidates = localCandidates;
221    }
222
223    public static void setUseExternalCandidates(boolean externalCandidates) {
224        JingleS5BTransportManager.useExternalCandidates = externalCandidates;
225    }
226
227    public static boolean isUseLocalCandidates() {
228        return useLocalCandidates;
229    }
230
231    public static boolean isUseExternalCandidates() {
232        return useExternalCandidates;
233    }
234}