mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-09-27 10:09:32 +02:00
953 lines
35 KiB
Java
953 lines
35 KiB
Java
/**
|
|
*
|
|
* Copyright 2003-2006 Jive Software.
|
|
*
|
|
* 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.jingleold.nat;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import org.jivesoftware.smack.SmackException;
|
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
|
import org.jivesoftware.smack.XMPPException;
|
|
import org.jivesoftware.smack.packet.IQ;
|
|
|
|
import org.jivesoftware.smackx.jingleold.ContentNegotiator;
|
|
import org.jivesoftware.smackx.jingleold.JingleActionEnum;
|
|
import org.jivesoftware.smackx.jingleold.JingleException;
|
|
import org.jivesoftware.smackx.jingleold.JingleNegotiator;
|
|
import org.jivesoftware.smackx.jingleold.JingleNegotiatorState;
|
|
import org.jivesoftware.smackx.jingleold.JingleSession;
|
|
import org.jivesoftware.smackx.jingleold.listeners.JingleListener;
|
|
import org.jivesoftware.smackx.jingleold.listeners.JingleTransportListener;
|
|
import org.jivesoftware.smackx.jingleold.nat.ICECandidate.Type;
|
|
import org.jivesoftware.smackx.jingleold.packet.Jingle;
|
|
import org.jivesoftware.smackx.jingleold.packet.JingleContent;
|
|
import org.jivesoftware.smackx.jingleold.packet.JingleTransport;
|
|
import org.jivesoftware.smackx.jingleold.packet.JingleTransport.JingleTransportCandidate;
|
|
|
|
/**
|
|
* Transport negotiator.
|
|
*
|
|
* This class is responsible for managing the transport negotiation process,
|
|
* handling all the stanza interchange and the stage control.
|
|
*
|
|
* @author Alvaro Saurin
|
|
*/
|
|
@SuppressWarnings("UnusedVariable")
|
|
public abstract class TransportNegotiator extends JingleNegotiator {
|
|
|
|
private static final Logger LOGGER = Logger.getLogger(TransportNegotiator.class.getName());
|
|
|
|
// The time we give to the candidates check before we accept or decline the
|
|
// transport (in milliseconds)
|
|
public static final int CANDIDATES_ACCEPT_PERIOD = 4000;
|
|
|
|
// The session this negotiator belongs to
|
|
// private final JingleSession session;
|
|
|
|
// The transport manager
|
|
private final TransportResolver resolver;
|
|
|
|
// Transport candidates we have offered
|
|
private final List<TransportCandidate> offeredCandidates = new ArrayList<>();
|
|
|
|
// List of remote transport candidates
|
|
private final List<TransportCandidate> remoteCandidates = new ArrayList<>();
|
|
|
|
// Valid remote candidates
|
|
private final List<TransportCandidate> validRemoteCandidates = new ArrayList<>();
|
|
|
|
// Accepted Remote Candidates
|
|
private final List<TransportCandidate> acceptedRemoteCandidates = new ArrayList<>();
|
|
|
|
// The best local candidate we have offered (and accepted by the other part)
|
|
private TransportCandidate acceptedLocalCandidate;
|
|
|
|
// The thread that will report the result to the other end
|
|
private Thread resultThread;
|
|
|
|
// Listener for the resolver
|
|
private TransportResolverListener.Resolver resolverListener;
|
|
|
|
private final ContentNegotiator parentNegotiator;
|
|
|
|
/**
|
|
* Default constructor.
|
|
*
|
|
* @param session The Jingle session
|
|
* @param transResolver The JingleTransportManager to use
|
|
*/
|
|
public TransportNegotiator(JingleSession session, TransportResolver transResolver, ContentNegotiator parentNegotiator) {
|
|
super(session);
|
|
|
|
resolver = transResolver;
|
|
this.parentNegotiator = parentNegotiator;
|
|
|
|
resultThread = null;
|
|
}
|
|
|
|
/**
|
|
* Get a new instance of the right TransportNegotiator class with this
|
|
* candidate.
|
|
*
|
|
* @return A TransportNegotiator instance
|
|
*/
|
|
public abstract JingleTransport getJingleTransport(TransportCandidate cand);
|
|
|
|
/**
|
|
* Return true if the transport candidate is acceptable for the current
|
|
* negotiator.
|
|
*
|
|
* @return true if the transport candidate is acceptable
|
|
*/
|
|
public abstract boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates);
|
|
|
|
/**
|
|
* Obtain the best local candidate we want to offer.
|
|
*
|
|
* @return the best local candidate
|
|
*/
|
|
public final TransportCandidate getBestLocalCandidate() {
|
|
return resolver.getPreferredCandidate();
|
|
}
|
|
|
|
/**
|
|
* Set the best local transport candidate we have offered and accepted by
|
|
* the other endpoint.
|
|
*
|
|
* @param bestLocalCandidate the acceptedLocalCandidate to set
|
|
*/
|
|
private void setAcceptedLocalCandidate(TransportCandidate bestLocalCandidate) {
|
|
for (int i = 0; i < resolver.getCandidateCount(); i++) {
|
|
// TODO FIX The EQUAL Sentence
|
|
if (resolver.getCandidate(i).getIp().equals(bestLocalCandidate.getIp())
|
|
&& resolver.getCandidate(i).getPort() == bestLocalCandidate.getPort()) {
|
|
acceptedLocalCandidate = resolver.getCandidate(i);
|
|
return;
|
|
}
|
|
}
|
|
LOGGER.fine("BEST: ip=" + bestLocalCandidate.getIp() + " port=" + bestLocalCandidate.getPort() + " has not been offered.");
|
|
// throw new XMPPException("Local transport candidate has not be offered.");
|
|
}
|
|
|
|
/**
|
|
* Get the best accepted local candidate we have offered.
|
|
*
|
|
* @return a transport candidate we have offered.
|
|
*/
|
|
public TransportCandidate getAcceptedLocalCandidate() {
|
|
return acceptedLocalCandidate;
|
|
}
|
|
|
|
/**
|
|
* Called from above to start the negotiator during a session-initiate.
|
|
*/
|
|
@Override
|
|
protected void doStart() {
|
|
|
|
try {
|
|
sendTransportCandidatesOffer();
|
|
setNegotiatorState(JingleNegotiatorState.PENDING);
|
|
} catch (Exception e) {
|
|
// TODO Auto-generated catch block
|
|
LOGGER.log(Level.WARNING, "exception", e);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Called from above to session-terminate.
|
|
*/
|
|
@Override
|
|
public void close() {
|
|
super.close();
|
|
|
|
}
|
|
|
|
/**
|
|
* Return a JingleTransport that best reflects this transport negotiator.
|
|
*/
|
|
public JingleTransport getJingleTransport() {
|
|
return getJingleTransport(getBestRemoteCandidate());
|
|
}
|
|
|
|
public List<TransportCandidate> getOfferedCandidates() {
|
|
return offeredCandidates;
|
|
}
|
|
|
|
/**
|
|
* Obtain the best common transport candidate obtained in the negotiation.
|
|
*
|
|
* @return the bestRemoteCandidate
|
|
*/
|
|
public abstract TransportCandidate getBestRemoteCandidate();
|
|
|
|
/**
|
|
* Get the list of remote candidates.
|
|
*
|
|
* @return the remoteCandidates
|
|
*/
|
|
private List<TransportCandidate> getRemoteCandidates() {
|
|
return remoteCandidates;
|
|
}
|
|
|
|
/**
|
|
* Add a remote candidate to the list. The candidate will be checked in
|
|
* order to verify if it is usable.
|
|
*
|
|
* @param rc a remote candidate to add and check.
|
|
*/
|
|
private void addRemoteCandidate(TransportCandidate rc) {
|
|
// Add the candidate to the list
|
|
if (rc != null) {
|
|
if (acceptableTransportCandidate(rc, offeredCandidates)) {
|
|
synchronized (remoteCandidates) {
|
|
remoteCandidates.add(rc);
|
|
}
|
|
|
|
// Check if the new candidate can be used.
|
|
checkRemoteCandidate(rc);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a offered candidate to the list.
|
|
*
|
|
* @param rc a remote candidate we have offered.
|
|
*/
|
|
private void addOfferedCandidate(TransportCandidate rc) {
|
|
// Add the candidate to the list
|
|
if (rc != null) {
|
|
synchronized (offeredCandidates) {
|
|
offeredCandidates.add(rc);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check asynchronously the new transport candidate.
|
|
*
|
|
* @param offeredCandidate a transport candidates to check
|
|
*/
|
|
private void checkRemoteCandidate(final TransportCandidate offeredCandidate) {
|
|
offeredCandidate.addListener(new TransportResolverListener.Checker() {
|
|
@Override
|
|
public void candidateChecked(TransportCandidate cand, final boolean validCandidate) {
|
|
if (validCandidate) {
|
|
if (getNegotiatorState() == JingleNegotiatorState.PENDING)
|
|
addValidRemoteCandidate(offeredCandidate);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void candidateChecking(TransportCandidate cand) {
|
|
}
|
|
|
|
});
|
|
offeredCandidate.check(resolver.getCandidatesList());
|
|
}
|
|
|
|
/**
|
|
* Return true if the transport is established.
|
|
*
|
|
* @return true if the transport is established.
|
|
*/
|
|
private boolean isEstablished() {
|
|
return getBestRemoteCandidate() != null && getAcceptedLocalCandidate() != null;
|
|
}
|
|
|
|
/**
|
|
* Return true if the transport is fully established.
|
|
*
|
|
* @return true if the transport is fully established.
|
|
*/
|
|
public final boolean isFullyEstablished() {
|
|
return (isEstablished() && ((getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) || (getNegotiatorState() == JingleNegotiatorState.FAILED)));
|
|
}
|
|
|
|
/**
|
|
* Launch a thread that checks, after some time, if any of the candidates
|
|
* offered by the other endpoint is usable. The thread does not check the
|
|
* candidates: it just checks if we have got a valid one and sends an Accept
|
|
* in that case.
|
|
*/
|
|
private void delayedCheckBestCandidate(final JingleSession js, final Jingle jin) {
|
|
//
|
|
// If this is the first insertion in the list, start the thread that
|
|
// will send the result of our checks...
|
|
//
|
|
if (resultThread == null && !getRemoteCandidates().isEmpty()) {
|
|
resultThread = new Thread(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
|
|
// Sleep for some time, waiting for the candidates checks
|
|
|
|
int totalTime = (CANDIDATES_ACCEPT_PERIOD + TransportResolver.CHECK_TIMEOUT);
|
|
int tries = (int) Math.ceil(totalTime / 1000);
|
|
|
|
for (int i = 0; i < tries - 1; i++) {
|
|
try {
|
|
Thread.sleep(1000);
|
|
} catch (InterruptedException e) {
|
|
LOGGER.log(Level.WARNING, "exception", e);
|
|
}
|
|
|
|
// Once we are in pending state, look for any valid remote
|
|
// candidate, and send an "accept" if we have one...
|
|
TransportCandidate bestRemote = getBestRemoteCandidate();
|
|
// State state = getState();
|
|
|
|
if (bestRemote != null
|
|
&& getNegotiatorState() == JingleNegotiatorState.PENDING) {
|
|
// Accepting the remote candidate
|
|
if (!acceptedRemoteCandidates.contains(bestRemote)) {
|
|
Jingle jout = new Jingle(JingleActionEnum.CONTENT_ACCEPT);
|
|
JingleContent content = parentNegotiator.getJingleContent();
|
|
content.addJingleTransport(getJingleTransport(bestRemote));
|
|
jout.addContent(content);
|
|
|
|
// Send the packet
|
|
try {
|
|
js.sendFormattedJingle(jin, jout);
|
|
}
|
|
catch (InterruptedException | NotConnectedException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
acceptedRemoteCandidates.add(bestRemote);
|
|
}
|
|
if (isEstablished() && getNegotiatorState() == JingleNegotiatorState.PENDING) {
|
|
setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
|
|
try {
|
|
triggerTransportEstablished(getAcceptedLocalCandidate(), bestRemote);
|
|
}
|
|
catch (InterruptedException | NotConnectedException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Once we are in pending state, look for any valid remote
|
|
// candidate, and send an "accept" if we have one...
|
|
TransportCandidate bestRemote = getBestRemoteCandidate();
|
|
|
|
if (bestRemote == null) {
|
|
boolean foundRemoteRelay = false;
|
|
for (TransportCandidate candidate : remoteCandidates) {
|
|
if (candidate instanceof ICECandidate) {
|
|
ICECandidate iceCandidate = (ICECandidate) candidate;
|
|
if (iceCandidate.getType().equals(Type.relay)) {
|
|
// TODO Check if the relay is reachable.
|
|
addValidRemoteCandidate(iceCandidate);
|
|
foundRemoteRelay = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not found, check if we offered a relay. If yes, we should accept any remote candidate.
|
|
// We should accept the Public One if we received it, otherwise, accepts any.
|
|
if (!foundRemoteRelay) {
|
|
boolean foundLocalRelay = false;
|
|
for (TransportCandidate candidate : offeredCandidates) {
|
|
if (candidate instanceof ICECandidate) {
|
|
ICECandidate iceCandidate = (ICECandidate) candidate;
|
|
if (iceCandidate.getType().equals(Type.relay)) {
|
|
foundLocalRelay = true;
|
|
}
|
|
}
|
|
}
|
|
if (foundLocalRelay) {
|
|
boolean foundRemotePublic = false;
|
|
for (TransportCandidate candidate : remoteCandidates) {
|
|
if (candidate instanceof ICECandidate) {
|
|
ICECandidate iceCandidate = (ICECandidate) candidate;
|
|
if (iceCandidate.getType().equals(ICECandidate.Type.srflx)) {
|
|
addValidRemoteCandidate(iceCandidate);
|
|
foundRemotePublic = true;
|
|
}
|
|
}
|
|
}
|
|
if (!foundRemotePublic) {
|
|
for (TransportCandidate candidate : remoteCandidates) {
|
|
if (candidate instanceof ICECandidate) {
|
|
ICECandidate iceCandidate = (ICECandidate) candidate;
|
|
addValidRemoteCandidate(iceCandidate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
try {
|
|
Thread.sleep(500);
|
|
} catch (InterruptedException e) {
|
|
LOGGER.log(Level.WARNING, "exception", e);
|
|
}
|
|
|
|
bestRemote = getBestRemoteCandidate();
|
|
// State state = getState();
|
|
if (bestRemote != null
|
|
&& getNegotiatorState() == JingleNegotiatorState.PENDING) {
|
|
if (!acceptedRemoteCandidates.contains(bestRemote)) {
|
|
Jingle jout = new Jingle(JingleActionEnum.CONTENT_ACCEPT);
|
|
JingleContent content = parentNegotiator.getJingleContent();
|
|
content.addJingleTransport(getJingleTransport(bestRemote));
|
|
jout.addContent(content);
|
|
|
|
// Send the packet
|
|
try {
|
|
js.sendFormattedJingle(jin, jout);
|
|
}
|
|
catch (InterruptedException | NotConnectedException e) {
|
|
throw new IllegalStateException(e);
|
|
}
|
|
acceptedRemoteCandidates.add(bestRemote);
|
|
}
|
|
if (isEstablished()) {
|
|
setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (getNegotiatorState() != JingleNegotiatorState.SUCCEEDED) {
|
|
try {
|
|
session
|
|
.terminate("Unable to negotiate session. This may be caused by firewall configuration problems.");
|
|
} catch (Exception e) {
|
|
LOGGER.log(Level.WARNING, "exception", e);
|
|
}
|
|
}
|
|
}
|
|
}, "Waiting for all the transport candidates checks...");
|
|
|
|
resultThread.setName("Transport Resolver Result");
|
|
resultThread.start();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a valid remote candidate to the list. The remote candidate has been
|
|
* checked, and the remote
|
|
*
|
|
* @param remoteCandidate a remote candidate to add
|
|
*/
|
|
private void addValidRemoteCandidate(TransportCandidate remoteCandidate) {
|
|
// Add the candidate to the list
|
|
if (remoteCandidate != null) {
|
|
synchronized (validRemoteCandidates) {
|
|
LOGGER.fine("Added valid candidate: " + remoteCandidate.getIp() + ":" + remoteCandidate.getPort());
|
|
validRemoteCandidates.add(remoteCandidate);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the list of valid (ie, checked) remote candidates.
|
|
*
|
|
* @return The list of valid (ie, already checked) remote candidates.
|
|
*/
|
|
final ArrayList<TransportCandidate> getValidRemoteCandidatesList() {
|
|
synchronized (validRemoteCandidates) {
|
|
return new ArrayList<>(validRemoteCandidates);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get an iterator for the list of valid (ie, checked) remote candidates.
|
|
*
|
|
* @return The iterator for the list of valid (ie, already checked) remote
|
|
* candidates.
|
|
*/
|
|
public final Iterator<TransportCandidate> getValidRemoteCandidates() {
|
|
return Collections.unmodifiableList(getRemoteCandidates()).iterator();
|
|
}
|
|
|
|
/**
|
|
* Add an offered remote candidate. The transport candidate can be unusable:
|
|
* we must check if we can use it.
|
|
*
|
|
* @param rc the remote candidate to add.
|
|
*/
|
|
private void addRemoteCandidates(List<TransportCandidate> rc) {
|
|
if (rc != null) {
|
|
if (rc.size() > 0) {
|
|
for (TransportCandidate aRc : rc) {
|
|
addRemoteCandidate(aRc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse the list of transport candidates from a Jingle packet.
|
|
*
|
|
* @param jin The input jingle packet
|
|
*/
|
|
private List<TransportCandidate> obtainCandidatesList(Jingle jingle) {
|
|
List<TransportCandidate> result = new ArrayList<>();
|
|
|
|
if (jingle != null) {
|
|
// Get the list of candidates from the packet
|
|
for (JingleContent jingleContent : jingle.getContentsList()) {
|
|
if (jingleContent.getName().equals(parentNegotiator.getName())) {
|
|
for (JingleTransport jingleTransport : jingleContent.getJingleTransportsList()) {
|
|
for (JingleTransportCandidate jingleTransportCandidate : jingleTransport.getCandidatesList()) {
|
|
TransportCandidate transCand = jingleTransportCandidate.getMediaTransport();
|
|
result.add(transCand);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Send an offer for a transport candidate
|
|
*
|
|
* @param cand
|
|
* @throws NotConnectedException
|
|
* @throws InterruptedException
|
|
*/
|
|
private synchronized void sendTransportCandidateOffer(TransportCandidate cand) throws NotConnectedException, InterruptedException {
|
|
if (!cand.isNull()) {
|
|
// Offer our new candidate...
|
|
addOfferedCandidate(cand);
|
|
JingleContent content = parentNegotiator.getJingleContent();
|
|
content.addJingleTransport(getJingleTransport(cand));
|
|
Jingle jingle = new Jingle(JingleActionEnum.TRANSPORT_INFO);
|
|
jingle.addContent(content);
|
|
|
|
// We SHOULD NOT be sending packets directly.
|
|
// This circumvents the state machinery.
|
|
// TODO - work this into the state machinery.
|
|
session.sendFormattedJingle(jingle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a Jingle stanza where we announce our transport candidates.
|
|
*
|
|
* @throws XMPPException
|
|
* @throws SmackException
|
|
* @throws InterruptedException
|
|
*/
|
|
private void sendTransportCandidatesOffer() throws XMPPException, SmackException, InterruptedException {
|
|
List<TransportCandidate> notOffered = resolver.getCandidatesList();
|
|
|
|
notOffered.removeAll(offeredCandidates);
|
|
|
|
// Send any unset candidate
|
|
for (Object aNotOffered : notOffered) {
|
|
sendTransportCandidateOffer((TransportCandidate) aNotOffered);
|
|
}
|
|
|
|
// .. and start a listener that will send any future candidate
|
|
if (resolverListener == null) {
|
|
// Add a listener that sends the offer when the resolver finishes...
|
|
resolverListener = new TransportResolverListener.Resolver() {
|
|
@Override
|
|
public void candidateAdded(TransportCandidate cand) throws NotConnectedException, InterruptedException {
|
|
sendTransportCandidateOffer(cand);
|
|
}
|
|
|
|
@Override
|
|
public void end() {
|
|
}
|
|
|
|
@Override
|
|
public void init() {
|
|
}
|
|
};
|
|
|
|
resolver.addListener(resolverListener);
|
|
}
|
|
|
|
if (!(resolver.isResolving() || resolver.isResolved())) {
|
|
// Resolve our IP and port
|
|
LOGGER.fine("RESOLVER CALLED");
|
|
resolver.resolve(session);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatch an incoming packet. The method is responsible for recognizing
|
|
* the stanza type and, depending on the current state, delivering the
|
|
* stanza to the right event handler and wait for a response.
|
|
*
|
|
* @param iq the stanza received
|
|
* @return the new Jingle stanza to send.
|
|
* @throws XMPPException
|
|
* @throws SmackException
|
|
* @throws InterruptedException
|
|
*/
|
|
@Override
|
|
public final List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException, InterruptedException {
|
|
List<IQ> responses = new ArrayList<>();
|
|
IQ response = null;
|
|
|
|
if (iq != null) {
|
|
if (iq.getType().equals(IQ.Type.error)) {
|
|
// Process errors
|
|
setNegotiatorState(JingleNegotiatorState.FAILED);
|
|
triggerTransportClosed(null);
|
|
// This next line seems wrong, and may subvert the normal closing process.
|
|
throw new JingleException(iq.getError().getDescriptiveText());
|
|
} else if (iq.getType().equals(IQ.Type.result)) {
|
|
// Process ACKs
|
|
if (isExpectedId(iq.getStanzaId())) {
|
|
response = receiveResult(iq);
|
|
removeExpectedId(iq.getStanzaId());
|
|
}
|
|
} else if (iq instanceof Jingle) {
|
|
// Get the action from the Jingle packet
|
|
Jingle jingle = (Jingle) iq;
|
|
JingleActionEnum action = jingle.getAction();
|
|
|
|
switch (action) {
|
|
case CONTENT_ACCEPT:
|
|
response = receiveContentAcceptAction(jingle);
|
|
break;
|
|
|
|
case CONTENT_MODIFY:
|
|
break;
|
|
|
|
case CONTENT_REMOVE:
|
|
break;
|
|
|
|
case SESSION_INFO:
|
|
break;
|
|
|
|
case SESSION_INITIATE:
|
|
response = receiveSessionInitiateAction(jingle);
|
|
break;
|
|
|
|
case SESSION_ACCEPT:
|
|
response = receiveSessionAcceptAction(jingle);
|
|
break;
|
|
|
|
case TRANSPORT_INFO:
|
|
response = receiveTransportInfoAction(jingle);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (response != null) {
|
|
addExpectedId(response.getStanzaId());
|
|
responses.add(response);
|
|
}
|
|
|
|
return responses;
|
|
}
|
|
|
|
/**
|
|
* The other endpoint has partially accepted our invitation: start
|
|
* offering a list of candidates.
|
|
*
|
|
* @return an IQ packet
|
|
* @throws XMPPException
|
|
* @throws SmackException
|
|
* @throws InterruptedException
|
|
*/
|
|
private Jingle receiveResult(IQ iq) throws XMPPException, SmackException, InterruptedException {
|
|
Jingle response = null;
|
|
|
|
sendTransportCandidatesOffer();
|
|
setNegotiatorState(JingleNegotiatorState.PENDING);
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* @param jingle
|
|
* @return the iq
|
|
* @throws SmackException
|
|
* @throws InterruptedException
|
|
*/
|
|
private IQ receiveSessionInitiateAction(Jingle jingle) throws XMPPException, SmackException, InterruptedException {
|
|
IQ response = null;
|
|
|
|
// Parse the Jingle and get any proposed transport candidates
|
|
// addRemoteCandidates(obtainCandidatesList(jin));
|
|
|
|
// Start offering candidates
|
|
sendTransportCandidatesOffer();
|
|
|
|
// All these candidates will be checked asynchronously. Wait for some
|
|
// time and check if we have a valid candidate to use...
|
|
delayedCheckBestCandidate(session, jingle);
|
|
|
|
// Set the next state
|
|
setNegotiatorState(JingleNegotiatorState.PENDING);
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* @param jingle
|
|
* @return the iq
|
|
*/
|
|
private IQ receiveTransportInfoAction(Jingle jingle) {
|
|
IQ response = null;
|
|
|
|
// Parse the Jingle and get any proposed transport candidates
|
|
// addRemoteCandidates(obtainCandidatesList(jin));
|
|
|
|
// // Start offering candidates
|
|
// sendTransportCandidatesOffer();
|
|
//
|
|
// // All these candidates will be checked asynchronously. Wait for some
|
|
// // time and check if we have a valid candidate to use...
|
|
// delayedCheckBestCandidate(session, jingle);
|
|
//
|
|
// // Set the next state
|
|
// setNegotiatorState(JingleNegotiatorState.PENDING);
|
|
|
|
// Parse the Jingle and get any proposed transport candidates
|
|
addRemoteCandidates(obtainCandidatesList(jingle));
|
|
|
|
// Wait for some time and check if we have a valid candidate to
|
|
// use...
|
|
delayedCheckBestCandidate(session, jingle);
|
|
|
|
response = session.createAck(jingle);
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* One of our transport candidates has been accepted.
|
|
*
|
|
* @return a Jingle packet
|
|
* @throws XMPPException an exception
|
|
* @see org.jivesoftware.smackx.jingleold.JingleNegotiator.State#eventAccept(org.jivesoftware.smackx.jingleold.packet.Jingle)
|
|
*/
|
|
private IQ receiveContentAcceptAction(Jingle jingle) throws XMPPException {
|
|
IQ response = null;
|
|
|
|
// Parse the Jingle and get the accepted candidate
|
|
List<TransportCandidate> accepted = obtainCandidatesList(jingle);
|
|
if (!accepted.isEmpty()) {
|
|
|
|
for (TransportCandidate cand : accepted) {
|
|
LOGGER.fine("Remote accepted candidate addr: " + cand.getIp());
|
|
}
|
|
|
|
TransportCandidate cand = accepted.get(0);
|
|
setAcceptedLocalCandidate(cand);
|
|
|
|
if (isEstablished()) {
|
|
LOGGER.fine(cand.getIp() + " is set active");
|
|
// setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
|
|
}
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* @param jingle
|
|
* @return the iq
|
|
*/
|
|
private static IQ receiveSessionAcceptAction(Jingle jingle) {
|
|
IQ response = null;
|
|
|
|
LOGGER.fine("Transport established");
|
|
// triggerTransportEstablished(getAcceptedLocalCandidate(), getBestRemoteCandidate());
|
|
|
|
// setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Trigger a Transport session established event.
|
|
*
|
|
* @param local TransportCandidate that has been agreed.
|
|
* @param remote TransportCandidate that has been agreed.
|
|
* @throws NotConnectedException
|
|
* @throws InterruptedException
|
|
*/
|
|
private void triggerTransportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException, InterruptedException {
|
|
List<JingleListener> listeners = getListenersList();
|
|
for (JingleListener li : listeners) {
|
|
if (li instanceof JingleTransportListener) {
|
|
JingleTransportListener mli = (JingleTransportListener) li;
|
|
LOGGER.fine("triggerTransportEstablished " + local.getLocalIp() + ":" + local.getPort() + " <-> "
|
|
+ remote.getIp() + ":" + remote.getPort());
|
|
mli.transportEstablished(local, remote);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trigger a Transport closed event.
|
|
*
|
|
* @param cand current TransportCandidate that is cancelled.
|
|
*/
|
|
private void triggerTransportClosed(TransportCandidate cand) {
|
|
List<JingleListener> listeners = getListenersList();
|
|
for (JingleListener li : listeners) {
|
|
if (li instanceof JingleTransportListener) {
|
|
JingleTransportListener mli = (JingleTransportListener) li;
|
|
mli.transportClosed(cand);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Subclasses
|
|
|
|
/**
|
|
* Raw-UDP transport negotiator.
|
|
*
|
|
* @author Alvaro Saurin
|
|
*/
|
|
public static final class RawUdp extends TransportNegotiator {
|
|
|
|
/**
|
|
* Default constructor, with a JingleSession and transport manager.
|
|
*
|
|
* @param js The Jingle session this negotiation belongs to.
|
|
* @param res The transport resolver to use.
|
|
*/
|
|
public RawUdp(JingleSession js, final TransportResolver res, ContentNegotiator parentNegotiator) {
|
|
super(js, res, parentNegotiator);
|
|
}
|
|
|
|
/**
|
|
* Get a TransportNegotiator instance.
|
|
*/
|
|
@Override
|
|
public org.jivesoftware.smackx.jingleold.packet.JingleTransport getJingleTransport(TransportCandidate bestRemote) {
|
|
org.jivesoftware.smackx.jingleold.packet.JingleTransport.RawUdp jt = new org.jivesoftware.smackx.jingleold.packet.JingleTransport.RawUdp();
|
|
jt.addCandidate(new org.jivesoftware.smackx.jingleold.packet.JingleTransport.RawUdp.Candidate(bestRemote));
|
|
return jt;
|
|
}
|
|
|
|
/**
|
|
* Obtain the best common transport candidate obtained in the
|
|
* negotiation.
|
|
*
|
|
* @return the bestRemoteCandidate
|
|
*/
|
|
@Override
|
|
public TransportCandidate getBestRemoteCandidate() {
|
|
// Hopefully, we only have one validRemoteCandidate
|
|
ArrayList<TransportCandidate> cands = getValidRemoteCandidatesList();
|
|
if (!cands.isEmpty()) {
|
|
LOGGER.fine("RAW CAND");
|
|
return cands.get(0);
|
|
} else {
|
|
LOGGER.fine("No Remote Candidate");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true for fixed candidates.
|
|
*/
|
|
@Override
|
|
public boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates) {
|
|
return tc instanceof TransportCandidate.Fixed;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ice transport negotiator.
|
|
*
|
|
* @author Alvaro Saurin
|
|
*/
|
|
public static final class Ice extends TransportNegotiator {
|
|
|
|
/**
|
|
* Default constructor, with a JingleSession and transport manager.
|
|
*
|
|
* @param js The Jingle session this negotiation belongs to.
|
|
* @param res The transport manager to use.
|
|
*/
|
|
public Ice(JingleSession js, final TransportResolver res, ContentNegotiator parentNegotiator) {
|
|
super(js, res, parentNegotiator);
|
|
}
|
|
|
|
/**
|
|
* Get a TransportNegotiator instance.
|
|
*
|
|
* @param candidate
|
|
*/
|
|
@Override
|
|
public org.jivesoftware.smackx.jingleold.packet.JingleTransport getJingleTransport(TransportCandidate candidate) {
|
|
org.jivesoftware.smackx.jingleold.packet.JingleTransport.Ice jt = new org.jivesoftware.smackx.jingleold.packet.JingleTransport.Ice();
|
|
jt.addCandidate(new org.jivesoftware.smackx.jingleold.packet.JingleTransport.Ice.Candidate(candidate));
|
|
return jt;
|
|
}
|
|
|
|
/**
|
|
* Obtain the best remote candidate obtained in the negotiation so far.
|
|
*
|
|
* @return the bestRemoteCandidate
|
|
*/
|
|
@Override
|
|
public TransportCandidate getBestRemoteCandidate() {
|
|
ICECandidate result = null;
|
|
|
|
ArrayList<TransportCandidate> cands = getValidRemoteCandidatesList();
|
|
if (!cands.isEmpty()) {
|
|
int highest = -1;
|
|
ICECandidate chose = null;
|
|
for (TransportCandidate transportCandidate : cands) {
|
|
if (transportCandidate instanceof ICECandidate) {
|
|
ICECandidate icecandidate = (ICECandidate) transportCandidate;
|
|
if (icecandidate.getPreference() > highest) {
|
|
chose = icecandidate;
|
|
highest = icecandidate.getPreference();
|
|
}
|
|
}
|
|
}
|
|
result = chose;
|
|
}
|
|
|
|
if (result != null && result.getType().equals(Type.relay))
|
|
LOGGER.fine("Relay Type");
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Return true for ICE candidates.
|
|
*/
|
|
@Override
|
|
public boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates) {
|
|
return tc instanceof ICECandidate;
|
|
}
|
|
}
|
|
}
|