001/** 002 * 003 * Copyright 2003-2006 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smackx.jingleold.nat; 019 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.Iterator; 023import java.util.List; 024import java.util.logging.Level; 025import java.util.logging.Logger; 026 027import org.jivesoftware.smack.SmackException; 028import org.jivesoftware.smack.SmackException.NotConnectedException; 029import org.jivesoftware.smack.XMPPException; 030import org.jivesoftware.smack.packet.IQ; 031 032import org.jivesoftware.smackx.jingleold.ContentNegotiator; 033import org.jivesoftware.smackx.jingleold.JingleActionEnum; 034import org.jivesoftware.smackx.jingleold.JingleException; 035import org.jivesoftware.smackx.jingleold.JingleNegotiator; 036import org.jivesoftware.smackx.jingleold.JingleNegotiatorState; 037import org.jivesoftware.smackx.jingleold.JingleSession; 038import org.jivesoftware.smackx.jingleold.listeners.JingleListener; 039import org.jivesoftware.smackx.jingleold.listeners.JingleTransportListener; 040import org.jivesoftware.smackx.jingleold.nat.ICECandidate.Type; 041import org.jivesoftware.smackx.jingleold.packet.Jingle; 042import org.jivesoftware.smackx.jingleold.packet.JingleContent; 043import org.jivesoftware.smackx.jingleold.packet.JingleTransport; 044import org.jivesoftware.smackx.jingleold.packet.JingleTransport.JingleTransportCandidate; 045 046/** 047 * Transport negotiator. 048 * 049 * This class is responsible for managing the transport negotiation process, 050 * handling all the stanza interchange and the stage control. 051 * 052 * @author Alvaro Saurin 053 */ 054@SuppressWarnings("UnusedVariable") 055public abstract class TransportNegotiator extends JingleNegotiator { 056 057 private static final Logger LOGGER = Logger.getLogger(TransportNegotiator.class.getName()); 058 059 // The time we give to the candidates check before we accept or decline the 060 // transport (in milliseconds) 061 public static final int CANDIDATES_ACCEPT_PERIOD = 4000; 062 063 // The session this negotiator belongs to 064 // private final JingleSession session; 065 066 // The transport manager 067 private final TransportResolver resolver; 068 069 // Transport candidates we have offered 070 private final List<TransportCandidate> offeredCandidates = new ArrayList<>(); 071 072 // List of remote transport candidates 073 private final List<TransportCandidate> remoteCandidates = new ArrayList<>(); 074 075 // Valid remote candidates 076 private final List<TransportCandidate> validRemoteCandidates = new ArrayList<>(); 077 078 // Accepted Remote Candidates 079 private final List<TransportCandidate> acceptedRemoteCandidates = new ArrayList<>(); 080 081 // The best local candidate we have offered (and accepted by the other part) 082 private TransportCandidate acceptedLocalCandidate; 083 084 // The thread that will report the result to the other end 085 private Thread resultThread; 086 087 // Listener for the resolver 088 private TransportResolverListener.Resolver resolverListener; 089 090 private final ContentNegotiator parentNegotiator; 091 092 /** 093 * Default constructor. 094 * 095 * @param session The Jingle session 096 * @param transResolver The JingleTransportManager to use 097 * @param parentNegotiator the parent ngeotiator. 098 */ 099 public TransportNegotiator(JingleSession session, TransportResolver transResolver, ContentNegotiator parentNegotiator) { 100 super(session); 101 102 resolver = transResolver; 103 this.parentNegotiator = parentNegotiator; 104 105 resultThread = null; 106 } 107 108 /** 109 * Get a new instance of the right TransportNegotiator class with this 110 * candidate. 111 * 112 * @param cand the transport candidate. 113 * @return A TransportNegotiator instance 114 */ 115 public abstract JingleTransport getJingleTransport(TransportCandidate cand); 116 117 /** 118 * Return true if the transport candidate is acceptable for the current 119 * negotiator. 120 * 121 * @param tc the transport candidate. 122 * @param localCandidates a list of local transport candidates. 123 * @return true if the transport candidate is acceptable 124 */ 125 public abstract boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates); 126 127 /** 128 * Obtain the best local candidate we want to offer. 129 * 130 * @return the best local candidate 131 */ 132 public final TransportCandidate getBestLocalCandidate() { 133 return resolver.getPreferredCandidate(); 134 } 135 136 /** 137 * Set the best local transport candidate we have offered and accepted by 138 * the other endpoint. 139 * 140 * @param bestLocalCandidate the acceptedLocalCandidate to set 141 */ 142 private void setAcceptedLocalCandidate(TransportCandidate bestLocalCandidate) { 143 for (int i = 0; i < resolver.getCandidateCount(); i++) { 144 // TODO FIX The EQUAL Sentence 145 if (resolver.getCandidate(i).getIp().equals(bestLocalCandidate.getIp()) 146 && resolver.getCandidate(i).getPort() == bestLocalCandidate.getPort()) { 147 acceptedLocalCandidate = resolver.getCandidate(i); 148 return; 149 } 150 } 151 LOGGER.fine("BEST: ip=" + bestLocalCandidate.getIp() + " port=" + bestLocalCandidate.getPort() + " has not been offered."); 152 // throw new XMPPException("Local transport candidate has not be offered."); 153 } 154 155 /** 156 * Get the best accepted local candidate we have offered. 157 * 158 * @return a transport candidate we have offered. 159 */ 160 public TransportCandidate getAcceptedLocalCandidate() { 161 return acceptedLocalCandidate; 162 } 163 164 /** 165 * Called from above to start the negotiator during a session-initiate. 166 */ 167 @Override 168 protected void doStart() { 169 170 try { 171 sendTransportCandidatesOffer(); 172 setNegotiatorState(JingleNegotiatorState.PENDING); 173 } catch (Exception e) { 174 // TODO Auto-generated catch block 175 LOGGER.log(Level.WARNING, "exception", e); 176 } 177 178 } 179 180 /** 181 * Called from above to session-terminate. 182 */ 183 @Override 184 public void close() { 185 super.close(); 186 187 } 188 189 /** 190 * Return a JingleTransport that best reflects this transport negotiator. 191 * 192 * @return the jingle transport. 193 */ 194 public JingleTransport getJingleTransport() { 195 return getJingleTransport(getBestRemoteCandidate()); 196 } 197 198 public List<TransportCandidate> getOfferedCandidates() { 199 return offeredCandidates; 200 } 201 202 /** 203 * Obtain the best common transport candidate obtained in the negotiation. 204 * 205 * @return the bestRemoteCandidate 206 */ 207 public abstract TransportCandidate getBestRemoteCandidate(); 208 209 /** 210 * Get the list of remote candidates. 211 * 212 * @return the remoteCandidates 213 */ 214 private List<TransportCandidate> getRemoteCandidates() { 215 return remoteCandidates; 216 } 217 218 /** 219 * Add a remote candidate to the list. The candidate will be checked in 220 * order to verify if it is usable. 221 * 222 * @param rc a remote candidate to add and check. 223 */ 224 private void addRemoteCandidate(TransportCandidate rc) { 225 // Add the candidate to the list 226 if (rc != null) { 227 if (acceptableTransportCandidate(rc, offeredCandidates)) { 228 synchronized (remoteCandidates) { 229 remoteCandidates.add(rc); 230 } 231 232 // Check if the new candidate can be used. 233 checkRemoteCandidate(rc); 234 } 235 } 236 } 237 238 /** 239 * Add a offered candidate to the list. 240 * 241 * @param rc a remote candidate we have offered. 242 */ 243 private void addOfferedCandidate(TransportCandidate rc) { 244 // Add the candidate to the list 245 if (rc != null) { 246 synchronized (offeredCandidates) { 247 offeredCandidates.add(rc); 248 } 249 } 250 } 251 252 /** 253 * Check asynchronously the new transport candidate. 254 * 255 * @param offeredCandidate a transport candidates to check 256 */ 257 private void checkRemoteCandidate(final TransportCandidate offeredCandidate) { 258 offeredCandidate.addListener(new TransportResolverListener.Checker() { 259 @Override 260 public void candidateChecked(TransportCandidate cand, final boolean validCandidate) { 261 if (validCandidate) { 262 if (getNegotiatorState() == JingleNegotiatorState.PENDING) 263 addValidRemoteCandidate(offeredCandidate); 264 } 265 } 266 267 @Override 268 public void candidateChecking(TransportCandidate cand) { 269 } 270 271 }); 272 offeredCandidate.check(resolver.getCandidatesList()); 273 } 274 275 /** 276 * Return true if the transport is established. 277 * 278 * @return true if the transport is established. 279 */ 280 private boolean isEstablished() { 281 return getBestRemoteCandidate() != null && getAcceptedLocalCandidate() != null; 282 } 283 284 /** 285 * Return true if the transport is fully established. 286 * 287 * @return true if the transport is fully established. 288 */ 289 public final boolean isFullyEstablished() { 290 return isEstablished() && ((getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) || (getNegotiatorState() == JingleNegotiatorState.FAILED)); 291 } 292 293 /** 294 * Launch a thread that checks, after some time, if any of the candidates 295 * offered by the other endpoint is usable. The thread does not check the 296 * candidates: it just checks if we have got a valid one and sends an Accept 297 * in that case. 298 */ 299 private void delayedCheckBestCandidate(final JingleSession js, final Jingle jin) { 300 // 301 // If this is the first insertion in the list, start the thread that 302 // will send the result of our checks... 303 // 304 if (resultThread == null && !getRemoteCandidates().isEmpty()) { 305 resultThread = new Thread(new Runnable() { 306 307 @Override 308 public void run() { 309 310 // Sleep for some time, waiting for the candidates checks 311 312 int totalTime = CANDIDATES_ACCEPT_PERIOD + TransportResolver.CHECK_TIMEOUT; 313 int tries = (int) Math.ceil(totalTime / 1000); 314 315 for (int i = 0; i < tries - 1; i++) { 316 try { 317 Thread.sleep(1000); 318 } catch (InterruptedException e) { 319 LOGGER.log(Level.WARNING, "exception", e); 320 } 321 322 // Once we are in pending state, look for any valid remote 323 // candidate, and send an "accept" if we have one... 324 TransportCandidate bestRemote = getBestRemoteCandidate(); 325 // State state = getState(); 326 327 if (bestRemote != null 328 && getNegotiatorState() == JingleNegotiatorState.PENDING) { 329 // Accepting the remote candidate 330 if (!acceptedRemoteCandidates.contains(bestRemote)) { 331 Jingle jout = new Jingle(JingleActionEnum.CONTENT_ACCEPT); 332 JingleContent content = parentNegotiator.getJingleContent(); 333 content.addJingleTransport(getJingleTransport(bestRemote)); 334 jout.addContent(content); 335 336 // Send the packet 337 try { 338 js.sendFormattedJingle(jin, jout); 339 } 340 catch (InterruptedException | NotConnectedException e) { 341 throw new IllegalStateException(e); 342 } 343 acceptedRemoteCandidates.add(bestRemote); 344 } 345 if (isEstablished() && getNegotiatorState() == JingleNegotiatorState.PENDING) { 346 setNegotiatorState(JingleNegotiatorState.SUCCEEDED); 347 try { 348 triggerTransportEstablished(getAcceptedLocalCandidate(), bestRemote); 349 } 350 catch (InterruptedException | NotConnectedException e) { 351 throw new IllegalStateException(e); 352 } 353 break; 354 } 355 } 356 } 357 358 // Once we are in pending state, look for any valid remote 359 // candidate, and send an "accept" if we have one... 360 TransportCandidate bestRemote = getBestRemoteCandidate(); 361 362 if (bestRemote == null) { 363 boolean foundRemoteRelay = false; 364 for (TransportCandidate candidate : remoteCandidates) { 365 if (candidate instanceof ICECandidate) { 366 ICECandidate iceCandidate = (ICECandidate) candidate; 367 if (iceCandidate.getType().equals(Type.relay)) { 368 // TODO Check if the relay is reachable. 369 addValidRemoteCandidate(iceCandidate); 370 foundRemoteRelay = true; 371 } 372 } 373 } 374 375 // If not found, check if we offered a relay. If yes, we should accept any remote candidate. 376 // We should accept the Public One if we received it, otherwise, accepts any. 377 if (!foundRemoteRelay) { 378 boolean foundLocalRelay = false; 379 for (TransportCandidate candidate : offeredCandidates) { 380 if (candidate instanceof ICECandidate) { 381 ICECandidate iceCandidate = (ICECandidate) candidate; 382 if (iceCandidate.getType().equals(Type.relay)) { 383 foundLocalRelay = true; 384 } 385 } 386 } 387 if (foundLocalRelay) { 388 boolean foundRemotePublic = false; 389 for (TransportCandidate candidate : remoteCandidates) { 390 if (candidate instanceof ICECandidate) { 391 ICECandidate iceCandidate = (ICECandidate) candidate; 392 if (iceCandidate.getType().equals(ICECandidate.Type.srflx)) { 393 addValidRemoteCandidate(iceCandidate); 394 foundRemotePublic = true; 395 } 396 } 397 } 398 if (!foundRemotePublic) { 399 for (TransportCandidate candidate : remoteCandidates) { 400 if (candidate instanceof ICECandidate) { 401 ICECandidate iceCandidate = (ICECandidate) candidate; 402 addValidRemoteCandidate(iceCandidate); 403 } 404 } 405 } 406 } 407 } 408 } 409 410 for (int i = 0; i < 6; i++) { 411 try { 412 Thread.sleep(500); 413 } catch (InterruptedException e) { 414 LOGGER.log(Level.WARNING, "exception", e); 415 } 416 417 bestRemote = getBestRemoteCandidate(); 418 // State state = getState(); 419 if (bestRemote != null 420 && getNegotiatorState() == JingleNegotiatorState.PENDING) { 421 if (!acceptedRemoteCandidates.contains(bestRemote)) { 422 Jingle jout = new Jingle(JingleActionEnum.CONTENT_ACCEPT); 423 JingleContent content = parentNegotiator.getJingleContent(); 424 content.addJingleTransport(getJingleTransport(bestRemote)); 425 jout.addContent(content); 426 427 // Send the packet 428 try { 429 js.sendFormattedJingle(jin, jout); 430 } 431 catch (InterruptedException | NotConnectedException e) { 432 throw new IllegalStateException(e); 433 } 434 acceptedRemoteCandidates.add(bestRemote); 435 } 436 if (isEstablished()) { 437 setNegotiatorState(JingleNegotiatorState.SUCCEEDED); 438 break; 439 } 440 } 441 } 442 443 if (getNegotiatorState() != JingleNegotiatorState.SUCCEEDED) { 444 try { 445 session 446 .terminate("Unable to negotiate session. This may be caused by firewall configuration problems."); 447 } catch (Exception e) { 448 LOGGER.log(Level.WARNING, "exception", e); 449 } 450 } 451 } 452 }, "Waiting for all the transport candidates checks..."); 453 454 resultThread.setName("Transport Resolver Result"); 455 resultThread.start(); 456 } 457 } 458 459 /** 460 * Add a valid remote candidate to the list. The remote candidate has been 461 * checked, and the remote 462 * 463 * @param remoteCandidate a remote candidate to add 464 */ 465 private void addValidRemoteCandidate(TransportCandidate remoteCandidate) { 466 // Add the candidate to the list 467 if (remoteCandidate != null) { 468 synchronized (validRemoteCandidates) { 469 LOGGER.fine("Added valid candidate: " + remoteCandidate.getIp() + ":" + remoteCandidate.getPort()); 470 validRemoteCandidates.add(remoteCandidate); 471 } 472 } 473 } 474 475 /** 476 * Get the list of valid (ie, checked) remote candidates. 477 * 478 * @return The list of valid (ie, already checked) remote candidates. 479 */ 480 final ArrayList<TransportCandidate> getValidRemoteCandidatesList() { 481 synchronized (validRemoteCandidates) { 482 return new ArrayList<>(validRemoteCandidates); 483 } 484 } 485 486 /** 487 * Get an iterator for the list of valid (ie, checked) remote candidates. 488 * 489 * @return The iterator for the list of valid (ie, already checked) remote 490 * candidates. 491 */ 492 public final Iterator<TransportCandidate> getValidRemoteCandidates() { 493 return Collections.unmodifiableList(getRemoteCandidates()).iterator(); 494 } 495 496 /** 497 * Add an offered remote candidate. The transport candidate can be unusable: 498 * we must check if we can use it. 499 * 500 * @param rc the remote candidate to add. 501 */ 502 private void addRemoteCandidates(List<TransportCandidate> rc) { 503 if (rc != null) { 504 if (rc.size() > 0) { 505 for (TransportCandidate aRc : rc) { 506 addRemoteCandidate(aRc); 507 } 508 } 509 } 510 } 511 512 /** 513 * Parse the list of transport candidates from a Jingle packet. 514 * 515 * @param jin The input jingle packet 516 */ 517 private List<TransportCandidate> obtainCandidatesList(Jingle jingle) { 518 List<TransportCandidate> result = new ArrayList<>(); 519 520 if (jingle != null) { 521 // Get the list of candidates from the packet 522 for (JingleContent jingleContent : jingle.getContentsList()) { 523 if (jingleContent.getName().equals(parentNegotiator.getName())) { 524 for (JingleTransport jingleTransport : jingleContent.getJingleTransportsList()) { 525 for (JingleTransportCandidate jingleTransportCandidate : jingleTransport.getCandidatesList()) { 526 TransportCandidate transCand = jingleTransportCandidate.getMediaTransport(); 527 result.add(transCand); 528 } 529 } 530 } 531 } 532 } 533 534 return result; 535 } 536 537 /** 538 * Send an offer for a transport candidate 539 * 540 * @param cand TODO javadoc me please 541 * @throws NotConnectedException if the XMPP connection is not connected. 542 * @throws InterruptedException if the calling thread was interrupted. 543 */ 544 private synchronized void sendTransportCandidateOffer(TransportCandidate cand) throws NotConnectedException, InterruptedException { 545 if (!cand.isNull()) { 546 // Offer our new candidate... 547 addOfferedCandidate(cand); 548 JingleContent content = parentNegotiator.getJingleContent(); 549 content.addJingleTransport(getJingleTransport(cand)); 550 Jingle jingle = new Jingle(JingleActionEnum.TRANSPORT_INFO); 551 jingle.addContent(content); 552 553 // We SHOULD NOT be sending packets directly. 554 // This circumvents the state machinery. 555 // TODO - work this into the state machinery. 556 session.sendFormattedJingle(jingle); 557 } 558 } 559 560 /** 561 * Create a Jingle stanza where we announce our transport candidates. 562 * 563 * @throws XMPPException if an XMPP protocol error was received. 564 * @throws SmackException if Smack detected an exceptional situation. 565 * @throws InterruptedException if the calling thread was interrupted. 566 */ 567 private void sendTransportCandidatesOffer() throws XMPPException, SmackException, InterruptedException { 568 List<TransportCandidate> notOffered = resolver.getCandidatesList(); 569 570 notOffered.removeAll(offeredCandidates); 571 572 // Send any unset candidate 573 for (Object aNotOffered : notOffered) { 574 sendTransportCandidateOffer((TransportCandidate) aNotOffered); 575 } 576 577 // .. and start a listener that will send any future candidate 578 if (resolverListener == null) { 579 // Add a listener that sends the offer when the resolver finishes... 580 resolverListener = new TransportResolverListener.Resolver() { 581 @Override 582 public void candidateAdded(TransportCandidate cand) throws NotConnectedException, InterruptedException { 583 sendTransportCandidateOffer(cand); 584 } 585 586 @Override 587 public void end() { 588 } 589 590 @Override 591 public void init() { 592 } 593 }; 594 595 resolver.addListener(resolverListener); 596 } 597 598 if (!(resolver.isResolving() || resolver.isResolved())) { 599 // Resolve our IP and port 600 LOGGER.fine("RESOLVER CALLED"); 601 resolver.resolve(session); 602 } 603 } 604 605 /** 606 * Dispatch an incoming packet. The method is responsible for recognizing 607 * the stanza type and, depending on the current state, delivering the 608 * stanza to the right event handler and wait for a response. 609 * 610 * @param iq the stanza received 611 * @return the new Jingle stanza to send. 612 * @throws XMPPException if an XMPP protocol error was received. 613 * @throws SmackException if Smack detected an exceptional situation. 614 * @throws InterruptedException if the calling thread was interrupted. 615 */ 616 @Override 617 public final List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException, InterruptedException { 618 List<IQ> responses = new ArrayList<>(); 619 IQ response = null; 620 621 if (iq != null) { 622 if (iq.getType().equals(IQ.Type.error)) { 623 // Process errors 624 setNegotiatorState(JingleNegotiatorState.FAILED); 625 triggerTransportClosed(null); 626 // This next line seems wrong, and may subvert the normal closing process. 627 throw new JingleException(iq.getError().getDescriptiveText()); 628 } else if (iq.getType().equals(IQ.Type.result)) { 629 // Process ACKs 630 if (isExpectedId(iq.getStanzaId())) { 631 response = receiveResult(iq); 632 removeExpectedId(iq.getStanzaId()); 633 } 634 } else if (iq instanceof Jingle) { 635 // Get the action from the Jingle packet 636 Jingle jingle = (Jingle) iq; 637 JingleActionEnum action = jingle.getAction(); 638 639 switch (action) { 640 case CONTENT_ACCEPT: 641 response = receiveContentAcceptAction(jingle); 642 break; 643 644 case CONTENT_MODIFY: 645 break; 646 647 case CONTENT_REMOVE: 648 break; 649 650 case SESSION_INFO: 651 break; 652 653 case SESSION_INITIATE: 654 response = receiveSessionInitiateAction(jingle); 655 break; 656 657 case SESSION_ACCEPT: 658 response = receiveSessionAcceptAction(jingle); 659 break; 660 661 case TRANSPORT_INFO: 662 response = receiveTransportInfoAction(jingle); 663 break; 664 665 default: 666 break; 667 } 668 } 669 } 670 671 if (response != null) { 672 addExpectedId(response.getStanzaId()); 673 responses.add(response); 674 } 675 676 return responses; 677 } 678 679 /** 680 * The other endpoint has partially accepted our invitation: start 681 * offering a list of candidates. 682 * 683 * @return an IQ packet 684 * @throws XMPPException if an XMPP protocol error was received. 685 * @throws SmackException if Smack detected an exceptional situation. 686 * @throws InterruptedException if the calling thread was interrupted. 687 */ 688 private Jingle receiveResult(IQ iq) throws XMPPException, SmackException, InterruptedException { 689 Jingle response = null; 690 691 sendTransportCandidatesOffer(); 692 setNegotiatorState(JingleNegotiatorState.PENDING); 693 694 return response; 695 } 696 697 /** 698 * @param jingle TODO javadoc me please 699 * @return the iq 700 * @throws SmackException if Smack detected an exceptional situation. 701 * @throws InterruptedException if the calling thread was interrupted. 702 */ 703 private IQ receiveSessionInitiateAction(Jingle jingle) throws XMPPException, SmackException, InterruptedException { 704 IQ response = null; 705 706 // Parse the Jingle and get any proposed transport candidates 707 // addRemoteCandidates(obtainCandidatesList(jin)); 708 709 // Start offering candidates 710 sendTransportCandidatesOffer(); 711 712 // All these candidates will be checked asynchronously. Wait for some 713 // time and check if we have a valid candidate to use... 714 delayedCheckBestCandidate(session, jingle); 715 716 // Set the next state 717 setNegotiatorState(JingleNegotiatorState.PENDING); 718 719 return response; 720 } 721 722 /** 723 * @param jingle TODO javadoc me please 724 * @return the iq 725 */ 726 private IQ receiveTransportInfoAction(Jingle jingle) { 727 IQ response = null; 728 729 // Parse the Jingle and get any proposed transport candidates 730 // addRemoteCandidates(obtainCandidatesList(jin)); 731 732 // // Start offering candidates 733 // sendTransportCandidatesOffer(); 734 // 735 // // All these candidates will be checked asynchronously. Wait for some 736 // // time and check if we have a valid candidate to use... 737 // delayedCheckBestCandidate(session, jingle); 738 // 739 // // Set the next state 740 // setNegotiatorState(JingleNegotiatorState.PENDING); 741 742 // Parse the Jingle and get any proposed transport candidates 743 addRemoteCandidates(obtainCandidatesList(jingle)); 744 745 // Wait for some time and check if we have a valid candidate to 746 // use... 747 delayedCheckBestCandidate(session, jingle); 748 749 response = session.createAck(jingle); 750 751 return response; 752 } 753 754 /** 755 * One of our transport candidates has been accepted. 756 * 757 * @return a Jingle packet 758 * @throws XMPPException an exception 759 * @see org.jivesoftware.smackx.jingleold.JingleNegotiator.State#eventAccept(org.jivesoftware.smackx.jingleold.packet.Jingle) 760 */ 761 private IQ receiveContentAcceptAction(Jingle jingle) throws XMPPException { 762 IQ response = null; 763 764 // Parse the Jingle and get the accepted candidate 765 List<TransportCandidate> accepted = obtainCandidatesList(jingle); 766 if (!accepted.isEmpty()) { 767 768 for (TransportCandidate cand : accepted) { 769 LOGGER.fine("Remote accepted candidate addr: " + cand.getIp()); 770 } 771 772 TransportCandidate cand = accepted.get(0); 773 setAcceptedLocalCandidate(cand); 774 775 if (isEstablished()) { 776 LOGGER.fine(cand.getIp() + " is set active"); 777 // setNegotiatorState(JingleNegotiatorState.SUCCEEDED); 778 } 779 } 780 return response; 781 } 782 783 /** 784 * @param jingle TODO javadoc me please 785 * @return the iq 786 */ 787 private static IQ receiveSessionAcceptAction(Jingle jingle) { 788 IQ response = null; 789 790 LOGGER.fine("Transport established"); 791 // triggerTransportEstablished(getAcceptedLocalCandidate(), getBestRemoteCandidate()); 792 793 // setNegotiatorState(JingleNegotiatorState.SUCCEEDED); 794 795 return response; 796 } 797 798 /** 799 * Trigger a Transport session established event. 800 * 801 * @param local TransportCandidate that has been agreed. 802 * @param remote TransportCandidate that has been agreed. 803 * @throws NotConnectedException if the XMPP connection is not connected. 804 * @throws InterruptedException if the calling thread was interrupted. 805 */ 806 private void triggerTransportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException, InterruptedException { 807 List<JingleListener> listeners = getListenersList(); 808 for (JingleListener li : listeners) { 809 if (li instanceof JingleTransportListener) { 810 JingleTransportListener mli = (JingleTransportListener) li; 811 LOGGER.fine("triggerTransportEstablished " + local.getLocalIp() + ":" + local.getPort() + " <-> " 812 + remote.getIp() + ":" + remote.getPort()); 813 mli.transportEstablished(local, remote); 814 } 815 } 816 } 817 818 /** 819 * Trigger a Transport closed event. 820 * 821 * @param cand current TransportCandidate that is cancelled. 822 */ 823 private void triggerTransportClosed(TransportCandidate cand) { 824 List<JingleListener> listeners = getListenersList(); 825 for (JingleListener li : listeners) { 826 if (li instanceof JingleTransportListener) { 827 JingleTransportListener mli = (JingleTransportListener) li; 828 mli.transportClosed(cand); 829 } 830 } 831 } 832 833 // Subclasses 834 835 /** 836 * Raw-UDP transport negotiator. 837 * 838 * @author Alvaro Saurin 839 */ 840 public static final class RawUdp extends TransportNegotiator { 841 842 /** 843 * Default constructor, with a JingleSession and transport manager. 844 * 845 * @param js The Jingle session this negotiation belongs to. 846 * @param res The transport resolver to use. 847 * @param parentNegotiator the parent content negotiator. 848 */ 849 public RawUdp(JingleSession js, final TransportResolver res, ContentNegotiator parentNegotiator) { 850 super(js, res, parentNegotiator); 851 } 852 853 /** 854 * Get a TransportNegotiator instance. 855 */ 856 @Override 857 public org.jivesoftware.smackx.jingleold.packet.JingleTransport getJingleTransport(TransportCandidate bestRemote) { 858 org.jivesoftware.smackx.jingleold.packet.JingleTransport.RawUdp jt = new org.jivesoftware.smackx.jingleold.packet.JingleTransport.RawUdp(); 859 jt.addCandidate(new org.jivesoftware.smackx.jingleold.packet.JingleTransport.RawUdp.Candidate(bestRemote)); 860 return jt; 861 } 862 863 /** 864 * Obtain the best common transport candidate obtained in the 865 * negotiation. 866 * 867 * @return the bestRemoteCandidate 868 */ 869 @Override 870 public TransportCandidate getBestRemoteCandidate() { 871 // Hopefully, we only have one validRemoteCandidate 872 ArrayList<TransportCandidate> cands = getValidRemoteCandidatesList(); 873 if (!cands.isEmpty()) { 874 LOGGER.fine("RAW CAND"); 875 return cands.get(0); 876 } else { 877 LOGGER.fine("No Remote Candidate"); 878 return null; 879 } 880 } 881 882 /** 883 * Return true for fixed candidates. 884 */ 885 @Override 886 public boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates) { 887 return tc instanceof TransportCandidate.Fixed; 888 } 889 } 890 891 /** 892 * Ice transport negotiator. 893 * 894 * @author Alvaro Saurin 895 */ 896 public static final class Ice extends TransportNegotiator { 897 898 /** 899 * Default constructor, with a JingleSession and transport manager. 900 * 901 * @param js The Jingle session this negotiation belongs to. 902 * @param res The transport manager to use. 903 * @param parentNegotiator the parent content negotiator. 904 */ 905 public Ice(JingleSession js, final TransportResolver res, ContentNegotiator parentNegotiator) { 906 super(js, res, parentNegotiator); 907 } 908 909 /** 910 * Get a TransportNegotiator instance. 911 * 912 * @param candidate TODO javadoc me please 913 */ 914 @Override 915 public org.jivesoftware.smackx.jingleold.packet.JingleTransport getJingleTransport(TransportCandidate candidate) { 916 org.jivesoftware.smackx.jingleold.packet.JingleTransport.Ice jt = new org.jivesoftware.smackx.jingleold.packet.JingleTransport.Ice(); 917 jt.addCandidate(new org.jivesoftware.smackx.jingleold.packet.JingleTransport.Ice.Candidate(candidate)); 918 return jt; 919 } 920 921 /** 922 * Obtain the best remote candidate obtained in the negotiation so far. 923 * 924 * @return the bestRemoteCandidate 925 */ 926 @Override 927 public TransportCandidate getBestRemoteCandidate() { 928 ICECandidate result = null; 929 930 ArrayList<TransportCandidate> cands = getValidRemoteCandidatesList(); 931 if (!cands.isEmpty()) { 932 int highest = -1; 933 ICECandidate chose = null; 934 for (TransportCandidate transportCandidate : cands) { 935 if (transportCandidate instanceof ICECandidate) { 936 ICECandidate icecandidate = (ICECandidate) transportCandidate; 937 if (icecandidate.getPreference() > highest) { 938 chose = icecandidate; 939 highest = icecandidate.getPreference(); 940 } 941 } 942 } 943 result = chose; 944 } 945 946 if (result != null && result.getType().equals(Type.relay)) 947 LOGGER.fine("Relay Type"); 948 949 return result; 950 } 951 952 /** 953 * Return true for ICE candidates. 954 */ 955 @Override 956 public boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates) { 957 return tc instanceof ICECandidate; 958 } 959 } 960}