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 * Acknowledge a IQ packet. 501 * 502 * @param iq The IQ to acknowledge. 503 * @return the ack IQ. 504 */ 505 public IQ createAck(IQ iq) { 506 IQ result = null; 507 508 if (iq != null) { 509 // Don't acknowledge ACKs, errors... 510 if (iq.getType().equals(IQ.Type.set)) { 511 IQ ack = IQ.createResultIQ(iq); 512 513 // No! Don't send it. Let it flow to the normal way IQ results get processed and sent. 514 // getConnection().sendStanza(ack); 515 result = ack; 516 } 517 } 518 return result; 519 } 520 521 /** 522 * Send a content info message. 523 */ 524 // public synchronized void sendContentInfo(ContentInfo ci) { 525 // sendStanza(new Jingle(new JingleContentInfo(ci))); 526 // } 527 528 @Override 529 public int hashCode() { 530 return Jingle.getSessionHash(getSid(), getInitiator()); 531 } 532 533 @Override 534 public boolean equals(Object obj) { 535 if (this == obj) { 536 return true; 537 } 538 if (obj == null) { 539 return false; 540 } 541 if (getClass() != obj.getClass()) { 542 return false; 543 } 544 545 final JingleSession other = (JingleSession) obj; 546 547 if (initiator == null) { 548 if (other.initiator != null) { 549 return false; 550 } 551 } else if (!initiator.equals(other.initiator)) { 552 // Todo check behavior 553 // return false; 554 } 555 556 if (responder == null) { 557 if (other.responder != null) { 558 return false; 559 } 560 } else if (!responder.equals(other.responder)) { 561 return false; 562 } 563 564 if (sid == null) { 565 if (other.sid != null) { 566 return false; 567 } 568 } else if (!sid.equals(other.sid)) { 569 return false; 570 } 571 572 return true; 573 } 574 575 // Instances management 576 577 /** 578 * Clean a session from the list. 579 * 580 * @param connection TODO javadoc me please 581 * The connection to clean up 582 */ 583 private static void unregisterInstanceFor(XMPPConnection connection) { 584 synchronized (sessions) { 585 sessions.remove(connection); 586 } 587 } 588 589 /** 590 * Register this instance. 591 */ 592 private void registerInstance() { 593 synchronized (sessions) { 594 sessions.put(getConnection(), this); 595 } 596 } 597 598 /** 599 * Returns the JingleSession related to a particular connection. 600 * 601 * @param con TODO javadoc me please 602 * A XMPP connection 603 * @return a Jingle session 604 */ 605 public static synchronized JingleSession getInstanceFor(XMPPConnection con) { 606 if (con == null) { 607 throw new IllegalArgumentException("XMPPConnection cannot be null"); 608 } 609 610 JingleSession result = null; 611 synchronized (sessions) { 612 if (sessions.containsKey(con)) { 613 result = sessions.get(con); 614 } 615 } 616 617 return result; 618 } 619 620 /** 621 * Configure a session, setting some action listeners... 622 * 623 * @param connection TODO javadoc me please 624 * The connection to set up 625 */ 626 private void installConnectionListeners(final XMPPConnection connection) { 627 if (connection != null) { 628 connectionListener = new AbstractConnectionClosedListener() { 629 @Override 630 public void connectionTerminated() { 631 unregisterInstanceFor(connection); 632 } 633 }; 634 connection.addConnectionListener(connectionListener); 635 } 636 } 637 638 private void removeConnectionListener() { 639 // CHECKSTYLE:OFF 640 if (connectionListener != null) { 641 getConnection().removeConnectionListener(connectionListener); 642 643 LOGGER.fine("JINGLE SESSION: REMOVE CONNECTION LISTENER"); 644 } 645 // CHECKSTYLE:ON 646 } 647 648 /** 649 * Remove the stanza listener used for processing packet. 650 */ 651 void removeAsyncPacketListener() { 652 if (packetListener != null) { 653 getConnection().removeAsyncStanzaListener(packetListener); 654 655 LOGGER.fine("JINGLE SESSION: REMOVE PACKET LISTENER"); 656 } 657 } 658 659 /** 660 * Install the stanza listener. The listener is responsible for responding 661 * to any stanza that we receive... 662 */ 663 void updatePacketListener() { 664 removeAsyncPacketListener(); 665 666 LOGGER.fine("UpdatePacketListener"); 667 668 packetListener = new StanzaListener() { 669 @Override 670 public void processStanza(Stanza packet) { 671 try { 672 receivePacketAndRespond((IQ) packet); 673 } catch (Exception e) { 674 LOGGER.log(Level.WARNING, "exception", e); 675 } 676 } 677 }; 678 679 packetFilter = new StanzaFilter() { 680 @Override 681 public boolean accept(Stanza packet) { 682 683 if (packet instanceof IQ) { 684 IQ iq = (IQ) packet; 685 686 Jid me = getConnection().getUser(); 687 688 if (!iq.getTo().equals(me)) { 689 return false; 690 } 691 692 Jid other = getResponder().equals(me) ? getInitiator() : getResponder(); 693 694 if (iq.getFrom() == null || !iq.getFrom().equals(other == null ? "" : other)) { 695 return false; 696 } 697 698 if (iq instanceof Jingle) { 699 Jingle jin = (Jingle) iq; 700 701 String sid = jin.getSid(); 702 if (sid == null || !sid.equals(getSid())) { 703 LOGGER.fine("Ignored Jingle(SID) " + sid + "|" + getSid() + " :" + iq.toXML()); 704 return false; 705 } 706 Jid ini = jin.getInitiator(); 707 if (!ini.equals(getInitiator())) { 708 LOGGER.fine("Ignored Jingle(INI): " + iq.toXML()); 709 return false; 710 } 711 } else { 712 // We accept some non-Jingle IQ packets: ERRORs and ACKs 713 if (iq.getType().equals(IQ.Type.set)) { 714 LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML()); 715 return false; 716 } else if (iq.getType().equals(IQ.Type.get)) { 717 LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML()); 718 return false; 719 } 720 } 721 return true; 722 } 723 return false; 724 } 725 }; 726 727 getConnection().addAsyncStanzaListener(packetListener, packetFilter); 728 } 729 730 // Listeners 731 732 /** 733 * Add a listener for jmf negotiation events. 734 * 735 * @param li TODO javadoc me please 736 * The listener 737 */ 738 public void addMediaListener(JingleMediaListener li) { 739 for (ContentNegotiator contentNegotiator : contentNegotiators) { 740 if (contentNegotiator.getMediaNegotiator() != null) { 741 contentNegotiator.getMediaNegotiator().addListener(li); 742 } 743 } 744 745 } 746 747 /** 748 * Remove a listener for jmf negotiation events. 749 * 750 * @param li TODO javadoc me please 751 * The listener 752 */ 753 public void removeMediaListener(JingleMediaListener li) { 754 for (ContentNegotiator contentNegotiator : contentNegotiators) { 755 if (contentNegotiator.getMediaNegotiator() != null) { 756 contentNegotiator.getMediaNegotiator().removeListener(li); 757 } 758 } 759 } 760 761 /** 762 * Add a listener for transport negotiation events. 763 * 764 * @param li TODO javadoc me please 765 * The listener 766 */ 767 public void addTransportListener(JingleTransportListener li) { 768 for (ContentNegotiator contentNegotiator : contentNegotiators) { 769 if (contentNegotiator.getTransportNegotiator() != null) { 770 contentNegotiator.getTransportNegotiator().addListener(li); 771 } 772 } 773 } 774 775 /** 776 * Remove a listener for transport negotiation events. 777 * 778 * @param li TODO javadoc me please 779 * The listener 780 */ 781 public void removeTransportListener(JingleTransportListener li) { 782 for (ContentNegotiator contentNegotiator : contentNegotiators) { 783 if (contentNegotiator.getTransportNegotiator() != null) { 784 contentNegotiator.getTransportNegotiator().removeListener(li); 785 } 786 } 787 } 788 789 /** 790 * Setup the listeners that act on events coming from the lower level negotiators. 791 */ 792 793 public void setupListeners() { 794 795 JingleMediaListener jingleMediaListener = new JingleMediaListener() { 796 @Override 797 public void mediaClosed(PayloadType cand) { 798 } 799 800 @Override 801 public void mediaEstablished(PayloadType pt) throws NotConnectedException, InterruptedException { 802 if (isFullyEstablished()) { 803 Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT); 804 805 // Build up a response packet from each media manager. 806 for (ContentNegotiator contentNegotiator : contentNegotiators) { 807 if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) 808 jout.addContent(contentNegotiator.getJingleContent()); 809 } 810 // Send the "accept" and wait for the ACK 811 addExpectedId(jout.getStanzaId()); 812 sendStanza(jout); 813 814 // triggerSessionEstablished(); 815 816 } 817 } 818 }; 819 820 JingleTransportListener jingleTransportListener = new JingleTransportListener() { 821 822 @Override 823 public void transportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException { 824 if (isFullyEstablished()) { 825 // CHECKSTYLE:OFF 826 // Indicate that this session is active. 827 setSessionState(JingleSessionStateActive.getInstance()); 828 829 for (ContentNegotiator contentNegotiator : contentNegotiators) { 830 // CHECKSTYLE:ON 831 if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) 832 contentNegotiator.triggerContentEstablished(); 833 } 834 835 if (getSessionState().equals(JingleSessionStatePending.getInstance())) { 836 837 Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT); 838 839 // Build up a response packet from each media manager. 840 for (ContentNegotiator contentNegotiator : contentNegotiators) { 841 if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) 842 jout.addContent(contentNegotiator.getJingleContent()); 843 } 844 // Send the "accept" and wait for the ACK 845 addExpectedId(jout.getStanzaId()); 846 sendStanza(jout); 847 } 848 } 849 } 850 851 @Override 852 public void transportClosed(TransportCandidate cand) { 853 } 854 855 @Override 856 public void transportClosedOnError(XMPPException e) { 857 } 858 }; 859 860 addMediaListener(jingleMediaListener); 861 addTransportListener(jingleTransportListener); 862 } 863 864 // Triggers 865 866 /** 867 * Trigger a session closed event. 868 * 869 * @param reason the reason. 870 */ 871 void triggerSessionClosed(String reason) { 872 // for (ContentNegotiator contentNegotiator : contentNegotiators) { 873 // 874 // contentNegotiator.stopJingleMediaSession(); 875 // 876 // for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) 877 // candidate.removeCandidateEcho(); 878 // } 879 880 List<JingleListener> listeners = getListenersList(); 881 for (JingleListener li : listeners) { 882 if (li instanceof JingleSessionListener) { 883 JingleSessionListener sli = (JingleSessionListener) li; 884 sli.sessionClosed(reason, this); 885 } 886 } 887 close(); 888 } 889 890 /** 891 * Trigger a session closed event due to an error. 892 * 893 * @param exc the exception. 894 */ 895 void triggerSessionClosedOnError(XMPPException exc) { 896 for (ContentNegotiator contentNegotiator : contentNegotiators) { 897 898 contentNegotiator.stopJingleMediaSession(); 899 900 for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) 901 candidate.removeCandidateEcho(); 902 } 903 List<JingleListener> listeners = getListenersList(); 904 for (JingleListener li : listeners) { 905 if (li instanceof JingleSessionListener) { 906 JingleSessionListener sli = (JingleSessionListener) li; 907 sli.sessionClosedOnError(exc, this); 908 } 909 } 910 close(); 911 } 912 913 914 /** 915 * Trigger a media received event. 916 * 917 * @param participant the participant. 918 */ 919 void triggerMediaReceived(String participant) { 920 List<JingleListener> listeners = getListenersList(); 921 for (JingleListener li : listeners) { 922 if (li instanceof JingleSessionListener) { 923 JingleSessionListener sli = (JingleSessionListener) li; 924 sli.sessionMediaReceived(this, participant); 925 } 926 } 927 } 928 929 /** 930 * Terminates the session with default reason. 931 * 932 * @throws XMPPException if an XMPP protocol error was received. 933 * @throws NotConnectedException if the XMPP connection is not connected. 934 * @throws InterruptedException if the calling thread was interrupted. 935 */ 936 public void terminate() throws XMPPException, NotConnectedException, InterruptedException { 937 terminate("Closed Locally"); 938 } 939 940 /** 941 * Terminates the session with a custom reason. 942 * 943 * @param reason the reason. 944 * @throws XMPPException if an XMPP protocol error was received. 945 * @throws NotConnectedException if the XMPP connection is not connected. 946 * @throws InterruptedException if the calling thread was interrupted. 947 */ 948 public void terminate(String reason) throws XMPPException, NotConnectedException, InterruptedException { 949 if (isClosed()) 950 return; 951 LOGGER.fine("Terminate " + reason); 952 Jingle jout = new Jingle(JingleActionEnum.SESSION_TERMINATE); 953 jout.setType(IQ.Type.set); 954 sendStanza(jout); 955 triggerSessionClosed(reason); 956 } 957 958 /** 959 * Terminate negotiations. 960 */ 961 @Override 962 public void close() { 963 if (isClosed()) 964 return; 965 966 // Set the session state to ENDED. 967 setSessionState(JingleSessionStateEnded.getInstance()); 968 969 for (ContentNegotiator contentNegotiator : contentNegotiators) { 970 971 contentNegotiator.stopJingleMediaSession(); 972 973 for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates()) 974 candidate.removeCandidateEcho(); 975 976 contentNegotiator.close(); 977 } 978 removeAsyncPacketListener(); 979 removeConnectionListener(); 980 getConnection().removeConnectionListener(connectionListener); 981 LOGGER.fine("Negotiation Closed: " + getConnection().getUser() + " " + sid); 982 super.close(); 983 984 } 985 986 public boolean isClosed() { 987 return getSessionState().equals(JingleSessionStateEnded.getInstance()); 988 } 989 990 // Packet and error creation 991 992 /** 993 * Complete and send an error. Complete all the null fields in an IQ error 994 * response, using the session information we have or some info from the 995 * incoming packet. 996 * 997 * @param iq 998 * The Jingle stanza we are responding to 999 * @param jingleError 1000 * the IQ stanza we want to complete and send 1001 * @return the jingle error IQ. 1002 */ 1003 public IQ createJingleError(IQ iq, JingleError jingleError) { 1004 IQ errorPacket = null; 1005 if (jingleError != null) { 1006 // TODO This is wrong according to XEP-166 ยง 10, but this jingle implementation is deprecated anyways 1007 StanzaError builder = StanzaError.getBuilder() 1008 .setCondition(StanzaError.Condition.undefined_condition) 1009 .addExtension(jingleError) 1010 .build(); 1011 1012 errorPacket = IQ.createErrorResponse(iq, builder); 1013 1014 // errorPacket.addExtension(jingleError); 1015 1016 // NO! Let the normal state machinery do all of the sending. 1017 // getConnection().sendStanza(perror); 1018 LOGGER.severe("Error sent: " + errorPacket.toXML()); 1019 } 1020 return errorPacket; 1021 } 1022 1023 /** 1024 * Called when new Media is received. 1025 */ 1026 @Override 1027 public void mediaReceived(String participant) { 1028 triggerMediaReceived(participant); 1029 } 1030 1031 /** 1032 * This is the starting point for initiating a new session. 1033 * 1034 * @throws IllegalStateException if an illegal state was encountered 1035 * @throws SmackException if Smack detected an exceptional situation. 1036 * @throws InterruptedException if the calling thread was interrupted. 1037 */ 1038 public void startOutgoing() throws IllegalStateException, SmackException, InterruptedException { 1039 1040 updatePacketListener(); 1041 setSessionState(JingleSessionStatePending.getInstance()); 1042 1043 Jingle jingle = new Jingle(JingleActionEnum.SESSION_INITIATE); 1044 1045 // Create a content negotiator for each media manager on the session. 1046 for (JingleMediaManager mediaManager : getMediaManagers()) { 1047 ContentNegotiator contentNeg = new ContentNegotiator(this, ContentNegotiator.INITIATOR, mediaManager.getName()); 1048 1049 // Create the media negotiator for this content description. 1050 contentNeg.setMediaNegotiator(new MediaNegotiator(this, mediaManager, mediaManager.getPayloads(), contentNeg)); 1051 1052 JingleTransportManager transportManager = mediaManager.getTransportManager(); 1053 TransportResolver resolver = null; 1054 try { 1055 resolver = transportManager.getResolver(this); 1056 } catch (XMPPException e) { 1057 LOGGER.log(Level.WARNING, "exception", e); 1058 } 1059 1060 if (resolver.getType().equals(TransportResolver.Type.rawupd)) { 1061 contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(this, resolver, contentNeg)); 1062 } 1063 if (resolver.getType().equals(TransportResolver.Type.ice)) { 1064 contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(this, resolver, contentNeg)); 1065 } 1066 1067 addContentNegotiator(contentNeg); 1068 } 1069 1070 // Give each of the content negotiators a chance to return a portion of the structure to make the Jingle packet. 1071 for (ContentNegotiator contentNegotiator : contentNegotiators) { 1072 jingle.addContent(contentNegotiator.getJingleContent()); 1073 } 1074 1075 // Save the session-initiate packet ID, so that we can respond to it. 1076 sessionInitPacketID = jingle.getStanzaId(); 1077 1078 sendStanza(jingle); 1079 1080 // Now setup to track the media negotiators, so that we know when (if) to send a session-accept. 1081 setupListeners(); 1082 1083 // Give each of the content negotiators a chance to start 1084 // and return a portion of the structure to make the Jingle packet. 1085 1086// Don't do this anymore. The problem is that the other side might not be ready. 1087// Later when we receive our first jingle packet from the other side we'll fire-up the negotiators 1088// before processing it. (See receivePacketAndRespond() above. 1089// for (ContentNegotiator contentNegotiator : contentNegotiators) { 1090// contentNegotiator.start(); 1091// } 1092 } 1093 1094 /** 1095 * This is the starting point for responding to a new session. 1096 */ 1097 public void startIncoming() { 1098 1099 // updatePacketListener(); 1100 } 1101 1102 @Override 1103 protected void doStart() { 1104 1105 } 1106 1107 /** 1108 * When we initiate a session we need to start a bunch of negotiators right after we receive the result 1109 * stanza for our session-initiate. This is where we start them. 1110 * 1111 */ 1112 private void startNegotiators() { 1113 1114 for (ContentNegotiator contentNegotiator : contentNegotiators) { 1115 TransportNegotiator transNeg = contentNegotiator.getTransportNegotiator(); 1116 transNeg.start(); 1117 } 1118 } 1119 1120 /** 1121 * The jingle session may have one or more media managers that are trying to establish media sessions. 1122 * When the media manager succeeds in creating a media session is registers it with the session by the 1123 * media manager's static name. This routine is where the media manager does the registering. 1124 * 1125 * @param mediaManagerName the name of the media manager. 1126 * @param mediaSession the jingle media session. 1127 */ 1128 public void addJingleMediaSession(String mediaManagerName, JingleMediaSession mediaSession) { 1129 mediaSessionMap.put(mediaManagerName, mediaSession); 1130 } 1131 1132 /** 1133 * The jingle session may have one or more media managers that are trying to establish media sessions. 1134 * When the media manager succeeds in creating a media session is registers it with the session by the 1135 * media manager's static name. This routine is where other objects can access the registered media sessions. 1136 * NB: If the media manager has not succeeded in establishing a media session then this could return null. 1137 * 1138 * @param mediaManagerName the name of the media manager. 1139 * @return the jingle media session. 1140 */ 1141 public JingleMediaSession getMediaSession(String mediaManagerName) { 1142 return mediaSessionMap.get(mediaManagerName); 1143 } 1144}