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