Smack/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transport/jingle_s5b/JingleS5BTransport.java

349 lines
15 KiB
Java
Raw Normal View History

2017-07-21 23:05:46 +02:00
/**
*
* 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.transport.jingle_s5b;
2017-07-19 23:15:17 +02:00
2017-07-21 18:29:27 +02:00
import java.io.IOException;
2017-07-19 23:15:17 +02:00
import java.util.Collections;
2017-07-21 18:29:27 +02:00
import java.util.Iterator;
2017-07-19 23:15:17 +02:00
import java.util.List;
2017-07-21 18:29:27 +02:00
import java.util.concurrent.TimeoutException;
2017-07-22 00:26:50 +02:00
import java.util.logging.Level;
import java.util.logging.Logger;
2017-07-19 23:15:17 +02:00
2017-07-21 18:29:27 +02:00
import org.jivesoftware.smack.SmackException;
2017-07-19 23:15:17 +02:00
import org.jivesoftware.smack.XMPPConnection;
2017-07-21 18:29:27 +02:00
import org.jivesoftware.smack.XMPPException;
2017-07-22 00:26:50 +02:00
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession;
2017-07-21 17:58:57 +02:00
import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy;
2017-07-21 18:29:27 +02:00
import org.jivesoftware.smackx.bytestreams.socks5.Socks5Utils;
2017-07-19 23:15:17 +02:00
import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
2017-07-22 00:26:50 +02:00
import org.jivesoftware.smackx.jingle.JingleManager;
2017-07-21 23:05:46 +02:00
import org.jivesoftware.smackx.jingle.element.JingleContentTransportInfoElement;
2017-07-22 00:26:50 +02:00
import org.jivesoftware.smackx.jingle.element.JingleElement;
import org.jivesoftware.smackx.jingle.internal.JingleContent;
2017-07-22 00:26:50 +02:00
import org.jivesoftware.smackx.jingle.internal.JingleSession;
import org.jivesoftware.smackx.jingle.internal.JingleTransport;
import org.jivesoftware.smackx.jingle.internal.JingleTransportCandidate;
2017-07-22 00:26:50 +02:00
import org.jivesoftware.smackx.jingle.transport.BytestreamSessionEstablishedListener;
import org.jivesoftware.smackx.jingle.transport.jingle_s5b.element.JingleS5BTransportCandidateElement;
import org.jivesoftware.smackx.jingle.transport.jingle_s5b.element.JingleS5BTransportElement;
import org.jivesoftware.smackx.jingle.transport.jingle_s5b.element.JingleS5BTransportInfoElement;
2017-07-19 23:15:17 +02:00
import org.jxmpp.jid.FullJid;
/**
2017-07-21 23:05:46 +02:00
* Jingle SOCKS5Bytestream transport component.
2017-07-19 23:15:17 +02:00
*/
public class JingleS5BTransport extends JingleTransport<JingleS5BTransportElement> {
2017-07-19 23:15:17 +02:00
2017-07-22 00:26:50 +02:00
private static final Logger LOGGER = Logger.getLogger(JingleS5BTransport.class.getName());
2017-07-19 23:15:17 +02:00
public static final String NAMESPACE_V1 = "urn:xmpp:jingle:transports:s5b:1";
public static final String NAMESPACE = NAMESPACE_V1;
2017-07-21 23:05:46 +02:00
public static final int MAX_TIMEOUT = 10 * 1000;
2017-07-19 23:15:17 +02:00
private final String sid;
private String dstAddr;
private Bytestream.Mode mode;
2017-07-21 18:29:27 +02:00
// PEERS candidate of OUR choice.
private JingleS5BTransportCandidate selectedCandidate;
2017-07-19 23:15:17 +02:00
2017-07-21 18:29:27 +02:00
/**
* Create fresh JingleS5BTransport.
2017-07-21 23:05:46 +02:00
* @param initiator initiator.
* @param responder responder.
2017-07-21 18:29:27 +02:00
*/
public JingleS5BTransport(FullJid initiator, FullJid responder, String sid, List<JingleTransportCandidate<?>> candidates) {
2017-07-21 23:05:46 +02:00
this(sid, Socks5Utils.createDigest(sid, initiator, responder), Bytestream.Mode.tcp, candidates);
}
public JingleS5BTransport(JingleContent content, JingleS5BTransport other, List<JingleTransportCandidate<?>> candidates) {
2017-07-21 23:05:46 +02:00
this(other.getSid(),
Socks5Utils.createDigest(other.getSid(), content.getParent().getInitiator(), content.getParent().getResponder()),
other.mode, candidates);
2017-07-21 18:29:27 +02:00
}
2017-07-19 23:15:17 +02:00
public JingleS5BTransport(String sid, String dstAddr, Bytestream.Mode mode, List<JingleTransportCandidate<?>> candidates) {
2017-07-19 23:15:17 +02:00
this.sid = sid;
this.dstAddr = dstAddr;
this.mode = mode;
for (JingleTransportCandidate<?> c : (candidates != null ?
2017-07-19 23:15:17 +02:00
candidates : Collections.<JingleS5BTransportCandidate>emptySet())) {
2017-07-21 17:58:57 +02:00
addCandidate(c);
2017-07-19 23:15:17 +02:00
}
}
@Override
public JingleS5BTransportElement getElement() {
JingleS5BTransportElement.Builder builder = JingleS5BTransportElement.getBuilder()
.setStreamId(sid)
.setDestinationAddress(dstAddr)
.setMode(mode);
for (JingleTransportCandidate candidate : getCandidates()) {
2017-07-21 17:58:57 +02:00
builder.addTransportCandidate((JingleS5BTransportCandidateElement) candidate.getElement());
2017-07-19 23:15:17 +02:00
}
return builder.build();
}
2017-07-21 23:05:46 +02:00
public String getSid() {
return sid;
}
2017-07-21 17:58:57 +02:00
public String getDstAddr() {
return dstAddr;
}
public Bytestream.Mode getMode() {
return mode;
}
2017-07-19 23:15:17 +02:00
@Override
public String getNamespace() {
return NAMESPACE;
}
@Override
2017-07-21 23:05:46 +02:00
public void establishIncomingBytestreamSession(BytestreamSessionEstablishedListener listener, XMPPConnection connection)
throws SmackException.NotConnectedException, InterruptedException {
establishBytestreamSession(listener, connection);
2017-07-19 23:15:17 +02:00
}
@Override
2017-07-21 23:05:46 +02:00
public void establishOutgoingBytestreamSession(BytestreamSessionEstablishedListener listener, XMPPConnection connection)
throws SmackException.NotConnectedException, InterruptedException {
establishBytestreamSession(listener, connection);
}
void establishBytestreamSession(BytestreamSessionEstablishedListener listener, XMPPConnection connection)
throws SmackException.NotConnectedException, InterruptedException {
Socks5Proxy.getSocks5Proxy().addTransfer(dstAddr);
JingleS5BTransportManager transportManager = JingleS5BTransportManager.getInstanceFor(connection);
this.selectedCandidate = connectToCandidates(MAX_TIMEOUT);
if (selectedCandidate == CANDIDATE_FAILURE) {
2017-07-21 23:05:46 +02:00
connection.createStanzaCollectorAndSend(transportManager.createCandidateError(this));
2017-07-22 00:26:50 +02:00
return;
2017-07-21 23:05:46 +02:00
}
2017-07-22 00:26:50 +02:00
if (selectedCandidate == null) {
throw new AssertionError("MUST NOT BE NULL.");
}
2017-07-19 23:15:17 +02:00
2017-07-22 00:26:50 +02:00
connection.createStanzaCollectorAndSend(transportManager.createCandidateUsed(this, selectedCandidate));
//connectIfReady();
2017-07-19 23:15:17 +02:00
}
public JingleS5BTransportCandidate connectToCandidates(int timeout) {
for (JingleTransportCandidate c : getCandidates()) {
int _timeout = timeout / getCandidates().size(); //TODO: Wise?
2017-07-21 18:29:27 +02:00
try {
return ((JingleS5BTransportCandidate) c).connect(_timeout);
2017-07-21 18:29:27 +02:00
} catch (IOException | TimeoutException | InterruptedException | SmackException | XMPPException e) {
e.printStackTrace();
}
}
// Failed to connect to any candidate.
return CANDIDATE_FAILURE;
2017-07-21 18:29:27 +02:00
}
2017-07-22 00:26:50 +02:00
void connectIfReady() {
JingleS5BTransportManager jingleS5BTransportManager = JingleS5BTransportManager.getInstanceFor(getParent().getParent().getJingleManager().getConnection());
JingleS5BTransport peers = (JingleS5BTransport) getPeersProposal();
JingleSession session = getParent().getParent();
if (getSelectedCandidate() == null || peers.getSelectedCandidate() == null) {
// Not yet ready if we or peer did not yet decide on a candidate.
LOGGER.log(Level.INFO, "Not ready.");
return;
}
if (getSelectedCandidate() == CANDIDATE_FAILURE && peers.getSelectedCandidate() == CANDIDATE_FAILURE) {
LOGGER.log(Level.INFO, "Failure.");
//jingleSession.onTransportMethodFailed(getNamespace());
return;
}
LOGGER.log(Level.INFO, "Ready.");
//Determine nominated candidate.
JingleS5BTransportCandidate nominated;
if (getSelectedCandidate() != CANDIDATE_FAILURE && peers.getSelectedCandidate() != CANDIDATE_FAILURE) {
if (getSelectedCandidate().getPriority() > peers.getSelectedCandidate().getPriority()) {
nominated = getSelectedCandidate();
} else if (getSelectedCandidate().getPriority() < peers.getSelectedCandidate().getPriority()) {
nominated = peers.getSelectedCandidate();
} else {
nominated = getParent().getParent().isInitiator() ? getSelectedCandidate() : peers.getSelectedCandidate();
}
} else if (getSelectedCandidate() != CANDIDATE_FAILURE) {
nominated = getSelectedCandidate();
} else {
nominated = peers.getSelectedCandidate();
}
if (nominated == peers.getSelectedCandidate()) {
LOGGER.log(Level.INFO, "Their choice, so our proposed candidate is used.");
boolean isProxy = nominated.getType() == JingleS5BTransportCandidateElement.Type.proxy;
try {
nominated = nominated.connect(MAX_TIMEOUT);
} 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(getSid());
activate.setMode(null);
activate.setType(IQ.Type.set);
activate.setTo(nominated.getStreamHost().getJID());
activate.setToActivate(getParent().getParent().getPeer());
activate.setFrom(getParent().getParent().getOurJid());
try {
getParent().getParent().getJingleManager().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.");
JingleElement candidateActivate = jingleS5BTransportManager.createCandidateActivated((JingleS5BTransport) nominated.getParent(), nominated);
try {
session.getJingleManager().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.getSocket(), !isProxy);
//callback.onSessionInitiated(bs);
}
//Our choice
else {
LOGGER.log(Level.INFO, "Our choice, so their candidate was used.");
boolean isProxy = nominated.getType() == JingleS5BTransportCandidateElement.Type.proxy;
if (!isProxy) {
LOGGER.log(Level.INFO, "Direct connection.");
Socks5BytestreamSession bs = new Socks5BytestreamSession(nominated.getSocket(), true);
//callback.onSessionInitiated(bs);
} else {
LOGGER.log(Level.INFO, "Our choice was their external proxy. wait for candidate-activate.");
}
}
}
2017-07-19 23:15:17 +02:00
@Override
2017-07-22 00:26:50 +02:00
public void handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping) {
2017-07-19 23:15:17 +02:00
switch (info.getElementName()) {
case JingleS5BTransportInfoElement.CandidateUsed.ELEMENT:
2017-07-22 00:26:50 +02:00
handleCandidateUsed((JingleS5BTransportInfoElement) info, wrapping);
2017-07-19 23:15:17 +02:00
return;
case JingleS5BTransportInfoElement.CandidateActivated.ELEMENT:
handleCandidateActivate((JingleS5BTransportInfoElement) info);
return;
case JingleS5BTransportInfoElement.CandidateError.ELEMENT:
handleCandidateError((JingleS5BTransportInfoElement) info);
return;
case JingleS5BTransportInfoElement.ProxyError.ELEMENT:
handleProxyError((JingleS5BTransportInfoElement) info);
return;
default:
throw new AssertionError("Unknown transport-info element: " + info.getElementName());
}
}
2017-07-22 00:26:50 +02:00
private void handleCandidateUsed(JingleS5BTransportInfoElement info, JingleElement wrapping) {
JingleManager jingleManager = getParent().getParent().getJingleManager();
2017-07-19 23:15:17 +02:00
String candidateId = ((JingleS5BTransportInfoElement.CandidateUsed) info).getCandidateId();
2017-07-21 18:29:27 +02:00
JingleS5BTransport peers = (JingleS5BTransport) getPeersProposal();
2017-07-22 00:26:50 +02:00
// Received second candidate-used -> out-of-order!
2017-07-21 18:29:27 +02:00
if (peers.getSelectedCandidate() != null) {
2017-07-22 00:26:50 +02:00
try {
jingleManager.getConnection().createStanzaCollectorAndSend(JingleElement.createJingleErrorOutOfOrder(wrapping));
} catch (SmackException.NotConnectedException | InterruptedException e) {
LOGGER.log(Level.SEVERE, "Could not respond to candidate-used transport-info: " + e, e);
}
2017-07-21 18:29:27 +02:00
return;
}
Iterator<JingleTransportCandidate<?>> ourCandidates = getCandidates().iterator();
2017-07-21 18:29:27 +02:00
while (ourCandidates.hasNext()) {
JingleS5BTransportCandidate candidate = (JingleS5BTransportCandidate) ourCandidates.next();
if (candidate.getCandidateId().equals(candidateId)) {
peers.setSelectedCandidate(candidate);
}
}
if (peers.getSelectedCandidate() == null) {
//TODO: Alert! Illegal candidateId!
2017-07-19 23:15:17 +02:00
}
//connectIfReady();
}
private void handleCandidateActivate(JingleS5BTransportInfoElement info) {
//Socks5BytestreamSession bs = new Socks5BytestreamSession(ourChoice.socket,
// ourChoice.candidate.getJid().asBareJid().equals(jingleSession.getRemote().asBareJid()));
//callback.onSessionInitiated(bs);
}
private void handleCandidateError(JingleS5BTransportInfoElement info) {
2017-07-21 18:29:27 +02:00
((JingleS5BTransport) getPeersProposal()).setSelectedCandidate(CANDIDATE_FAILURE);
2017-07-19 23:15:17 +02:00
//connectIfReady();
}
private void handleProxyError(JingleS5BTransportInfoElement info) {
//TODO
}
2017-07-21 18:29:27 +02:00
public void setSelectedCandidate(JingleS5BTransportCandidate candidate) {
selectedCandidate = candidate;
}
public JingleS5BTransportCandidate getSelectedCandidate() {
return selectedCandidate;
}
2017-07-19 23:15:17 +02:00
/**
* Internal dummy candidate used to represent failure.
* Kinda depressing, isn't it?
*/
private final static JingleS5BTransportCandidate CANDIDATE_FAILURE = new JingleS5BTransportCandidate(null, null, -1, null);
}