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