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