2017-08-17 12:18:33 +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;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
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.XMPPConnection;
|
|
|
|
import org.jivesoftware.smack.XMPPException;
|
|
|
|
import org.jivesoftware.smack.packet.IQ;
|
|
|
|
import org.jivesoftware.smack.util.Async;
|
|
|
|
import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession;
|
|
|
|
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;
|
2017-08-25 19:04:52 +02:00
|
|
|
import org.jivesoftware.smackx.jingle.callback.JingleTransportCallback;
|
2017-08-17 12:18:33 +02:00
|
|
|
import org.jivesoftware.smackx.jingle.component.JingleSession;
|
|
|
|
import org.jivesoftware.smackx.jingle.component.JingleTransport;
|
|
|
|
import org.jivesoftware.smackx.jingle.component.JingleTransportCandidate;
|
|
|
|
import org.jivesoftware.smackx.jingle.element.JingleContentTransportCandidateElement;
|
|
|
|
import org.jivesoftware.smackx.jingle.element.JingleContentTransportElement;
|
|
|
|
import org.jivesoftware.smackx.jingle.element.JingleContentTransportInfoElement;
|
|
|
|
import org.jivesoftware.smackx.jingle.element.JingleElement;
|
|
|
|
import org.jivesoftware.smackx.jingle.exception.FailedTransportException;
|
|
|
|
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;
|
|
|
|
|
|
|
|
import org.jxmpp.jid.FullJid;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Jingle SOCKS5Bytestream transport component.
|
|
|
|
*/
|
|
|
|
public class JingleS5BTransport extends JingleTransport<JingleS5BTransportElement> {
|
|
|
|
|
|
|
|
private static final Logger LOGGER = Logger.getLogger(JingleS5BTransport.class.getName());
|
|
|
|
|
|
|
|
public static final String NAMESPACE_V1 = "urn:xmpp:jingle:transports:s5b:1";
|
|
|
|
public static final String NAMESPACE = NAMESPACE_V1;
|
|
|
|
|
|
|
|
private static final int MAX_TIMEOUT = 10 * 1000;
|
|
|
|
|
|
|
|
private final String sid;
|
|
|
|
|
|
|
|
private String ourDstAddr;
|
|
|
|
private String theirDstAddr;
|
|
|
|
|
|
|
|
private Bytestream.Mode mode;
|
|
|
|
|
|
|
|
// PEERS candidate of OUR choice.
|
|
|
|
private JingleS5BTransportCandidate ourSelectedCandidate;
|
|
|
|
private JingleS5BTransportCandidate theirSelectedCandidate;
|
|
|
|
|
|
|
|
private JingleTransportCallback callback;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create transport as initiator.
|
|
|
|
* @param initiator initiator of the Jingle session.
|
|
|
|
* @param responder responder.
|
|
|
|
* @param sid sessionId of the Jingle session.
|
|
|
|
* @param mode TCP/UDP.
|
|
|
|
* @param ourCandidates our proxy candidates.
|
|
|
|
*/
|
|
|
|
JingleS5BTransport(FullJid initiator, FullJid responder, String sid, Bytestream.Mode mode, List<JingleTransportCandidate<?>> ourCandidates) {
|
|
|
|
this.sid = sid;
|
|
|
|
this.mode = mode;
|
|
|
|
this.ourDstAddr = Socks5Utils.createDigest(sid, initiator, responder);
|
|
|
|
Socks5Proxy.getSocks5Proxy().addTransfer(ourDstAddr);
|
|
|
|
|
|
|
|
for (JingleTransportCandidate<?> c : ourCandidates) {
|
|
|
|
addOurCandidate(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create simple transport as responder.
|
|
|
|
* @param initiator initiator of the Jingle session.
|
|
|
|
* @param responder responder.
|
|
|
|
* @param ourCandidates our proxy candidates.
|
|
|
|
* @param other transport of the other party.
|
|
|
|
*/
|
|
|
|
JingleS5BTransport(FullJid initiator, FullJid responder, List<JingleTransportCandidate<?>> ourCandidates, JingleS5BTransport other) {
|
|
|
|
this.sid = other.getStreamId();
|
|
|
|
this.mode = other.mode;
|
|
|
|
this.ourDstAddr = Socks5Utils.createDigest(sid, responder, initiator);
|
|
|
|
Socks5Proxy.getSocks5Proxy().addTransfer(ourDstAddr);
|
|
|
|
this.theirDstAddr = other.theirDstAddr;
|
|
|
|
|
|
|
|
for (JingleTransportCandidate<?> c : ourCandidates) {
|
|
|
|
addOurCandidate(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (JingleTransportCandidate<?> c : other.getTheirCandidates()) {
|
|
|
|
addTheirCandidate(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create custom transport as responder.
|
|
|
|
* @param sid sessionId of the Jingle session.
|
|
|
|
* @param mode UPD/TCP.
|
|
|
|
* @param ourDstAddr SOCKS5 destination address (digest)
|
|
|
|
* @param theirDstAddr SOCKS5 destination address (digest)
|
|
|
|
* @param ourCandidates our proxy candidates.
|
|
|
|
* @param theirCandidates their proxy candidates.
|
|
|
|
*/
|
|
|
|
JingleS5BTransport(String sid, Bytestream.Mode mode, String ourDstAddr, String theirDstAddr, List<JingleTransportCandidate<?>> ourCandidates, List<JingleTransportCandidate<?>> theirCandidates) {
|
|
|
|
this.sid = sid;
|
|
|
|
this.mode = mode;
|
|
|
|
this.ourDstAddr = ourDstAddr;
|
|
|
|
Socks5Proxy.getSocks5Proxy().addTransfer(ourDstAddr);
|
|
|
|
this.theirDstAddr = theirDstAddr;
|
|
|
|
|
|
|
|
for (JingleTransportCandidate<?> c : (ourCandidates != null ? ourCandidates :
|
|
|
|
Collections.<JingleS5BTransportCandidate>emptySet())) {
|
|
|
|
addOurCandidate(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (JingleTransportCandidate<?> c : (theirCandidates != null ? theirCandidates :
|
|
|
|
Collections.<JingleS5BTransportCandidate>emptySet())) {
|
|
|
|
addTheirCandidate(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy constructor.
|
|
|
|
* @param original which will be copied.
|
|
|
|
*/
|
|
|
|
public JingleS5BTransport(JingleS5BTransport original) {
|
|
|
|
this.sid = original.sid;
|
|
|
|
this.ourDstAddr = original.ourDstAddr;
|
|
|
|
this.theirDstAddr = original.theirDstAddr;
|
|
|
|
this.mode = original.mode;
|
|
|
|
|
|
|
|
for (JingleTransportCandidate<?> c : original.getOurCandidates()) {
|
|
|
|
addOurCandidate(new JingleS5BTransportCandidate((JingleS5BTransportCandidate) c));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (JingleTransportCandidate<?> c : original.getTheirCandidates()) {
|
|
|
|
addTheirCandidate(new JingleS5BTransportCandidate((JingleS5BTransportCandidate) c));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public JingleS5BTransportElement getElement() {
|
|
|
|
JingleS5BTransportElement.Builder builder = JingleS5BTransportElement.getBuilder()
|
|
|
|
.setStreamId(sid)
|
|
|
|
.setDestinationAddress(ourDstAddr)
|
|
|
|
.setMode(mode);
|
|
|
|
|
|
|
|
for (JingleTransportCandidate<?> candidate : getOurCandidates()) {
|
|
|
|
builder.addTransportCandidate((JingleS5BTransportCandidateElement) candidate.getElement());
|
|
|
|
}
|
|
|
|
|
|
|
|
return builder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getStreamId() {
|
|
|
|
return sid;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getOurDstAddr() {
|
|
|
|
return ourDstAddr;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getTheirDstAddr() {
|
|
|
|
return theirDstAddr;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Bytestream.Mode getMode() {
|
|
|
|
return mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getNamespace() {
|
|
|
|
return NAMESPACE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void prepare(XMPPConnection connection) {
|
|
|
|
JingleSession session = getParent().getParent();
|
|
|
|
if (getOurDstAddr() == null) {
|
|
|
|
ourDstAddr = Socks5Utils.createDigest(getStreamId(), session.getOurJid(), session.getPeer());
|
|
|
|
Socks5Proxy.getSocks5Proxy().addTransfer(ourDstAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == null) {
|
|
|
|
mode = Bytestream.Mode.tcp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (getOurCandidates().size() == 0) {
|
|
|
|
List<JingleTransportCandidate<?>> candidates = JingleS5BTransportManager.getInstanceFor(connection).collectCandidates();
|
|
|
|
for (JingleTransportCandidate<?> c : candidates) {
|
|
|
|
addOurCandidate(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void establishIncomingBytestreamSession(XMPPConnection connection, JingleTransportCallback callback, JingleSession session)
|
|
|
|
throws SmackException.NotConnectedException, InterruptedException {
|
|
|
|
LOGGER.log(Level.INFO, "Establishing incoming bytestream.");
|
|
|
|
this.callback = callback;
|
|
|
|
establishBytestreamSession(connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void establishOutgoingBytestreamSession(XMPPConnection connection, JingleTransportCallback callback, JingleSession session)
|
|
|
|
throws SmackException.NotConnectedException, InterruptedException {
|
|
|
|
LOGGER.log(Level.INFO, "Establishing outgoing bytestream.");
|
|
|
|
this.callback = callback;
|
|
|
|
establishBytestreamSession(connection);
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("ReferenceEquality")
|
|
|
|
private void establishBytestreamSession(XMPPConnection connection)
|
|
|
|
throws SmackException.NotConnectedException, InterruptedException {
|
|
|
|
Socks5Proxy.getSocks5Proxy().addTransfer(ourDstAddr);
|
|
|
|
this.ourSelectedCandidate = connectToCandidates(MAX_TIMEOUT);
|
|
|
|
|
|
|
|
if (ourSelectedCandidate == CANDIDATE_FAILURE) {
|
|
|
|
connection.createStanzaCollectorAndSend(JingleS5BTransportManager.createCandidateError(this));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ourSelectedCandidate == null) {
|
|
|
|
throw new AssertionError("MUST NOT BE NULL.");
|
|
|
|
}
|
|
|
|
|
|
|
|
connection.createStanzaCollectorAndSend(JingleS5BTransportManager.createCandidateUsed(this, ourSelectedCandidate));
|
|
|
|
connectIfReady();
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("ReferenceEquality")
|
|
|
|
private JingleS5BTransportCandidate connectToCandidates(int timeout) {
|
|
|
|
|
|
|
|
if (getTheirCandidates().size() == 0) {
|
|
|
|
LOGGER.log(Level.INFO, "They provided 0 candidates.");
|
|
|
|
return CANDIDATE_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
int _timeout = timeout / getTheirCandidates().size(); //TODO: Wise?
|
|
|
|
for (JingleTransportCandidate<?> c : getTheirCandidates()) {
|
|
|
|
JingleS5BTransportCandidate candidate = (JingleS5BTransportCandidate) c;
|
|
|
|
try {
|
|
|
|
return candidate.connect(_timeout, true);
|
|
|
|
} catch (IOException | TimeoutException | InterruptedException | SmackException | XMPPException e) {
|
|
|
|
LOGGER.log(Level.WARNING, "Exception while connecting to candidate: " + e, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Failed to connect to any candidate.
|
|
|
|
return CANDIDATE_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void cleanup() {
|
|
|
|
Socks5Proxy.getSocks5Proxy().removeTransfer(ourDstAddr);
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("ReferenceEquality")
|
|
|
|
private void connectIfReady() {
|
|
|
|
final JingleSession session = getParent().getParent();
|
|
|
|
|
|
|
|
if (ourSelectedCandidate == null || theirSelectedCandidate == null) {
|
|
|
|
// Not yet ready if we or peer did not yet decide on a candidate.
|
|
|
|
LOGGER.log(Level.INFO, "Not ready.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ourSelectedCandidate == CANDIDATE_FAILURE && theirSelectedCandidate == CANDIDATE_FAILURE) {
|
|
|
|
LOGGER.log(Level.INFO, "Failure.");
|
|
|
|
callback.onTransportFailed(new FailedTransportException(null));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LOGGER.log(Level.INFO, (session.isInitiator() ? "Initiator" : "Responder") + " is ready.");
|
|
|
|
|
|
|
|
//Determine nominated candidate.
|
|
|
|
JingleS5BTransportCandidate nominated;
|
|
|
|
if (ourSelectedCandidate != CANDIDATE_FAILURE && theirSelectedCandidate != CANDIDATE_FAILURE) {
|
|
|
|
|
|
|
|
if (ourSelectedCandidate.getPriority() > theirSelectedCandidate.getPriority()) {
|
|
|
|
nominated = ourSelectedCandidate;
|
|
|
|
} else if (ourSelectedCandidate.getPriority() < theirSelectedCandidate.getPriority()) {
|
|
|
|
nominated = theirSelectedCandidate;
|
|
|
|
} else {
|
|
|
|
nominated = getParent().getParent().isInitiator() ? ourSelectedCandidate : theirSelectedCandidate;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (ourSelectedCandidate != CANDIDATE_FAILURE) {
|
|
|
|
nominated = ourSelectedCandidate;
|
|
|
|
} else {
|
|
|
|
nominated = theirSelectedCandidate;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean isProxy = nominated.getType() == JingleS5BTransportCandidateElement.Type.proxy;
|
|
|
|
|
|
|
|
if (nominated == theirSelectedCandidate) {
|
|
|
|
|
|
|
|
LOGGER.log(Level.INFO, "Their choice, so our proposed candidate is used.");
|
|
|
|
|
|
|
|
try {
|
|
|
|
nominated = nominated.connect(MAX_TIMEOUT, false);
|
|
|
|
} catch (InterruptedException | IOException | XMPPException | SmackException | TimeoutException e) {
|
|
|
|
LOGGER.log(Level.INFO, "Could not connect to our candidate.", e);
|
|
|
|
|
|
|
|
Async.go(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
try {
|
|
|
|
session.getJingleManager().getConnection().createStanzaCollectorAndSend(JingleS5BTransportManager.createProxyError(JingleS5BTransport.this));
|
|
|
|
} catch (SmackException.NotConnectedException | InterruptedException e1) {
|
|
|
|
LOGGER.log(Level.SEVERE, "Could not send proxy error: " + e, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
callback.onTransportFailed(new S5BTransportException.CandidateError(e));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isProxy) {
|
|
|
|
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);
|
|
|
|
callback.onTransportFailed(new S5BTransportException.ProxyError(e));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
LOGGER.log(Level.INFO, "Start transmission on " + nominated.getCandidateId());
|
|
|
|
this.bytestreamSession = new Socks5BytestreamSession(nominated.getSocket(), !isProxy);
|
|
|
|
callback.onTransportReady(this.bytestreamSession);
|
|
|
|
|
|
|
|
}
|
|
|
|
//Our choice
|
|
|
|
else {
|
|
|
|
LOGGER.log(Level.INFO, "Our choice, so their candidate was used.");
|
|
|
|
if (!isProxy) {
|
|
|
|
LOGGER.log(Level.INFO, "Start transmission on " + nominated.getCandidateId());
|
|
|
|
this.bytestreamSession = new Socks5BytestreamSession(nominated.getSocket(), true);
|
|
|
|
callback.onTransportReady(this.bytestreamSession);
|
|
|
|
} else {
|
|
|
|
LOGGER.log(Level.INFO, "Our choice was their external proxy. wait for candidate-activate.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void handleSessionAccept(JingleContentTransportElement transportElement, XMPPConnection connection) {
|
|
|
|
JingleS5BTransportElement transport = (JingleS5BTransportElement) transportElement;
|
|
|
|
theirDstAddr = transport.getDestinationAddress();
|
|
|
|
for (JingleContentTransportCandidateElement c : transport.getCandidates()) {
|
|
|
|
JingleS5BTransportCandidateElement candidate = (JingleS5BTransportCandidateElement) c;
|
|
|
|
addTheirCandidate(new JingleS5BTransportCandidate(candidate));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public IQ handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping) {
|
|
|
|
switch (info.getElementName()) {
|
|
|
|
|
|
|
|
case JingleS5BTransportInfoElement.CandidateUsed.ELEMENT:
|
|
|
|
handleCandidateUsed((JingleS5BTransportInfoElement) info, wrapping);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JingleS5BTransportInfoElement.CandidateActivated.ELEMENT:
|
|
|
|
handleCandidateActivate((JingleS5BTransportInfoElement) info);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JingleS5BTransportInfoElement.CandidateError.ELEMENT:
|
|
|
|
handleCandidateError((JingleS5BTransportInfoElement) info);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case JingleS5BTransportInfoElement.ProxyError.ELEMENT:
|
|
|
|
handleProxyError((JingleS5BTransportInfoElement) info);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
throw new AssertionError("Unknown transport-info element: " + info.getElementName());
|
|
|
|
}
|
|
|
|
|
|
|
|
return IQ.createResultIQ(wrapping);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void handleCandidateUsed(JingleS5BTransportInfoElement info, JingleElement wrapping) {
|
|
|
|
JingleManager jingleManager = getParent().getParent().getJingleManager();
|
|
|
|
String candidateId = ((JingleS5BTransportInfoElement.CandidateUsed) info).getCandidateId();
|
|
|
|
|
|
|
|
// Received second candidate-used -> out-of-order!
|
|
|
|
if (theirSelectedCandidate != null) {
|
|
|
|
try {
|
|
|
|
jingleManager.getConnection().sendStanza(JingleElement.createJingleErrorOutOfOrder(wrapping));
|
|
|
|
//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);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (JingleTransportCandidate<?> jingleTransportCandidate : getOurCandidates()) {
|
|
|
|
JingleS5BTransportCandidate candidate = (JingleS5BTransportCandidate) jingleTransportCandidate;
|
|
|
|
if (candidate.getCandidateId().equals(candidateId)) {
|
|
|
|
theirSelectedCandidate = candidate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (theirSelectedCandidate == null) {
|
|
|
|
LOGGER.log(Level.SEVERE, "ILLEGAL CANDIDATE ID!!!");
|
|
|
|
//TODO: Alert! Illegal candidateId!
|
|
|
|
}
|
|
|
|
|
|
|
|
connectIfReady();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void handleCandidateActivate(JingleS5BTransportInfoElement info) {
|
|
|
|
this.bytestreamSession = new Socks5BytestreamSession(ourSelectedCandidate.getSocket(),
|
|
|
|
ourSelectedCandidate.getStreamHost().getJID().asBareJid().equals(getParent().getParent().getPeer().asBareJid()));
|
|
|
|
callback.onTransportReady(this.bytestreamSession);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void handleCandidateError(JingleS5BTransportInfoElement info) {
|
|
|
|
theirSelectedCandidate = CANDIDATE_FAILURE;
|
|
|
|
connectIfReady();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void handleProxyError(JingleS5BTransportInfoElement info) {
|
|
|
|
callback.onTransportFailed(new S5BTransportException.ProxyError(null));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal dummy candidate used to represent failure.
|
|
|
|
* Kinda depressing, isn't it?
|
|
|
|
*/
|
|
|
|
private final static JingleS5BTransportCandidate CANDIDATE_FAILURE = new JingleS5BTransportCandidate(null, null, -1, null);
|
|
|
|
}
|