JingleS5BTransportSession.java
- /**
- *
- * Copyright 2017 Paul Schaub
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.jivesoftware.smackx.jingle.transports.jingle_s5b;
- import java.io.IOException;
- import java.net.InetAddress;
- import java.net.Socket;
- import java.util.Collections;
- import java.util.List;
- import java.util.concurrent.TimeoutException;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import org.jivesoftware.smack.SmackException;
- import org.jivesoftware.smack.XMPPException;
- import org.jivesoftware.smack.packet.IQ;
- import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession;
- import org.jivesoftware.smackx.bytestreams.socks5.Socks5Client;
- import org.jivesoftware.smackx.bytestreams.socks5.Socks5ClientForInitiator;
- import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy;
- import org.jivesoftware.smackx.bytestreams.socks5.Socks5Utils;
- import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
- import org.jivesoftware.smackx.jingle.JingleManager;
- import org.jivesoftware.smackx.jingle.JingleSession;
- import org.jivesoftware.smackx.jingle.element.Jingle;
- import org.jivesoftware.smackx.jingle.element.JingleContent;
- import org.jivesoftware.smackx.jingle.element.JingleContentTransport;
- import org.jivesoftware.smackx.jingle.element.JingleContentTransportCandidate;
- import org.jivesoftware.smackx.jingle.transports.JingleTransportInitiationCallback;
- import org.jivesoftware.smackx.jingle.transports.JingleTransportSession;
- import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport;
- import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransportCandidate;
- import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransportInfo;
- /**
- * Handler that handles Jingle Socks5Bytestream transports (XEP-0260).
- */
- public class JingleS5BTransportSession extends JingleTransportSession<JingleS5BTransport> {
- private static final Logger LOGGER = Logger.getLogger(JingleS5BTransportSession.class.getName());
- private JingleTransportInitiationCallback callback;
- public JingleS5BTransportSession(JingleSession jingleSession) {
- super(jingleSession);
- }
- private UsedCandidate ourChoice, theirChoice;
- @Override
- public JingleS5BTransport createTransport() {
- if (ourProposal == null) {
- ourProposal = createTransport(JingleManager.randomId(), Bytestream.Mode.tcp);
- }
- return ourProposal;
- }
- @Override
- public void setTheirProposal(JingleContentTransport transport) {
- theirProposal = (JingleS5BTransport) transport;
- }
- public JingleS5BTransport createTransport(String sid, Bytestream.Mode mode) {
- JingleS5BTransport.Builder jb = JingleS5BTransport.getBuilder()
- .setStreamId(sid).setMode(mode).setDestinationAddress(
- Socks5Utils.createDigest(sid, jingleSession.getLocal(), jingleSession.getRemote()));
- // Local host
- if (JingleS5BTransportManager.isUseLocalCandidates()) {
- for (Bytestream.StreamHost host : transportManager().getLocalStreamHosts()) {
- jb.addTransportCandidate(new JingleS5BTransportCandidate(host, 100, JingleS5BTransportCandidate.Type.direct));
- }
- }
- List<Bytestream.StreamHost> remoteHosts = Collections.emptyList();
- if (JingleS5BTransportManager.isUseExternalCandidates()) {
- try {
- remoteHosts = transportManager().getAvailableStreamHosts();
- } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
- LOGGER.log(Level.WARNING, "Could not determine available StreamHosts.", e);
- }
- }
- for (Bytestream.StreamHost host : remoteHosts) {
- jb.addTransportCandidate(new JingleS5BTransportCandidate(host, 0, JingleS5BTransportCandidate.Type.proxy));
- }
- return jb.build();
- }
- public void setTheirTransport(JingleContentTransport transport) {
- theirProposal = (JingleS5BTransport) transport;
- }
- @Override
- public void initiateOutgoingSession(JingleTransportInitiationCallback callback) {
- this.callback = callback;
- initiateSession();
- }
- @Override
- public void initiateIncomingSession(JingleTransportInitiationCallback callback) {
- this.callback = callback;
- initiateSession();
- }
- private void initiateSession() {
- Socks5Proxy.getSocks5Proxy().addTransfer(createTransport().getDestinationAddress());
- JingleContent content = jingleSession.getContents().get(0);
- UsedCandidate usedCandidate = chooseFromProposedCandidates(theirProposal);
- if (usedCandidate == null) {
- ourChoice = CANDIDATE_FAILURE;
- Jingle candidateError = transportManager().createCandidateError(
- jingleSession.getRemote(), jingleSession.getInitiator(), jingleSession.getSessionId(),
- content.getSenders(), content.getCreator(), content.getName(), theirProposal.getStreamId());
- try {
- jingleSession.getConnection().sendStanza(candidateError);
- } catch (SmackException.NotConnectedException | InterruptedException e) {
- LOGGER.log(Level.WARNING, "Could not send candidate-error.", e);
- }
- } else {
- ourChoice = usedCandidate;
- Jingle jingle = transportManager().createCandidateUsed(jingleSession.getRemote(), jingleSession.getInitiator(), jingleSession.getSessionId(),
- content.getSenders(), content.getCreator(), content.getName(), theirProposal.getStreamId(), ourChoice.candidate.getCandidateId());
- try {
- jingleSession.getConnection().sendIqRequestAndWaitForResponse(jingle);
- } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
- LOGGER.log(Level.WARNING, "Could not send candidate-used.", e);
- }
- }
- connectIfReady();
- }
- private UsedCandidate chooseFromProposedCandidates(JingleS5BTransport proposal) {
- for (JingleContentTransportCandidate c : proposal.getCandidates()) {
- JingleS5BTransportCandidate candidate = (JingleS5BTransportCandidate) c;
- try {
- return connectToTheirCandidate(candidate);
- } catch (InterruptedException | TimeoutException | XMPPException | SmackException | IOException e) {
- LOGGER.log(Level.WARNING, "Could not connect to " + candidate.getHost(), e);
- }
- }
- LOGGER.log(Level.WARNING, "Failed to connect to any candidate.");
- return null;
- }
- private UsedCandidate connectToTheirCandidate(JingleS5BTransportCandidate candidate)
- throws InterruptedException, TimeoutException, SmackException, XMPPException, IOException {
- Bytestream.StreamHost streamHost = candidate.getStreamHost();
- InetAddress address = streamHost.getAddress().asInetAddress();
- Socks5Client socks5Client = new Socks5Client(streamHost, theirProposal.getDestinationAddress());
- Socket socket = socks5Client.getSocket(10 * 1000);
- LOGGER.log(Level.INFO, "Connected to their StreamHost " + address + " using dstAddr "
- + theirProposal.getDestinationAddress());
- return new UsedCandidate(theirProposal, candidate, socket);
- }
- private UsedCandidate connectToOurCandidate(JingleS5BTransportCandidate candidate)
- throws InterruptedException, TimeoutException, SmackException, XMPPException, IOException {
- Bytestream.StreamHost streamHost = candidate.getStreamHost();
- InetAddress address = streamHost.getAddress().asInetAddress();
- Socks5ClientForInitiator socks5Client = new Socks5ClientForInitiator(
- streamHost, ourProposal.getDestinationAddress(), jingleSession.getConnection(),
- ourProposal.getStreamId(), jingleSession.getRemote());
- Socket socket = socks5Client.getSocket(10 * 1000);
- LOGGER.log(Level.INFO, "Connected to our StreamHost " + address + " using dstAddr "
- + ourProposal.getDestinationAddress());
- return new UsedCandidate(ourProposal, candidate, socket);
- }
- @Override
- public String getNamespace() {
- return JingleS5BTransport.NAMESPACE_V1;
- }
- @Override
- public IQ handleTransportInfo(Jingle transportInfo) {
- JingleS5BTransportInfo info = (JingleS5BTransportInfo) transportInfo.getContents().get(0).getTransport().getInfo();
- switch (info.getElementName()) {
- case JingleS5BTransportInfo.CandidateUsed.ELEMENT:
- return handleCandidateUsed(transportInfo);
- case JingleS5BTransportInfo.CandidateActivated.ELEMENT:
- return handleCandidateActivate(transportInfo);
- case JingleS5BTransportInfo.CandidateError.ELEMENT:
- return handleCandidateError(transportInfo);
- case JingleS5BTransportInfo.ProxyError.ELEMENT:
- return handleProxyError(transportInfo);
- }
- // We should never go here, but lets be gracious...
- return IQ.createResultIQ(transportInfo);
- }
- public IQ handleCandidateUsed(Jingle jingle) {
- JingleS5BTransportInfo info = (JingleS5BTransportInfo) jingle.getContents().get(0).getTransport().getInfo();
- String candidateId = ((JingleS5BTransportInfo.CandidateUsed) info).getCandidateId();
- theirChoice = new UsedCandidate(ourProposal, ourProposal.getCandidate(candidateId), null);
- if (theirChoice.candidate == null) {
- /*
- TODO: Booooooh illegal candidateId!! Go home!!!!11elf
- */
- }
- connectIfReady();
- return IQ.createResultIQ(jingle);
- }
- public IQ handleCandidateActivate(Jingle jingle) {
- LOGGER.log(Level.INFO, "handleCandidateActivate");
- Socks5BytestreamSession bs = new Socks5BytestreamSession(ourChoice.socket,
- ourChoice.candidate.getJid().asBareJid().equals(jingleSession.getRemote().asBareJid()));
- callback.onSessionInitiated(bs);
- return IQ.createResultIQ(jingle);
- }
- public IQ handleCandidateError(Jingle jingle) {
- theirChoice = CANDIDATE_FAILURE;
- connectIfReady();
- return IQ.createResultIQ(jingle);
- }
- public IQ handleProxyError(Jingle jingle) {
- // TODO
- return IQ.createResultIQ(jingle);
- }
- /**
- * Determine, which candidate (ours/theirs) is the nominated one.
- * Connect to this candidate. If it is a proxy and it is ours, activate it and connect.
- * If its a proxy and it is theirs, wait for activation.
- * If it is not a proxy, just connect.
- */
- private void connectIfReady() {
- JingleContent content = jingleSession.getContents().get(0);
- if (ourChoice == null || theirChoice == null) {
- // Not yet ready.
- LOGGER.log(Level.INFO, "Not ready.");
- return;
- }
- if (ourChoice == CANDIDATE_FAILURE && theirChoice == CANDIDATE_FAILURE) {
- LOGGER.log(Level.INFO, "Failure.");
- jingleSession.onTransportMethodFailed(getNamespace());
- return;
- }
- LOGGER.log(Level.INFO, "Ready.");
- // Determine nominated candidate.
- UsedCandidate nominated;
- if (ourChoice != CANDIDATE_FAILURE && theirChoice != CANDIDATE_FAILURE) {
- if (ourChoice.candidate.getPriority() > theirChoice.candidate.getPriority()) {
- nominated = ourChoice;
- } else if (ourChoice.candidate.getPriority() < theirChoice.candidate.getPriority()) {
- nominated = theirChoice;
- } else {
- nominated = jingleSession.isInitiator() ? ourChoice : theirChoice;
- }
- } else if (ourChoice != CANDIDATE_FAILURE) {
- nominated = ourChoice;
- } else {
- nominated = theirChoice;
- }
- if (nominated == theirChoice) {
- LOGGER.log(Level.INFO, "Their choice, so our proposed candidate is used.");
- boolean isProxy = nominated.candidate.getType() == JingleS5BTransportCandidate.Type.proxy;
- try {
- nominated = connectToOurCandidate(nominated.candidate);
- } catch (InterruptedException | IOException | XMPPException | SmackException | TimeoutException e) {
- LOGGER.log(Level.INFO, "Could not connect to our candidate.", e);
- // TODO: Proxy-Error
- return;
- }
- if (isProxy) {
- LOGGER.log(Level.INFO, "Is external proxy. Activate it.");
- Bytestream activate = new Bytestream(ourProposal.getStreamId());
- activate.setMode(null);
- activate.setType(IQ.Type.set);
- activate.setTo(nominated.candidate.getJid());
- activate.setToActivate(jingleSession.getRemote());
- activate.setFrom(jingleSession.getLocal());
- try {
- jingleSession.getConnection().sendIqRequestAndWaitForResponse(activate);
- } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
- LOGGER.log(Level.WARNING, "Could not activate proxy.", e);
- return;
- }
- LOGGER.log(Level.INFO, "Send candidate-activate.");
- Jingle candidateActivate = transportManager().createCandidateActivated(
- jingleSession.getRemote(), jingleSession.getInitiator(), jingleSession.getSessionId(),
- content.getSenders(), content.getCreator(), content.getName(), nominated.transport.getStreamId(),
- nominated.candidate.getCandidateId());
- try {
- jingleSession.getConnection().sendIqRequestAndWaitForResponse(candidateActivate);
- } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
- LOGGER.log(Level.WARNING, "Could not send candidate-activated", e);
- return;
- }
- }
- LOGGER.log(Level.INFO, "Start transmission.");
- Socks5BytestreamSession bs = new Socks5BytestreamSession(nominated.socket, !isProxy);
- callback.onSessionInitiated(bs);
- }
- // Our choice
- else {
- LOGGER.log(Level.INFO, "Our choice, so their candidate was used.");
- boolean isProxy = nominated.candidate.getType() == JingleS5BTransportCandidate.Type.proxy;
- if (!isProxy) {
- LOGGER.log(Level.INFO, "Direct connection.");
- Socks5BytestreamSession bs = new Socks5BytestreamSession(nominated.socket, true);
- callback.onSessionInitiated(bs);
- } else {
- LOGGER.log(Level.INFO, "Our choice was their external proxy. wait for candidate-activate.");
- }
- }
- }
- @Override
- public JingleS5BTransportManager transportManager() {
- return JingleS5BTransportManager.getInstanceFor(jingleSession.getConnection());
- }
- private static final class UsedCandidate {
- private final Socket socket;
- private final JingleS5BTransport transport;
- private final JingleS5BTransportCandidate candidate;
- private UsedCandidate(JingleS5BTransport transport, JingleS5BTransportCandidate candidate, Socket socket) {
- this.socket = socket;
- this.transport = transport;
- this.candidate = candidate;
- }
- }
- private static final UsedCandidate CANDIDATE_FAILURE = new UsedCandidate(null, null, null);
- }