001/** 002 * 003 * Copyright the original author or authors 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 */ 017package org.jivesoftware.smackx.jingleold; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Random; 024import java.util.logging.Level; 025import java.util.logging.Logger; 026 027import org.jivesoftware.smack.AbstractConnectionClosedListener; 028import org.jivesoftware.smack.ConnectionListener; 029import org.jivesoftware.smack.SmackException; 030import org.jivesoftware.smack.SmackException.NoResponseException; 031import org.jivesoftware.smack.SmackException.NotConnectedException; 032import org.jivesoftware.smack.StanzaListener; 033import org.jivesoftware.smack.XMPPConnection; 034import org.jivesoftware.smack.XMPPException; 035import org.jivesoftware.smack.XMPPException.XMPPErrorException; 036import org.jivesoftware.smack.filter.StanzaFilter; 037import org.jivesoftware.smack.packet.IQ; 038import org.jivesoftware.smack.packet.Stanza; 039import org.jivesoftware.smack.packet.StanzaError; 040 041import org.jivesoftware.smackx.jingleold.listeners.JingleListener; 042import org.jivesoftware.smackx.jingleold.listeners.JingleMediaListener; 043import org.jivesoftware.smackx.jingleold.listeners.JingleSessionListener; 044import org.jivesoftware.smackx.jingleold.listeners.JingleTransportListener; 045import org.jivesoftware.smackx.jingleold.media.JingleMediaManager; 046import org.jivesoftware.smackx.jingleold.media.JingleMediaSession; 047import org.jivesoftware.smackx.jingleold.media.MediaNegotiator; 048import org.jivesoftware.smackx.jingleold.media.MediaReceivedListener; 049import org.jivesoftware.smackx.jingleold.media.PayloadType; 050import org.jivesoftware.smackx.jingleold.nat.JingleTransportManager; 051import org.jivesoftware.smackx.jingleold.nat.TransportCandidate; 052import org.jivesoftware.smackx.jingleold.nat.TransportNegotiator; 053import org.jivesoftware.smackx.jingleold.nat.TransportResolver; 054import org.jivesoftware.smackx.jingleold.packet.Jingle; 055import org.jivesoftware.smackx.jingleold.packet.JingleError; 056 057import org.jxmpp.jid.Jid; 058 059/** 060 * An abstract Jingle session. This class contains some basic properties of 061 * every Jingle session. However, the concrete implementation can be found in 062 * subclasses. 063 * 064 * @author Alvaro Saurin 065 * @author Jeff Williams 066 */ 067public final class JingleSession extends JingleNegotiator implements MediaReceivedListener { 068 069 private static final Logger LOGGER = Logger.getLogger(JingleSession.class.getName()); 070 071 // static 072 private static final HashMap<XMPPConnection, JingleSession> sessions = new HashMap<>(); 073 074 private static final Random randomGenerator = new Random(); 075 076 // non-static 077 078 private Jid initiator; // Who started the communication 079 080 private Jid responder; // The other endpoint 081 082 private String sid; // A unique id that identifies this session 083 084 private ConnectionListener connectionListener; 085 086 private StanzaListener packetListener; 087 088 private StanzaFilter packetFilter; 089 090 List<JingleMediaManager> jingleMediaManagers = null; 091 092 private JingleSessionState sessionState; 093 094 private final List<ContentNegotiator> contentNegotiators; 095 096 private final XMPPConnection connection; 097 098 private String sessionInitPacketID; 099 100 private final Map<String, JingleMediaSession> mediaSessionMap; 101 102 /** 103 * Full featured JingleSession constructor. 104 * 105 * @param conn TODO javadoc me please 106 * the XMPPConnection which is used 107 * @param initiator TODO javadoc me please 108 * the initiator JID 109 * @param responder TODO javadoc me please 110 * the responder JID 111 * @param sessionid TODO javadoc me please 112 * the session ID 113 * @param jingleMediaManagers TODO javadoc me please 114 * the jingleMediaManager 115 */ 116 public JingleSession(XMPPConnection conn, Jid initiator, Jid responder, String sessionid, 117 List<JingleMediaManager> jingleMediaManagers) { 118 super(); 119 120 this.initiator = initiator; 121 this.responder = responder; 122 this.sid = sessionid; 123 this.jingleMediaManagers = jingleMediaManagers; 124 this.setSession(this); 125 this.connection = conn; 126 127 // Initially, we don't known the session state. 128 setSessionState(JingleSessionStateUnknown.getInstance()); 129 130 contentNegotiators = new ArrayList<>(); 131 mediaSessionMap = new HashMap<>(); 132 133 // Add the session to the list and register the listeners 134 registerInstance(); 135 installConnectionListeners(conn); 136 } 137 138 /** 139 * JingleSession constructor (for an outgoing Jingle session). 140 * 141 * @param conn 142 * Connection 143 * @param request the request. 144 * @param initiator 145 * the initiator JID 146 * @param responder 147 * the responder JID 148 * @param jingleMediaManagers 149 * the jingleMediaManager 150 */ 151 public JingleSession(XMPPConnection conn, JingleSessionRequest request, Jid initiator, Jid responder, 152 List<JingleMediaManager> jingleMediaManagers) { 153 this(conn, initiator, responder, generateSessionId(), jingleMediaManagers); 154 // sessionRequest = request; // unused 155 } 156 157 /** 158 * Get the session initiator. 159 * 160 * @return the initiator 161 */ 162 public Jid getInitiator() { 163 return initiator; 164 } 165 166 @Override 167 public XMPPConnection getConnection() { 168 return connection; 169 } 170 171 /** 172 * Set the session initiator. 173 * 174 * @param initiator TODO javadoc me please 175 * the initiator to set 176 */ 177 public void setInitiator(Jid initiator) { 178 this.initiator = initiator; 179 } 180 181 /** 182 * Get the Media Manager of this Jingle Session. 183 * 184 * @return the JingleMediaManagers 185 */ 186 public List<JingleMediaManager> getMediaManagers() { 187 return jingleMediaManagers; 188 } 189 190 /** 191 * Set the Media Manager of this Jingle Session. 192 * 193 * @param jingleMediaManagers TODO javadoc me please 194 */ 195 public void setMediaManagers(List<JingleMediaManager> jingleMediaManagers) { 196 this.jingleMediaManagers = jingleMediaManagers; 197 } 198 199 /** 200 * Get the session responder. 201 * 202 * @return the responder 203 */ 204 public Jid getResponder() { 205 return responder; 206 } 207 208 /** 209 * Set the session responder. 210 * 211 * @param responder TODO javadoc me please 212 * the receptor to set 213 */ 214 public void setResponder(Jid responder) { 215 this.responder = responder; 216 } 217 218 /** 219 * Get the session ID. 220 * 221 * @return the sid 222 */ 223 public String getSid() { 224 return sid; 225 } 226 227 /** 228 * Set the session ID 229 * 230 * @param sessionId TODO javadoc me please 231 * the sid to set 232 */ 233 void setSid(String sessionId) { 234 sid = sessionId; 235 } 236 237 /** 238 * Generate a unique session ID. 239 * 240 * @return the generated session ID. 241 */ 242 static String generateSessionId() { 243 return String.valueOf(randomGenerator.nextInt(Integer.MAX_VALUE) + randomGenerator.nextInt(Integer.MAX_VALUE)); 244 } 245 246 /** 247 * Validate the state changes. 248 * 249 * @param stateIs the jingle session state. 250 */ 251 252 public void setSessionState(JingleSessionState stateIs) { 253 254 LOGGER.fine("Session state change: " + sessionState + "->" + stateIs); 255 stateIs.enter(); 256 sessionState = stateIs; 257 } 258 259 public JingleSessionState getSessionState() { 260 return sessionState; 261 } 262 263 /** 264 * Return true if all of the media managers have finished. 265 * 266 * @return <code>true</code> if fully established. 267 */ 268 public boolean isFullyEstablished() { 269 boolean result = true; 270 for (ContentNegotiator contentNegotiator : contentNegotiators) { 271 if (!contentNegotiator.isFullyEstablished()) 272 result = false; 273 } 274 return result; 275 } 276 277 // ---------------------------------------------------------------------------------------------------------- 278 // Receive section 279 // ---------------------------------------------------------------------------------------------------------- 280 281 /** 282 * Process and respond to an incoming packet. This method is called 283 * from the stanza listener dispatcher when a new stanza has arrived. The 284 * method is responsible for recognizing the stanza type and, depending on 285 * the current state, delivering it to the right event handler and wait for 286 * a response. The response will be another Jingle stanza that will be sent 287 * to the other end point. 288 * 289 * @param iq TODO javadoc me please 290 * the stanza received 291 * @throws XMPPException if an XMPP protocol error was received. 292 * @throws SmackException if Smack detected an exceptional situation. 293 * @throws InterruptedException if the calling thread was interrupted. 294 */ 295 public synchronized void receivePacketAndRespond(IQ iq) throws XMPPException, SmackException, InterruptedException { 296 List<IQ> responses = new ArrayList<>(); 297 298 String responseId; 299 300 LOGGER.fine("Packet: " + iq.toXML()); 301 302 try { 303 304 // Dispatch the packet to the JingleNegotiators and get back a list of the results. 305 responses.addAll(dispatchIncomingPacket(iq, null)); 306 307 if (iq != null) { 308 responseId = iq.getStanzaId(); 309 310 // Send the IQ to each of the content negotiators for further processing. 311 // Each content negotiator may pass back a list of JingleContent for addition to the response packet. 312 // CHECKSTYLE:OFF 313 for (ContentNegotiator contentNegotiator : contentNegotiators) { 314 // If at this point the content negotiator isn't started, it's because we sent a session-init jingle 315 // packet from startOutgoing() and we're waiting for the other side to let us know they're ready 316 // to take jingle packets. (This packet might be a session-terminate, but that will get handled 317 // later. 318 if (!contentNegotiator.isStarted()) { 319 contentNegotiator.start(); 320 } 321 responses.addAll(contentNegotiator.dispatchIncomingPacket(iq, responseId)); 322 } 323 // CHECKSTYLE:ON 324 325 } 326 // Acknowledge the IQ reception 327 // Not anymore. The state machine generates an appropriate response IQ that 328 // gets sent back at the end of this routine. 329 // sendAck(iq); 330 331 } catch (JingleException e) { 332 // Send an error message, if present 333 JingleError error = e.getError(); 334 if (error != null) { 335 responses.add(createJingleError(iq, error)); 336 } 337 338 // Notify the session end and close everything... 339 triggerSessionClosedOnError(e); 340 } 341 342 // // If the response is anything other than a RESULT then send it now. 343 // if ((response != null) && (!response.getType().equals(IQ.Type.result))) { 344 // getConnection().sendStanza(response); 345 // } 346 347 // Loop through all of the responses and send them. 348 for (IQ response : responses) { 349 sendStanza(response); 350 } 351 } 352 353 /** 354 * Dispatch an incoming packet. The method is responsible for recognizing 355 * the stanza type and, depending on the current state, delivering the 356 * stanza to the right event handler and wait for a response. 357 * 358 * @param iq TODO javadoc me please 359 * the stanza received 360 * @return the new Jingle stanza to send. 361 * @throws XMPPException if an XMPP protocol error was received. 362 * @throws SmackException if Smack detected an exceptional situation. 363 * @throws InterruptedException if the calling thread was interrupted. 364 */ 365 @Override 366 public List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException, InterruptedException { 367 List<IQ> responses = new ArrayList<>(); 368 IQ response = null; 369 370 if (iq != null) { 371 if (iq.getType().equals(IQ.Type.error)) { 372 // Process errors 373 // TODO getState().eventError(iq); 374 } else if (iq.getType().equals(IQ.Type.result)) { 375 // Process ACKs 376 if (isExpectedId(iq.getStanzaId())) { 377 378 // The other side provisionally accepted our session-initiate. 379 // Kick off some negotiators. 380 if (iq.getStanzaId().equals(sessionInitPacketID)) { 381 startNegotiators(); 382 } 383 removeExpectedId(iq.getStanzaId()); 384 } 385 } else if (iq instanceof Jingle) { 386 // It is not an error: it is a Jingle packet... 387 Jingle jin = (Jingle) iq; 388 JingleActionEnum action = jin.getAction(); 389 390 // Depending on the state we're in we'll get different processing actions. 391 // (See Design Patterns AKA GoF State behavioral pattern.) 392 response = getSessionState().processJingle(this, jin, action); 393 } 394 } 395 396 if (response != null) { 397 // Save the packet id, for recognizing ACKs... 398 addExpectedId(response.getStanzaId()); 399 responses.add(response); 400 } 401 402 return responses; 403 } 404 405 /** 406 * Add a new content negotiator on behalf of a <content/> section received. 407 * 408 * @param inContentNegotiator the content negotiator. 409 */ 410 public void addContentNegotiator(ContentNegotiator inContentNegotiator) { 411 contentNegotiators.add(inContentNegotiator); 412 } 413 414 415 416 // ---------------------------------------------------------------------------------------------------------- 417 // Send section 418 // ---------------------------------------------------------------------------------------------------------- 419 420 public void sendStanza(IQ iq) throws NotConnectedException, InterruptedException { 421 422 if (iq instanceof Jingle) { 423 424 sendFormattedJingle((Jingle) iq); 425 426 } else { 427 428 getConnection().sendStanza(iq); 429 } 430 } 431 432 /** 433 * Complete and send a packet. Complete all the null fields in a Jingle 434 * response, using the session information we have. 435 * 436 * @param jout 437 * the Jingle stanza we want to complete and send 438 * @return the Jingle stanza. 439 * @throws NotConnectedException if the XMPP connection is not connected. 440 * @throws InterruptedException if the calling thread was interrupted. 441 */ 442 public Jingle sendFormattedJingle(Jingle jout) throws NotConnectedException, InterruptedException { 443 return sendFormattedJingle(null, jout); 444 } 445 446 /** 447 * Complete and send a packet. Complete all the null fields in a Jingle 448 * response, using the session information we have or some info from the 449 * incoming packet. 450 * 451 * @param iq The Jingle stanza we are responding to 452 * @param jout the Jingle stanza we want to complete and send 453 * @return the Jingle stanza. 454 * @throws NotConnectedException if the XMPP connection is not connected. 455 * @throws InterruptedException if the calling thread was interrupted. 456 */ 457 public Jingle sendFormattedJingle(IQ iq, Jingle jout) throws NotConnectedException, InterruptedException { 458 if (jout != null) { 459 if (jout.getInitiator() == null) { 460 jout.setInitiator(getInitiator()); 461 } 462 463 if (jout.getResponder() == null) { 464 jout.setResponder(getResponder()); 465 } 466 467 if (jout.getSid() == null) { 468 jout.setSid(getSid()); 469 } 470 471 Jid me = getConnection().getUser(); 472 Jid other = getResponder().equals(me) ? getInitiator() : getResponder(); 473 474 if (jout.getTo() == null) { 475 if (iq != null) { 476 jout.setTo(iq.getFrom()); 477 } else { 478 jout.setTo(other); 479 } 480 } 481 482 if (jout.getFrom() == null) { 483 if (iq != null) { 484 jout.setFrom(iq.getTo()); 485 } else { 486 jout.setFrom(me); 487 } 488 } 489 490 // the packet. 491 // CHECKSTYLE:OFF 492 if ((getConnection() != null) && getConnection().isConnected()) 493 getConnection().sendStanza(jout); 494 // CHECKSTYLE:ON 495 } 496 return jout; 497 } 498 499 /** 500 * @param inJingle TODO javadoc me please 501 * @param inAction TODO javadoc me please 502 */ 503 // private void sendUnknownStateAction(Jingle inJingle, JingleActionEnum inAction) { 504 // 505 // if (inAction == JingleActionEnum.SESSION_INITIATE) { 506 // // Prepare to receive and act on response packets. 507 // updatePacketListener(); 508 // 509 // // Send the actual packet. 510 // sendStanza(inJingle); 511 // 512 // // Change to the PENDING state. 513 // setSessionState(JingleSessionStateEnum.PENDING); 514 // } else { 515 // throw new IllegalStateException("Only session-initiate allowed in the UNKNOWN state."); 516 // } 517 // } 518 519 /** 520 * Acknowledge a IQ packet. 521 * 522 * @param iq The IQ to acknowledge. 523 * @return the ack IQ. 524 */ 525 public IQ createAck(IQ iq) { 526 IQ result = null; 527 528 if (iq != null) { 529 // Don't acknowledge ACKs, errors... 530 if (iq.getType().equals(IQ.Type.set)) { 531 IQ ack = IQ.createResultIQ(iq); 532 533 // No! Don't send it. Let it flow to the normal way IQ results get processed and sent. 534 // getConnection().sendStanza(ack); 535 result = ack; 536 } 537 } 538 return result; 539 } 540 541 /** 542 * Send a content info message. 543 */ 544 // public synchronized void sendContentInfo(ContentInfo ci) { 545 // sendStanza(new Jingle(new JingleContentInfo(ci))); 546 // } 547 /* 548 * (non-Javadoc) 549 * 550 * @see java.lang.Object#hashCode() 551 */ 552 @Override 553 public int hashCode() { 554 return Jingle.getSessionHash(getSid(), getInitiator()); 555 } 556 557 /* 558 * (non-Javadoc) 559 * 560 * @see java.lang.Object#equals(java.lang.Object) 561 */ 562 @Override 563 public boolean equals(Object obj) { 564 if (this == obj) { 565 return true; 566 } 567 if (obj == null) { 568 return false; 569 } 570 if (getClass() != obj.getClass()) { 571 return false; 572 } 573 574 final JingleSession other = (JingleSession) obj; 575 576 if (initiator == null) { 577 if (other.initiator != null) { 578 return false; 579 } 580 } else if (!initiator.equals(other.initiator)) { 581 // Todo check behavior 582 // return false; 583 } 584 585 if (responder == null) { 586 if (other.responder != null) { 587 return false; 588 } 589 } else if (!responder.equals(other.responder)) { 590 return false; 591 } 592 593 if (sid == null) { 594 if (other.sid != null) { 595 return false; 596 } 597 } else if (!sid.equals(other.sid)) { 598 return false; 599 } 600 601 return true; 602 } 603 604 // Instances management 605 606 /** 607 * Clean a session from the list. 608 * 609 * @param connection TODO javadoc me please 610 * The connection to clean up 611 */ 612 private static void unregisterInstanceFor(XMPPConnection connection) { 613 synchronized (sessions) { 614 sessions.remove(connection); 615 } 616 } 617 618 /** 619 * Register this instance. 620 */ 621 private void registerInstance() { 622 synchronized (sessions) { 623 sessions.put(getConnection(), this); 624 } 625 } 626 627 /** 628 * Returns the JingleSession related to a particular connection. 629 * 630 * @param con TODO javadoc me please 631 * A XMPP connection 632 * @return a Jingle session 633 */ 634 public static synchronized JingleSession getInstanceFor(XMPPConnection con) { 635 if (con == null) { 636 throw new IllegalArgumentException("XMPPConnection cannot be null"); 637 } 638 639 JingleSession result = null; 640 synchronized (sessions) { 641 if (sessions.containsKey(con)) { 642 result = sessions.get(con); 643 } 644 } 645 646 return result; 647 } 648 649 /** 650 * Configure a session, setting some action listeners... 651 * 652 * @param connection TODO javadoc me please 653 * The connection to set up 654 */ 655 private void installConnectionListeners(final XMPPConnection connection) { 656 if (connection != null) { 657 connectionListener = new AbstractConnectionClosedListener() { 658 @Override 659 public void connectionTerminated() { 660 unregisterInstanceFor(connection); 661 } 662 }; 663 connection.addConnectionListener(connectionListener); 664 } 665 } 666 667 private void removeConnectionListener() { 668 // CHECKSTYLE:OFF 669 if (connectionListener != null) { 670 getConnection().removeConnectionListener(connectionListener); 671 672 LOGGER.fine("JINGLE SESSION: REMOVE CONNECTION LISTENER"); 673 } 674 // CHECKSTYLE:ON 675 } 676 677 /** 678 * Remove the stanza listener used for processing packet. 679 */ 680 void removeAsyncPacketListener() { 681 if (packetListener != null) { 682 getConnection().removeAsyncStanzaListener(packetListener); 683 684 LOGGER.fine("JINGLE SESSION: REMOVE PACKET LISTENER"); 685 } 686 } 687 688 /** 689 * Install the stanza listener. The listener is responsible for responding 690 * to any stanza that we receive... 691 */ 692 void updatePacketListener() { 693 removeAsyncPacketListener(); 694 695 LOGGER.fine("UpdatePacketListener"); 696 697 packetListener = new StanzaListener() { 698 @Override 699 public void processStanza(Stanza packet) { 700 try { 701 receivePacketAndRespond((IQ) packet); 702 } catch (Exception e) { 703 LOGGER.log(Level.WARNING, "exception", e); 704 } 705 } 706 }; 707 708 packetFilter = new StanzaFilter() { 709 @Override 710 public boolean accept(Stanza packet) { 711 712 if (packet instanceof IQ) { 713 IQ iq = (IQ) packet; 714 715 Jid me = getConnection().getUser(); 716 717 if (!iq.getTo().equals(me)) { 718 return false; 719 } 720 721 Jid other = getResponder().equals(me) ? getInitiator() : getResponder(); 722 723 if (iq.getFrom() == null || !iq.getFrom().equals(other == null ? "" : other)) { 724 return false; 725 } 726 727 if (iq instanceof Jingle) { 728 Jingle jin = (Jingle) iq; 729 730 String sid = jin.getSid(); 731 if (sid == null || !sid.equals(getSid())) { 732 LOGGER.fine("Ignored Jingle(SID) " + sid + "|" + getSid() + " :" + iq.toXML()); 733 return false; 734 } 735 Jid ini = jin.getInitiator(); 736 if (!ini.equals(getInitiator())) { 737 LOGGER.fine("Ignored Jingle(INI): " + iq.toXML()); 738 return false; 739 } 740 } else { 741 // We accept some non-Jingle IQ packets: ERRORs and ACKs 742 if (iq.getType().equals(IQ.Type.set)) { 743 LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML()); 744 return false; 745 } else if (iq.getType().equals(IQ.Type.get)) { 746 LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML()); 747 return false; 748 } 749 } 750 return true; 751 } 752 return false; 753 } 754 }; 755 756 getConnection().addAsyncStanzaListener(packetListener, packetFilter); 757 } 758 759 // Listeners 760 761 /** 762 * Add a listener for jmf negotiation events. 763 * 764 * @param li TODO javadoc me please 765 * The listener 766 */ 767 public void addMediaListener(JingleMediaListener li) { 768 for (ContentNegotiator contentNegotiator : contentNegotiators) { 769 if (contentNegotiator.getMediaNegotiator() != null) { 770 contentNegotiator.getMediaNegotiator().addListener(li); 771 } 772 } 773 774 } 775 776 /** 777 * Remove a listener for jmf negotiation events. 778 * 779 * @param li TODO javadoc me please 780 * The listener 781 */ 782 public void removeMediaListener(JingleMediaListener li) { 783 for (ContentNegotiator contentNegotiator : contentNegotiators) { 784 if (contentNegotiator.getMediaNegotiator() != null) { 785 contentNegotiator.getMediaNegotiator().removeListener(li); 786 } 787 } 788 } 789 790 /** 791 * Add a listener for transport negotiation events. 792 * 793 * @param li TODO javadoc me please 794 * The listener 795 */ 796 public void addTransportListener(JingleTransportListener li) { 797 for (ContentNegotiator contentNegotiator : contentNegotiators) { 798 if (contentNegotiator.getTransportNegotiator() != null) { 799 contentNegotiator.getTransportNegotiator().addListener(li); 800 } 801 } 802 } 803 804 /** 805 * Remove a listener for transport negotiation events. 806 * 807 * @param li TODO javadoc me please 808 * The listener 809 */ 810 public void removeTransportListener(JingleTransportListener li) { 811 for (ContentNegotiator contentNegotiator : contentNegotiators) { 812 if (contentNegotiator.getTransportNegotiator() != null) { 813 contentNegotiator.getTransportNegotiator().removeListener(li); 814 } 815 } 816 } 817 818 /** 819 * Setup the listeners that act on events coming from the lower level negotiators. 820 */ 821 822 public void setupListeners() { 823 824 JingleMediaListener jingleMediaListener = new JingleMediaListener() { 825 @Override 826 public void mediaClosed(PayloadType cand) { 827 } 828 829 @Override 830 public void mediaEstablished(PayloadType pt) throws NotConnectedException, InterruptedException { 831 if (isFullyEstablished()) { 832 Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT); 833 834 // Build up a response packet from each media manager. 835 for (ContentNegotiator contentNegotiator : contentNegotiators) { 836 if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) 837 jout.addContent(contentNegotiator.getJingleContent()); 838 } 839 // Send the "accept" and wait for the ACK 840 addExpectedId(jout.getStanzaId()); 841 sendStanza(jout); 842 843 // triggerSessionEstablished(); 844 845 } 846 } 847 }; 848 849 JingleTransportListener jingleTransportListener = new JingleTransportListener() { 850 851 @Override 852 public void transportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { 853 if (isFullyEstablished()) { 854 // CHECKSTYLE:OFF 855 // Indicate that this session is active. 856 setSessionState(JingleSessionStateActive.getInstance()); 857 858 for (ContentNegotiator contentNegotiator : contentNegotiators) { 859 // CHECKSTYLE:ON 860 if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) 861 contentNegotiator.triggerContentEstablished(); 862 } 863 864 if (getSessionState().equals(JingleSessionStatePending.getInstance())) { 865 866 Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT); 867 868 // Build up a response packet from each media manager. 869 for (ContentNegotiator contentNegotiator : contentNegotiators) { 870 if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) 871 jout.addContent(contentNegotiator.getJingleContent()); 872 } 873 // Send the "accept" and wait for the ACK 874 addExpectedId(jout.getStanzaId()); 875 sendStanza(jout); 876 } 877 } 878 } 879 880 @Override 881 public void transportClosed(TransportCandidate cand) { 882 } 883 884 @Override 885 public void transportClosedOnError(XMPPException e) { 886 } 887 }; 888 889 addMediaListener(jingleMediaListener); 890 addTransportListener(jingleTransportListener); 891 } 892 893 // Triggers 894 895 /** 896 * Trigger a session closed event. 897 * 898 * @param reason the reason. 899 */ 900 void triggerSessionClosed(String reason) { 901 // for (ContentNegotiator contentNegotiator : contentNegotiators) { 902 // 903 // contentNegotiator.stopJingleMediaSession(); 904 // 905 // for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) 906 // candidate.removeCandidateEcho(); 907 // } 908 909 List<JingleListener> listeners = getListenersList(); 910 for (JingleListener li : listeners) { 911 if (li instanceof JingleSessionListener) { 912 JingleSessionListener sli = (JingleSessionListener) li; 913 sli.sessionClosed(reason, this); 914 } 915 } 916 close(); 917 } 918 919 /** 920 * Trigger a session closed event due to an error. 921 * 922 * @param exc the exception. 923 */ 924 void triggerSessionClosedOnError(XMPPException exc) { 925 for (ContentNegotiator contentNegotiator : contentNegotiators) { 926 927 contentNegotiator.stopJingleMediaSession(); 928 929 for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) 930 candidate.removeCandidateEcho(); 931 } 932 List<JingleListener> listeners = getListenersList(); 933 for (JingleListener li : listeners) { 934 if (li instanceof JingleSessionListener) { 935 JingleSessionListener sli = (JingleSessionListener) li; 936 sli.sessionClosedOnError(exc, this); 937 } 938 } 939 close(); 940 } 941 942 /** 943 * Trigger a session established event. 944 */ 945 // protected void triggerSessionEstablished() { 946 // List<JingleListener> listeners = getListenersList(); 947 // for (JingleListener li : listeners) { 948 // if (li instanceof JingleSessionListener) { 949 // JingleSessionListener sli = (JingleSessionListener) li; 950 // sli.sessionEstablished(this); 951 // } 952 // } 953 // } 954 /** 955 * Trigger a media received event. 956 * 957 * @param participant the participant. 958 */ 959 void triggerMediaReceived(String participant) { 960 List<JingleListener> listeners = getListenersList(); 961 for (JingleListener li : listeners) { 962 if (li instanceof JingleSessionListener) { 963 JingleSessionListener sli = (JingleSessionListener) li; 964 sli.sessionMediaReceived(this, participant); 965 } 966 } 967 } 968 969 /** 970 * Trigger a session redirect event. 971 */ 972 // protected void triggerSessionRedirect(String arg) { 973 // List<JingleListener> listeners = getListenersList(); 974 // for (JingleListener li : listeners) { 975 // if (li instanceof JingleSessionListener) { 976 // JingleSessionListener sli = (JingleSessionListener) li; 977 // sli.sessionRedirected(arg, this); 978 // } 979 // } 980 // } 981 /** 982 * Trigger a session decline event. 983 */ 984 // protected void triggerSessionDeclined(String reason) { 985 // List<JingleListener> listeners = getListenersList(); 986 // for (JingleListener li : listeners) { 987 // if (li instanceof JingleSessionListener) { 988 // JingleSessionListener sli = (JingleSessionListener) li; 989 // sli.sessionDeclined(reason, this); 990 // } 991 // } 992 // for (ContentNegotiator contentNegotiator : contentNegotiators) { 993 // for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) 994 // candidate.removeCandidateEcho(); 995 // } 996 // } 997 /** 998 * Terminates the session with default reason. 999 * 1000 * @throws XMPPException if an XMPP protocol error was received. 1001 * @throws NotConnectedException if the XMPP connection is not connected. 1002 * @throws InterruptedException if the calling thread was interrupted. 1003 */ 1004 public void terminate() throws XMPPException, NotConnectedException, InterruptedException { 1005 terminate("Closed Locally"); 1006 } 1007 1008 /** 1009 * Terminates the session with a custom reason. 1010 * 1011 * @param reason the reason. 1012 * @throws XMPPException if an XMPP protocol error was received. 1013 * @throws NotConnectedException if the XMPP connection is not connected. 1014 * @throws InterruptedException if the calling thread was interrupted. 1015 */ 1016 public void terminate(String reason) throws XMPPException, NotConnectedException, InterruptedException { 1017 if (isClosed()) 1018 return; 1019 LOGGER.fine("Terminate " + reason); 1020 Jingle jout = new Jingle(JingleActionEnum.SESSION_TERMINATE); 1021 jout.setType(IQ.Type.set); 1022 sendStanza(jout); 1023 triggerSessionClosed(reason); 1024 } 1025 1026 /** 1027 * Terminate negotiations. 1028 */ 1029 @Override 1030 public void close() { 1031 if (isClosed()) 1032 return; 1033 1034 // Set the session state to ENDED. 1035 setSessionState(JingleSessionStateEnded.getInstance()); 1036 1037 for (ContentNegotiator contentNegotiator : contentNegotiators) { 1038 1039 contentNegotiator.stopJingleMediaSession(); 1040 1041 for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) 1042 candidate.removeCandidateEcho(); 1043 1044 contentNegotiator.close(); 1045 } 1046 removeAsyncPacketListener(); 1047 removeConnectionListener(); 1048 getConnection().removeConnectionListener(connectionListener); 1049 LOGGER.fine("Negotiation Closed: " + getConnection().getUser() + " " + sid); 1050 super.close(); 1051 1052 } 1053 1054 public boolean isClosed() { 1055 return getSessionState().equals(JingleSessionStateEnded.getInstance()); 1056 } 1057 1058 // Packet and error creation 1059 1060 /** 1061 * Complete and send an error. Complete all the null fields in an IQ error 1062 * response, using the session information we have or some info from the 1063 * incoming packet. 1064 * 1065 * @param iq 1066 * The Jingle stanza we are responding to 1067 * @param jingleError 1068 * the IQ stanza we want to complete and send 1069 * @return the jingle error IQ. 1070 */ 1071 public IQ createJingleError(IQ iq, JingleError jingleError) { 1072 IQ errorPacket = null; 1073 if (jingleError != null) { 1074 // TODO This is wrong according to XEP-166 ยง 10, but this jingle implementation is deprecated anyways 1075 StanzaError builder = StanzaError.getBuilder() 1076 .setCondition(StanzaError.Condition.undefined_condition) 1077 .addExtension(jingleError) 1078 .build(); 1079 1080 errorPacket = IQ.createErrorResponse(iq, builder); 1081 1082 // errorPacket.addExtension(jingleError); 1083 1084 // NO! Let the normal state machinery do all of the sending. 1085 // getConnection().sendStanza(perror); 1086 LOGGER.severe("Error sent: " + errorPacket.toXML()); 1087 } 1088 return errorPacket; 1089 } 1090 1091 /** 1092 * Called when new Media is received. 1093 */ 1094 @Override 1095 public void mediaReceived(String participant) { 1096 triggerMediaReceived(participant); 1097 } 1098 1099 /** 1100 * This is the starting point for initiating a new session. 1101 * 1102 * @throws IllegalStateException if an illegal state was encountered 1103 * @throws SmackException if Smack detected an exceptional situation. 1104 * @throws InterruptedException if the calling thread was interrupted. 1105 */ 1106 public void startOutgoing() throws IllegalStateException, SmackException, InterruptedException { 1107 1108 updatePacketListener(); 1109 setSessionState(JingleSessionStatePending.getInstance()); 1110 1111 Jingle jingle = new Jingle(JingleActionEnum.SESSION_INITIATE); 1112 1113 // Create a content negotiator for each media manager on the session. 1114 for (JingleMediaManager mediaManager : getMediaManagers()) { 1115 ContentNegotiator contentNeg = new ContentNegotiator(this, ContentNegotiator.INITIATOR, mediaManager.getName()); 1116 1117 // Create the media negotiator for this content description. 1118 contentNeg.setMediaNegotiator(new MediaNegotiator(this, mediaManager, mediaManager.getPayloads(), contentNeg)); 1119 1120 JingleTransportManager transportManager = mediaManager.getTransportManager(); 1121 TransportResolver resolver = null; 1122 try { 1123 resolver = transportManager.getResolver(this); 1124 } catch (XMPPException e) { 1125 LOGGER.log(Level.WARNING, "exception", e); 1126 } 1127 1128 if (resolver.getType().equals(TransportResolver.Type.rawupd)) { 1129 contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(this, resolver, contentNeg)); 1130 } 1131 if (resolver.getType().equals(TransportResolver.Type.ice)) { 1132 contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(this, resolver, contentNeg)); 1133 } 1134 1135 addContentNegotiator(contentNeg); 1136 } 1137 1138 // Give each of the content negotiators a chance to return a portion of the structure to make the Jingle packet. 1139 for (ContentNegotiator contentNegotiator : contentNegotiators) { 1140 jingle.addContent(contentNegotiator.getJingleContent()); 1141 } 1142 1143 // Save the session-initiate packet ID, so that we can respond to it. 1144 sessionInitPacketID = jingle.getStanzaId(); 1145 1146 sendStanza(jingle); 1147 1148 // Now setup to track the media negotiators, so that we know when (if) to send a session-accept. 1149 setupListeners(); 1150 1151 // Give each of the content negotiators a chance to start 1152 // and return a portion of the structure to make the Jingle packet. 1153 1154// Don't do this anymore. The problem is that the other side might not be ready. 1155// Later when we receive our first jingle packet from the other side we'll fire-up the negotiators 1156// before processing it. (See receivePacketAndRespond() above. 1157// for (ContentNegotiator contentNegotiator : contentNegotiators) { 1158// contentNegotiator.start(); 1159// } 1160 } 1161 1162 /** 1163 * This is the starting point for responding to a new session. 1164 */ 1165 public void startIncoming() { 1166 1167 // updatePacketListener(); 1168 } 1169 1170 @Override 1171 protected void doStart() { 1172 1173 } 1174 1175 /** 1176 * When we initiate a session we need to start a bunch of negotiators right after we receive the result 1177 * stanza for our session-initiate. This is where we start them. 1178 * 1179 */ 1180 private void startNegotiators() { 1181 1182 for (ContentNegotiator contentNegotiator : contentNegotiators) { 1183 TransportNegotiator transNeg = contentNegotiator.getTransportNegotiator(); 1184 transNeg.start(); 1185 } 1186 } 1187 1188 /** 1189 * The jingle session may have one or more media managers that are trying to establish media sessions. 1190 * When the media manager succeeds in creating a media session is registers it with the session by the 1191 * media manager's static name. This routine is where the media manager does the registering. 1192 * 1193 * @param mediaManagerName the name of the media manager. 1194 * @param mediaSession the jingle media session. 1195 */ 1196 public void addJingleMediaSession(String mediaManagerName, JingleMediaSession mediaSession) { 1197 mediaSessionMap.put(mediaManagerName, mediaSession); 1198 } 1199 1200 /** 1201 * The jingle session may have one or more media managers that are trying to establish media sessions. 1202 * When the media manager succeeds in creating a media session is registers it with the session by the 1203 * media manager's static name. This routine is where other objects can access the registered media sessions. 1204 * NB: If the media manager has not succeeded in establishing a media session then this could return null. 1205 * 1206 * @param mediaManagerName the name of the media manager. 1207 * @return the jingle media session. 1208 */ 1209 public JingleMediaSession getMediaSession(String mediaManagerName) { 1210 return mediaSessionMap.get(mediaManagerName); 1211 } 1212}