1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-06-26 13:24:49 +02:00
Smack/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportSession.java
Florian Schmaus 4133eb175c Replace XPP3 by XmlPullParser interface wrapping StAX and XPP3
Introducing Smack's own XmlPullParser interface which tries to stay as
compatible as possible to XPP3. The interface is used to either wrap
StAX's XMLStreamReader if Smack is used on Java SE, and XPP3's
XmlPullParser if Smack is used on on Android.

Fixes SMACK-591.

Also introduce JUnit 5 and non-strict javadoc projects.
2019-05-06 22:10:50 +02:00

363 lines
16 KiB
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.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().createStanzaCollectorAndSend(jingle)
.nextResultOrThrow();
} 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();
String address = streamHost.getAddress();
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();
String address = streamHost.getAddress();
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().createStanzaCollectorAndSend(activate).nextResultOrThrow();
} 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().createStanzaCollectorAndSend(candidateActivate)
.nextResultOrThrow();
} 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);
}