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