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;
030
031import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager;
032import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy;
033import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
034import org.jivesoftware.smackx.jingle.JingleSession;
035import org.jivesoftware.smackx.jingle.element.Jingle;
036import org.jivesoftware.smackx.jingle.element.JingleAction;
037import org.jivesoftware.smackx.jingle.element.JingleContent;
038import org.jivesoftware.smackx.jingle.provider.JingleContentProviderManager;
039import org.jivesoftware.smackx.jingle.transports.JingleTransportManager;
040import org.jivesoftware.smackx.jingle.transports.JingleTransportSession;
041import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport;
042import org.jivesoftware.smackx.jingle.transports.jingle_s5b.provider.JingleS5BTransportProvider;
043
044import org.jxmpp.jid.FullJid;
045import org.jxmpp.jid.Jid;
046
047/**
048 * Manager for Jingle SOCKS5 Bytestream transports (XEP-0261).
049 */
050public final class JingleS5BTransportManager extends JingleTransportManager<JingleS5BTransport> {
051
052    private static final Logger LOGGER = Logger.getLogger(JingleS5BTransportManager.class.getName());
053
054    private static final WeakHashMap<XMPPConnection, JingleS5BTransportManager> INSTANCES = new WeakHashMap<>();
055
056    private List<Bytestream.StreamHost> localStreamHosts = null;
057    private List<Bytestream.StreamHost> availableStreamHosts = null;
058
059    private static boolean useLocalCandidates = true;
060    private static boolean useExternalCandidates = true;
061
062    private JingleS5BTransportManager(XMPPConnection connection) {
063        super(connection);
064        JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleS5BTransportProvider());
065    }
066
067    public static synchronized JingleS5BTransportManager getInstanceFor(XMPPConnection connection) {
068        JingleS5BTransportManager manager = INSTANCES.get(connection);
069        if (manager == null) {
070            manager = new JingleS5BTransportManager(connection);
071            INSTANCES.put(connection, manager);
072        }
073        return manager;
074    }
075
076    @Override
077    public String getNamespace() {
078        return JingleS5BTransport.NAMESPACE_V1;
079    }
080
081    @Override
082    public JingleTransportSession<JingleS5BTransport> transportSession(JingleSession jingleSession) {
083        return new JingleS5BTransportSession(jingleSession);
084    }
085
086    private List<Bytestream.StreamHost> queryAvailableStreamHosts() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
087        Socks5BytestreamManager s5m = Socks5BytestreamManager.getBytestreamManager(getConnection());
088        List<Jid> proxies = s5m.determineProxies();
089        return determineStreamHostInfo(proxies);
090    }
091
092    private List<Bytestream.StreamHost> queryLocalStreamHosts() {
093        return Socks5BytestreamManager.getBytestreamManager(getConnection())
094                .getLocalStreamHost();
095    }
096
097    public List<Bytestream.StreamHost> getAvailableStreamHosts() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
098        if (availableStreamHosts == null) {
099            availableStreamHosts = queryAvailableStreamHosts();
100        }
101        return availableStreamHosts;
102    }
103
104    public List<Bytestream.StreamHost> getLocalStreamHosts() {
105        if (localStreamHosts == null) {
106            localStreamHosts = queryLocalStreamHosts();
107        }
108        return localStreamHosts;
109    }
110
111    public List<Bytestream.StreamHost> determineStreamHostInfo(List<Jid> proxies) {
112        XMPPConnection connection = getConnection();
113        List<Bytestream.StreamHost> streamHosts = new ArrayList<>();
114
115        Iterator<Jid> iterator = proxies.iterator();
116        while (iterator.hasNext()) {
117            Jid proxy = iterator.next();
118            Bytestream request = new Bytestream();
119            request.setType(IQ.Type.get);
120            request.setTo(proxy);
121            try {
122                Bytestream response = connection.sendIqRequestAndWaitForResponse(request);
123                streamHosts.addAll(response.getStreamHosts());
124            }
125            catch (Exception e) {
126                iterator.remove();
127            }
128        }
129
130        return streamHosts;
131    }
132
133
134    @Override
135    public void authenticated(XMPPConnection connection, boolean resumed) {
136        if (!resumed) try {
137            Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy();
138            if (!socks5Proxy.isRunning()) {
139                socks5Proxy.start();
140            }
141            localStreamHosts = queryLocalStreamHosts();
142            availableStreamHosts = queryAvailableStreamHosts();
143        } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
144            LOGGER.log(Level.WARNING, "Could not query available StreamHosts: " + e, e);
145        }
146    }
147
148    public Jingle createCandidateUsed(FullJid recipient, FullJid initiator, String sessionId, JingleContent.Senders contentSenders,
149                                      JingleContent.Creator contentCreator, String contentName, String streamId,
150                                      String candidateId) {
151        Jingle.Builder jb = Jingle.builder(connection());
152        jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info);
153
154        JingleContent.Builder cb = JingleContent.getBuilder();
155        cb.setName(contentName).setCreator(contentCreator).setSenders(contentSenders);
156
157        JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder();
158        tb.setCandidateUsed(candidateId).setStreamId(streamId);
159
160        Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build();
161        jingle.setFrom(getConnection().getUser().asFullJidOrThrow());
162        jingle.setTo(recipient);
163
164        return jingle;
165    }
166
167    public Jingle createCandidateError(FullJid remote, FullJid initiator, String sessionId, JingleContent.Senders senders, JingleContent.Creator creator, String name, String streamId) {
168        Jingle.Builder jb = Jingle.builder(connection());
169        jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info);
170
171        JingleContent.Builder cb = JingleContent.getBuilder();
172        cb.setName(name).setCreator(creator).setSenders(senders);
173
174        JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder();
175        tb.setCandidateError().setStreamId(streamId);
176
177        Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build();
178        jingle.setFrom(getConnection().getUser().asFullJidOrThrow());
179        jingle.setTo(remote);
180
181        return jingle;
182    }
183
184    public Jingle createProxyError(FullJid remote, FullJid initiator, String sessionId,
185                                   JingleContent.Senders senders, JingleContent.Creator creator,
186                                   String name, String streamId) {
187        Jingle.Builder jb = Jingle.builder(connection());
188        jb.setSessionId(sessionId).setAction(JingleAction.transport_info).setInitiator(initiator);
189
190        JingleContent.Builder cb = JingleContent.getBuilder();
191        cb.setSenders(senders).setCreator(creator).setName(name);
192
193        JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder();
194        tb.setStreamId(sessionId).setProxyError().setStreamId(streamId);
195
196        Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build();
197        jingle.setTo(remote);
198        jingle.setFrom(getConnection().getUser().asFullJidOrThrow());
199        return jingle;
200    }
201
202    public Jingle createCandidateActivated(FullJid remote, FullJid initiator, String sessionId,
203                                           JingleContent.Senders senders, JingleContent.Creator creator,
204                                           String name, String streamId, String candidateId) {
205        Jingle.Builder jb = Jingle.builder(connection());
206        jb.setInitiator(initiator).setSessionId(sessionId).setAction(JingleAction.transport_info);
207
208        JingleContent.Builder cb = JingleContent.getBuilder();
209        cb.setName(name).setCreator(creator).setSenders(senders);
210
211        JingleS5BTransport.Builder tb = JingleS5BTransport.getBuilder();
212        tb.setStreamId(streamId).setCandidateActivated(candidateId);
213
214        Jingle jingle = jb.addJingleContent(cb.setTransport(tb.build()).build()).build();
215        jingle.setFrom(getConnection().getUser().asFullJidOrThrow());
216        jingle.setTo(remote);
217        return jingle;
218    }
219
220    public static void setUseLocalCandidates(boolean localCandidates) {
221        JingleS5BTransportManager.useLocalCandidates = localCandidates;
222    }
223
224    public static void setUseExternalCandidates(boolean externalCandidates) {
225        JingleS5BTransportManager.useExternalCandidates = externalCandidates;
226    }
227
228    public static boolean isUseLocalCandidates() {
229        return useLocalCandidates;
230    }
231
232    public static boolean isUseExternalCandidates() {
233        return useExternalCandidates;
234    }
235}