mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-12-08 22:21:08 +01:00
Updated Jingle implementation. SMACK-240. Thanks to Jeff Williams.
git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@10419 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
parent
1cbfdcc7db
commit
646271abac
12 changed files with 1199 additions and 0 deletions
|
|
@ -0,0 +1,49 @@
|
|||
package org.jivesoftware.smackx.jingle;
|
||||
|
||||
/**
|
||||
* The "action" in the jingle packet, as an enum.
|
||||
*
|
||||
* Changed to reflect XEP-166 rev: 20JUN07
|
||||
*
|
||||
* @author Jeff Williams
|
||||
*/
|
||||
public enum JingleActionEnum {
|
||||
|
||||
UNKNOWN("unknown"),
|
||||
CONTENT_ACCEPT("content-accept"),
|
||||
CONTENT_ADD("content-add"),
|
||||
CONTENT_MODIFY("content-modify"),
|
||||
CONTENT_REMOVE("content-remove"),
|
||||
SESSION_ACCEPT("session-accept"),
|
||||
SESSION_INFO("session-info"),
|
||||
SESSION_INITIATE("session-initiate"),
|
||||
SESSION_TERMINATE("session-terminate"),
|
||||
TRANSPORT_INFO("transport-info");
|
||||
|
||||
private String actionCode;
|
||||
|
||||
private JingleActionEnum(String inActionCode) {
|
||||
actionCode = inActionCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the String value for an Action.
|
||||
*/
|
||||
|
||||
public String toString() {
|
||||
return actionCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Action enum for a String action value.
|
||||
*/
|
||||
public static JingleActionEnum getAction(String inActionCode) {
|
||||
for (JingleActionEnum jingleAction : JingleActionEnum.values()) {
|
||||
if (jingleAction.actionCode.equals(inActionCode)) {
|
||||
return jingleAction;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package org.jivesoftware.smackx.jingle;
|
||||
|
||||
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smackx.packet.JingleError;
|
||||
|
||||
/**
|
||||
* A Jingle exception.
|
||||
*
|
||||
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
|
||||
*/
|
||||
public class JingleException extends XMPPException {
|
||||
|
||||
private final JingleError error;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public JingleException() {
|
||||
super();
|
||||
error = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with an error message.
|
||||
*
|
||||
* @param msg The message.
|
||||
*/
|
||||
public JingleException(String msg) {
|
||||
super(msg);
|
||||
error = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with an error response.
|
||||
*
|
||||
* @param error The error message.
|
||||
*/
|
||||
public JingleException(JingleError error) {
|
||||
super();
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the error message.
|
||||
*
|
||||
* @return the error
|
||||
*/
|
||||
public JingleError getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package org.jivesoftware.smackx.jingle;
|
||||
|
||||
/**
|
||||
* @author Jeff Williams
|
||||
*/
|
||||
public enum JingleNegotiatorState {
|
||||
PENDING,
|
||||
FAILED,
|
||||
SUCCEEDED
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package org.jivesoftware.smackx.jingle;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smackx.packet.Jingle;
|
||||
|
||||
/**
|
||||
* Implement the Jingle Session state using the State Behavioral pattern.
|
||||
* (From the book Design Patterns, AKA GoF.)
|
||||
* These classes also employ the Flyweight and Singleton patterns as recommended for the State pattern by GoF.
|
||||
*
|
||||
* There seems to be three ways to go with the State pattern in Java: interface, abstract class and enums.
|
||||
* Most of the accepted models use abstract classes. It wasn't clear to me that any of the three models was
|
||||
* superior, so I went with the most common example.
|
||||
*
|
||||
* @author Jeff Williams
|
||||
*/
|
||||
public abstract class JingleSessionState {
|
||||
|
||||
/**
|
||||
* Called when entering the state.
|
||||
*/
|
||||
public static JingleSessionState getInstance() {
|
||||
// Since we can never instantiate this class there is nothing to return (ever).
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when entering the state.
|
||||
*/
|
||||
public abstract void enter();
|
||||
|
||||
/**
|
||||
* Called when exiting the state.
|
||||
*/
|
||||
public abstract void exit();
|
||||
|
||||
/**
|
||||
* Process an incoming Jingle Packet.
|
||||
* When you look at the GoF State pattern this method roughly corresponds to example on p310: ProcessOctect().
|
||||
*/
|
||||
public abstract IQ processJingle(JingleSession session, Jingle jingle, JingleActionEnum action);
|
||||
|
||||
/**
|
||||
* For debugging just emit the short name of the class.
|
||||
*/
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package org.jivesoftware.smackx.jingle;
|
||||
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smackx.packet.Jingle;
|
||||
import org.jivesoftware.smackx.packet.JingleError;
|
||||
|
||||
/**
|
||||
* @author Jeff Williams
|
||||
* @see JingleSessionState
|
||||
*/
|
||||
public class JingleSessionStateActive extends JingleSessionState {
|
||||
private static JingleSessionStateActive INSTANCE = null;
|
||||
|
||||
protected JingleSessionStateActive() {
|
||||
// Prevent instantiation of the class.
|
||||
}
|
||||
|
||||
/**
|
||||
* A thread-safe means of getting the one instance of this class.
|
||||
* @return The singleton instance of this class.
|
||||
*/
|
||||
public synchronized static JingleSessionState getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new JingleSessionStateActive();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void enter() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public IQ processJingle(JingleSession session, Jingle jingle, JingleActionEnum action) {
|
||||
IQ response = null;
|
||||
|
||||
switch (action) {
|
||||
|
||||
case CONTENT_ACCEPT:
|
||||
break;
|
||||
|
||||
case CONTENT_ADD:
|
||||
break;
|
||||
|
||||
case CONTENT_MODIFY:
|
||||
break;
|
||||
|
||||
case CONTENT_REMOVE:
|
||||
break;
|
||||
|
||||
case SESSION_INFO:
|
||||
break;
|
||||
|
||||
case SESSION_TERMINATE:
|
||||
receiveSessionTerminateAction(session, jingle);
|
||||
break;
|
||||
|
||||
case TRANSPORT_INFO:
|
||||
break;
|
||||
|
||||
default:
|
||||
// Anything other action is an error.
|
||||
response = session.createJingleError(jingle, JingleError.OUT_OF_ORDER);
|
||||
break;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and process the <session-terminate> action.
|
||||
*/
|
||||
private IQ receiveSessionTerminateAction(JingleSession session, Jingle jingle) {
|
||||
|
||||
// According to XEP-166 the only thing we can do is ack.
|
||||
IQ response = session.createAck(jingle);
|
||||
|
||||
try {
|
||||
session.terminate("Closed remotely");
|
||||
} catch (XMPPException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package org.jivesoftware.smackx.jingle;
|
||||
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smackx.packet.Jingle;
|
||||
import org.jivesoftware.smackx.packet.JingleError;
|
||||
|
||||
/**
|
||||
* @author Jeff Williams
|
||||
* @see JingleSessionState
|
||||
*/
|
||||
public class JingleSessionStateEnded extends JingleSessionState {
|
||||
private static JingleSessionStateEnded INSTANCE = null;
|
||||
|
||||
protected JingleSessionStateEnded() {
|
||||
// Prevent instantiation of the class.
|
||||
}
|
||||
|
||||
/**
|
||||
* A thread-safe means of getting the one instance of this class.
|
||||
* @return The singleton instance of this class.
|
||||
*/
|
||||
public synchronized static JingleSessionState getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new JingleSessionStateEnded();
|
||||
}
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void enter() {
|
||||
System.out.println("Session Ended");
|
||||
System.out.println("-------------------------------------------------------------------");
|
||||
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty much nothing is valid for receiving once we've ended the session.
|
||||
*/
|
||||
public IQ processJingle(JingleSession session, Jingle jingle, JingleActionEnum action) {
|
||||
IQ response = null;
|
||||
|
||||
response = session.createJingleError(jingle, JingleError.MALFORMED_STANZA);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package org.jivesoftware.smackx.jingle;
|
||||
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smackx.packet.Jingle;
|
||||
|
||||
/**
|
||||
* @author Jeff Williams
|
||||
* @see JingleSessionState
|
||||
*/
|
||||
|
||||
public class JingleSessionStatePending extends JingleSessionState {
|
||||
private static JingleSessionStatePending INSTANCE = null;
|
||||
|
||||
protected JingleSessionStatePending() {
|
||||
// Prevent instantiation of the class.
|
||||
}
|
||||
|
||||
/**
|
||||
* A thread-safe means of getting the one instance of this class.
|
||||
* @return The singleton instance of this class.
|
||||
*/
|
||||
public synchronized static JingleSessionState getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new JingleSessionStatePending();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void enter() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public IQ processJingle(JingleSession session, Jingle jingle, JingleActionEnum action) {
|
||||
IQ response = null;
|
||||
|
||||
switch (action) {
|
||||
|
||||
case CONTENT_ACCEPT:
|
||||
response = receiveContentAcceptAction(jingle);
|
||||
break;
|
||||
|
||||
case CONTENT_MODIFY:
|
||||
break;
|
||||
|
||||
case CONTENT_REMOVE:
|
||||
break;
|
||||
|
||||
case SESSION_ACCEPT:
|
||||
response = receiveSessionAcceptAction(session, jingle);
|
||||
break;
|
||||
|
||||
case SESSION_INFO:
|
||||
break;
|
||||
|
||||
case SESSION_TERMINATE:
|
||||
response = receiveSessionTerminateAction(session, jingle);
|
||||
break;
|
||||
|
||||
case TRANSPORT_INFO:
|
||||
break;
|
||||
|
||||
default:
|
||||
// Anything other action is an error.
|
||||
//response = createJingleError(inJingle, JingleError.OUT_OF_ORDER);
|
||||
break;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and process the <session-accept> action.
|
||||
*/
|
||||
private IQ receiveContentAcceptAction(Jingle inJingle) {
|
||||
|
||||
// According to XEP-167 the only thing we can do is ack.
|
||||
//setSessionState(JingleSessionStateEnum.ACTIVE);
|
||||
//return createAck(inJingle);
|
||||
|
||||
// This is now handled by the media negotiator for the matching <content> segment.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and process the <session-accept> action.
|
||||
*/
|
||||
private IQ receiveSessionAcceptAction(JingleSession session, Jingle inJingle) {
|
||||
|
||||
// According to XEP-166 the only thing we can do is ack.
|
||||
session.setSessionState(JingleSessionStateActive.getInstance());
|
||||
return session.createAck(inJingle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and process the <session-terminate> action.
|
||||
*/
|
||||
private IQ receiveSessionTerminateAction(JingleSession session, Jingle jingle) {
|
||||
|
||||
// According to XEP-166 the only thing we can do is ack.
|
||||
IQ response = session.createAck(jingle);
|
||||
|
||||
try {
|
||||
session.terminate("Closed remotely");
|
||||
} catch (XMPPException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
package org.jivesoftware.smackx.jingle;
|
||||
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.IQ;
|
||||
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
|
||||
import org.jivesoftware.smackx.jingle.media.MediaNegotiator;
|
||||
import org.jivesoftware.smackx.jingle.media.PayloadType;
|
||||
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
|
||||
import org.jivesoftware.smackx.jingle.nat.TransportNegotiator;
|
||||
import org.jivesoftware.smackx.jingle.nat.TransportResolver;
|
||||
import org.jivesoftware.smackx.packet.Jingle;
|
||||
import org.jivesoftware.smackx.packet.JingleContent;
|
||||
import org.jivesoftware.smackx.packet.JingleDescription;
|
||||
import org.jivesoftware.smackx.packet.JingleError;
|
||||
import org.jivesoftware.smackx.packet.JingleTransport;
|
||||
|
||||
/**
|
||||
* @author Jeff Williams
|
||||
* @see JingleSessionState
|
||||
*/
|
||||
public class JingleSessionStateUnknown extends JingleSessionState {
|
||||
private static JingleSessionStateUnknown INSTANCE = null;
|
||||
|
||||
protected JingleSessionStateUnknown() {
|
||||
// Prevent instantiation of the class.
|
||||
}
|
||||
|
||||
/**
|
||||
* A thread-safe means of getting the one instance of this class.
|
||||
* @return The singleton instance of this class.
|
||||
*/
|
||||
public synchronized static JingleSessionState getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new JingleSessionStateUnknown();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void enter() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public void exit() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public IQ processJingle(JingleSession session, Jingle jingle, JingleActionEnum action) {
|
||||
IQ response = null;
|
||||
|
||||
switch (action) {
|
||||
case SESSION_INITIATE:
|
||||
response = receiveSessionInitiateAction(session, jingle);
|
||||
break;
|
||||
|
||||
case SESSION_TERMINATE:
|
||||
response = receiveSessionTerminateAction(session, jingle);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Anything other than session-initiate is an error.
|
||||
response = session.createJingleError(jingle, JingleError.MALFORMED_STANZA);
|
||||
break;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* In the UNKNOWN state we received a <session-initiate> action.
|
||||
* This method processes that action.
|
||||
*/
|
||||
|
||||
private IQ receiveSessionInitiateAction(JingleSession session, Jingle inJingle) {
|
||||
|
||||
IQ response = null;
|
||||
boolean shouldAck = true;
|
||||
|
||||
// According to XEP-166 when we get a session-initiate we need to check for:
|
||||
// 1. Initiator unknown
|
||||
// 2. Receiver redirection
|
||||
// 3. Does not support Jingle
|
||||
// 4. Does not support any <description> formats
|
||||
// 5. Does not support any <transport> formats
|
||||
// If all of the above are OK then we send an IQ type = result to ACK the session-initiate.
|
||||
|
||||
// 1. Initiator unknown
|
||||
// TODO
|
||||
|
||||
// 2. Receiver redirection
|
||||
// TODO
|
||||
|
||||
// 3. Does not support Jingle
|
||||
// Handled by Smack's lower layer.
|
||||
|
||||
// 4. Does not support any <description> formats
|
||||
// TODO
|
||||
|
||||
// 5. Does not support any <transport> formats
|
||||
// TODO
|
||||
|
||||
if (!shouldAck) {
|
||||
|
||||
response = session.createJingleError(inJingle, JingleError.NEGOTIATION_ERROR);
|
||||
|
||||
} else {
|
||||
|
||||
// Create the Ack
|
||||
response = session.createAck(inJingle);
|
||||
|
||||
session.setSessionState(JingleSessionStatePending.getInstance());
|
||||
|
||||
// Now set up all of the initial content negotiators for the session.
|
||||
for (JingleContent jingleContent : inJingle.getContentsList()) {
|
||||
// First create the content negotiator for this <content> section.
|
||||
ContentNegotiator contentNeg = new ContentNegotiator(session, jingleContent.getCreator(), jingleContent
|
||||
.getName());
|
||||
|
||||
// Get the media negotiator that goes with the <description> of this content.
|
||||
JingleDescription jingleDescription = jingleContent.getDescription();
|
||||
|
||||
// Loop through each media manager looking for the ones that matches the incoming
|
||||
// session-initiate <content> choices.
|
||||
// (Set the first media manager as the default, so that in case things don't match we can still negotiate.)
|
||||
JingleMediaManager chosenMediaManager = session.getMediaManagers().get(0);
|
||||
for (JingleMediaManager mediaManager : session.getMediaManagers()) {
|
||||
boolean matches = true;
|
||||
for (PayloadType mediaPayloadType : mediaManager.getPayloads()) {
|
||||
for (PayloadType descPayloadType2 : jingleDescription.getPayloadTypesList()) {
|
||||
if (mediaPayloadType.getId() != descPayloadType2.getId()) {
|
||||
matches = false;
|
||||
}
|
||||
}
|
||||
if (matches) {
|
||||
chosenMediaManager = mediaManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the media negotiator for this content description.
|
||||
contentNeg.setMediaNegotiator(new MediaNegotiator(session, chosenMediaManager, jingleDescription
|
||||
.getPayloadTypesList(), contentNeg));
|
||||
|
||||
// For each transport type in this content, try to find the corresponding transport manager.
|
||||
// Then create a transport negotiator for that transport.
|
||||
for (JingleTransport jingleTransport : jingleContent.getJingleTransportsList()) {
|
||||
for (JingleMediaManager mediaManager : session.getMediaManagers()) {
|
||||
|
||||
JingleTransportManager transportManager = mediaManager.getTransportManager();
|
||||
TransportResolver resolver = null;
|
||||
try {
|
||||
resolver = transportManager.getResolver(session);
|
||||
} catch (XMPPException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (resolver.getType().equals(TransportResolver.Type.rawupd)) {
|
||||
contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(session, resolver, contentNeg));
|
||||
}
|
||||
if (resolver.getType().equals(TransportResolver.Type.ice)) {
|
||||
contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(session, resolver, contentNeg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the content negotiator to the session.
|
||||
session.addContentNegotiator(contentNeg);
|
||||
}
|
||||
|
||||
// Now setup to track the media negotiators, so that we know when (if) to send a session-accept.
|
||||
session.setupListeners();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive and process the <session-terminate> action.
|
||||
*/
|
||||
private IQ receiveSessionTerminateAction(JingleSession session, Jingle jingle) {
|
||||
|
||||
// According to XEP-166 the only thing we can do is ack.
|
||||
IQ response = session.createAck(jingle);
|
||||
|
||||
try {
|
||||
session.terminate("Closed remotely");
|
||||
} catch (XMPPException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue