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