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