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