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