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