1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-09-12 02:29:38 +02:00

Prefix subprojects with 'smack-'

instead of using the old baseName=smack appendix=project.name approach,
we are now going convention over configuration and renaming the
subprojects directories to the proper name.

Having a prefix is actually very helpful, because the resulting
libraries will be named like the subproject. And a core-4.0.0-rc1.jar is
not as explicit about what it actually *is* as a
smack-core-4.0.0-rc1.jar.

SMACK-265
This commit is contained in:
Florian Schmaus 2014-04-28 19:27:53 +02:00
parent b6fb1f3743
commit 91fd15ad86
758 changed files with 42 additions and 42 deletions

View file

@ -0,0 +1,176 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.demo;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.TCPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleManager;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.JingleSessionRequest;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.mediaimpl.jspeex.SpeexMediaManager;
import org.jivesoftware.smackx.jingle.mediaimpl.sshare.ScreenShareMediaManager;
import org.jivesoftware.smackx.jingle.nat.ICETransportManager;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
/**
* Jingle Demo Application. It register in a XMPP Server and let users place calls using a full JID and auto-receive calls.
* Parameters: Server User Pass.
*/
public class Demo extends JFrame {
private static final long serialVersionUID = -6584021277434403855L;
private JingleTransportManager transportManager = null;
private XMPPConnection xmppConnection = null;
private String server = null;
private String user = null;
private String pass = null;
private JingleManager jm = null;
private JingleSession incoming = null;
private JingleSession outgoing = null;
private JTextField jid;
public Demo(String server, String user, String pass) {
this.server = server;
this.user = user;
this.pass = pass;
if (user.equals("jeffw")) {
jid = new JTextField("eowyn" + "@" + server + "/Smack");
} else {
jid = new JTextField("jeffw" + "@" + server + "/Smack");
}
xmppConnection = new XMPPTCPConnection(server);
try {
xmppConnection.connect();
xmppConnection.login(user, pass);
initialize();
}
catch (XMPPException e) {
e.printStackTrace();
}
}
public void initialize() {
ICETransportManager icetm0 = new ICETransportManager(xmppConnection, "10.47.47.53", 3478);
List<JingleMediaManager> mediaManagers = new ArrayList<JingleMediaManager>();
//mediaManagers.add(new JmfMediaManager(icetm0));
mediaManagers.add(new SpeexMediaManager(icetm0));
mediaManagers.add(new ScreenShareMediaManager(icetm0));
jm = new JingleManager(xmppConnection, mediaManagers);
jm.addCreationListener(icetm0);
jm.addJingleSessionRequestListener(new JingleSessionRequestListener() {
public void sessionRequested(JingleSessionRequest request) {
// if (incoming != null)
// return;
try {
// Accept the call
incoming = request.accept();
// Start the call
incoming.startIncoming();
}
catch (XMPPException e) {
e.printStackTrace();
}
}
});
createGUI();
}
public void createGUI() {
JPanel jPanel = new JPanel();
jPanel.add(jid);
jPanel.add(new JButton(new AbstractAction("Call") {
private static final long serialVersionUID = 4308448034795312815L;
public void actionPerformed(ActionEvent e) {
if (outgoing != null) return;
try {
outgoing = jm.createOutgoingJingleSession(jid.getText());
outgoing.startOutgoing();
}
catch (XMPPException e1) {
e1.printStackTrace();
}
}
}));
jPanel.add(new JButton(new AbstractAction("Hangup") {
private static final long serialVersionUID = -4508007389146723587L;
public void actionPerformed(ActionEvent e) {
if (outgoing != null)
try {
outgoing.terminate();
}
catch (XMPPException e1) {
e1.printStackTrace();
}
finally {
outgoing = null;
}
if (incoming != null)
try {
incoming.terminate();
}
catch (XMPPException e1) {
e1.printStackTrace();
}
finally {
incoming = null;
}
}
}));
this.add(jPanel);
}
public static void main(String args[]) {
Demo demo = null;
if (args.length > 2) {
demo = new Demo(args[0], args[1], args[2]);
demo.pack();
demo.setVisible(true);
demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
}

View file

@ -0,0 +1,538 @@
package org.jivesoftware.smackx.jingle;
/**
* <p/>
* Copyright 2003-2006 Jive Software.
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.jivesoftware.smack.TCPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.test.SmackTestCase;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.mediaimpl.jmf.AudioChannel;
import org.jivesoftware.smackx.jingle.mediaimpl.jmf.JmfMediaManager;
import org.jivesoftware.smackx.jingle.mediaimpl.jspeex.SpeexMediaManager;
import org.jivesoftware.smackx.jingle.mediaimpl.multi.MultiMediaManager;
import org.jivesoftware.smackx.jingle.mediaimpl.sshare.ScreenShareMediaManager;
import org.jivesoftware.smackx.jingle.nat.BridgedTransportManager;
import org.jivesoftware.smackx.jingle.nat.ICETransportManager;
import org.jivesoftware.smackx.jingle.nat.STUNTransportManager;
import org.jivesoftware.smackx.jingle.packet.JingleError;
import javax.media.MediaLocator;
import javax.media.format.AudioFormat;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
/**
* Test the Jingle Media using the high level API
* </p>
*
* @author Thiago Camargo
*/
public class JingleMediaTest extends SmackTestCase {
public JingleMediaTest(final String name) {
super(name);
}
public void testCompleteJmf() {
XMPPTCPConnection x0 = getConnection(0);
XMPPTCPConnection x1 = getConnection(1);
for (int i = 0; i < 1; i++)
try {
ICETransportManager icetm0 = new ICETransportManager(x0, "jivesoftware.com", 3478);
ICETransportManager icetm1 = new ICETransportManager(x1, "jivesoftware.com", 3478);
JingleMediaManager jingleMediaManager0 = new JmfMediaManager(icetm0);
JingleMediaManager jingleMediaManager1 = new JmfMediaManager(icetm1);
List<JingleMediaManager> jml0 = new ArrayList<JingleMediaManager>();
List<JingleMediaManager> jml1 = new ArrayList<JingleMediaManager>();
jml0.add(jingleMediaManager0);
jml1.add(jingleMediaManager1);
final JingleManager jm0 = new JingleManager(x0, jml0);
final JingleManager jm1 = new JingleManager(x1, jml1);
jm0.addCreationListener(icetm0);
jm1.addCreationListener(icetm1);
JingleSessionRequestListener jingleSessionRequestListener = new JingleSessionRequestListener() {
public void sessionRequested(final JingleSessionRequest request) {
try {
JingleSession session = request.accept();
session.startIncoming();
// session.addStateListener(new JingleSessionStateListener() {
// public void beforeChange(JingleNegotiator.State old, JingleNegotiator.State newOne)
// throws JingleNegotiator.JingleException {
// if (newOne instanceof IncomingJingleSession.Active) {
// throw new JingleNegotiator.JingleException();
// }
// }
//
// public void afterChanged(JingleNegotiator.State old, JingleNegotiator.State newOne) {
//
// }
// });
} catch (XMPPException e) {
e.printStackTrace();
}
}
};
jm1.addJingleSessionRequestListener(jingleSessionRequestListener);
JingleSession js0 = jm0.createOutgoingJingleSession(x1.getUser());
js0.startOutgoing();
Thread.sleep(20000);
JingleSession incomingJingleSession = jm1.getSession(js0.getConnection().getUser());
//JingleSession.removeAllStateListeners();
Thread.sleep(15000);
js0.terminate();
jm1.removeJingleSessionRequestListener(jingleSessionRequestListener);
Thread.sleep(60000);
} catch (Exception e) {
e.printStackTrace();
}
}
public void testCompleteMulti() {
try {
XMPPTCPConnection x0 = getConnection(0);
XMPPTCPConnection x1 = getConnection(1);
ICETransportManager icetm0 = new ICETransportManager(x0, "jivesoftware.com", 3478);
ICETransportManager icetm1 = new ICETransportManager(x1, "jivesoftware.com", 3478);
MultiMediaManager jingleMediaManager0 = new MultiMediaManager(icetm0);
jingleMediaManager0.addMediaManager(new JmfMediaManager(icetm0));
jingleMediaManager0.addMediaManager(new SpeexMediaManager(icetm0));
jingleMediaManager0.setPreferredPayloadType(jingleMediaManager0.getPayloads().get(1));
List<JingleMediaManager> jml0 = new ArrayList<JingleMediaManager>();
jml0.add(jingleMediaManager0);
MultiMediaManager jingleMediaManager1 = new MultiMediaManager(icetm1);
jingleMediaManager1.addMediaManager(new JmfMediaManager(icetm1));
jingleMediaManager1.addMediaManager(new SpeexMediaManager(icetm1));
jingleMediaManager1.setPreferredPayloadType(jingleMediaManager1.getPayloads().get(2));
List<JingleMediaManager> jml1 = new ArrayList<JingleMediaManager>();
jml1.add(jingleMediaManager1);
final JingleManager jm0 = new JingleManager(x0, jml0);
final JingleManager jm1 = new JingleManager(x1, jml1);
jm0.addCreationListener(icetm0);
jm1.addCreationListener(icetm1);
jm1.addJingleSessionRequestListener(new JingleSessionRequestListener() {
public void sessionRequested(final JingleSessionRequest request) {
try {
JingleSession session = request.accept();
try {
Thread.sleep(12000);
} catch (InterruptedException e) {
e.printStackTrace();
}
session.startIncoming();
} catch (XMPPException e) {
e.printStackTrace();
}
}
});
for (int i = 0; i < 10; i++) {
JingleSession js0 = jm0.createOutgoingJingleSession(x1.getUser());
// js0.addStateListener(new JingleSessionStateListener() {
//
// public void beforeChange(JingleNegotiator.State old, JingleNegotiator.State newOne)
// throws JingleNegotiator.JingleException {
// }
//
// public void afterChanged(JingleNegotiator.State old, JingleNegotiator.State newOne) {
// if (newOne != null) {
// if ((newOne instanceof OutgoingJingleSession.Active))
// System.err.println("|||" + newOne.getClass().getCanonicalName() + "|||");
// }
// }
// });
js0.startOutgoing();
Thread.sleep(45000);
js0.terminate();
Thread.sleep(1500);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void testCompleteSpeex() {
try {
//TCPConnection.DEBUG_ENABLED = true;
XMPPTCPConnection x0 = getConnection(0);
XMPPTCPConnection x1 = getConnection(1);
JingleMediaManager jingleMediaManager0 = new SpeexMediaManager(new STUNTransportManager());
JingleMediaManager jingleMediaManager1 = new SpeexMediaManager(new STUNTransportManager());
List<JingleMediaManager> jml0 = new ArrayList<JingleMediaManager>();
List<JingleMediaManager> jml1 = new ArrayList<JingleMediaManager>();
jml0.add(jingleMediaManager0);
jml1.add(jingleMediaManager1);
final JingleManager jm0 = new JingleManager(x0, jml0);
final JingleManager jm1 = new JingleManager(x1, jml1);
jm1.addJingleSessionRequestListener(new JingleSessionRequestListener() {
public void sessionRequested(final JingleSessionRequest request) {
try {
JingleSession session = request.accept();
session.startIncoming();
} catch (XMPPException e) {
e.printStackTrace();
}
}
});
JingleSession js0 = jm0.createOutgoingJingleSession(x1.getUser());
js0.startOutgoing();
Thread.sleep(150000);
js0.terminate();
Thread.sleep(6000);
x0.disconnect();
x1.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
public void testCompleteScreenShare() {
try {
XMPPTCPConnection x0 = getConnection(0);
XMPPTCPConnection x1 = getConnection(1);
ICETransportManager icetm0 = new ICETransportManager(x0, "stun.xten.net", 3478);
ICETransportManager icetm1 = new ICETransportManager(x1, "stun.xten.net", 3478);
JingleMediaManager jingleMediaManager0 = new ScreenShareMediaManager(icetm0);
JingleMediaManager jingleMediaManager1 = new ScreenShareMediaManager(icetm1);
List<JingleMediaManager> jml0 = new ArrayList<JingleMediaManager>();
List<JingleMediaManager> jml1 = new ArrayList<JingleMediaManager>();
jml0.add(jingleMediaManager0);
jml1.add(jingleMediaManager1);
final JingleManager jm0 = new JingleManager(x0, jml0);
final JingleManager jm1 = new JingleManager(x1, jml1);
jm1.addJingleSessionRequestListener(new JingleSessionRequestListener() {
public void sessionRequested(final JingleSessionRequest request) {
try {
JingleSession session = request.accept();
session.startIncoming();
} catch (XMPPException e) {
e.printStackTrace();
}
}
});
JingleSession js0 = jm0.createOutgoingJingleSession(x1.getUser());
js0.startOutgoing();
Thread.sleep(150000);
js0.terminate();
Thread.sleep(6000);
x0.disconnect();
x1.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
public void testCompleteWithBridge() {
for (int i = 0; i < 1; i += 2) {
final int n = i;
Thread t = new Thread(new Runnable() {
public void run() {
try {
XMPPTCPConnection x0 = getConnection(n);
XMPPTCPConnection x1 = getConnection(n + 1);
BridgedTransportManager btm0 = new BridgedTransportManager(x0);
BridgedTransportManager btm1 = new BridgedTransportManager(x1);
JingleMediaManager jingleMediaManager0 = new JmfMediaManager(btm0);
JingleMediaManager jingleMediaManager1 = new JmfMediaManager(btm1);
List<JingleMediaManager> jml0 = new ArrayList<JingleMediaManager>();
List<JingleMediaManager> jml1 = new ArrayList<JingleMediaManager>();
jml0.add(jingleMediaManager0);
jml1.add(jingleMediaManager1);
final JingleManager jm0 = new JingleManager(x0, jml0);
final JingleManager jm1 = new JingleManager(x1, jml1);
jm0.addCreationListener(btm0);
jm1.addCreationListener(btm1);
jm1.addJingleSessionRequestListener(new JingleSessionRequestListener() {
public void sessionRequested(final JingleSessionRequest request) {
try {
JingleSession session = request.accept();
session.startIncoming();
} catch (XMPPException e) {
e.printStackTrace();
}
}
});
JingleSession js0 = jm0.createOutgoingJingleSession(x1.getUser());
js0.startOutgoing();
Thread.sleep(20000);
//js0.sendFormattedError(JingleError.UNSUPPORTED_TRANSPORTS);
js0.sendPacket(js0.createJingleError(null, JingleError.UNSUPPORTED_TRANSPORTS));
Thread.sleep(20000);
js0.terminate();
Thread.sleep(3000);
x0.disconnect();
x1.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
});
t.start();
}
try {
Thread.sleep(250000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void testCompleteWithBridgeB() {
try {
//TCPConnection.DEBUG_ENABLED = true;
XMPPTCPConnection x0 = getConnection(0);
XMPPTCPConnection x1 = getConnection(1);
BridgedTransportManager btm0 = new BridgedTransportManager(x0);
BridgedTransportManager btm1 = new BridgedTransportManager(x1);
JingleMediaManager jingleMediaManager0 = new JmfMediaManager(btm0);
JingleMediaManager jingleMediaManager1 = new JmfMediaManager(btm1);
List<JingleMediaManager> jml0 = new ArrayList<JingleMediaManager>();
List<JingleMediaManager> jml1 = new ArrayList<JingleMediaManager>();
jml0.add(jingleMediaManager0);
jml1.add(jingleMediaManager1);
final JingleManager jm0 = new JingleManager(x0, jml0);
final JingleManager jm1 = new JingleManager(x1, jml1);
jm1.addJingleSessionRequestListener(new JingleSessionRequestListener() {
public void sessionRequested(final JingleSessionRequest request) {
try {
JingleSession session = request.accept();
session.startIncoming();
} catch (XMPPException e) {
e.printStackTrace();
}
}
});
JingleSession js0 = jm0.createOutgoingJingleSession(x1.getUser());
js0.startOutgoing();
Thread.sleep(20000);
js0.terminate();
Thread.sleep(3000);
js0 = jm0.createOutgoingJingleSession(x1.getUser());
js0.startOutgoing();
Thread.sleep(20000);
js0.terminate();
Thread.sleep(3000);
x0.disconnect();
x1.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
public void testAudioChannelOpenClose() {
for (int i = 0; i < 5; i++) {
try {
AudioChannel audioChannel0 = new AudioChannel(new MediaLocator("javasound://"), InetAddress.getLocalHost()
.getHostAddress(), InetAddress.getLocalHost().getHostAddress(), 7002, 7020, new AudioFormat(
AudioFormat.GSM_RTP), null);
AudioChannel audioChannel1 = new AudioChannel(new MediaLocator("javasound://"), InetAddress.getLocalHost()
.getHostAddress(), InetAddress.getLocalHost().getHostAddress(), 7020, 7002, new AudioFormat(
AudioFormat.GSM_RTP), null);
audioChannel0.start();
audioChannel1.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
audioChannel0.stop();
audioChannel1.stop();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void testAudioChannelStartStop() {
try {
AudioChannel audioChannel0 = new AudioChannel(new MediaLocator("javasound://"), InetAddress.getLocalHost()
.getHostAddress(), InetAddress.getLocalHost().getHostAddress(), 7002, 7020,
new AudioFormat(AudioFormat.GSM_RTP), null);
AudioChannel audioChannel1 = new AudioChannel(new MediaLocator("javasound://"), InetAddress.getLocalHost()
.getHostAddress(), InetAddress.getLocalHost().getHostAddress(), 7020, 7002,
new AudioFormat(AudioFormat.GSM_RTP), null);
for (int i = 0; i < 5; i++) {
audioChannel0.start();
audioChannel1.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
audioChannel0.stop();
audioChannel1.stop();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
protected int getMaxConnections() {
return 2;
}
}

View file

@ -0,0 +1,84 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.test.SmackTestCase;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.mediaimpl.test.TestMediaManager;
import org.jivesoftware.smackx.jingle.nat.FixedResolver;
import org.jivesoftware.smackx.jingle.nat.FixedTransportManager;
import java.util.ArrayList;
import java.util.List;
public class JingleSessionTest extends SmackTestCase {
public JingleSessionTest(final String name) {
super(name);
}
public void testEqualsObject() {
FixedResolver tr1 = new FixedResolver("127.0.0.1", 54222);
FixedTransportManager ftm1 = new FixedTransportManager(tr1);
TestMediaManager tmm1 = new TestMediaManager(ftm1);
List<JingleMediaManager> trl1 = new ArrayList<JingleMediaManager>();
trl1.add(tmm1);
JingleSession js1 = new JingleSession(getConnection(0), "res1", null, "10", trl1);
JingleSession js2 = new JingleSession(getConnection(1), "res1", null, "10", trl1);
JingleSession js3 = new JingleSession(getConnection(2), "res2", null, "11", trl1);
System.out.println(js1.getSid());
System.out.println(js2.getSid());
js1.setInitiator("js1");
js2.setInitiator("js1");
js1.setSid("10");
js2.setSid("10");
assertEquals(js1, js2);
assertEquals(js2, js1);
assertFalse(js1.equals(js3));
}
public void testGetInstanceFor() {
String ini1 = "initiator1";
String sid1 = "sid1";
String ini2 = "initiator2";
String sid2 = "sid2";
FixedResolver tr1 = new FixedResolver("127.0.0.1", 54222);
FixedTransportManager ftm1 = new FixedTransportManager(tr1);
TestMediaManager tmm1 = new TestMediaManager(ftm1);
List<JingleMediaManager> trl1 = new ArrayList<JingleMediaManager>();
trl1.add(tmm1);
JingleSession js1 = new JingleSession(getConnection(0), ini1, null, sid1, trl1);
JingleSession js2 = new JingleSession(getConnection(1), ini2, null, sid2, trl1);
// For a packet, we should be able to get a session that handles that...
assertNotNull(JingleSession.getInstanceFor(getConnection(0)));
assertNotNull(JingleSession.getInstanceFor(getConnection(1)));
assertEquals(JingleSession.getInstanceFor(getConnection(0)), js1);
assertEquals(JingleSession.getInstanceFor(getConnection(1)), js2);
}
protected int getMaxConnections() {
return 3;
}
}

View file

@ -0,0 +1,37 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import junit.framework.Test;
import junit.framework.TestSuite;
/**
* Test suite that runs all the Jingle support tests
*
* @author Alvaro Saurin
*/
public class JingleSupportTests {
public static Test suite() {
TestSuite suite = new TestSuite("High and low level API tests for Jingle support");
// $JUnit-BEGIN$
suite.addTest(new TestSuite(JingleManagerTest.class));
// $JUnit-END$
return suite;
}
}

View file

@ -0,0 +1,104 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import java.util.ArrayList;
import org.jivesoftware.smack.test.SmackTestCase;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.media.PayloadType.Audio;
public class PayloadTypeTest extends SmackTestCase {
public PayloadTypeTest(final String arg0) {
super(arg0);
}
public void testEqualsObject() {
PayloadType p1 = new PayloadType(0, "pt1", 2);
PayloadType p2 = new PayloadType(0, "pt1", 2);
assertTrue(p1.equals(p2));
}
/**
* Test for the difference of payloads.
*/
public void testDifference() {
ArrayList<Audio> set1 = new ArrayList<Audio>();
ArrayList<Audio> set2 = new ArrayList<Audio>();
PayloadType.Audio common1 = new PayloadType.Audio(34, "supercodec-1", 2, 14000);
PayloadType.Audio common2 = new PayloadType.Audio(56, "supercodec-2", 1, 44000);
set1.add(common1);
set1.add(common2);
set1.add(new PayloadType.Audio(36, "supercodec-3", 2, 28000));
set1.add(new PayloadType.Audio(45, "supercodec-4", 1, 98000));
set2.add(new PayloadType.Audio(27, "supercodec-3", 2, 28000));
set2.add(common2);
set2.add(new PayloadType.Audio(32, "supercodec-4", 1, 98000));
set2.add(common1);
// Get the difference
ArrayList<Audio> commonSet = new ArrayList<Audio>();
commonSet.addAll(set1);
commonSet.retainAll(set2);
assertTrue(commonSet.size() == 2);
System.out.println("Codec " + ((PayloadType.Audio)commonSet.get(0)).getId());
System.out.println("Codec " + ((PayloadType.Audio)commonSet.get(1)).getId());
assertTrue(commonSet.contains(common1));
assertTrue(commonSet.contains(common2));
}
/**
* Test for the difference of payloads when we are handling the same sets.
*/
public void testDifferenceSameSet() {
ArrayList<Audio> set1 = new ArrayList<Audio>();
ArrayList<Audio> set2 = new ArrayList<Audio>();
PayloadType.Audio common1 = new PayloadType.Audio(34, "supercodec-1", 2, 14000);
PayloadType.Audio common2 = new PayloadType.Audio(56, "supercodec-2", 1, 44000);
PayloadType.Audio common3 = new PayloadType.Audio(0, "supercodec-3", 1, 44000);
PayloadType.Audio common4 = new PayloadType.Audio(120, "supercodec-4", 2, 66060);
set1.add(common1);
set1.add(common2);
set1.add(common3);
set1.add(common4);
set2.add(common1);
set2.add(common2);
set2.add(common3);
set2.add(common4);
// Get the difference
ArrayList<Audio> commonSet = new ArrayList<Audio>();
commonSet.addAll(set1);
commonSet.retainAll(set2);
assertTrue(commonSet.size() == 4);
assertTrue(commonSet.contains(common1));
assertTrue(commonSet.contains(common2));
}
protected int getMaxConnections() {
return 0;
}
}

View file

@ -0,0 +1,117 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.test.SmackTestCase;
import java.util.ArrayList;
public class BasicResolverTest extends SmackTestCase {
private int counter;
private final Object mutex = new Object();
public BasicResolverTest(String arg) {
super(arg);
}
// Counter management
private void resetCounter() {
synchronized (mutex) {
counter = 0;
}
}
private void incCounter() {
synchronized (mutex) {
counter++;
}
}
private int valCounter() {
int val;
synchronized (mutex) {
val = counter;
}
return val;
}
public void testCheckValidHostname() {
String validHostname = new String("slashdot.org");
BasicResolver br = new BasicResolver();
TransportCandidate tc = new TransportCandidate.Fixed(validHostname, 0);
resetCounter();
tc.addListener(new TransportResolverListener.Checker() {
public void candidateChecked(TransportCandidate cand, boolean result) {
if(result == true) {
System.out.println(cand.getIp() + " is reachable (as expected)");
incCounter();
}
}
public void candidateChecking(TransportCandidate cand) {
}
});
tc.check(new ArrayList<TransportCandidate>());
try {
Thread.sleep(TransportResolver.CHECK_TIMEOUT);
} catch (Exception e) {
}
assertTrue(valCounter() > 0);
}
public void testCheckInvalidHostname() {
String invalidHostname = new String("camupilosupino.org");
BasicResolver br = new BasicResolver();
TransportCandidate tc = new TransportCandidate.Fixed(invalidHostname, 0);
resetCounter();
tc.addListener(new TransportResolverListener.Checker() {
public void candidateChecked(TransportCandidate cand, boolean result) {
if(result == false) {
System.out.println(cand.getIp() + " is _not_ reachable (as expected)");
incCounter();
}
}
public void candidateChecking(TransportCandidate cand) {
}
});
tc.check(new ArrayList<TransportCandidate>());
try {
Thread.sleep(TransportResolver.CHECK_TIMEOUT);
} catch (Exception e) {
}
assertTrue(valCounter() > 0);
}
protected int getMaxConnections() {
return 0;
}
}

View file

@ -0,0 +1,119 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.test.SmackTestCase;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class BridgedResolverTest extends SmackTestCase {
private int counter;
private final Object mutex = new Object();
public BridgedResolverTest(String arg) {
super(arg);
}
// Counter management
private void resetCounter() {
synchronized (mutex) {
counter = 0;
}
}
private void incCounter() {
synchronized (mutex) {
counter++;
}
}
private int valCounter() {
int val;
synchronized (mutex) {
val = counter;
}
return val;
}
public void testCheckService() {
assertTrue(RTPBridge.serviceAvailable(getConnection(0)));
}
public void testGetBridge() {
resetCounter();
RTPBridge rtpBridge = RTPBridge.getRTPBridge(getConnection(0), "001");
System.out.println(rtpBridge.getIp() + " portA:" + rtpBridge.getPortA() + " portB:" + rtpBridge.getPortB());
if (rtpBridge != null) {
if (rtpBridge.getIp() != null) incCounter();
if (rtpBridge.getPortA() != -1) incCounter();
if (rtpBridge.getPortB() != -1) incCounter();
}
assertTrue(valCounter() == 3);
}
public void testGetPublicIp() {
resetCounter();
String publicIp = RTPBridge.getPublicIP(getConnection(0));
System.out.println(publicIp);
if (publicIp != null) {
incCounter();
}
try {
InetAddress localaddr = InetAddress.getLocalHost();
System.out.println("main Local IP Address : " + localaddr.getHostAddress());
System.out.println("main Local hostname : " + localaddr.getHostName());
InetAddress[] localaddrs = InetAddress.getAllByName("localhost");
for (int i = 0; i < localaddrs.length; i++) {
if (!localaddrs[i].equals(localaddr)) {
System.out.println("alt Local IP Address : " + localaddrs[i].getHostAddress());
System.out.println("alt Local hostname : " + localaddrs[i].getHostName());
System.out.println();
}
}
}
catch (UnknownHostException e) {
System.err.println("Can't detect localhost : " + e);
}
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
assertTrue(valCounter() == 1);
}
protected int getMaxConnections() {
return 1;
}
}

View file

@ -0,0 +1,25 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import junit.framework.TestCase;
public class LocalhostTest extends TestCase {
public void testGetLocalhost() {
System.out.println(BridgedResolver.getLocalHost());
}
}

View file

@ -0,0 +1,406 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import de.javawi.jstun.test.demo.StunServer;
import de.javawi.jstun.test.demo.ice.Candidate;
import de.javawi.jstun.test.demo.ice.ICENegociator;
import de.javawi.jstun.util.UtilityException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.test.SmackTestCase;
import org.jivesoftware.smackx.jingle.JingleManager;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.JingleSessionRequest;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.mediaimpl.test.TestMediaManager;
import org.jivesoftware.smackx.jingle.nat.STUNResolver.STUNService;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* Test the STUN IP resolver.
*
* @author Thiago Camargo
*/
public class STUNResolverTest extends SmackTestCase {
// Counter management
public STUNResolverTest(final String arg) {
super(arg);
}
private int counter;
private final Object mutex = new Object();
private void resetCounter() {
synchronized (mutex) {
counter = 0;
}
}
private void incCounter() {
synchronized (mutex) {
counter++;
}
}
private int valCounter() {
int val;
synchronized (mutex) {
val = counter;
}
return val;
}
/**
* Test for getPreferredCandidate()
*
* @throws Exception
*/
public void testGetPreferredCandidate() throws Exception {
int highestPref = 100;
TransportCandidate cand1 = new ICECandidate("192.168.2.1", 3, 2, "password", 3468, "username1", 1, ICECandidate.Type.prflx);
TransportCandidate cand2 = new ICECandidate("192.168.5.1", 2, 10, "password", 3469, "username2", 15,
ICECandidate.Type.prflx);
TransportCandidate candH = new ICECandidate("192.168.2.11", 1, 2, "password", 3468, "usernameH", highestPref,
ICECandidate.Type.prflx);
TransportCandidate cand3 = new ICECandidate("192.168.2.10", 2, 10, "password", 3469, "username3", 2,
ICECandidate.Type.prflx);
TransportCandidate cand4 = new ICECandidate("192.168.4.1", 3, 2, "password", 3468, "username4", 78, ICECandidate.Type.prflx);
STUNResolver stunResolver = new STUNResolver() {
};
stunResolver.addCandidate(cand1);
stunResolver.addCandidate(cand2);
stunResolver.addCandidate(candH);
stunResolver.addCandidate(cand3);
stunResolver.addCandidate(cand4);
assertEquals(stunResolver.getPreferredCandidate(), candH);
}
/**
* Test for getPreferredCandidate()
*
* @throws Exception
*/
public void testGetPreferredCandidateICE() throws Exception {
int highestPref = 100;
TransportCandidate cand1 = new ICECandidate("192.168.2.1", 3, 2, "password", 3468, "username1", 1, ICECandidate.Type.prflx);
TransportCandidate cand2 = new ICECandidate("192.168.5.1", 2, 10, "password", 3469, "username2", 15,
ICECandidate.Type.prflx);
TransportCandidate candH = new ICECandidate("192.168.2.11", 1, 2, "password", 3468, "usernameH", highestPref,
ICECandidate.Type.prflx);
TransportCandidate cand3 = new ICECandidate("192.168.2.10", 2, 10, "password", 3469, "username3", 2,
ICECandidate.Type.prflx);
TransportCandidate cand4 = new ICECandidate("192.168.4.1", 3, 2, "password", 3468, "username4", 78, ICECandidate.Type.prflx);
ICEResolver iceResolver = new ICEResolver(getConnection(0), "stun.xten.net", 3478) {
};
iceResolver.addCandidate(cand1);
iceResolver.addCandidate(cand2);
iceResolver.addCandidate(candH);
iceResolver.addCandidate(cand3);
iceResolver.addCandidate(cand4);
assertEquals(iceResolver.getPreferredCandidate(), candH);
}
/**
* Test priority generated by STUN lib
*
* @throws Exception
*/
public void testICEPriority() throws Exception {
String first = "";
for (int i = 0; i < 100; i++) {
ICENegociator cc = new ICENegociator((short) 1);
// gather candidates
cc.gatherCandidateAddresses();
// priorize candidates
cc.prioritizeCandidates();
// get SortedCandidates
for (Candidate candidate : cc.getSortedCandidates()) {
short nicNum = 0;
try {
Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
short tempNic = 0;
NetworkInterface nic = NetworkInterface.getByInetAddress(candidate.getAddress().getInetAddress());
while(nics.hasMoreElements()) {
NetworkInterface checkNIC = nics.nextElement();
if (checkNIC.equals(nic)) {
nicNum = tempNic;
break;
}
i++;
}
} catch (SocketException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
TransportCandidate transportCandidate = new ICECandidate(candidate.getAddress().getInetAddress()
.getHostAddress(), 1, nicNum, "1", candidate.getPort(), "1", candidate.getPriority(),
ICECandidate.Type.prflx);
transportCandidate.setLocalIp(candidate.getBase().getAddress().getInetAddress().getHostAddress());
System.out.println("C: " + candidate.getAddress().getInetAddress() + "|"
+ candidate.getBase().getAddress().getInetAddress() + " p:" + candidate.getPriority());
} catch (UtilityException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
Candidate candidate = cc.getSortedCandidates().get(0);
String temp = "C: " + candidate.getAddress().getInetAddress() + "|" + candidate.getBase().getAddress().getInetAddress()
+ " p:" + candidate.getPriority();
if (first.equals(""))
first = temp;
assertEquals(first, temp);
first = temp;
}
}
/**
* Test for loadSTUNServers()
*
* @throws Exception
*/
public void testLoadSTUNServers() throws Exception {
STUNResolver stunResolver = new STUNResolver() {
};
ArrayList<STUNService> stunServers = stunResolver.loadSTUNServers();
assertTrue(stunServers.size() > 0);
System.out.println(stunServers.size() + " servers loaded");
}
public void testGetSTUNServer() {
System.out.println(STUN.serviceAvailable(getConnection(0)));
STUN stun = STUN.getSTUNServer(getConnection(0));
for (STUN.StunServerAddress stunServerAddress : stun.getServers())
System.out.println(stunServerAddress.getServer() + ":" + stunServerAddress.getPort());
System.out.println(stun.getPublicIp());
}
/**
* Test for resolve()
*
* @throws Exception
*/
public void testResolve() throws Exception {
final STUNResolver stunResolver = new STUNResolver() {
};
stunResolver.addListener(new TransportResolverListener.Resolver() {
public void candidateAdded(final TransportCandidate cand) {
incCounter();
String addr = cand.getIp();
int port = cand.getPort();
System.out.println("Addr: " + addr + " port:" + port);
}
public void init() {
System.out.println("Resolution started");
}
public void end() {
System.out.println("Resolution finished");
}
});
try {
stunResolver.initializeAndWait();
Thread.sleep(55000);
assertTrue(valCounter() > 0);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Generate a list of payload types
*
* @return A testing list
*/
private ArrayList<PayloadType> getTestPayloads1() {
ArrayList<PayloadType> result = new ArrayList<PayloadType>();
result.add(new PayloadType.Audio(34, "supercodec-1", 2, 14000));
result.add(new PayloadType.Audio(56, "supercodec-2", 1, 44000));
result.add(new PayloadType.Audio(36, "supercodec-3", 2, 28000));
result.add(new PayloadType.Audio(45, "supercodec-4", 1, 98000));
return result;
}
private ArrayList<PayloadType> getTestPayloads2() {
ArrayList<PayloadType> result = new ArrayList<PayloadType>();
result.add(new PayloadType.Audio(27, "supercodec-3", 2, 28000));
result.add(new PayloadType.Audio(56, "supercodec-2", 1, 44000));
result.add(new PayloadType.Audio(32, "supercodec-4", 1, 98000));
result.add(new PayloadType.Audio(34, "supercodec-1", 2, 14000));
return result;
}
/**
* This is a simple test where the user_2 rejects the Jingle session.
*/
public void testSTUNJingleSession() {
resetCounter();
try {
TransportResolver tr1 = new STUNResolver() {
};
TransportResolver tr2 = new STUNResolver() {
};
// Explicit resolution
tr1.resolve(null);
tr2.resolve(null);
STUNTransportManager stm0 = new STUNTransportManager();
TestMediaManager tmm0 = new TestMediaManager(stm0);
tmm0.setPayloads(getTestPayloads1());
List<JingleMediaManager> trl0 = new ArrayList<JingleMediaManager>();
trl0.add(tmm0);
STUNTransportManager stm1 = new STUNTransportManager();
TestMediaManager tmm1 = new TestMediaManager(stm1);
tmm1.setPayloads(getTestPayloads2());
List<JingleMediaManager> trl1 = new ArrayList<JingleMediaManager>();
trl1.add(tmm1);
final JingleManager man0 = new JingleManager(getConnection(0), trl0);
final JingleManager man1 = new JingleManager(getConnection(1), trl1);
man1.addJingleSessionRequestListener(new JingleSessionRequestListener() {
/**
* Called when a new session request is detected
*/
public void sessionRequested(final JingleSessionRequest request) {
System.out.println("Session request detected, from " + request.getFrom() + ": accepting.");
// We accept the request
JingleSession session1;
try {
session1 = request.accept();
session1.addListener(new JingleSessionListener() {
public void sessionClosed(String reason, JingleSession jingleSession) {
}
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession) {
}
public void sessionDeclined(String reason, JingleSession jingleSession) {
}
public void sessionEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc,
JingleSession jingleSession) {
incCounter();
System.out.println("Responder: the session is fully established.");
System.out.println("+ Payload Type: " + pt.getId());
System.out.println("+ Local IP/port: " + lc.getIp() + ":" + lc.getPort());
System.out.println("+ Remote IP/port: " + rc.getIp() + ":" + rc.getPort());
}
public void sessionRedirected(String redirection, JingleSession jingleSession) {
}
public void sessionMediaReceived(JingleSession jingleSession, String participant) {
// Do Nothing
}
});
session1.startIncoming();
} catch (XMPPException e) {
e.printStackTrace();
}
}
});
// Session 0 starts a request
System.out.println("Starting session request, to " + getFullJID(1) + "...");
JingleSession session0 = man0.createOutgoingJingleSession(getFullJID(1));
session0.addListener(new JingleSessionListener() {
public void sessionClosed(String reason, JingleSession jingleSession) {
}
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession) {
}
public void sessionDeclined(String reason, JingleSession jingleSession) {
}
public void sessionEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc,
JingleSession jingleSession) {
incCounter();
System.out.println("Initiator: the session is fully established.");
System.out.println("+ Payload Type: " + pt.getId());
System.out.println("+ Local IP/port: " + lc.getIp() + ":" + lc.getPort());
System.out.println("+ Remote IP/port: " + rc.getIp() + ":" + rc.getPort());
}
public void sessionMediaReceived(JingleSession jingleSession, String participant) {
// Do Nothing
}
public void sessionRedirected(String redirection, JingleSession jingleSession) {
}
});
session0.startOutgoing();
Thread.sleep(60000);
assertTrue(valCounter() == 2);
} catch (Exception e) {
e.printStackTrace();
fail("An error occured with Jingle");
}
}
protected int getMaxConnections() {
return 2;
}
}

View file

@ -0,0 +1,74 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.util.ArrayList;
import java.util.Collections;
import org.jivesoftware.smack.test.SmackTestCase;
public class TransportCandidateTest extends SmackTestCase {
public TransportCandidateTest(final String arg0) {
super(arg0);
}
/**
* Test for equals()
*/
public void testEqualsObject() {
TransportCandidate cand1 = new ICECandidate("192.168.2.1", 1, 2,
"password", 3468, "username", 25, ICECandidate.Type.prflx);
TransportCandidate cand2 = new ICECandidate("192.168.2.1", 1, 2,
"password", 3468, "username", 25, ICECandidate.Type.prflx);
TransportCandidate cand3 = new ICECandidate("192.168.2.1", 1, 2,
"password", 3469, "username", 25, ICECandidate.Type.prflx);
assertEquals(cand1, cand2);
assertFalse(cand1.equals(cand3));
}
/**
* Test for compareTo()
*/
public void testCompareTo() {
int highestPref = 100;
ICECandidate cand1 = new ICECandidate("192.168.2.1", 3, 2,
"password", 3468, "username", 1, ICECandidate.Type.prflx);
ICECandidate cand2 = new ICECandidate("192.168.5.1", 2, 10,
"password", 3469, "username", 15,ICECandidate.Type.prflx);
ICECandidate candH = new ICECandidate("192.168.2.1", 1, 2,
"password", 3468, "username", highestPref, ICECandidate.Type.prflx);
ICECandidate cand3 = new ICECandidate("192.168.2.10", 2, 10,
"password", 3469, "username", 2, ICECandidate.Type.prflx);
ICECandidate cand4 = new ICECandidate("192.168.4.1", 3, 2,
"password", 3468, "username", 78, ICECandidate.Type.prflx);
ArrayList<ICECandidate> candList = new ArrayList<ICECandidate>();
candList.add(cand1);
candList.add(cand2);
candList.add(candH);
candList.add(cand3);
candList.add(cand4);
Collections.sort(candList);
assertEquals(candList.get(candList.size() - 1), candH);
}
protected int getMaxConnections() {
return 0;
}
}

View file

@ -0,0 +1,64 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.test.SmackTestCase;
public class TransportResolverTest extends SmackTestCase {
public TransportResolverTest(final String arg) {
super(arg);
}
public void testIsResolving() {
final TransportResolver tr = new BasicResolver();
tr.addListener(
new TransportResolverListener.Resolver() {
public void candidateAdded(final TransportCandidate cand) {
System.out.println("candidateAdded() called.");
assertTrue(tr.isResolving() || (!tr.isResolving() && tr.isResolved()));
}
public void end() {
System.out.println("end() called.");
assertFalse(tr.isResolving());
assertTrue(tr.isResolved());
}
public void init() {
System.out.println("init() called.");
assertTrue(tr.isResolving());
assertFalse(tr.isResolved());
}
});
assertFalse(tr.isResolving());
assertFalse(tr.isResolved());
try {
tr.resolve(null);
} catch (XMPPException e) {
e.printStackTrace();
fail("Error resolving");
}
}
protected int getMaxConnections() {
return 0;
}
}

View file

@ -0,0 +1,126 @@
/**
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.test.SmackTestCase;
import org.jivesoftware.smackx.jingle.packet.Jingle;
public class JingleProviderTest extends SmackTestCase {
public JingleProviderTest(final String name) {
super(name);
}
public void testProviderManager() {
IQProvider iqProv;
String elementNamee = Jingle.getElementName();
String nameSpace = Jingle.getNamespace();
System.out.println("Testing if the Jingle IQ provider is registered...");
// Verify that the Jingle IQProvider is registered.
iqProv = (IQProvider)ProviderManager.getInstance().getIQProvider(elementNamee, nameSpace);
assertNotNull(iqProv);
}
/**
* Test for parsing a Jingle
*/
public void testParseIQSimple() {
// Create a dummy packet for testing...
IQfake iqSent = new IQfake (
" <jingle xmlns='urn:xmpp:tmp:jingle'" +
" initiator=\"gorrino@viejo.com\"" +
" responder=\"colico@hepatico.com\"" +
" action=\"transport-info\" sid=\"\">" +
" <transport xmlns='urn:xmpp:tmp:jingle:transports:ice-udp'>" +
" <candidate generation=\"1\"" +
" ip=\"192.168.1.1\"" +
" password=\"secret\"" +
" port=\"8080\"" +
" username=\"username\"" +
" preference=\"1\"/>" +
" </transport>" +
"</jingle>");
iqSent.setTo(getFullJID(0));
iqSent.setFrom(getFullJID(0));
iqSent.setType(IQ.Type.GET);
// Create a filter and a collector...
PacketFilter filter = new PacketTypeFilter(IQ.class);
PacketCollector collector = getConnection(0).createPacketCollector(filter);
System.out.println("Testing if a Jingle IQ can be sent and received...");
// Send the iq packet with an invalid namespace
getConnection(0).sendPacket(iqSent);
// Receive the packet
IQ iqReceived = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
// Stop queuing results
collector.cancel();
if (iqReceived == null) {
fail("No response from server");
}
else if (iqReceived.getType() == IQ.Type.ERROR) {
fail("The server did reply with an error packet: " + iqReceived.getError().getCode());
}
else {
assertTrue(iqReceived instanceof Jingle);
Jingle jin = (Jingle) iqReceived;
System.out.println("Sent: " + iqSent.toXML());
System.out.println("Received: " + jin.toXML());
}
}
/**
* Simple class for testing an IQ...
* @author Alvaro Saurin
*/
private class IQfake extends IQ {
private String s;
public IQfake(final String s) {
super();
this.s = s;
}
public String getChildElementXML() {
StringBuilder buf = new StringBuilder();
buf.append(s);
return buf.toString();
}
}
protected int getMaxConnections() {
return 2;
}
}

View file

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- Default configuration for Smack test cases. -->
<testcase>
<!-- Host and port of the XMPP server to use -->
<host>localhost</host>
<port>5222</port>
<!-- Chat and MUC domain names to use -->
<chat>chat</chat>
<muc>conference</muc>
</testcase>

View file

@ -0,0 +1,341 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
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.TransportCandidate;
import org.jivesoftware.smackx.jingle.nat.TransportNegotiator;
import org.jivesoftware.smackx.jingle.packet.Jingle;
import org.jivesoftware.smackx.jingle.packet.JingleContent;
/**
* @author Jeff Williams
*/
public class ContentNegotiator extends JingleNegotiator {
public static final String INITIATOR = "initiator";
public static final String RESPONDER = "responder";
private List<TransportNegotiator> transportNegotiators;
private MediaNegotiator mediaNeg; // The description...
private TransportNegotiator transNeg; // and transport negotiators
private JingleTransportManager jingleTransportManager;
private String creator;
private String name;
private JingleMediaSession jingleMediaSession = null;
public ContentNegotiator(JingleSession session, String inCreator, String inName) {
super(session);
creator = inCreator;
name = inName;
transportNegotiators = new ArrayList<TransportNegotiator>();
}
public List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException {
List<IQ> responses = new ArrayList<IQ>();
// First only process IQ packets that contain <content> stanzas that
// match this media manager.
if (iq != null) {
if (iq.getType().equals(IQ.Type.ERROR)) {
// Process errors
// TODO getState().eventError(iq);
} else if (iq.getType().equals(IQ.Type.RESULT)) {
// Process ACKs
if (isExpectedId(iq.getPacketID())) {
removeExpectedId(iq.getPacketID());
}
} else if (iq instanceof Jingle) {
Jingle jingle = (Jingle) iq;
// There are 1 or more <content> sections in a Jingle packet.
// Find out which <content> section belongs to this content negotiator, and
// then dispatch the Jingle packet to the media and transport negotiators.
for (JingleContent jingleContent : jingle.getContentsList()) {
if (jingleContent.getName().equals(name)) {
if (mediaNeg != null) {
responses.addAll(mediaNeg.dispatchIncomingPacket(iq, id));
}
if (transNeg != null) {
responses.addAll(transNeg.dispatchIncomingPacket(iq, id));
}
}
}
}
}
return responses;
}
public String getCreator() {
return creator;
}
public String getName() {
return name;
}
/**
* Get the JingleMediaSession of this Jingle Session
*
* @return the JingleMediaSession
*/
public JingleMediaSession getJingleMediaSession() {
return jingleMediaSession;
}
public void addTransportNegotiator(TransportNegotiator transportNegotiator) {
transportNegotiators.add(transportNegotiator);
}
/**
* @param jingleTransportManager
*/
public void setJingleTransportManager(JingleTransportManager jingleTransportManager) {
this.jingleTransportManager = jingleTransportManager;
}
/**
* @return the JingleTransportManager
*/
public JingleTransportManager getTransportManager() {
return jingleTransportManager;
}
/**
* Called from above when starting a new session.
*/
protected void doStart() {
// JingleContent result = new JingleContent(creator, name);
// result.setDescription(mediaNeg.start());
// result.addJingleTransport(transNeg.start());
//
// return result;
mediaNeg.start();
transNeg.start();
}
/**
* Prepare to close the media manager.
*/
public void close() {
destroyMediaNegotiator();
destroyTransportNegotiator();
}
/**
* Obtain the description negotiator for this session
*
* @return the description negotiator
*/
public MediaNegotiator getMediaNegotiator() {
return mediaNeg;
}
/**
* Set the jmf negotiator.
*
* @param mediaNeg
* the description negotiator to set
*/
protected void setMediaNegotiator(MediaNegotiator mediaNeg) {
destroyMediaNegotiator();
this.mediaNeg = mediaNeg;
}
/**
* Destroy the jmf negotiator.
*/
protected void destroyMediaNegotiator() {
if (mediaNeg != null) {
mediaNeg.close();
mediaNeg = null;
}
}
/**
* Obtain the transport negotiator for this session.
*
* @return the transport negotiator instance
*/
public TransportNegotiator getTransportNegotiator() {
return transNeg;
}
/**
* Set TransportNegociator
*
* @param transNeg
* the transNeg to set
*/
protected void setTransportNegotiator(TransportNegotiator transNeg) {
destroyTransportNegotiator();
this.transNeg = transNeg;
}
/**
* Destroy the transport negotiator.
*/
protected void destroyTransportNegotiator() {
if (transNeg != null) {
transNeg.close();
transNeg = null;
}
}
/**
* Return true if the transport and content negotiators have finished
*/
public boolean isFullyEstablished() {
boolean result = true;
MediaNegotiator mediaNeg = getMediaNegotiator();
if ((mediaNeg == null) || (!mediaNeg.isFullyEstablished())) {
result = false;
}
TransportNegotiator transNeg = getTransportNegotiator();
if ((transNeg == null) || (!transNeg.isFullyEstablished())) {
result = false;
}
return result;
}
public JingleContent getJingleContent() {
JingleContent result = new JingleContent(creator, name);
// PayloadType.Audio bestCommonAudioPt = getMediaNegotiator().getBestCommonAudioPt();
// TransportCandidate bestRemoteCandidate = getTransportNegotiator().getBestRemoteCandidate();
//
// // Ok, send a packet saying that we accept this session
// // with the audio payload type and the transport
// // candidate
// result.setDescription(new JingleDescription.Audio(new PayloadType(bestCommonAudioPt)));
// result.addJingleTransport(this.getTransportNegotiator().getJingleTransport(bestRemoteCandidate));
if (mediaNeg != null) {
result.setDescription(mediaNeg.getJingleDescription());
}
if (transNeg != null) {
result.addJingleTransport(transNeg.getJingleTransport());
}
return result;
}
public void triggerContentEstablished() throws NotConnectedException {
PayloadType bestCommonAudioPt = getMediaNegotiator().getBestCommonAudioPt();
TransportCandidate bestRemoteCandidate = getTransportNegotiator().getBestRemoteCandidate();
TransportCandidate acceptedLocalCandidate = getTransportNegotiator().getAcceptedLocalCandidate();
// Trigger the session established flag
triggerContentEstablished(bestCommonAudioPt, bestRemoteCandidate, acceptedLocalCandidate);
}
/**
* Trigger a session established event.
* @throws NotConnectedException
*/
private void triggerContentEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc) throws NotConnectedException {
// Let the session know that we've established a content/media segment.
JingleSession session = getSession();
if (session != null) {
List<JingleListener> listeners = session.getListenersList();
for (JingleListener li : listeners) {
if (li instanceof JingleSessionListener) {
JingleSessionListener sli = (JingleSessionListener) li;
sli.sessionEstablished(pt, rc, lc, session);
}
}
}
// Create a media session for each media manager in the session.
if (mediaNeg.getMediaManager() != null) {
rc.removeCandidateEcho();
lc.removeCandidateEcho();
jingleMediaSession = getMediaNegotiator().getMediaManager().createMediaSession(pt, rc, lc, session);
jingleMediaSession.addMediaReceivedListener(session);
if (jingleMediaSession != null) {
jingleMediaSession.startTrasmit();
jingleMediaSession.startReceive();
for (TransportCandidate candidate : getTransportNegotiator().getOfferedCandidates())
candidate.removeCandidateEcho();
}
JingleMediaManager mediaManager = getMediaNegotiator().getMediaManager();
getSession().addJingleMediaSession(mediaManager.getName(), jingleMediaSession);
}
}
/**
* Stop a Jingle media session.
*/
public void stopJingleMediaSession() {
if (jingleMediaSession != null) {
jingleMediaSession.stopTrasmit();
jingleMediaSession.stopReceive();
}
}
/**
* The negotiator state for the ContentNegotiators is a special case.
* It is a roll-up of the sub-negotiator states.
*/
public JingleNegotiatorState getNegotiatorState() {
JingleNegotiatorState result = JingleNegotiatorState.PENDING;
if ((mediaNeg != null) && (transNeg != null)) {
if ((mediaNeg.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
|| (transNeg.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED))
result = JingleNegotiatorState.SUCCEEDED;
if ((mediaNeg.getNegotiatorState() == JingleNegotiatorState.FAILED)
|| (transNeg.getNegotiatorState() == JingleNegotiatorState.FAILED))
result = JingleNegotiatorState.FAILED;
}
// Store the state (to make it easier to know when debugging.)
setNegotiatorState(result);
return result;
}
}

View file

@ -0,0 +1,65 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
}
}

View file

@ -0,0 +1,69 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.packet.JingleError;
/**
* A Jingle exception.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public class JingleException extends XMPPException {
private static final long serialVersionUID = -1521230401958103382L;
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;
}
}

View file

@ -0,0 +1,594 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.jingle.listeners.CreatedJingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionRequestListener;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.BasicTransportManager;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import org.jivesoftware.smackx.jingle.nat.TransportResolver;
import org.jivesoftware.smackx.jingle.packet.Jingle;
import org.jivesoftware.smackx.jingle.provider.JingleProvider;
/**
* Jingle is a session establishment protocol defined in (XEP-0166).
* It defines a framework for negotiating and managing out-of-band ( data that is send and receive through other connection than XMPP connection) data sessions over XMPP.
* With this protocol you can setup VOIP Calls, Video Streaming, File transfers and whatever out-of-band session based transmission.
* <p/>
* To create a Jingle Session you need a Transport method and a Payload type.
* <p/>
* A transport method is how it will trasmit and receive network packets. Transport MUST have one or more candidates.
* A transport candidate is an IP Address with a defined port, that other party must send data to.
* <p/>
* A supported payload type, is the data encoding format that the jmf will be transmitted.
* For instance an Audio Payload "GSM".
* <p/>
* A Jingle session negociates a payload type and a pair of transport candidates.
* Which means that when a Jingle Session is establhished you will have two defined transport candidates with addresses
* and a defined Payload type.
* In other words, you will have two IP address with their respective ports, and a Codec type defined.
* <p/>
* The JingleManager is a facade built upon Jabber Jingle (XEP-166) to allow the
* use of Jingle. This implementation allows the user to simply
* use this class for setting the Jingle parameters, create and receive Jingle Sessions.
* <p/>
* In order to use the Jingle, the user must provide a
* TransportManager that will handle the resolution of potential IP addresses taht can be used to transport the streaming (jmf).
* This TransportManager can be initialized with several default resolvers,
* including a fixed solver that can be used when the address and port are know
* in advance.
* This API have ready to use Transport Managers, for instance: BasicTransportManager, STUNTransportManager, BridgedTransportManager.
* <p/>
* You should also especify a JingleMediaManager if you want that JingleManager assume Media control
* Using a JingleMediaManager implementation is the easier way to implement a Jingle Application.
* <p/>
* Otherwise before creating an outgoing connection, the user must create jingle session
* listeners that will be called when different events happen. The most
* important event is <i>sessionEstablished()</i>, that will be called when all
* the negotiations are finished, providing the payload type for the
* transmission as well as the remote and local addresses and ports for the
* communication. See JingleSessionListener for a complete list of events that can be
* observed.
* <p/>
* This is an example of how to use the JingleManager:
* <i>This example implements a Jingle VOIP Call between two users.</i>
* <p/>
* <pre>
* <p/>
* To wait for an Incoming Jingle Session:
* <p/>
* try {
* <p/>
* // Connect to a XMPP Server
* XMPPConnection x1 = new XMPPTCPConnection("xmpp.com");
* x1.connect();
* x1.login("juliet", "juliet");
* <p/>
* // Create a JingleManager using a BasicResolver
* final JingleManager jm1 = new JingleManager(
* x1, new BasicTransportManager());
* <p/>
* // Create a JingleMediaManager. In this case using Jingle Audio Media API
* JingleMediaManager jingleMediaManager = new AudioMediaManager();
* <p/>
* // Set the JingleMediaManager
* jm1.setMediaManager(jingleMediaManager);
* <p/>
* // Listen for incoming calls
* jm1.addJingleSessionRequestListener(new JingleSessionRequestListener() {
* public void sessionRequested(JingleSessionRequest request) {
* <p/>
* try {
* // Accept the call
* IncomingJingleSession session = request.accept();
* <p/>
* <p/>
* // Start the call
* session.start();
* } catch (XMPPException e) {
* e.printStackTrace();
* }
* <p/>
* }
* });
* <p/>
* Thread.sleep(15000);
* <p/>
* } catch (Exception e) {
* e.printStackTrace();
* }
* <p/>
* To create an Outgoing Jingle Session:
* <p/>
* try {
* <p/>
* // Connect to a XMPP Server
* XMPPConnection x0 = new XMPPTCPConnection("xmpp.com");
* x0.connect();
* x0.login("romeo", "romeo");
* <p/>
* // Create a JingleManager using a BasicResolver
* final JingleManager jm0 = new JingleManager(
* x0, new BasicTransportManager());
* <p/>
* // Create a JingleMediaManager. In this case using Jingle Audio Media API
* JingleMediaManager jingleMediaManager = new AudioMediaManager(); // Using Jingle Media API
* <p/>
* // Set the JingleMediaManager
* jm0.setMediaManager(jingleMediaManager);
* <p/>
* // Create a new Jingle Call with a full JID
* OutgoingJingleSession js0 = jm0.createOutgoingJingleSession("juliet@xmpp.com/Smack");
* <p/>
* // Start the call
* js0.start();
* <p/>
* Thread.sleep(10000);
* js0.terminate();
* <p/>
* Thread.sleep(3000);
* <p/>
* } catch (Exception e) {
* e.printStackTrace();
* }
* </pre>
*
* @author Thiago Camargo
* @author Alvaro Saurin
* @author Jeff Williams
* @see JingleListener
* @see TransportResolver
* @see JingleSession
* @see JingleSession
* @see JingleMediaManager
* @see BasicTransportManager , STUNTransportManager, BridgedTransportManager, TransportResolver, BridgedResolver, ICEResolver, STUNResolver and BasicResolver.
*/
public class JingleManager implements JingleSessionListener {
private static final Logger LOGGER = Logger.getLogger(JingleManager.class.getName());
// non-static
final List<JingleSession> jingleSessions = new ArrayList<JingleSession>();
// Listeners for manager events (ie, session requests...)
private List<JingleSessionRequestListener> jingleSessionRequestListeners;
// Listeners for created JingleSessions
private List<CreatedJingleSessionListener> creationListeners = new ArrayList<CreatedJingleSessionListener>();
// The XMPP connection
private XMPPConnection connection;
// The Media Managers
private List<JingleMediaManager> jingleMediaManagers;
/**
* Default constructor with a defined XMPPConnection, Transport Resolver and a Media Manager
* If a fully implemented JingleMediaSession is entered, JingleManager manage Jingle signalling and jmf
*
* @param connection XMPP XMPPConnection to be used
* @param jingleMediaManagers an implemeted JingleMediaManager to be used.
* @throws SmackException
* @throws XMPPException
*/
public JingleManager(XMPPConnection connection, List<JingleMediaManager> jingleMediaManagers) throws XMPPException, SmackException {
this.connection = connection;
this.jingleMediaManagers = jingleMediaManagers;
connection.getRoster().addRosterListener(new RosterListener() {
public void entriesAdded(Collection<String> addresses) {
}
public void entriesUpdated(Collection<String> addresses) {
}
public void entriesDeleted(Collection<String> addresses) {
}
public void presenceChanged(Presence presence) {
if (!presence.isAvailable()) {
String xmppAddress = presence.getFrom();
JingleSession aux = null;
for (JingleSession jingleSession : jingleSessions) {
if (jingleSession.getInitiator().equals(xmppAddress) || jingleSession.getResponder().equals(xmppAddress)) {
aux = jingleSession;
}
}
if (aux != null)
try {
aux.terminate();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
/**
* Setup the jingle system to let the remote clients know we support Jingle.
* (This used to be a static part of construction. The problem is a remote client might
* attempt a Jingle connection to us after we've created a XMPPConnection, but before we've
* setup an instance of a JingleManager. We will appear to not support Jingle. With the new
* method you just call it once and all new connections will report Jingle support.)
*/
public static void setJingleServiceEnabled() {
ProviderManager providerManager = ProviderManager.getInstance();
providerManager.addIQProvider("jingle", "urn:xmpp:tmp:jingle", new JingleProvider());
// Enable the Jingle support on every established connection
// The ServiceDiscoveryManager class should have been already
// initialized
XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() {
public void connectionCreated(XMPPConnection connection) {
JingleManager.setServiceEnabled(connection, true);
}
});
}
/**
* Enables or disables the Jingle support on a given connection.
* <p/>
* <p/>
* Before starting any Jingle jmf session, check that the user can handle
* it. Enable the Jingle support to indicate that this client handles Jingle
* messages.
*
* @param connection the connection where the service will be enabled or
* disabled
* @param enabled indicates if the service will be enabled or disabled
*/
public synchronized static void setServiceEnabled(XMPPConnection connection, boolean enabled) {
if (isServiceEnabled(connection) == enabled) {
return;
}
if (enabled) {
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(Jingle.NAMESPACE);
} else {
ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(Jingle.NAMESPACE);
}
}
/**
* Returns true if the Jingle support is enabled for the given connection.
*
* @param connection the connection to look for Jingle support
* @return a boolean indicating if the Jingle support is enabled for the
* given connection
*/
public static boolean isServiceEnabled(XMPPConnection connection) {
return ServiceDiscoveryManager.getInstanceFor(connection).includesFeature(Jingle.NAMESPACE);
}
/**
* Returns true if the specified user handles Jingle messages.
*
* @param connection the connection to use to perform the service discovery
* @param userID the user to check. A fully qualified xmpp ID, e.g.
* jdoe@example.com
* @return a boolean indicating whether the specified user handles Jingle
* messages
* @throws SmackException if there was no response from the server.
* @throws XMPPException
*/
public static boolean isServiceEnabled(XMPPConnection connection, String userID) throws XMPPException, SmackException {
return ServiceDiscoveryManager.getInstanceFor(connection).supportsFeature(userID, Jingle.NAMESPACE);
}
/**
* Get the Media Managers of this Jingle Manager
*
* @return the list of JingleMediaManagers
*/
public List<JingleMediaManager> getMediaManagers() {
return jingleMediaManagers;
}
/**
* Set the Media Managers of this Jingle Manager
*
* @param jingleMediaManagers JingleMediaManager to be used for open, close, start and stop jmf streamings
*/
public void setMediaManagers(List<JingleMediaManager> jingleMediaManagers) {
this.jingleMediaManagers = jingleMediaManagers;
}
/**
* Add a Jingle session request listenerJingle to listen to incoming session
* requests.
*
* @param jingleSessionRequestListener an implemented JingleSessionRequestListener
* @see #removeJingleSessionRequestListener(JingleSessionRequestListener)
* @see JingleListener
*/
public synchronized void addJingleSessionRequestListener(final JingleSessionRequestListener jingleSessionRequestListener) {
if (jingleSessionRequestListener != null) {
if (jingleSessionRequestListeners == null) {
initJingleSessionRequestListeners();
}
synchronized (jingleSessionRequestListeners) {
jingleSessionRequestListeners.add(jingleSessionRequestListener);
}
}
}
/**
* Removes a Jingle session listenerJingle.
*
* @param jingleSessionRequestListener The jingle session jingleSessionRequestListener to be removed
* @see #addJingleSessionRequestListener(JingleSessionRequestListener)
* @see JingleListener
*/
public void removeJingleSessionRequestListener(JingleSessionRequestListener jingleSessionRequestListener) {
if (jingleSessionRequestListeners == null) {
return;
}
synchronized (jingleSessionRequestListeners) {
jingleSessionRequestListeners.remove(jingleSessionRequestListener);
}
}
/**
* Adds a CreatedJingleSessionListener.
* This listener will be called when a session is created by the JingleManager instance.
*
* @param createdJingleSessionListener
*/
public void addCreationListener(CreatedJingleSessionListener createdJingleSessionListener) {
this.creationListeners.add(createdJingleSessionListener);
}
/**
* Removes a CreatedJingleSessionListener.
* This listener will be called when a session is created by the JingleManager instance.
*
* @param createdJingleSessionListener
*/
public void removeCreationListener(CreatedJingleSessionListener createdJingleSessionListener) {
this.creationListeners.remove(createdJingleSessionListener);
}
/**
* Trigger CreatedJingleSessionListeners that a session was created.
*
* @param jingleSession
*/
public void triggerSessionCreated(JingleSession jingleSession) {
jingleSessions.add(jingleSession);
jingleSession.addListener(this);
for (CreatedJingleSessionListener createdJingleSessionListener : creationListeners) {
try {
createdJingleSessionListener.sessionCreated(jingleSession);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void sessionEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc, JingleSession jingleSession) {
}
public void sessionDeclined(String reason, JingleSession jingleSession) {
jingleSession.removeListener(this);
jingleSessions.remove(jingleSession);
jingleSession.close();
LOGGER.severe("Declined:" + reason);
}
public void sessionRedirected(String redirection, JingleSession jingleSession) {
jingleSession.removeListener(this);
jingleSessions.remove(jingleSession);
}
public void sessionClosed(String reason, JingleSession jingleSession) {
jingleSession.removeListener(this);
jingleSessions.remove(jingleSession);
}
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession) {
jingleSession.removeListener(this);
jingleSessions.remove(jingleSession);
}
public void sessionMediaReceived(JingleSession jingleSession, String participant) {
// Do Nothing
}
/**
* Register the listenerJingles, waiting for a Jingle packet that tries to
* establish a new session.
*/
private void initJingleSessionRequestListeners() {
PacketFilter initRequestFilter = new PacketFilter() {
// Return true if we accept this packet
public boolean accept(Packet pin) {
if (pin instanceof IQ) {
IQ iq = (IQ) pin;
if (iq.getType().equals(IQ.Type.SET)) {
if (iq instanceof Jingle) {
Jingle jin = (Jingle) pin;
if (jin.getAction().equals(JingleActionEnum.SESSION_INITIATE)) {
return true;
}
}
}
}
return false;
}
};
jingleSessionRequestListeners = new ArrayList<JingleSessionRequestListener>();
// Start a packet listener for session initiation requests
connection.addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
triggerSessionRequested((Jingle) packet);
}
}, initRequestFilter);
}
/**
* Disconnect all Jingle Sessions
*/
public void disconnectAllSessions() {
List<JingleSession> sessions = jingleSessions.subList(0, jingleSessions.size());
for (JingleSession jingleSession : sessions)
try {
jingleSession.terminate();
} catch (Exception e) {
e.printStackTrace();
}
sessions.clear();
}
/**
* Activates the listenerJingles on a Jingle session request.
*
* @param initJin the packet that must be passed to the jingleSessionRequestListener.
*/
void triggerSessionRequested(Jingle initJin) {
JingleSessionRequestListener[] jingleSessionRequestListeners = null;
// Make a synchronized copy of the listenerJingles
synchronized (this.jingleSessionRequestListeners) {
jingleSessionRequestListeners = new JingleSessionRequestListener[this.jingleSessionRequestListeners.size()];
this.jingleSessionRequestListeners.toArray(jingleSessionRequestListeners);
}
// ... and let them know of the event
JingleSessionRequest request = new JingleSessionRequest(this, initJin);
for (int i = 0; i < jingleSessionRequestListeners.length; i++) {
jingleSessionRequestListeners[i].sessionRequested(request);
}
}
// Session creation
/**
* Creates an Jingle session to start a communication with another user.
*
* @param responder the fully qualified jabber ID with resource of the other
* user.
* @return The session on which the negotiation can be run.
*/
public JingleSession createOutgoingJingleSession(String responder) throws XMPPException {
if (responder == null || StringUtils.parseName(responder).length() <= 0 || StringUtils.parseServer(responder).length() <= 0
|| StringUtils.parseResource(responder).length() <= 0) {
throw new IllegalArgumentException("The provided user id was not fully qualified");
}
JingleSession session = new JingleSession(connection, (JingleSessionRequest) null, connection.getUser(), responder, jingleMediaManagers);
triggerSessionCreated(session);
return session;
}
/**
* Creates an Jingle session to start a communication with another user.
*
* @param responder the fully qualified jabber ID with resource of the other
* user.
* @return the session on which the negotiation can be run.
*/
// public OutgoingJingleSession createOutgoingJingleSession(String responder) throws XMPPException {
// if (this.getMediaManagers() == null) return null;
// return createOutgoingJingleSession(responder, this.getMediaManagers());
// }
/**
* When the session request is acceptable, this method should be invoked. It
* will create an JingleSession which allows the negotiation to procede.
*
* @param request the remote request that is being accepted.
* @return the session which manages the rest of the negotiation.
*/
public JingleSession createIncomingJingleSession(JingleSessionRequest request) throws XMPPException {
if (request == null) {
throw new NullPointerException("Received request cannot be null");
}
JingleSession session = new JingleSession(connection, request, request.getFrom(), connection.getUser(), jingleMediaManagers);
triggerSessionCreated(session);
return session;
}
/**
* When the session request is acceptable, this method should be invoked. It
* will create an JingleSession which allows the negotiation to procede.
* This method use JingleMediaManager to select the supported Payload types.
*
* @param request the remote request that is being accepted.
* @return the session which manages the rest of the negotiation.
*/
// IncomingJingleSession createIncomingJingleSession(JingleSessionRequest request) throws XMPPException {
// if (request == null) {
// throw new NullPointerException("JingleMediaManager is not defined");
// }
// if (jingleMediaManager != null)
// return createIncomingJingleSession(request, jingleMediaManager.getPayloads());
//
// return createIncomingJingleSession(request, null);
// }
/**
* Get a session with the informed JID. If no session is found, return null.
*
* @param jid
* @return the JingleSession
*/
public JingleSession getSession(String jid) {
for (JingleSession jingleSession : jingleSessions) {
if (jingleSession.getResponder().equals(jid)) {
return jingleSession;
}
}
return null;
}
}

View file

@ -0,0 +1,259 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
/**
* Basic Jingle negotiator.
* <p/>
* </p>
* <p/>
* JingleNegotiator implements some basic behavior for every Jingle negotiation.
* It implements a "state" pattern: each stage should process Jingle packets and
* act depending on the current state in the negotiation...
* <p/>
* </p>
*
* @author Alvaro Saurin
* @author Jeff Williams
*/
public abstract class JingleNegotiator {
private static final Logger LOGGER = Logger.getLogger(JingleNegotiator.class.getName());
//private XMPPConnection connection; // The connection associated
protected JingleSession session;
private final List<JingleListener> listeners = new ArrayList<JingleListener>();
private String expectedAckId;
private JingleNegotiatorState state;
private boolean isStarted;
/**
* Default constructor.
*/
public JingleNegotiator() {
this(null);
}
/**
* Default constructor with a Connection
*
* @param session the jingle session
*/
public JingleNegotiator(JingleSession session) {
this.session = session;
state = JingleNegotiatorState.PENDING;
}
public JingleNegotiatorState getNegotiatorState() {
return state;
}
public void setNegotiatorState(JingleNegotiatorState stateIs) {
JingleNegotiatorState stateWas = state;
LOGGER.fine("Negotiator state change: " + stateWas + "->" + stateIs + "(" + this.getClass().getSimpleName() + ")");
switch (stateIs) {
case PENDING:
break;
case FAILED:
break;
case SUCCEEDED:
break;
default:
break;
}
this.state = stateIs;
}
public XMPPConnection getConnection() {
if (session != null) {
return session.getConnection();
} else {
return null;
}
}
/**
* Get the XMPP connection associated with this negotiation.
*
* @return the connection
*/
public JingleSession getSession() {
return session;
}
/**
* Set the XMPP connection associated.
*
* @param session the jingle session
*/
public void setSession(JingleSession session) {
this.session = session;
}
// Acks management
/**
* Add expected ID
*
* @param id
*/
public void addExpectedId(String id) {
expectedAckId = id;
}
/**
* Check if the passed ID is the expected ID
*
* @param id
* @return true if is expected id
*/
public boolean isExpectedId(String id) {
if (id != null) {
return id.equals(expectedAckId);
} else {
return false;
}
}
/**
* Remove and expected ID
*
* @param id
*/
public void removeExpectedId(String id) {
addExpectedId((String) null);
}
// Listeners
/**
* Add a Jingle session listener to listen to incoming session requests.
*
* @param li The listener
* @see org.jivesoftware.smackx.jingle.listeners.JingleListener
*/
public void addListener(JingleListener li) {
synchronized (listeners) {
listeners.add(li);
}
}
/**
* Removes a Jingle session listener.
*
* @param li The jingle session listener to be removed
* @see org.jivesoftware.smackx.jingle.listeners.JingleListener
*/
public void removeListener(JingleListener li) {
synchronized (listeners) {
listeners.remove(li);
}
}
/**
* Get a copy of the listeners
*
* @return a copy of the listeners
*/
protected List<JingleListener> getListenersList() {
ArrayList<JingleListener> result;
synchronized (listeners) {
result = new ArrayList<JingleListener>(listeners);
}
return result;
}
/**
* Dispatch an incoming packet.
*
* The negotiators form a tree relationship that roughly matches the Jingle packet format:
*
* JingleSession
* Content Negotiator
* Media Negotiator
* Transport Negotiator
* Content Negotiator
* Media Negotiator
* Transport Negotiator
*
* <jingle>
* <content>
* <description>
* <transport>
* <content>
* <description>
* <transport>
*
* This way, each segment of a Jingle packet has a corresponding negotiator that know how to deal with that
* part of the Jingle packet. It also allows us to support Jingle packets of arbitraty complexity.
*
* Each parent calls dispatchIncomingPacket for each of its children. The children then pass back a List<> of
* results that will get sent when we reach the top level negotiator (JingleSession).
*
* @param iq the packet received
* @param id the ID of the response that will be sent
* @return the new packet to send (either a Jingle or an IQ error).
* @throws XMPPException
*/
public abstract List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException;
public void start() {
isStarted = true;
doStart();
}
public boolean isStarted() {
return isStarted;
}
/**
* Each of the negotiators has their individual behavior when they start.
*/
protected abstract void doStart();
/**
* Close the negotiation.
*/
public void close() {
}
}

View file

@ -0,0 +1,26 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
/**
* @author Jeff Williams
*/
public enum JingleNegotiatorState {
PENDING,
FAILED,
SUCCEEDED
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.packet.Jingle;
/**
* A Jingle session request.
* <p/>
* This class is a facade of a received Jingle request. The user can have direct
* access to the Jingle packet (<i>JingleSessionRequest.getJingle() </i>) of
* the request or can use the convencience methods provided by this class.
*
* @author Alvaro Saurin
*/
public class JingleSessionRequest {
private static final Logger LOGGER = Logger.getLogger(JingleSessionRequest.class.getName());
private final Jingle jingle; // The Jingle packet
private final JingleManager manager; // The manager associated to this
// request
/**
* A recieve request is constructed from the Jingle Initiation request
* received from the initator.
*
* @param manager The manager handling this request
* @param jingle The jingle IQ recieved from the initiator.
*/
public JingleSessionRequest(JingleManager manager, Jingle jingle) {
this.manager = manager;
this.jingle = jingle;
}
/**
* Returns the fully-qualified jabber ID of the user that requested this
* session.
*
* @return Returns the fully-qualified jabber ID of the user that requested
* this session.
*/
public String getFrom() {
return jingle.getFrom();
}
/**
* Returns the session ID that uniquely identifies this session.
*
* @return Returns the session ID that uniquely identifies this session
*/
public String getSessionID() {
return jingle.getSid();
}
/**
* Returns the Jingle packet that was sent by the requester which contains
* the parameters of the session.
*/
public Jingle getJingle() {
return jingle;
}
/**
* Accepts this request and creates the incoming Jingle session.
*
* @param pts list of supported Payload Types
* @return Returns the <b><i>IncomingJingleSession</b></i> on which the
* negotiation can be carried out.
*/
// public synchronized JingleSession accept(List<PayloadType> pts) throws XMPPException {
// JingleSession session = null;
// synchronized (manager) {
// session = manager.createIncomingJingleSession(this, pts);
// // Acknowledge the IQ reception
// session.setSid(this.getSessionID());
// //session.sendAck(this.getJingle());
// //session.respond(this.getJingle());
// }
// return session;
// }
/**
* Accepts this request and creates the incoming Jingle session.
*
* @return Returns the <b><i>IncomingJingleSession</b></i> on which the
* negotiation can be carried out.
* @throws SmackException
*/
public synchronized JingleSession accept() throws XMPPException, SmackException {
JingleSession session = null;
synchronized (manager) {
session = manager.createIncomingJingleSession(this);
// Acknowledge the IQ reception
session.setSid(this.getSessionID());
//session.sendAck(this.getJingle());
session.updatePacketListener();
session.receivePacketAndRespond(this.getJingle());
}
return session;
}
/**
* Rejects the session request.
*/
public synchronized void reject() {
JingleSession session = null;
synchronized (manager) {
try {
session = manager.createIncomingJingleSession(this);
// Acknowledge the IQ reception
session.setSid(this.getSessionID());
//session.sendAck(this.getJingle());
session.updatePacketListener();
session.terminate("Declined");
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Exception in reject", e);
}
}
}
}

View file

@ -0,0 +1,66 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.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) throws SmackException;
/**
* For debugging just emit the short name of the class.
*/
public String toString() {
return this.getClass().getSimpleName();
}
}

View file

@ -0,0 +1,107 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.packet.Jingle;
import org.jivesoftware.smackx.jingle.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 (Exception e) {
e.printStackTrace();
}
return response;
}
}

View file

@ -0,0 +1,72 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import java.util.logging.Logger;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.packet.Jingle;
import org.jivesoftware.smackx.jingle.packet.JingleError;
/**
* @author Jeff Williams
* @see JingleSessionState
*/
public class JingleSessionStateEnded extends JingleSessionState {
private static final Logger LOGGER = Logger.getLogger(JingleSessionStateEnded.class.getName());
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() {
LOGGER.fine("Session Ended");
LOGGER.fine("-------------------------------------------------------------------");
}
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;
}
}

View file

@ -0,0 +1,132 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.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 (Exception e) {
e.printStackTrace();
}
return response;
}
}

View file

@ -0,0 +1,213 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle;
import org.jivesoftware.smack.SmackException;
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.jingle.packet.Jingle;
import org.jivesoftware.smackx.jingle.packet.JingleContent;
import org.jivesoftware.smackx.jingle.packet.JingleDescription;
import org.jivesoftware.smackx.jingle.packet.JingleError;
import org.jivesoftware.smackx.jingle.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) throws SmackException {
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.
* @throws SmackException
*/
private IQ receiveSessionInitiateAction(JingleSession session, Jingle inJingle) throws SmackException {
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 (Exception e) {
e.printStackTrace();
}
return response;
}
}

View file

@ -0,0 +1,30 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smackx.jingle.JingleSession;
/**
* Inteface used to dispatch a event when a Jingle session is created.
*
* @author Thiago Camargo
*/
public interface CreatedJingleSessionListener {
public void sessionCreated(JingleSession jingleSession);
}

View file

@ -0,0 +1,37 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
/**
* Jingle listeners interface.
*
* This is the list of events that can be observed from a JingleSession and some
* sub negotiators. This listeners can be added to different elements of the
* Jingle model.
*
* For example, a JingleManager can notify any SessionRequestListenerListener
* listener when a new session request is received. In this case, the
* <i>sessionRequested()</i> of the listener will be executed, and the listener
* will be able to <i>accept()</i> or <i>decline()</i> the invitation.
*
* @author Thiago Camargo
*/
public interface JingleListener {
}

View file

@ -0,0 +1,48 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
/**
* Interface for listening to jmf info events.
* @author Thiago Camargo
*/
public interface JingleMediaInfoListener extends JingleListener {
/**
* The other end is busy.
*/
public void mediaInfoBusy();
/**
* We are on hold.
*/
public void mediaInfoHold();
/**
* The jmf is muted.
*/
public void mediaInfoMute();
/**
* We are queued.
*/
public void mediaInfoQueued();
/**
* We are ringing.
*/
public void mediaInfoRinging();
}

View file

@ -0,0 +1,42 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smackx.jingle.media.PayloadType;
/**
* Interface for listening to jmf events.
* @author Thiago Camargo
*/
public interface JingleMediaListener extends JingleListener {
/**
* Notification that the jmf has been negotiated and established.
*
* @param pt The payload type agreed.
* @throws NotConnectedException
*/
public void mediaEstablished(PayloadType pt) throws NotConnectedException;
/**
* Notification that a payload type must be cancelled
*
* @param cand The payload type that must be closed
*/
public void mediaClosed(PayloadType cand);
}

View file

@ -0,0 +1,84 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* Interface for listening for session events.
* @author Thiago Camargo
*/
public interface JingleSessionListener extends JingleListener {
/**
* Notification that the session has been established. Arguments specify
* the payload type and transport to use.
*
* @param pt the Payload tyep to use
* @param remoteCandidate the remote candidate to use for connecting to the remote
* service.
* @param localCandidate the local candidate where we must listen for connections
* @param jingleSession Session that called the method
* @throws NotConnectedException
*/
public void sessionEstablished(PayloadType pt, TransportCandidate remoteCandidate,
TransportCandidate localCandidate, JingleSession jingleSession) throws NotConnectedException;
/**
* Notification that the session was declined.
*
* @param reason the reason (if any).
* @param jingleSession Session that called the method
*/
public void sessionDeclined(String reason, JingleSession jingleSession);
/**
* Notification that the session was redirected.
*
* @param redirection
* @param jingleSession session that called the method
*/
public void sessionRedirected(String redirection, JingleSession jingleSession);
/**
* Notification that the session was closed normally.
*
* @param reason the reason (if any).
* @param jingleSession Session that called the method
*/
public void sessionClosed(String reason, JingleSession jingleSession);
/**
* Notification that the session was closed due to an exception.
*
* @param e the exception.
* @param jingleSession session that called the method
*/
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession);
/**
* Notification that the Media has arrived for this session.
*
* @param jingleSession session that called the method
* @param participant description of the participant
*/
public void sessionMediaReceived(JingleSession jingleSession, String participant);
}

View file

@ -0,0 +1,33 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smackx.jingle.JingleSessionRequest;
/**
* Interface to listener Jingle session requests.
*
* @author Alvaro Saurin
*/
public interface JingleSessionRequestListener extends JingleListener {
/**
* A request to start a session has been recieved from another user.
*
* @param request The request from the other user.
*/
public void sessionRequested(JingleSessionRequest request);
}

View file

@ -0,0 +1,57 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.listeners;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* Interface for listening to transport events.
*
* @author Thiago Camargo
*/
public interface JingleTransportListener extends JingleListener {
/**
* Notification that the transport has been established.
*
* @param local The transport candidate that has been used for listening
* in the local machine
* @param remote The transport candidate that has been used for
* transmitting to the remote machine
* @throws NotConnectedException
*/
public void transportEstablished(TransportCandidate local,
TransportCandidate remote) throws NotConnectedException;
/**
* Notification that a transport must be cancelled.
*
* @param cand The transport candidate that must be cancelled. A value
* of "null" means all the transports for this session.
*/
public void transportClosed(TransportCandidate cand);
/**
* Notification that the transport was closed due to an exception.
*
* @param e the exception.
*/
public void transportClosedOnError(XMPPException e);
}

View file

@ -0,0 +1,76 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.media;
import java.util.Locale;
/**
* Content info. Content info messages are complementary messages that can be
* transmitted for informing of events like "busy", "ringtone", etc.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class ContentInfo {
/**
* Audio content info messages.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public static class Audio extends ContentInfo {
public static final ContentInfo.Audio BUSY = new ContentInfo.Audio("busy");
public static final ContentInfo.Audio HOLD = new ContentInfo.Audio("hold");
public static final ContentInfo.Audio MUTE = new ContentInfo.Audio("mute");
public static final ContentInfo.Audio QUEUED = new ContentInfo.Audio("queued");
public static final ContentInfo.Audio RINGING = new ContentInfo.Audio("ringing");
private String value;
public Audio(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* Returns the MediaInfo constant associated with the String value.
*/
public static ContentInfo fromString(String value) {
value = value.toLowerCase(Locale.US);
if (value.equals("busy")) {
return BUSY;
} else if (value.equals("hold")) {
return HOLD;
} else if (value.equals("mute")) {
return MUTE;
} else if (value.equals("queued")) {
return QUEUED;
} else if (value.equals("ringing")) {
return RINGING;
} else {
return null;
}
}
}
}

View file

@ -0,0 +1,85 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.media;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import java.util.List;
/**
* This class provides necessary Jingle Session jmf methods and behavior.
* <p/>
* The goal of this class is to provide a flexible way to make JingleManager control jmf streaming APIs without implement them.
* For instance you can implement a file transfer using java sockets or a VOIP Media Manager using JMF.
* You can implement many JingleMediaManager according to you necessity.
*
* @author Thiago Camargo
*/
public abstract class JingleMediaManager {
public static final String MEDIA_NAME = "JingleMediaManager";
// Each media manager must keep track of the transport manager that it uses.
private JingleTransportManager transportManager;
public JingleMediaManager(JingleTransportManager transportManager) {
this.transportManager = transportManager;
}
/**
* Return The transport manager that goes with this media manager.
*/
public JingleTransportManager getTransportManager() {
return transportManager;
}
/**
* Return all supported Payloads for this Manager
*
* @return The Payload List
*/
public abstract List<PayloadType> getPayloads();
/**
* Returns the Preferred PayloadType of the Media Manager
*
* @return The PayloadType
*/
public PayloadType getPreferredPayloadType() {
return getPayloads().size() > 0 ? getPayloads().get(0) : null;
}
/**
* Create a Media Session Implementation
*
* @param payloadType
* @param remote
* @param local
* @return the media session
*/
public abstract JingleMediaSession createMediaSession(PayloadType payloadType, final TransportCandidate remote,
final TransportCandidate local, JingleSession jingleSession);
// This is to set the attributes of the <content> element of the Jingle packet.
public String getName() {
return MEDIA_NAME;
}
}

View file

@ -0,0 +1,188 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.media;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import java.util.ArrayList;
import java.util.List;
/**
* Public Abstract Class provides a clear interface between Media Session and Jingle API.
* <p/>
* When a Jingle Session is fully stablished, we will have a Payload Type and two transport candidates defined for it.
* Smack Jingle API don't implement Media Transmit and Receive methods.
* But provides an interface to let the user implements it using another API. For instance: JMF.
* <p/>
* <i>The Class that implements this one, must have the support to transmit and receive the jmf.</i>
* <i>This interface let the user choose his own jmf API.</i>
*
* @author Thiago Camargo
*/
public abstract class JingleMediaSession {
// Payload Type of the Session
private PayloadType payloadType;
// Local Transport details
private TransportCandidate local;
// Remote Transport details
private TransportCandidate remote;
// Media Locator
private String mediaLocator;
// Media Received Listener
private List<MediaReceivedListener> mediaReceivedListeners = new ArrayList<MediaReceivedListener>();
// Jingle Session
private JingleSession jingleSession;
/**
* Creates a new JingleMediaSession Instance to handle Media methods.
*
* @param payloadType Payload Type of the transmittion
* @param remote Remote accepted Transport Candidate
* @param local Local accepted Transport Candidate
* @param mediaLocator Media Locator of the capture device
*/
public JingleMediaSession(PayloadType payloadType, TransportCandidate remote,
TransportCandidate local, String mediaLocator, JingleSession jingleSession) {
this.local = local;
this.remote = remote;
this.payloadType = payloadType;
this.mediaLocator = mediaLocator;
this.jingleSession = jingleSession;
}
/**
* Returns the PayloadType of the Media Session
*
* @return the PayloadType
*/
public PayloadType getPayloadType() {
return payloadType;
}
/**
* Returns the Media Session local Candidate
*
* @return the TransportCandidate
*/
public TransportCandidate getLocal() {
return local;
}
/**
* Returns the Media Session remote Candidate
*
* @return the TransportCandidate
*/
public TransportCandidate getRemote() {
return remote;
}
/**
* Return the media locator or null if not defined
*
* @return media locator
*/
public String getMediaLocator() {
return mediaLocator;
}
/**
* Set the media locator
*
* @param mediaLocator media locator or null to use default
*/
public void setMediaLocator(String mediaLocator) {
this.mediaLocator = mediaLocator;
}
/**
* Adds a Media Received Listener
*
* @param mediaReceivedListener
*/
public void addMediaReceivedListener(MediaReceivedListener mediaReceivedListener) {
mediaReceivedListeners.add(mediaReceivedListener);
}
/**
* Removes a Media Received Listener
*
* @param mediaReceivedListener
*/
public void removeMediaReceivedListener(MediaReceivedListener mediaReceivedListener) {
mediaReceivedListeners.remove(mediaReceivedListener);
}
/**
* Removes all Media Received Listeners
*/
public void removeAllMediaReceivedListener() {
mediaReceivedListeners.clear();
}
/**
* Initialize the RTP Channel preparing to transmit and receive.
*/
public abstract void initialize();
/**
* Starts a RTP / UDP / TCP Transmission to the remote Candidate
*/
public abstract void startTrasmit();
/**
* Starts a RTP / UDP / TCP Receiver from the remote Candidate to local Candidate
*/
public abstract void startReceive();
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
*
* @param active
*/
public abstract void setTrasmit(boolean active);
/**
* Stops a RTP / UDP / TCP Transmission to the remote Candidate
*/
public abstract void stopTrasmit();
/**
* Stops a RTP / UDP / TCP Receiver from the remote Candidate to local Candidate
*/
public abstract void stopReceive();
/**
* Called when new Media is received.
*/
public void mediaReceived(String participant) {
for (MediaReceivedListener mediaReceivedListener : mediaReceivedListeners) {
mediaReceivedListener.mediaReceived(participant);
}
}
/**
* Gets associated JingleSession
* @return associated JingleSession
*/
public JingleSession getJingleSession() {
return jingleSession;
}
}

View file

@ -0,0 +1,537 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.media;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.ContentNegotiator;
import org.jivesoftware.smackx.jingle.JingleActionEnum;
import org.jivesoftware.smackx.jingle.JingleException;
import org.jivesoftware.smackx.jingle.JingleNegotiator;
import org.jivesoftware.smackx.jingle.JingleNegotiatorState;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
import org.jivesoftware.smackx.jingle.listeners.JingleMediaListener;
import org.jivesoftware.smackx.jingle.packet.Jingle;
import org.jivesoftware.smackx.jingle.packet.JingleContent;
import org.jivesoftware.smackx.jingle.packet.JingleDescription;
import org.jivesoftware.smackx.jingle.packet.JingleError;
/**
* Manager for jmf descriptor negotiation. <p/> <p/> This class is responsible
* for managing the descriptor negotiation process, handling all the xmpp
* packets interchange and the stage control. handling all the xmpp packets
* interchange and the stage control.
*
* @author Thiago Camargo
*/
public class MediaNegotiator extends JingleNegotiator {
private static final Logger LOGGER = Logger.getLogger(MediaNegotiator.class.getName());
//private JingleSession session; // The session this negotiation
private final JingleMediaManager mediaManager;
// Local and remote payload types...
private final List<PayloadType> localAudioPts = new ArrayList<PayloadType>();
private final List<PayloadType> remoteAudioPts = new ArrayList<PayloadType>();
private PayloadType bestCommonAudioPt;
private ContentNegotiator parentNegotiator;
/**
* Default constructor. The constructor establishes some basic parameters,
* but it does not start the negotiation. For starting the negotiation, call
* startNegotiation.
*
* @param session
* The jingle session.
*/
public MediaNegotiator(JingleSession session, JingleMediaManager mediaManager, List<PayloadType> pts,
ContentNegotiator parentNegotiator) {
super(session);
this.mediaManager = mediaManager;
this.parentNegotiator = parentNegotiator;
bestCommonAudioPt = null;
if (pts != null) {
if (pts.size() > 0) {
localAudioPts.addAll(pts);
}
}
}
/**
* Return The media manager for this negotiator.
*/
public JingleMediaManager getMediaManager() {
return mediaManager;
}
/**
* Dispatch an incoming packet. The method is responsible for recognizing
* the packet type and, depending on the current state, delivering the
* packet to the right event handler and wait for a response.
*
* @param iq
* the packet received
* @return the new Jingle packet to send.
* @throws XMPPException
* @throws NotConnectedException
*/
public List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, NotConnectedException {
List<IQ> responses = new ArrayList<IQ>();
IQ response = null;
if (iq.getType().equals(IQ.Type.ERROR)) {
// Process errors
setNegotiatorState(JingleNegotiatorState.FAILED);
triggerMediaClosed(getBestCommonAudioPt());
// This next line seems wrong, and may subvert the normal closing process.
throw new JingleException(iq.getError().getMessage());
} else if (iq.getType().equals(IQ.Type.RESULT)) {
// Process ACKs
if (isExpectedId(iq.getPacketID())) {
receiveResult(iq);
removeExpectedId(iq.getPacketID());
}
} else if (iq instanceof Jingle) {
Jingle jingle = (Jingle) iq;
JingleActionEnum action = jingle.getAction();
// Only act on the JingleContent sections that belong to this media negotiator.
for (JingleContent jingleContent : jingle.getContentsList()) {
if (jingleContent.getName().equals(parentNegotiator.getName())) {
JingleDescription description = jingleContent.getDescription();
if (description != null) {
switch (action) {
case CONTENT_ACCEPT:
response = receiveContentAcceptAction(jingle, description);
break;
case CONTENT_MODIFY:
break;
case CONTENT_REMOVE:
break;
case SESSION_INFO:
response = receiveSessionInfoAction(jingle, description);
break;
case SESSION_INITIATE:
response = receiveSessionInitiateAction(jingle, description);
break;
case SESSION_ACCEPT:
response = receiveSessionAcceptAction(jingle, description);
break;
default:
break;
}
}
}
}
}
if (response != null) {
addExpectedId(response.getPacketID());
responses.add(response);
}
return responses;
}
/**
* Process the ACK of our list of codecs (our offer).
*/
private Jingle receiveResult(IQ iq) throws XMPPException {
Jingle response = null;
// if (!remoteAudioPts.isEmpty()) {
// // Calculate the best common codec
// bestCommonAudioPt = calculateBestCommonAudioPt(remoteAudioPts);
//
// // and send an accept if we havee an agreement...
// if (bestCommonAudioPt != null) {
// response = createAcceptMessage();
// } else {
// throw new JingleException(JingleError.NO_COMMON_PAYLOAD);
// }
// }
return response;
}
/**
* The other side has sent us a content-accept. The payload types in that message may not match with what
* we sent, but XEP-167 says that the other side should retain the order of the payload types we first sent.
*
* This means we can walk through our list, in order, until we find one from their list that matches. This
* will be the best payload type to use.
*
* @param jingle
* @return the iq
* @throws NotConnectedException
*/
private IQ receiveContentAcceptAction(Jingle jingle, JingleDescription description) throws XMPPException, NotConnectedException {
IQ response = null;
List<PayloadType> offeredPayloads = new ArrayList<PayloadType>();
offeredPayloads = description.getAudioPayloadTypesList();
bestCommonAudioPt = calculateBestCommonAudioPt(offeredPayloads);
if (bestCommonAudioPt == null) {
setNegotiatorState(JingleNegotiatorState.FAILED);
response = session.createJingleError(jingle, JingleError.NEGOTIATION_ERROR);
} else {
setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
triggerMediaEstablished(getBestCommonAudioPt());
LOGGER.severe("Media choice:" + getBestCommonAudioPt().getName());
response = session.createAck(jingle);
}
return response;
}
/**
* Receive a session-initiate packet.
* @param jingle
* @param description
* @return the iq
*/
private IQ receiveSessionInitiateAction(Jingle jingle, JingleDescription description) {
IQ response = null;
List<PayloadType> offeredPayloads = new ArrayList<PayloadType>();
offeredPayloads = description.getAudioPayloadTypesList();
bestCommonAudioPt = calculateBestCommonAudioPt(offeredPayloads);
synchronized (remoteAudioPts) {
remoteAudioPts.addAll(offeredPayloads);
}
// If there are suitable/matching payload types then accept this content.
if (bestCommonAudioPt != null) {
// Let thre transport negotiators sort-out connectivity and content-accept instead.
//response = createAudioPayloadTypesOffer();
setNegotiatorState(JingleNegotiatorState.PENDING);
} else {
// Don't really know what to send here. XEP-166 is not clear.
setNegotiatorState(JingleNegotiatorState.FAILED);
}
return response;
}
/**
* A content info has been received. This is done for publishing the
* list of payload types...
*
* @param jin
* The input packet
* @return a Jingle packet
* @throws JingleException
*/
private IQ receiveSessionInfoAction(Jingle jingle, JingleDescription description) throws JingleException {
IQ response = null;
PayloadType oldBestCommonAudioPt = bestCommonAudioPt;
List<PayloadType> offeredPayloads;
boolean ptChange = false;
offeredPayloads = description.getAudioPayloadTypesList();
if (!offeredPayloads.isEmpty()) {
synchronized (remoteAudioPts) {
remoteAudioPts.clear();
remoteAudioPts.addAll(offeredPayloads);
}
// Calculate the best common codec
bestCommonAudioPt = calculateBestCommonAudioPt(remoteAudioPts);
if (bestCommonAudioPt != null) {
// and send an accept if we have an agreement...
ptChange = !bestCommonAudioPt.equals(oldBestCommonAudioPt);
if (oldBestCommonAudioPt == null || ptChange) {
//response = createAcceptMessage();
}
} else {
throw new JingleException(JingleError.NO_COMMON_PAYLOAD);
}
}
// Parse the Jingle and get the payload accepted
return response;
}
/**
* A jmf description has been accepted. In this case, we must save the
* accepted payload type and notify any listener...
*
* @param jin
* The input packet
* @return a Jingle packet
* @throws JingleException
*/
private IQ receiveSessionAcceptAction(Jingle jingle, JingleDescription description) throws JingleException {
IQ response = null;
PayloadType.Audio agreedCommonAudioPt;
List<PayloadType> offeredPayloads = new ArrayList<PayloadType>();
if (bestCommonAudioPt == null) {
// Update the best common audio PT
bestCommonAudioPt = calculateBestCommonAudioPt(remoteAudioPts);
//response = createAcceptMessage();
}
offeredPayloads = description.getAudioPayloadTypesList();
if (!offeredPayloads.isEmpty()) {
if (offeredPayloads.size() == 1) {
agreedCommonAudioPt = (PayloadType.Audio) offeredPayloads.get(0);
if (bestCommonAudioPt != null) {
// If the accepted PT matches the best payload
// everything is fine
if (!agreedCommonAudioPt.equals(bestCommonAudioPt)) {
throw new JingleException(JingleError.NEGOTIATION_ERROR);
}
}
} else if (offeredPayloads.size() > 1) {
throw new JingleException(JingleError.MALFORMED_STANZA);
}
}
return response;
}
/**
* Return true if the content is negotiated.
*
* @return true if the content is negotiated.
*/
public boolean isEstablished() {
return getBestCommonAudioPt() != null;
}
/**
* Return true if the content is fully negotiated.
*
* @return true if the content is fully negotiated.
*/
public boolean isFullyEstablished() {
return (isEstablished() && ((getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) || (getNegotiatorState() == JingleNegotiatorState.FAILED)));
}
// Payload types
private PayloadType calculateBestCommonAudioPt(List<PayloadType> remoteAudioPts) {
final ArrayList<PayloadType> commonAudioPtsHere = new ArrayList<PayloadType>();
final ArrayList<PayloadType> commonAudioPtsThere = new ArrayList<PayloadType>();
PayloadType result = null;
if (!remoteAudioPts.isEmpty()) {
commonAudioPtsHere.addAll(localAudioPts);
commonAudioPtsHere.retainAll(remoteAudioPts);
commonAudioPtsThere.addAll(remoteAudioPts);
commonAudioPtsThere.retainAll(localAudioPts);
if (!commonAudioPtsHere.isEmpty() && !commonAudioPtsThere.isEmpty()) {
if (session.getInitiator().equals(session.getConnection().getUser())) {
PayloadType.Audio bestPtHere = null;
PayloadType payload = mediaManager.getPreferredPayloadType();
if (payload != null && payload instanceof PayloadType.Audio)
if (commonAudioPtsHere.contains(payload))
bestPtHere = (PayloadType.Audio) payload;
if (bestPtHere == null)
for (PayloadType payloadType : commonAudioPtsHere)
if (payloadType instanceof PayloadType.Audio) {
bestPtHere = (PayloadType.Audio) payloadType;
break;
}
result = bestPtHere;
} else {
PayloadType.Audio bestPtThere = null;
for (PayloadType payloadType : commonAudioPtsThere)
if (payloadType instanceof PayloadType.Audio) {
bestPtThere = (PayloadType.Audio) payloadType;
break;
}
result = bestPtThere;
}
}
}
return result;
}
/**
* Adds a payload type to the list of remote payloads.
*
* @param pt
* the remote payload type
*/
public void addRemoteAudioPayloadType(PayloadType.Audio pt) {
if (pt != null) {
synchronized (remoteAudioPts) {
remoteAudioPts.add(pt);
}
}
}
// /**
// * Create an offer for the list of audio payload types.
// *
// * @return a new Jingle packet with the list of audio Payload Types
// */
// private Jingle createAudioPayloadTypesOffer() {
//
// JingleContent jingleContent = new JingleContent(parentNegotiator.getCreator(), parentNegotiator.getName());
// JingleDescription audioDescr = new JingleDescription.Audio();
//
// // Add the list of payloads for audio and create a
// // JingleDescription
// // where we announce our payloads...
// audioDescr.addAudioPayloadTypes(localAudioPts);
// jingleContent.setDescription(audioDescr);
//
// Jingle jingle = new Jingle(JingleActionEnum.CONTENT_ACCEPT);
// jingle.addContent(jingleContent);
//
// return jingle;
// }
// Predefined messages and Errors
/**
* Create an IQ "accept" message.
*/
// private Jingle createAcceptMessage() {
// Jingle jout = null;
//
// // If we have a common best codec, send an accept right now...
// jout = new Jingle(JingleActionEnum.CONTENT_ACCEPT);
// JingleContent content = new JingleContent(parentNegotiator.getCreator(), parentNegotiator.getName());
// content.setDescription(new JingleDescription.Audio(bestCommonAudioPt));
// jout.addContent(content);
//
// return jout;
// }
// Payloads
/**
* Get the best common codec between both parts.
*
* @return The best common PayloadType codec.
*/
public PayloadType getBestCommonAudioPt() {
return bestCommonAudioPt;
}
// Events
/**
* Trigger a session established event.
*
* @param bestPt
* payload type that has been agreed.
* @throws NotConnectedException
*/
protected void triggerMediaEstablished(PayloadType bestPt) throws NotConnectedException {
List<JingleListener> listeners = getListenersList();
for (JingleListener li : listeners) {
if (li instanceof JingleMediaListener) {
JingleMediaListener mli = (JingleMediaListener) li;
mli.mediaEstablished(bestPt);
}
}
}
/**
* Trigger a jmf closed event.
*
* @param currPt
* current payload type that is cancelled.
*/
protected void triggerMediaClosed(PayloadType currPt) {
List<JingleListener> listeners = getListenersList();
for (JingleListener li : listeners) {
if (li instanceof JingleMediaListener) {
JingleMediaListener mli = (JingleMediaListener) li;
mli.mediaClosed(currPt);
}
}
}
/**
* Called from above when starting a new session.
*/
protected void doStart() {
}
/**
* Terminate the jmf negotiator
*/
public void close() {
super.close();
triggerMediaClosed(getBestCommonAudioPt());
}
/**
* Create a JingleDescription that matches this negotiator.
*/
public JingleDescription getJingleDescription() {
JingleDescription result = null;
PayloadType payloadType = getBestCommonAudioPt();
if (payloadType != null) {
result = new JingleDescription.Audio(payloadType);
} else {
// If we haven't settled on a best payload type yet then just use the first one in our local list.
result = new JingleDescription.Audio();
result.addAudioPayloadTypes(localAudioPts);
}
return result;
}
}

View file

@ -0,0 +1,29 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.media;
/**
* Listener for new Incoming Media Streams
*/
public interface MediaReceivedListener {
/**
* Called when new Media is received.
*/
public void mediaReceived(String participant);
}

View file

@ -0,0 +1,365 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.media;
/**
* Represents a payload type.
*
* @author Alvaro Saurin
*/
public class PayloadType {
public static final String NODENAME = "payload-type";
public static int MAX_FIXED_PT = 95;
public static int INVALID_PT = 65535;
private int id;
private String name;
private int channels;
/**
* Constructor with Id, name and number of channels
*
* @param id The identifier
* @param name A name
* @param channels The number of channels
*/
public PayloadType(int id, final String name, int channels) {
super();
this.id = id;
this.name = name;
this.channels = channels;
}
/**
* Default constructor.
*/
public PayloadType() {
this(INVALID_PT, null, 1);
}
/**
* Constructor with Id and name
*
* @param id The identification
* @param name A name
*/
public PayloadType(int id, String name) {
this(id, name, 1);
}
/**
* Copy constructor
*
* @param pt The other payload type.
*/
public PayloadType(PayloadType pt) {
this(pt.getId(), pt.getName(), pt.getChannels());
}
/**
* Get the ID.
*
* @return the ID
*/
public int getId() {
return id;
}
/**
* Set the ID.
*
* @param id ID
*/
public void setId(int id) {
this.id = id;
}
/**
* Get the printable name.
*
* @return printable name for the payload type
*/
public String getName() {
return name;
}
/**
* Set the printable name.
*
* @param name the printable name
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the number of channels used by this payload type.
*
* @return the number of channels
*/
public int getChannels() {
return channels;
}
/**
* Set the numer of channels for a payload type.
*
* @param channels The number of channels
*/
public void setChannels(int channels) {
this.channels = channels;
}
/**
* Return true if the Payload type is not valid
*
* @return true if the payload type is invalid
*/
public boolean isNull() {
if (getId() == INVALID_PT) {
return true;
}
else if (getName() == null) {
return true;
}
return false;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + getChannels();
result = PRIME * result + getId();
result = PRIME * result + (getName() == null ? 0 : getName().hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PayloadType other = (PayloadType) obj;
if (getChannels() != other.getChannels()) {
return false;
}
if (getId() != other.getId()) {
return false;
}
// Compare names only for dynamic payload types
if (getId() > MAX_FIXED_PT) {
if (getName() == null) {
if (other.getName() != null) {
return false;
}
}
else if (!getName().equals(other.getName())) {
return false;
}
}
return true;
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public static String getElementName() {
return NODENAME;
}
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName()).append(" ");
// We covert here the payload type to XML
if (this.getId() != PayloadType.INVALID_PT) {
buf.append(" id=\"").append(this.getId()).append("\"");
}
if (this.getName() != null) {
buf.append(" name=\"").append(this.getName()).append("\"");
}
if (this.getChannels() != 0) {
buf.append(" channels=\"").append(this.getChannels()).append("\"");
}
if (getChildAttributes() != null) {
buf.append(getChildAttributes());
}
buf.append("/>");
return buf.toString();
}
protected String getChildAttributes() {
StringBuilder buf = new StringBuilder();
if (this instanceof PayloadType.Audio) {
PayloadType.Audio pta = (PayloadType.Audio) this;
buf.append(" clockrate=\"").append(pta.getClockRate()).append("\" ");
}
return buf.toString();
}
/**
* Audio payload type.
*/
public static class Audio extends PayloadType {
private int clockRate;
/**
* Constructor with all the attributes of an Audio payload type
*
* @param id The identifier
* @param name The name assigned to this payload type
* @param channels The number of channels
* @param rate The clock rate
*/
public Audio(int id, String name, int channels, int rate) {
super(id, name, channels);
clockRate = rate;
}
/**
* Constructor with all the attributes of an Audio payload type
*
* @param id The identifier
* @param name The name assigned to this payload type
* @param rate The clock rate
*/
public Audio(int id, String name, int rate) {
super(id, name);
clockRate = rate;
}
/**
* Empty constructor.
*/
public Audio() {
super();
clockRate = 0;
}
/**
* Constructor with Id and name
*
* @param id the Id for the payload type
* @param name the name of the payload type
*/
public Audio(int id, String name) {
super(id, name);
clockRate = 0;
}
/**
* Copy constructor
*
* @param pt the other payload type
*/
public Audio(PayloadType pt) {
super(pt);
clockRate = 0;
}
/**
* Copy constructor
*
* @param pt the other payload type
*/
public Audio(PayloadType.Audio pt) {
super(pt);
clockRate = pt.getClockRate();
}
/**
* Get the sampling clockRate for a payload type
*
* @return The sampling clockRate
*/
public int getClockRate() {
return clockRate;
}
/**
* Set tha sampling clockRate for a playload type.
*
* @param rate The sampling clockRate
*/
public void setClockRate(int rate) {
clockRate = rate;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
final int PRIME = 31;
int result = super.hashCode();
result = PRIME * result + getClockRate();
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Audio other = (Audio) obj;
if (getClockRate() != other.getClockRate()) {
return false;
}
return true;
}
}
}

View file

@ -0,0 +1,299 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.Toolkit;
import java.util.Vector;
import java.util.logging.Logger;
import javax.media.Format;
import javax.media.PlugInManager;
import javax.media.Renderer;
import javax.media.format.AudioFormat;
import com.sun.media.ExclusiveUse;
import com.sun.media.util.Registry;
public class JMFInit extends Frame implements Runnable {
private static final long serialVersionUID = 6476412003260641680L;
private static final Logger LOGGER = Logger.getLogger(JMFInit.class.getName());
private String tempDir = "/tmp";
private boolean done = false;
private String userHome;
private boolean visible = false;
public JMFInit(String[] args, boolean visible) {
super("Initializing JMF...");
this.visible = visible;
Registry.set("secure.allowCaptureFromApplets", true);
Registry.set("secure.allowSaveFileFromApplets", true);
updateTemp(args);
try {
Registry.commit();
}
catch (Exception e) {
message("Failed to commit to JMFRegistry!");
}
Thread detectThread = new Thread(this);
detectThread.run();
/*
* int slept = 0; while (!done && slept < 60 * 1000 * 2) { try {
* Thread.currentThread().sleep(500); } catch (InterruptedException ie) { }
* slept += 500; }
*
* if (!done) { console.error("Detection is taking too long!
* Aborting!"); message("Detection is taking too long! Aborting!"); }
*
* try { Thread.currentThread().sleep(2000); } catch
* (InterruptedException ie) { }
*/
}
public void run() {
detectDirectAudio();
detectS8DirectAudio();
detectCaptureDevices();
done = true;
}
private void updateTemp(String[] args) {
if (args != null && args.length > 0) {
tempDir = args[0];
message("Setting cache directory to " + tempDir);
Registry r = new Registry();
try {
r.set("secure.cacheDir", tempDir);
r.commit();
message("Updated registry");
}
catch (Exception e) {
message("Couldn't update registry!");
}
}
}
private void detectCaptureDevices() {
// check if JavaSound capture is available
message("Looking for Audio capturer");
Class<?> dsauto;
try {
dsauto = Class.forName("DirectSoundAuto");
dsauto.newInstance();
message("Finished detecting DirectSound capturer");
}
catch (ThreadDeath td) {
throw td;
}
catch (Throwable t) {
//Do nothing
}
Class<?> jsauto;
try {
jsauto = Class.forName("JavaSoundAuto");
jsauto.newInstance();
message("Finished detecting javasound capturer");
}
catch (ThreadDeath td) {
throw td;
}
catch (Throwable t) {
message("JavaSound capturer detection failed!");
}
/*
// Check if VFWAuto or SunVideoAuto is available
message("Looking for video capture devices");
Class auto = null;
Class autoPlus = null;
try {
auto = Class.forName("VFWAuto");
}
catch (Exception e) {
}
if (auto == null) {
try {
auto = Class.forName("SunVideoAuto");
}
catch (Exception ee) {
}
try {
autoPlus = Class.forName("SunVideoPlusAuto");
}
catch (Exception ee) {
}
}
if (auto == null) {
try {
auto = Class.forName("V4LAuto");
}
catch (Exception ee) {
}
}
try {
Object instance = auto.newInstance();
if (autoPlus != null) {
Object instancePlus = autoPlus.newInstance();
}
message("Finished detecting video capture devices");
}
catch (ThreadDeath td) {
throw td;
}
catch (Throwable t) {
message("Capture device detection failed!");
}
*/
}
private void detectDirectAudio() {
Class<?> cls;
int plType = PlugInManager.RENDERER;
String dar = "com.sun.media.renderer.audio.DirectAudioRenderer";
try {
// Check if this is the Windows Performance Pack - hack
cls = Class.forName("VFWAuto");
// Check if DS capture is supported, otherwise fail DS renderer
// since NT doesn't have capture
cls = Class.forName("com.sun.media.protocol.dsound.DSound");
// Find the renderer class and instantiate it.
cls = Class.forName(dar);
Renderer rend = (Renderer) cls.newInstance();
try {
// Set the format and open the device
AudioFormat af = new AudioFormat(AudioFormat.LINEAR, 44100, 16,
2);
rend.setInputFormat(af);
rend.open();
Format[] inputFormats = rend.getSupportedInputFormats();
// Register the device
PlugInManager.addPlugIn(dar, inputFormats, new Format[0],
plType);
// Move it to the top of the list
Vector<String> rendList = PlugInManager.getPlugInList(null, null,
plType);
int listSize = rendList.size();
if (rendList.elementAt(listSize - 1).equals(dar)) {
rendList.removeElementAt(listSize - 1);
rendList.insertElementAt(dar, 0);
PlugInManager.setPlugInList(rendList, plType);
PlugInManager.commit();
// Log.debug("registered");
}
rend.close();
}
catch (Throwable t) {
// Log.debug("Error " + t);
}
}
catch (Throwable tt) {
//Do nothing
}
}
private void detectS8DirectAudio() {
Class<?> cls;
int plType = PlugInManager.RENDERER;
String dar = "com.sun.media.renderer.audio.DirectAudioRenderer";
try {
// Check if this is the solaris Performance Pack - hack
cls = Class.forName("SunVideoAuto");
// Find the renderer class and instantiate it.
cls = Class.forName(dar);
Renderer rend = (Renderer) cls.newInstance();
if (rend instanceof ExclusiveUse
&& !((ExclusiveUse) rend).isExclusive()) {
// sol8+, DAR supports mixing
Vector<String> rendList = PlugInManager.getPlugInList(null, null,
plType);
int listSize = rendList.size();
boolean found = false;
String rname = null;
for (int i = 0; i < listSize; i++) {
rname = (String) (rendList.elementAt(i));
if (rname.equals(dar)) { // DAR is in the registry
found = true;
rendList.removeElementAt(i);
break;
}
}
if (found) {
rendList.insertElementAt(dar, 0);
PlugInManager.setPlugInList(rendList, plType);
PlugInManager.commit();
}
}
}
catch (Throwable tt) {
//Do nothing
}
}
private void message(String mesg) {
LOGGER.fine(mesg);
}
private void createGUI() {
TextArea textBox = new TextArea(5, 50);
add("Center", textBox);
textBox.setEditable(false);
addNotify();
pack();
int scrWidth = (int) Toolkit.getDefaultToolkit().getScreenSize()
.getWidth();
int scrHeight = (int) Toolkit.getDefaultToolkit().getScreenSize()
.getHeight();
setLocation((scrWidth - getWidth()) / 2, (scrHeight - getHeight()) / 2);
setVisible(visible);
}
public static void start(boolean visible) {
new JMFInit(null, visible);
}
}

View file

@ -0,0 +1,549 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.jmf;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.media.Codec;
import javax.media.Controller;
import javax.media.ControllerClosedEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Format;
import javax.media.MediaLocator;
import javax.media.NoProcessorException;
import javax.media.Processor;
import javax.media.UnsupportedPlugInException;
import javax.media.control.BufferControl;
import javax.media.control.PacketSizeControl;
import javax.media.control.TrackControl;
import javax.media.format.AudioFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushBufferDataSource;
import javax.media.protocol.PushBufferStream;
import javax.media.rtp.InvalidSessionAddressException;
import javax.media.rtp.RTPManager;
import javax.media.rtp.SendStream;
import javax.media.rtp.SessionAddress;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
/**
* An Easy to use Audio Channel implemented using JMF.
* It sends and receives jmf for and from desired IPs and ports.
* Also has a rport Symetric behavior for better NAT Traversal.
* It send data from a defined port and receive data in the same port, making NAT binds easier.
* <p/>
* Send from portA to portB and receive from portB in portA.
* <p/>
* Sending
* portA ---> portB
* <p/>
* Receiving
* portB ---> portA
* <p/>
* <i>Transmit and Receive are interdependents. To receive you MUST trasmit. </i>
*
* @author Thiago Camargo
*/
public class AudioChannel {
private static final Logger LOGGER = Logger.getLogger(AudioChannel.class.getName());
private MediaLocator locator;
private String localIpAddress;
private String remoteIpAddress;
private int localPort;
private int portBase;
private Format format;
private Processor processor = null;
private RTPManager rtpMgrs[];
private DataSource dataOutput = null;
private AudioReceiver audioReceiver;
private List<SendStream> sendStreams = new ArrayList<SendStream>();
private JingleMediaSession jingleMediaSession;
private boolean started = false;
/**
* Creates an Audio Channel for a desired jmf locator. For instance: new MediaLocator("dsound://")
*
* @param locator media locator
* @param localIpAddress local IP address
* @param remoteIpAddress remote IP address
* @param localPort local port number
* @param remotePort remote port number
* @param format audio format
*/
public AudioChannel(MediaLocator locator,
String localIpAddress,
String remoteIpAddress,
int localPort,
int remotePort,
Format format, JingleMediaSession jingleMediaSession) {
this.locator = locator;
this.localIpAddress = localIpAddress;
this.remoteIpAddress = remoteIpAddress;
this.localPort = localPort;
this.portBase = remotePort;
this.format = format;
this.jingleMediaSession = jingleMediaSession;
}
/**
* Starts the transmission. Returns null if transmission started ok.
* Otherwise it returns a string with the reason why the setup failed.
* Starts receive also.
*
* @return result description
*/
public synchronized String start() {
if (started) return null;
// Create a processor for the specified jmf locator
String result = createProcessor();
if (result != null) {
started = false;
}
// Create an RTP session to transmit the output of the
// processor to the specified IP address and port no.
result = createTransmitter();
if (result != null) {
processor.close();
processor = null;
started = false;
}
else {
started = true;
}
// Start the transmission
processor.start();
return null;
}
/**
* Stops the transmission if already started.
* Stops the receiver also.
*/
public void stop() {
if (!started) return;
synchronized (this) {
try {
started = false;
if (processor != null) {
processor.stop();
processor = null;
for (RTPManager rtpMgr : rtpMgrs) {
rtpMgr.removeReceiveStreamListener(audioReceiver);
rtpMgr.removeSessionListener(audioReceiver);
rtpMgr.removeTargets("Session ended.");
rtpMgr.dispose();
}
sendStreams.clear();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
private String createProcessor() {
if (locator == null)
return "Locator is null";
DataSource ds;
try {
ds = javax.media.Manager.createDataSource(locator);
}
catch (Exception e) {
// Try JavaSound Locator as a last resort
try {
ds = javax.media.Manager.createDataSource(new MediaLocator("javasound://"));
}
catch (Exception ee) {
return "Couldn't create DataSource";
}
}
// Try to create a processor to handle the input jmf locator
try {
processor = javax.media.Manager.createProcessor(ds);
}
catch (NoProcessorException npe) {
npe.printStackTrace();
return "Couldn't create processor";
}
catch (IOException ioe) {
ioe.printStackTrace();
return "IOException creating processor";
}
// Wait for it to configure
boolean result = waitForState(processor, Processor.Configured);
if (!result){
return "Couldn't configure processor";
}
// Get the tracks from the processor
TrackControl[] tracks = processor.getTrackControls();
// Do we have atleast one track?
if (tracks == null || tracks.length < 1){
return "Couldn't find tracks in processor";
}
// Set the output content descriptor to RAW_RTP
// This will limit the supported formats reported from
// Track.getSupportedFormats to only valid RTP formats.
ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
processor.setContentDescriptor(cd);
Format supported[];
Format chosen = null;
boolean atLeastOneTrack = false;
// Program the tracks.
for (int i = 0; i < tracks.length; i++) {
if (tracks[i].isEnabled()) {
supported = tracks[i].getSupportedFormats();
if (supported.length > 0) {
for (Format format : supported) {
if (format instanceof AudioFormat) {
if (this.format.matches(format))
chosen = format;
}
}
if (chosen != null) {
tracks[i].setFormat(chosen);
LOGGER.severe("Track " + i + " is set to transmit as: " + chosen);
if (tracks[i].getFormat() instanceof AudioFormat) {
int packetRate = 20;
PacketSizeControl pktCtrl = (PacketSizeControl) processor.getControl(PacketSizeControl.class.getName());
if (pktCtrl != null) {
try {
pktCtrl.setPacketSize(getPacketSize(tracks[i].getFormat(), packetRate));
}
catch (IllegalArgumentException e) {
pktCtrl.setPacketSize(80);
// Do nothing
}
}
if (tracks[i].getFormat().getEncoding().equals(AudioFormat.ULAW_RTP)) {
Codec codec[] = new Codec[3];
codec[0] = new com.ibm.media.codec.audio.rc.RCModule();
codec[1] = new com.ibm.media.codec.audio.ulaw.JavaEncoder();
codec[2] = new com.sun.media.codec.audio.ulaw.Packetizer();
((com.sun.media.codec.audio.ulaw.Packetizer) codec
[2]).setPacketSize(160);
try {
tracks[i].setCodecChain(codec);
}
catch (UnsupportedPlugInException e) {
e.printStackTrace();
}
}
}
atLeastOneTrack = true;
}
else
tracks[i].setEnabled(false);
}
else
tracks[i].setEnabled(false);
}
}
if (!atLeastOneTrack)
return "Couldn't set any of the tracks to a valid RTP format";
result = waitForState(processor, Controller.Realized);
if (!result)
return "Couldn't realize processor";
// Get the output data source of the processor
dataOutput = processor.getDataOutput();
return null;
}
/**
* Get the best packet size for a given codec and a codec rate
*
* @param codecFormat
* @param milliseconds
* @return the best packet size
* @throws IllegalArgumentException
*/
private int getPacketSize(Format codecFormat, int milliseconds) throws IllegalArgumentException {
String encoding = codecFormat.getEncoding();
if (encoding.equalsIgnoreCase(AudioFormat.GSM) ||
encoding.equalsIgnoreCase(AudioFormat.GSM_RTP)) {
return milliseconds * 4; // 1 byte per millisec
}
else if (encoding.equalsIgnoreCase(AudioFormat.ULAW) ||
encoding.equalsIgnoreCase(AudioFormat.ULAW_RTP)) {
return milliseconds * 8;
}
else {
throw new IllegalArgumentException("Unknown codec type");
}
}
/**
* Use the RTPManager API to create sessions for each jmf
* track of the processor.
*
* @return description
*/
private String createTransmitter() {
// Cheated. Should have checked the type.
PushBufferDataSource pbds = (PushBufferDataSource) dataOutput;
PushBufferStream pbss[] = pbds.getStreams();
rtpMgrs = new RTPManager[pbss.length];
SessionAddress localAddr, destAddr;
InetAddress ipAddr;
SendStream sendStream;
audioReceiver = new AudioReceiver(this, jingleMediaSession);
int port;
for (int i = 0; i < pbss.length; i++) {
try {
rtpMgrs[i] = RTPManager.newInstance();
port = portBase + 2 * i;
ipAddr = InetAddress.getByName(remoteIpAddress);
localAddr = new SessionAddress(InetAddress.getByName(this.localIpAddress),
localPort);
destAddr = new SessionAddress(ipAddr, port);
rtpMgrs[i].addReceiveStreamListener(audioReceiver);
rtpMgrs[i].addSessionListener(audioReceiver);
BufferControl bc = (BufferControl) rtpMgrs[i].getControl("javax.media.control.BufferControl");
if (bc != null) {
int bl = 160;
bc.setBufferLength(bl);
}
try {
rtpMgrs[i].initialize(localAddr);
}
catch (InvalidSessionAddressException e) {
// In case the local address is not allowed to read, we user another local address
SessionAddress sessAddr = new SessionAddress();
localAddr = new SessionAddress(sessAddr.getDataAddress(),
localPort);
rtpMgrs[i].initialize(localAddr);
}
rtpMgrs[i].addTarget(destAddr);
LOGGER.severe("Created RTP session at " + localPort + " to: " + remoteIpAddress + " " + port);
sendStream = rtpMgrs[i].createSendStream(dataOutput, i);
sendStreams.add(sendStream);
sendStream.start();
}
catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}
return null;
}
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
*
* @param active active state
*/
public void setTrasmit(boolean active) {
for (SendStream sendStream : sendStreams) {
try {
if (active) {
sendStream.start();
LOGGER.fine("START");
}
else {
sendStream.stop();
LOGGER.fine("STOP");
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* *************************************************************
* Convenience methods to handle processor's state changes.
* **************************************************************
*/
private Integer stateLock = 0;
private boolean failed = false;
Integer getStateLock() {
return stateLock;
}
void setFailed() {
failed = true;
}
private synchronized boolean waitForState(Processor p, int state) {
p.addControllerListener(new StateListener());
failed = false;
// Call the required method on the processor
if (state == Processor.Configured) {
p.configure();
}
else if (state == Processor.Realized) {
p.realize();
}
// Wait until we get an event that confirms the
// success of the method, or a failure event.
// See StateListener inner class
while (p.getState() < state && !failed) {
synchronized (getStateLock()) {
try {
getStateLock().wait();
}
catch (InterruptedException ie) {
return false;
}
}
}
return !failed;
}
/**
* *************************************************************
* Inner Classes
* **************************************************************
*/
class StateListener implements ControllerListener {
public void controllerUpdate(ControllerEvent ce) {
// If there was an error during configure or
// realize, the processor will be closed
if (ce instanceof ControllerClosedEvent)
setFailed();
// All controller events, send a notification
// to the waiting thread in waitForState method.
if (ce != null) {
synchronized (getStateLock()) {
getStateLock().notifyAll();
}
}
}
}
public static void main(String args[]) {
InetAddress localhost;
try {
localhost = InetAddress.getLocalHost();
AudioChannel audioChannel0 = new AudioChannel(new MediaLocator("javasound://8000"), localhost.getHostAddress(), localhost.getHostAddress(), 7002, 7020, new AudioFormat(AudioFormat.GSM_RTP), null);
AudioChannel audioChannel1 = new AudioChannel(new MediaLocator("javasound://8000"), localhost.getHostAddress(), localhost.getHostAddress(), 7020, 7002, new AudioFormat(AudioFormat.GSM_RTP), null);
audioChannel0.start();
audioChannel1.start();
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
audioChannel0.setTrasmit(false);
audioChannel1.setTrasmit(false);
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
audioChannel0.setTrasmit(true);
audioChannel1.setTrasmit(true);
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
audioChannel0.stop();
audioChannel1.stop();
}
catch (UnknownHostException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,52 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.jmf;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import javax.media.format.AudioFormat;
/**
* Audio Format Utils.
*
* @author Thiago Camargo
*/
public class AudioFormatUtils {
/**
* Return a JMF AudioFormat for a given Jingle Payload type.
* Return null if the payload is not supported by this jmf API.
*
* @param payloadtype payloadtype
* @return correspondent audioType
*/
public static AudioFormat getAudioFormat(PayloadType payloadtype) {
switch (payloadtype.getId()) {
case 0:
return new AudioFormat(AudioFormat.ULAW_RTP);
case 3:
return new AudioFormat(AudioFormat.GSM_RTP);
case 4:
return new AudioFormat(AudioFormat.G723_RTP);
default:
return null;
}
}
}

View file

@ -0,0 +1,161 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.jmf;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.logging.Logger;
import javax.media.MediaLocator;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* This Class implements a complete JingleMediaSession.
* It sould be used to transmit and receive audio captured from the Mic.
* This Class should be automaticly controlled by JingleSession.
* But you could also use in any VOIP application.
* For better NAT Traversal support this implementation don't support only receive or only transmit.
* To receive you MUST transmit. So the only implemented and functionally methods are startTransmit() and stopTransmit()
*
* @author Thiago Camargo
*/
public class AudioMediaSession extends JingleMediaSession {
private static final Logger LOGGER = Logger.getLogger(AudioMediaSession.class.getName());
private AudioChannel audioChannel;
/**
* Creates a org.jivesoftware.jingleaudio.jmf.AudioMediaSession with defined payload type, remote and local candidates
*
* @param payloadType Payload of the jmf
* @param remote the remote information. The candidate that the jmf will be sent to.
* @param local the local information. The candidate that will receive the jmf
* @param locator media locator
*/
public AudioMediaSession(final PayloadType payloadType, final TransportCandidate remote,
final TransportCandidate local, String locator, JingleSession jingleSession) {
super(payloadType, remote, local, locator==null?"dsound://":locator,jingleSession);
initialize();
}
/**
* Initialize the Audio Channel to make it able to send and receive audio
*/
public void initialize() {
String ip;
String localIp;
int localPort;
int remotePort;
if (this.getLocal().getSymmetric() != null) {
ip = this.getLocal().getIp();
localIp = this.getLocal().getLocalIp();
localPort = getFreePort();
remotePort = this.getLocal().getSymmetric().getPort();
LOGGER.fine(this.getLocal().getConnection() + " " + ip + ": " + localPort + "->" + remotePort);
}
else {
ip = this.getRemote().getIp();
localIp = this.getLocal().getLocalIp();
localPort = this.getLocal().getPort();
remotePort = this.getRemote().getPort();
}
audioChannel = new AudioChannel(new MediaLocator(this.getMediaLocator()), localIp, ip, localPort, remotePort, AudioFormatUtils.getAudioFormat(this.getPayloadType()),this);
}
/**
* Starts transmission and for NAT Traversal reasons start receiving also.
*/
public void startTrasmit() {
audioChannel.start();
}
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
*
* @param active active state
*/
public void setTrasmit(boolean active) {
audioChannel.setTrasmit(active);
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void startReceive() {
// Do nothing
}
/**
* Stops transmission and for NAT Traversal reasons stop receiving also.
*/
public void stopTrasmit() {
if (audioChannel != null)
audioChannel.stop();
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void stopReceive() {
// Do nothing
}
/**
* Obtain a free port we can use.
*
* @return A free port number.
*/
protected int getFreePort() {
ServerSocket ss;
int freePort = 0;
for (int i = 0; i < 10; i++) {
freePort = (int) (10000 + Math.round(Math.random() * 10000));
freePort = freePort % 2 == 0 ? freePort : freePort + 1;
try {
ss = new ServerSocket(freePort);
freePort = ss.getLocalPort();
ss.close();
return freePort;
}
catch (IOException e) {
e.printStackTrace();
}
}
try {
ss = new ServerSocket(0);
freePort = ss.getLocalPort();
ss.close();
}
catch (IOException e) {
e.printStackTrace();
}
return freePort;
}
}

View file

@ -0,0 +1,168 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.jmf;
import java.util.logging.Logger;
import javax.media.ControllerErrorEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Player;
import javax.media.RealizeCompleteEvent;
import javax.media.protocol.DataSource;
import javax.media.rtp.Participant;
import javax.media.rtp.RTPControl;
import javax.media.rtp.ReceiveStream;
import javax.media.rtp.ReceiveStreamListener;
import javax.media.rtp.SessionListener;
import javax.media.rtp.event.ByeEvent;
import javax.media.rtp.event.NewParticipantEvent;
import javax.media.rtp.event.NewReceiveStreamEvent;
import javax.media.rtp.event.ReceiveStreamEvent;
import javax.media.rtp.event.RemotePayloadChangeEvent;
import javax.media.rtp.event.SessionEvent;
import javax.media.rtp.event.StreamMappedEvent;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
/**
* This class implements receive methods and listeners to be used in AudioChannel
*
* @author Thiago Camargo
*/
public class AudioReceiver implements ReceiveStreamListener, SessionListener,
ControllerListener {
private static final Logger LOGGER = Logger.getLogger(AudioReceiver.class.getName());
boolean dataReceived = false;
Object dataSync;
JingleMediaSession jingleMediaSession;
public AudioReceiver(final Object dataSync, final JingleMediaSession jingleMediaSession) {
this.dataSync = dataSync;
this.jingleMediaSession = jingleMediaSession;
}
/**
* JingleSessionListener.
*/
public synchronized void update(SessionEvent evt) {
if (evt instanceof NewParticipantEvent) {
Participant p = ((NewParticipantEvent) evt).getParticipant();
LOGGER.fine(" - A new participant had just joined: " + p.getCNAME());
}
}
/**
* ReceiveStreamListener
*/
public synchronized void update(ReceiveStreamEvent evt) {
Participant participant = evt.getParticipant(); // could be null.
ReceiveStream stream = evt.getReceiveStream(); // could be null.
if (evt instanceof RemotePayloadChangeEvent) {
LOGGER.severe(" - Received an RTP PayloadChangeEvent.");
LOGGER.severe("Sorry, cannot handle payload change.");
}
else if (evt instanceof NewReceiveStreamEvent) {
try {
stream = evt.getReceiveStream();
DataSource ds = stream.getDataSource();
// Find out the formats.
RTPControl ctl = (RTPControl) ds.getControl("javax.jmf.rtp.RTPControl");
if (ctl != null) {
LOGGER.severe(" - Recevied new RTP stream: " + ctl.getFormat());
}
else
LOGGER.severe(" - Recevied new RTP stream");
if (participant == null)
LOGGER.severe(" The sender of this stream had yet to be identified.");
else {
LOGGER.severe(" The stream comes from: " + participant.getCNAME());
}
// create a player by passing datasource to the Media Manager
Player p = javax.media.Manager.createPlayer(ds);
if (p == null)
return;
p.addControllerListener(this);
p.realize();
jingleMediaSession.mediaReceived(participant != null ? participant.getCNAME() : "");
// Notify intialize() that a new stream had arrived.
synchronized (dataSync) {
dataReceived = true;
dataSync.notifyAll();
}
}
catch (Exception e) {
LOGGER.severe("NewReceiveStreamEvent exception " + e.getMessage());
return;
}
}
else if (evt instanceof StreamMappedEvent) {
if (stream != null && stream.getDataSource() != null) {
DataSource ds = stream.getDataSource();
// Find out the formats.
RTPControl ctl = (RTPControl) ds.getControl("javax.jmf.rtp.RTPControl");
LOGGER.severe(" - The previously unidentified stream ");
if (ctl != null)
LOGGER.severe(" " + ctl.getFormat());
LOGGER.severe(" had now been identified as sent by: " + participant.getCNAME());
}
}
else if (evt instanceof ByeEvent) {
LOGGER.severe(" - Got \"bye\" from: " + participant.getCNAME());
}
}
/**
* ControllerListener for the Players.
*/
public synchronized void controllerUpdate(ControllerEvent ce) {
Player p = (Player) ce.getSourceController();
if (p == null)
return;
// Get this when the internal players are realized.
if (ce instanceof RealizeCompleteEvent) {
p.start();
}
if (ce instanceof ControllerErrorEvent) {
p.removeControllerListener(this);
LOGGER.severe("Receiver internal error: " + ce);
}
}
}

View file

@ -0,0 +1,166 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.jmf;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.mediaimpl.JMFInit;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* Implements a jingleMediaManager using JMF based API.
* It supports GSM and G723 codecs.
* <i>This API only currently works on windows and Mac.</i>
*
* @author Thiago Camargo
*/
public class JmfMediaManager extends JingleMediaManager {
private static final Logger LOGGER = Logger.getLogger(JmfMediaManager.class.getName());
public static final String MEDIA_NAME = "JMF";
private List<PayloadType> payloads = new ArrayList<PayloadType>();
private String mediaLocator = null;
/**
* Creates a Media Manager instance
*/
public JmfMediaManager(JingleTransportManager transportManager) {
super(transportManager);
setupPayloads();
}
/**
* Creates a Media Manager instance
*
* @param mediaLocator Media Locator
*/
public JmfMediaManager(String mediaLocator, JingleTransportManager transportManager) {
super(transportManager);
this.mediaLocator = mediaLocator;
setupPayloads();
}
/**
* Returns a new jingleMediaSession
*
* @param payloadType payloadType
* @param remote remote Candidate
* @param local local Candidate
* @return JingleMediaSession
*/
public JingleMediaSession createMediaSession(final PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local, final JingleSession jingleSession) {
return new AudioMediaSession(payloadType, remote, local, mediaLocator, jingleSession);
}
/**
* Setup API supported Payloads
*/
private void setupPayloads() {
payloads.add(new PayloadType.Audio(3, "gsm"));
payloads.add(new PayloadType.Audio(4, "g723"));
payloads.add(new PayloadType.Audio(0, "PCMU", 16000));
}
/**
* Return all supported Payloads for this Manager
*
* @return The Payload List
*/
public List<PayloadType> getPayloads() {
return payloads;
}
/**
* Return the media locator or null if not defined
*
* @return media locator
*/
public String getMediaLocator() {
return mediaLocator;
}
/**
* Set the media locator
*
* @param mediaLocator media locator or null to use default
*/
public void setMediaLocator(String mediaLocator) {
this.mediaLocator = mediaLocator;
}
/**
* Runs JMFInit the first time the application is started so that capture
* devices are properly detected and initialized by JMF.
*/
public static void setupJMF() {
// .jmf is the place where we store the jmf.properties file used
// by JMF. if the directory does not exist or it does not contain
// a jmf.properties file. or if the jmf.properties file has 0 length
// then this is the first time we're running and should continue to
// with JMFInit
String homeDir = System.getProperty("user.home");
File jmfDir = new File(homeDir, ".jmf");
String classpath = System.getProperty("java.class.path");
classpath += System.getProperty("path.separator")
+ jmfDir.getAbsolutePath();
System.setProperty("java.class.path", classpath);
if (!jmfDir.exists())
jmfDir.mkdir();
File jmfProperties = new File(jmfDir, "jmf.properties");
if (!jmfProperties.exists()) {
try {
jmfProperties.createNewFile();
}
catch (IOException ex) {
LOGGER.fine("Failed to create jmf.properties");
ex.printStackTrace();
}
}
// if we're running on linux checkout that libjmutil.so is where it
// should be and put it there.
runLinuxPreInstall();
//if (jmfProperties.length() == 0) {
new JMFInit(null, false);
//}
}
private static void runLinuxPreInstall() {
// @TODO Implement Linux Pre-Install
}
public String getName() {
return MEDIA_NAME;
}
}

View file

@ -0,0 +1,241 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.jspeex;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.security.GeneralSecurityException;
import java.util.logging.Logger;
import javax.media.NoProcessorException;
import javax.media.format.UnsupportedFormatException;
import javax.media.rtp.rtcp.SenderReport;
import javax.media.rtp.rtcp.SourceDescription;
import mil.jfcom.cie.media.session.MediaSession;
import mil.jfcom.cie.media.session.MediaSessionListener;
import mil.jfcom.cie.media.session.StreamPlayer;
import mil.jfcom.cie.media.srtp.packetizer.SpeexFormat;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* This Class implements a complete JingleMediaSession.
* It sould be used to transmit and receive audio captured from the Mic.
* This Class should be automaticly controlled by JingleSession.
* But you could also use in any VOIP application.
* For better NAT Traversal support this implementation don't support only receive or only transmit.
* To receive you MUST transmit. So the only implemented and functionally methods are startTransmit() and stopTransmit()
*
* @author Thiago Camargo
*/
public class AudioMediaSession extends JingleMediaSession implements MediaSessionListener {
private static final Logger LOGGER = Logger.getLogger(AudioMediaSession.class.getName());
private MediaSession mediaSession;
/**
* Create a Session using Speex Codec
*
* @param localhost localHost
* @param localPort localPort
* @param remoteHost remoteHost
* @param remotePort remotePort
* @param eventHandler eventHandler
* @param quality quality
* @param secure secure
* @param micOn micOn
* @return MediaSession
* @throws NoProcessorException
* @throws UnsupportedFormatException
* @throws IOException
* @throws GeneralSecurityException
*/
public static MediaSession createSession(String localhost, int localPort, String remoteHost, int remotePort, MediaSessionListener eventHandler, int quality, boolean secure, boolean micOn) throws NoProcessorException, UnsupportedFormatException, IOException, GeneralSecurityException {
SpeexFormat.setFramesPerPacket(1);
/**
* The master key. Hardcoded for now.
*/
byte[] masterKey = new byte[]{(byte) 0xE1, (byte) 0xF9, 0x7A, 0x0D, 0x3E, 0x01, (byte) 0x8B, (byte) 0xE0, (byte) 0xD6, 0x4F, (byte) 0xA3, 0x2C, 0x06, (byte) 0xDE, 0x41, 0x39};
/**
* The master salt. Hardcoded for now.
*/
byte[] masterSalt = new byte[]{0x0E, (byte) 0xC6, 0x75, (byte) 0xAD, 0x49, (byte) 0x8A, (byte) 0xFE, (byte) 0xEB, (byte) 0xB6, (byte) 0x96, 0x0B, 0x3A, (byte) 0xAB, (byte) 0xE6};
DatagramSocket[] localPorts = MediaSession.getLocalPorts(InetAddress.getByName(localhost), localPort);
MediaSession session = MediaSession.createInstance(remoteHost, remotePort, localPorts, quality, secure, masterKey, masterSalt);
session.setListener(eventHandler);
session.setSourceDescription(new SourceDescription[]{new SourceDescription(SourceDescription.SOURCE_DESC_NAME, "Superman", 1, false), new SourceDescription(SourceDescription.SOURCE_DESC_EMAIL, "cdcie.tester@je.jfcom.mil", 1, false), new SourceDescription(SourceDescription.SOURCE_DESC_LOC, InetAddress.getByName(localhost) + " Port " + session.getLocalDataPort(), 1, false), new SourceDescription(SourceDescription.SOURCE_DESC_TOOL, "JFCOM CDCIE Audio Chat", 1, false)});
return session;
}
/**
* Creates a org.jivesoftware.jingleaudio.jspeex.AudioMediaSession with defined payload type, remote and local candidates
*
* @param payloadType Payload of the jmf
* @param remote the remote information. The candidate that the jmf will be sent to.
* @param local the local information. The candidate that will receive the jmf
* @param locator media locator
*/
public AudioMediaSession(final PayloadType payloadType, final TransportCandidate remote,
final TransportCandidate local, String locator, JingleSession jingleSession) {
super(payloadType, remote, local, locator == null ? "dsound://" : locator, jingleSession);
initialize();
}
/**
* Initialize the Audio Channel to make it able to send and receive audio
*/
public void initialize() {
String ip;
String localIp;
int localPort;
int remotePort;
if (this.getLocal().getSymmetric() != null) {
ip = this.getLocal().getIp();
localIp = this.getLocal().getLocalIp();
localPort = getFreePort();
remotePort = this.getLocal().getSymmetric().getPort();
LOGGER.fine(this.getLocal().getConnection() + " " + ip + ": " + localPort + "->" + remotePort);
}
else {
ip = this.getRemote().getIp();
localIp = this.getLocal().getLocalIp();
localPort = this.getLocal().getPort();
remotePort = this.getRemote().getPort();
}
try {
mediaSession = createSession(localIp, localPort, ip, remotePort, this, 2, false, true);
}
catch (NoProcessorException e) {
e.printStackTrace();
}
catch (UnsupportedFormatException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
catch (GeneralSecurityException e) {
e.printStackTrace();
}
}
/**
* Starts transmission and for NAT Traversal reasons start receiving also.
*/
public void startTrasmit() {
try {
LOGGER.fine("start");
mediaSession.start(true);
this.mediaReceived("");
}
catch (IOException e) {
e.printStackTrace();
}
}
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
*
* @param active active state
*/
public void setTrasmit(boolean active) {
// Do nothing
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void startReceive() {
// Do nothing
}
/**
* Stops transmission and for NAT Traversal reasons stop receiving also.
*/
public void stopTrasmit() {
if (mediaSession != null)
mediaSession.close();
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void stopReceive() {
// Do nothing
}
public void newStreamIdentified(StreamPlayer streamPlayer) {
}
public void senderReportReceived(SenderReport report) {
}
public void streamClosed(StreamPlayer stream, boolean timeout) {
}
/**
* Obtain a free port we can use.
*
* @return A free port number.
*/
protected int getFreePort() {
ServerSocket ss;
int freePort = 0;
for (int i = 0; i < 10; i++) {
freePort = (int) (10000 + Math.round(Math.random() * 10000));
freePort = freePort % 2 == 0 ? freePort : freePort + 1;
try {
ss = new ServerSocket(freePort);
freePort = ss.getLocalPort();
ss.close();
return freePort;
}
catch (IOException e) {
e.printStackTrace();
}
}
try {
ss = new ServerSocket(0);
freePort = ss.getLocalPort();
ss.close();
}
catch (IOException e) {
e.printStackTrace();
}
return freePort;
}
}

View file

@ -0,0 +1,131 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.jspeex;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.mediaimpl.JMFInit;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* Implements a jingleMediaManager using JMF based API and JSpeex.
* It supports Speex codec.
* <i>This API only currently works on windows.</i>
*
* @author Thiago Camargo
*/
public class SpeexMediaManager extends JingleMediaManager {
private static final Logger LOGGER = Logger.getLogger(SpeexMediaManager.class.getName());
public static final String MEDIA_NAME = "Speex";
private List<PayloadType> payloads = new ArrayList<PayloadType>();
public SpeexMediaManager(JingleTransportManager transportManager) {
super(transportManager);
setupPayloads();
setupJMF();
}
/**
* Returns a new jingleMediaSession
*
* @param payloadType payloadType
* @param remote remote Candidate
* @param local local Candidate
* @return JingleMediaSession
*/
public JingleMediaSession createMediaSession(PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local, final JingleSession jingleSession) {
return new AudioMediaSession(payloadType, remote, local, null,null);
}
/**
* Setup API supported Payloads
*/
private void setupPayloads() {
payloads.add(new PayloadType.Audio(15, "speex"));
}
/**
* Return all supported Payloads for this Manager
*
* @return The Payload List
*/
public List<PayloadType> getPayloads() {
return payloads;
}
/**
* Runs JMFInit the first time the application is started so that capture
* devices are properly detected and initialized by JMF.
*/
public static void setupJMF() {
// .jmf is the place where we store the jmf.properties file used
// by JMF. if the directory does not exist or it does not contain
// a jmf.properties file. or if the jmf.properties file has 0 length
// then this is the first time we're running and should continue to
// with JMFInit
String homeDir = System.getProperty("user.home");
File jmfDir = new File(homeDir, ".jmf");
String classpath = System.getProperty("java.class.path");
classpath += System.getProperty("path.separator")
+ jmfDir.getAbsolutePath();
System.setProperty("java.class.path", classpath);
if (!jmfDir.exists())
jmfDir.mkdir();
File jmfProperties = new File(jmfDir, "jmf.properties");
if (!jmfProperties.exists()) {
try {
jmfProperties.createNewFile();
}
catch (IOException ex) {
LOGGER.fine("Failed to create jmf.properties");
ex.printStackTrace();
}
}
// if we're running on linux checkout that libjmutil.so is where it
// should be and put it there.
runLinuxPreInstall();
if (jmfProperties.length() == 0) {
new JMFInit(null, false);
}
}
private static void runLinuxPreInstall() {
// @TODO Implement Linux Pre-Install
}
public String getName() {
return MEDIA_NAME;
}
}

View file

@ -0,0 +1,102 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.multi;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import java.util.ArrayList;
import java.util.List;
/**
* Implements a MultiMediaManager using other JingleMediaManager implementations.
* It supports every Codecs that JingleMediaManagers added has.
*
* @author Thiago Camargo
*/
public class MultiMediaManager extends JingleMediaManager {
public static final String MEDIA_NAME = "Multi";
private List<JingleMediaManager> managers = new ArrayList<JingleMediaManager>();
private PayloadType preferredPayloadType = null;
public MultiMediaManager(JingleTransportManager transportManager) {
super(transportManager);
}
public void addMediaManager(JingleMediaManager manager) {
managers.add(manager);
}
public void removeMediaManager(JingleMediaManager manager) {
managers.remove(manager);
}
/**
* Return all supported Payloads for this Manager.
*
* @return The Payload List
*/
public List<PayloadType> getPayloads() {
List<PayloadType> list = new ArrayList<PayloadType>();
if (preferredPayloadType != null) list.add(preferredPayloadType);
for (JingleMediaManager manager : managers) {
for (PayloadType payloadType : manager.getPayloads()) {
if (!list.contains(payloadType) && !payloadType.equals(preferredPayloadType))
list.add(payloadType);
}
}
return list;
}
/**
* Returns a new JingleMediaSession
*
* @param payloadType payloadType
* @param remote remote Candidate
* @param local local Candidate
* @return JingleMediaSession JingleMediaSession
*/
public JingleMediaSession createMediaSession(PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local, final JingleSession jingleSession) {
for (JingleMediaManager manager : managers) {
if (manager.getPayloads().contains(payloadType)) {
return manager.createMediaSession(payloadType, remote, local, jingleSession);
}
}
return null;
}
public PayloadType getPreferredPayloadType() {
if (preferredPayloadType != null) return preferredPayloadType;
return super.getPreferredPayloadType();
}
public void setPreferredPayloadType(PayloadType preferredPayloadType) {
this.preferredPayloadType = preferredPayloadType;
}
public String getName() {
return MEDIA_NAME;
}
}

View file

@ -0,0 +1,111 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageDecoder;
import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageEncoder;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import java.util.ArrayList;
import java.util.List;
/**
* Implements a JingleMediaManager for ScreenSharing.
* It currently uses an Audio payload Type. Which needs to be fixed in the next version.
*
* @author Thiago Camargo
*/
public class ScreenShareMediaManager extends JingleMediaManager {
public static final String MEDIA_NAME = "ScreenShare";
private List<PayloadType> payloads = new ArrayList<PayloadType>();
private ImageDecoder decoder = null;
private ImageEncoder encoder = null;
public ScreenShareMediaManager(JingleTransportManager transportManager) {
super(transportManager);
setupPayloads();
}
/**
* Setup API supported Payloads
*/
private void setupPayloads() {
payloads.add(new PayloadType.Audio(30, "sshare"));
}
/**
* Return all supported Payloads for this Manager.
*
* @return The Payload List
*/
public List<PayloadType> getPayloads() {
return payloads;
}
/**
* Returns a new JingleMediaSession
*
* @param payloadType payloadType
* @param remote remote Candidate
* @param local local Candidate
* @return JingleMediaSession JingleMediaSession
*/
public JingleMediaSession createMediaSession(PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local, final JingleSession jingleSession) {
ScreenShareSession session = null;
session = new ScreenShareSession(payloadType, remote, local, "Screen", jingleSession);
if (encoder != null) {
session.setEncoder(encoder);
}
if (decoder != null) {
session.setDecoder(decoder);
}
return session;
}
public PayloadType getPreferredPayloadType() {
return super.getPreferredPayloadType();
}
public ImageDecoder getDecoder() {
return decoder;
}
public void setDecoder(ImageDecoder decoder) {
this.decoder = decoder;
}
public ImageEncoder getEncoder() {
return encoder;
}
public void setEncoder(ImageEncoder encoder) {
this.encoder = encoder;
}
public String getName() {
return MEDIA_NAME;
}
}

View file

@ -0,0 +1,203 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare;
import java.awt.Rectangle;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageDecoder;
import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageEncoder;
import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageReceiver;
import org.jivesoftware.smackx.jingle.mediaimpl.sshare.api.ImageTransmitter;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* This Class implements a complete JingleMediaSession.
* It sould be used to transmit and receive captured images from the Display.
* This Class should be automaticly controlled by JingleSession.
* For better NAT Traversal support this implementation don't support only receive or only transmit.
* To receive you MUST transmit. So the only implemented and functionally methods are startTransmit() and stopTransmit()
*
* @author Thiago Camargo
*/
public class ScreenShareSession extends JingleMediaSession {
private static final Logger LOGGER = Logger.getLogger(ScreenShareSession.class.getName());
private ImageTransmitter transmitter = null;
private ImageReceiver receiver = null;
private int width = 600;
private int height = 600;
/**
* Creates a org.jivesoftware.jingleaudio.jmf.AudioMediaSession with defined payload type, remote and local candidates
*
* @param payloadType Payload of the jmf
* @param remote the remote information. The candidate that the jmf will be sent to.
* @param local the local information. The candidate that will receive the jmf
* @param locator media locator
*/
public ScreenShareSession(final PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local,
final String locator, JingleSession jingleSession) {
super(payloadType, remote, local, "Screen", jingleSession);
initialize();
}
/**
* Initialize the screen share channels.
*/
public void initialize() {
JingleSession session = getJingleSession();
if ((session != null) && (session.getInitiator().equals(session.getConnection().getUser()))) {
// If the initiator of the jingle session is us then we transmit a screen share.
try {
InetAddress remote = InetAddress.getByName(getRemote().getIp());
transmitter = new ImageTransmitter(new DatagramSocket(getLocal().getPort()), remote, getRemote().getPort(),
new Rectangle(0, 0, width, height));
} catch (Exception e) {
e.printStackTrace();
}
} else {
// Otherwise we receive a screen share.
JFrame window = new JFrame();
JPanel jp = new JPanel();
window.add(jp);
window.setLocation(0, 0);
window.setSize(600, 600);
window.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
receiver.stop();
}
});
try {
receiver = new ImageReceiver(InetAddress.getByName("0.0.0.0"), getRemote().getPort(), getLocal().getPort(), width,
height);
LOGGER.fine("Receiving on:" + receiver.getLocalPort());
} catch (UnknownHostException e) {
e.printStackTrace();
}
jp.add(receiver);
receiver.setVisible(true);
window.setAlwaysOnTop(true);
window.setVisible(true);
}
}
/**
* Starts transmission and for NAT Traversal reasons start receiving also.
*/
public void startTrasmit() {
new Thread(transmitter).start();
}
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
*
* @param active active state
*/
public void setTrasmit(boolean active) {
transmitter.setTransmit(true);
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void startReceive() {
// Do nothing
}
/**
* Stops transmission and for NAT Traversal reasons stop receiving also.
*/
public void stopTrasmit() {
if (transmitter != null) {
transmitter.stop();
}
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void stopReceive() {
if (receiver != null) {
receiver.stop();
}
}
/**
* Obtain a free port we can use.
*
* @return A free port number.
*/
protected int getFreePort() {
ServerSocket ss;
int freePort = 0;
for (int i = 0; i < 10; i++) {
freePort = (int) (10000 + Math.round(Math.random() * 10000));
freePort = freePort % 2 == 0 ? freePort : freePort + 1;
try {
ss = new ServerSocket(freePort);
freePort = ss.getLocalPort();
ss.close();
return freePort;
} catch (IOException e) {
e.printStackTrace();
}
}
try {
ss = new ServerSocket(0);
freePort = ss.getLocalPort();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
return freePort;
}
public void setEncoder(ImageEncoder encoder) {
if (encoder != null) {
this.transmitter.setEncoder(encoder);
}
}
public void setDecoder(ImageDecoder decoder) {
if (decoder != null) {
this.receiver.setDecoder(decoder);
}
}
}

View file

@ -0,0 +1,98 @@
/**
*
* Copyright 2006 Jerry Huxtable
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ColorModel;
/**
* A convenience class which implements those methods of BufferedImageOp which are rarely changed.
*/
public abstract class AbstractBufferedImageOp implements BufferedImageOp, Cloneable {
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) {
if ( dstCM == null )
dstCM = src.getColorModel();
return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dstCM.isAlphaPremultiplied(), null);
}
public Rectangle2D getBounds2D( BufferedImage src ) {
return new Rectangle(0, 0, src.getWidth(), src.getHeight());
}
public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
if ( dstPt == null )
dstPt = new Point2D.Double();
dstPt.setLocation( srcPt.getX(), srcPt.getY() );
return dstPt;
}
public RenderingHints getRenderingHints() {
return null;
}
/**
* A convenience method for getting ARGB pixels from an image. This tries to avoid the performance
* penalty of BufferedImage.getRGB unmanaging the image.
* @param image a BufferedImage object
* @param x the left edge of the pixel block
* @param y the right edge of the pixel block
* @param width the width of the pixel arry
* @param height the height of the pixel arry
* @param pixels the array to hold the returned pixels. May be null.
* @return the pixels
* @see #setRGB
*/
public int[] getRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) {
int type = image.getType();
if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB )
return (int [])image.getRaster().getDataElements( x, y, width, height, pixels );
return image.getRGB( x, y, width, height, pixels, 0, width );
}
/**
* A convenience method for setting ARGB pixels in an image. This tries to avoid the performance
* penalty of BufferedImage.setRGB unmanaging the image.
* @param image a BufferedImage object
* @param x the left edge of the pixel block
* @param y the right edge of the pixel block
* @param width the width of the pixel arry
* @param height the height of the pixel arry
* @param pixels the array of pixels to set
* @see #getRGB
*/
public void setRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) {
int type = image.getType();
if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB )
image.getRaster().setDataElements( x, y, width, height, pixels );
else
image.setRGB( x, y, width, height, pixels, 0, width );
}
public Object clone() {
try {
return super.clone();
}
catch ( CloneNotSupportedException e ) {
return null;
}
}
}

View file

@ -0,0 +1,32 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* Implements a default PNG decoder.
*/
public class DefaultDecoder implements ImageDecoder {
public BufferedImage decode(ByteArrayInputStream stream) throws IOException {
return ImageIO.read(stream);
}
}

View file

@ -0,0 +1,40 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* Implements a default PNG Encoder
*/
public class DefaultEncoder implements ImageEncoder{
public ByteArrayOutputStream encode(BufferedImage bufferedImage) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ImageIO.write(bufferedImage, "png", baos);
}
catch (IOException e) {
e.printStackTrace();
baos = null;
}
return baos;
}
}

View file

@ -0,0 +1,31 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
/**
* Image Decoder Interface use this interface if you want to change the default decoder
*
* @author Thiago Rocha Camargo
*/
public interface ImageDecoder {
public BufferedImage decode(ByteArrayInputStream stream) throws IOException;
}

View file

@ -0,0 +1,29 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
/**
* Image Encoder Interface use this interface if you want to change the default encoder
*
* @author Thiago Rocha Camargo
*/
public interface ImageEncoder {
public ByteArrayOutputStream encode(BufferedImage bufferedImage);
}

View file

@ -0,0 +1,167 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
* UDP Image Receiver.
* It uses PNG Tiles into UDP packets.
*
* @author Thiago Rocha Camargo
*/
public class ImageReceiver extends Canvas {
private static final long serialVersionUID = -7000112305305269025L;
private boolean on = true;
private DatagramSocket socket;
private BufferedImage tiles[][];
private static final int tileWidth = ImageTransmitter.tileWidth;
private InetAddress localHost;
private InetAddress remoteHost;
private int localPort;
private int remotePort;
private ImageDecoder decoder;
public ImageReceiver(final InetAddress remoteHost, final int remotePort, final int localPort, int width, int height) {
tiles = new BufferedImage[width][height];
try {
socket = new DatagramSocket(localPort);
localHost = socket.getLocalAddress();
this.remoteHost = remoteHost;
this.remotePort = remotePort;
this.localPort = localPort;
this.decoder = new DefaultDecoder();
new Thread(new Runnable() {
public void run() {
byte buf[] = new byte[1024];
DatagramPacket p = new DatagramPacket(buf, 1024);
try {
while (on) {
socket.receive(p);
int length = p.getLength();
BufferedImage bufferedImage = decoder.decode(new ByteArrayInputStream(p.getData(), 0, length - 2));
if (bufferedImage != null) {
int x = p.getData()[length - 2];
int y = p.getData()[length - 1];
drawTile(x, y, bufferedImage);
}
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
byte buf[] = new byte[1024];
DatagramPacket p = new DatagramPacket(buf, 1024);
try {
while (on) {
p.setAddress(remoteHost);
p.setPort(remotePort);
socket.send(p);
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
catch (SocketException e) {
e.printStackTrace();
}
this.setSize(width, height);
}
public InetAddress getLocalHost() {
return localHost;
}
public InetAddress getRemoteHost() {
return remoteHost;
}
public int getLocalPort() {
return localPort;
}
public int getRemotePort() {
return remotePort;
}
public DatagramSocket getDatagramSocket() {
return socket;
}
public void drawTile(int x, int y, BufferedImage bufferedImage) {
tiles[x][y] = bufferedImage;
//repaint(x * tileWidth, y * tileWidth, tileWidth, tileWidth);
this.getGraphics().drawImage(bufferedImage, tileWidth * x, tileWidth * y, this);
}
public void paint(Graphics g) {
for (int i = 0; i < tiles.length; i++) {
for (int j = 0; j < tiles[0].length; j++) {
g.drawImage(tiles[i][j], tileWidth * i, tileWidth * j, this);
}
}
}
public ImageDecoder getDecoder() {
return decoder;
}
public void setDecoder(ImageDecoder decoder) {
this.decoder = decoder;
}
public void stop(){
this.on=false;
socket.close();
}
}

View file

@ -0,0 +1,219 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.logging.Logger;
/**
* UDP Image Receiver.
* It uses PNG Tiles into UDP packets.
*
* @author Thiago Rocha Camargo
*/
public class ImageTransmitter implements Runnable {
private static final Logger LOGGER = Logger.getLogger(ImageTransmitter.class.getName());
private Robot robot;
private InetAddress localHost;
private InetAddress remoteHost;
private int localPort;
private int remotePort;
public static final int tileWidth = 25;
private boolean on = true;
private boolean transmit = false;
private DatagramSocket socket;
private Rectangle area;
private int tiles[][][];
private int maxI;
private int maxJ;
private ImageEncoder encoder;
public final static int KEYFRAME = 10;
public ImageTransmitter(DatagramSocket socket, InetAddress remoteHost, int remotePort, Rectangle area) {
try {
robot = new Robot();
maxI = (int) Math.ceil(area.getWidth() / tileWidth);
maxJ = (int) Math.ceil(area.getHeight() / tileWidth);
tiles = new int[maxI][maxJ][tileWidth * tileWidth];
this.area = area;
this.socket = socket;
localHost = socket.getLocalAddress();
localPort = socket.getLocalPort();
this.remoteHost = remoteHost;
this.remotePort = remotePort;
this.encoder = new DefaultEncoder();
transmit = true;
}
catch (AWTException e) {
e.printStackTrace();
}
}
public void start() {
byte buf[] = new byte[1024];
final DatagramPacket p = new DatagramPacket(buf, 1024);
int keyframe = 0;
while (on) {
if (transmit) {
BufferedImage capture = robot.createScreenCapture(area);
QuantizeFilter filter = new QuantizeFilter();
capture = filter.filter(capture, null);
long trace = System.currentTimeMillis();
if (++keyframe > KEYFRAME) {
keyframe = 0;
}
LOGGER.fine("KEYFRAME:" + keyframe);
for (int i = 0; i < maxI; i++) {
for (int j = 0; j < maxJ; j++) {
final BufferedImage bufferedImage = capture.getSubimage(i * tileWidth, j * tileWidth, tileWidth, tileWidth);
int pixels[] = new int[tileWidth * tileWidth];
PixelGrabber pg = new PixelGrabber(bufferedImage, 0, 0, tileWidth, tileWidth, pixels, 0, tileWidth);
try {
if (pg.grabPixels()) {
if (keyframe == KEYFRAME || !Arrays.equals(tiles[i][j], pixels)) {
ByteArrayOutputStream baos = encoder.encode(bufferedImage);
if (baos != null) {
try {
Thread.sleep(1);
baos.write(i);
baos.write(j);
byte[] bytesOut = baos.toByteArray();
if (bytesOut.length > 1000)
LOGGER.severe("Bytes out > 1000. Equals " + bytesOut.length);
p.setData(bytesOut);
p.setAddress(remoteHost);
p.setPort(remotePort);
try {
socket.send(p);
}
catch (IOException e) {
e.printStackTrace();
}
tiles[i][j] = pixels;
}
catch (Exception e) {
}
}
}
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
trace = (System.currentTimeMillis() - trace);
LOGGER.fine("Loop Time:" + trace);
if (trace < 500) {
try {
Thread.sleep(500 - trace);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public void run() {
start();
}
/**
* Set Transmit Enabled/Disabled
*
* @param transmit boolean Enabled/Disabled
*/
public void setTransmit(boolean transmit) {
this.transmit = transmit;
}
/**
* Get the encoder used to encode Images Tiles
*
* @return encoder
*/
public ImageEncoder getEncoder() {
return encoder;
}
/**
* Set the encoder used to encode Image Tiles
*
* @param encoder encoder
*/
public void setEncoder(ImageEncoder encoder) {
this.encoder = encoder;
}
/**
* Stops Transmitter
*/
public void stop() {
this.transmit = false;
this.on = false;
socket.close();
}
}

View file

@ -0,0 +1,286 @@
/**
*
* Copyright 2006 Jerry Huxtable
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import java.io.PrintStream;
import java.util.Vector;
import java.util.logging.Logger;
/**
* An image Quantizer based on the Octree algorithm. This is a very basic implementation
* at present and could be much improved by picking the nodes to reduce more carefully
* (i.e. not completely at random) when I get the time.
*/
public class OctTreeQuantizer implements Quantizer {
private static final Logger LOGGER = Logger.getLogger(OctTreeQuantizer.class.getName());
/**
* The greatest depth the tree is allowed to reach
*/
final static int MAX_LEVEL = 5;
/**
* An Octtree node.
*/
class OctTreeNode {
int children;
int level;
OctTreeNode parent;
OctTreeNode leaf[] = new OctTreeNode[8];
boolean isLeaf;
int count;
int totalRed;
int totalGreen;
int totalBlue;
int index;
/**
* A debugging method which prints the tree out.
*/
public void list(PrintStream s, int level) {
String indentStr = "";
for (int i = 0; i < level; i++)
indentStr += " ";
if (count == 0)
LOGGER.fine(indentStr + index + ": count=" + count);
else
LOGGER.fine(indentStr + index + ": count=" + count + " red=" + (totalRed/count) + " green=" + (totalGreen / count) + " blue=" + (totalBlue / count));
for (int i = 0; i < 8; i++)
if (leaf[i] != null)
leaf[i].list(s, level+2);
}
}
private int nodes = 0;
private OctTreeNode root;
private int reduceColors;
private int maximumColors;
private int colors = 0;
private Vector<OctTreeNode>[] colorList;
public OctTreeQuantizer() {
setup(256);
colorList = new Vector[MAX_LEVEL+1];
for (int i = 0; i < MAX_LEVEL+1; i++)
colorList[i] = new Vector<OctTreeNode>();
root = new OctTreeNode();
}
/**
* Initialize the quantizer. This should be called before adding any pixels.
* @param numColors the number of colors we're quantizing to.
*/
public void setup(int numColors) {
maximumColors = numColors;
reduceColors = Math.max(512, numColors * 2);
}
/**
* Add pixels to the quantizer.
* @param pixels the array of ARGB pixels
* @param offset the offset into the array
* @param count the count of pixels
*/
public void addPixels(int[] pixels, int offset, int count) {
for (int i = 0; i < count; i++) {
insertColor(pixels[i+offset]);
if (colors > reduceColors)
reduceTree(reduceColors);
}
}
/**
* Get the color table index for a color.
* @param rgb the color
* @return the index
*/
public int getIndexForColor(int rgb) {
int red = (rgb >> 16) & 0xff;
int green = (rgb >> 8) & 0xff;
int blue = rgb & 0xff;
OctTreeNode node = root;
for (int level = 0; level <= MAX_LEVEL; level++) {
OctTreeNode child;
int bit = 0x80 >> level;
int index = 0;
if ((red & bit) != 0)
index += 4;
if ((green & bit) != 0)
index += 2;
if ((blue & bit) != 0)
index += 1;
child = node.leaf[index];
if (child == null)
return node.index;
else if (child.isLeaf)
return child.index;
else
node = child;
}
LOGGER.fine("getIndexForColor failed");
return 0;
}
private void insertColor(int rgb) {
int red = (rgb >> 16) & 0xff;
int green = (rgb >> 8) & 0xff;
int blue = rgb & 0xff;
OctTreeNode node = root;
// LOGGER.fine("insertColor="+Integer.toHexString(rgb));
for (int level = 0; level <= MAX_LEVEL; level++) {
OctTreeNode child;
int bit = 0x80 >> level;
int index = 0;
if ((red & bit) != 0)
index += 4;
if ((green & bit) != 0)
index += 2;
if ((blue & bit) != 0)
index += 1;
child = node.leaf[index];
if (child == null) {
node.children++;
child = new OctTreeNode();
child.parent = node;
node.leaf[index] = child;
node.isLeaf = false;
nodes++;
colorList[level].addElement(child);
if (level == MAX_LEVEL) {
child.isLeaf = true;
child.count = 1;
child.totalRed = red;
child.totalGreen = green;
child.totalBlue = blue;
child.level = level;
colors++;
return;
}
node = child;
} else if (child.isLeaf) {
child.count++;
child.totalRed += red;
child.totalGreen += green;
child.totalBlue += blue;
return;
} else
node = child;
}
LOGGER.fine("insertColor failed");
}
private void reduceTree(int numColors) {
for (int level = MAX_LEVEL-1; level >= 0; level--) {
Vector<OctTreeNode> v = colorList[level];
if (v != null && v.size() > 0) {
for (int j = 0; j < v.size(); j++) {
OctTreeNode node = v.elementAt(j);
if (node.children > 0) {
for (int i = 0; i < 8; i++) {
OctTreeNode child = node.leaf[i];
if (child != null) {
if (!child.isLeaf)
LOGGER.fine("not a leaf!");
node.count += child.count;
node.totalRed += child.totalRed;
node.totalGreen += child.totalGreen;
node.totalBlue += child.totalBlue;
node.leaf[i] = null;
node.children--;
colors--;
nodes--;
colorList[level+1].removeElement(child);
}
}
node.isLeaf = true;
colors++;
if (colors <= numColors)
return;
}
}
}
}
LOGGER.fine("Unable to reduce the OctTree");
}
/**
* Build the color table.
* @return the color table
*/
public int[] buildColorTable() {
int[] table = new int[colors];
buildColorTable(root, table, 0);
return table;
}
/**
* A quick way to use the quantizer. Just create a table the right size and pass in the pixels.
* @param inPixels the input colors
* @param table the output color table
*/
public void buildColorTable(int[] inPixels, int[] table) {
int count = inPixels.length;
maximumColors = table.length;
for (int i = 0; i < count; i++) {
insertColor(inPixels[i]);
if (colors > reduceColors)
reduceTree(reduceColors);
}
if (colors > maximumColors)
reduceTree(maximumColors);
buildColorTable(root, table, 0);
}
private int buildColorTable(OctTreeNode node, int[] table, int index) {
if (colors > maximumColors)
reduceTree(maximumColors);
if (node.isLeaf) {
int count = node.count;
table[index] = 0xff000000 |
((node.totalRed/count) << 16) |
((node.totalGreen/count) << 8) |
node.totalBlue/count;
node.index = index++;
} else {
for (int i = 0; i < 8; i++) {
if (node.leaf[i] != null) {
node.index = index;
index = buildColorTable(node.leaf[i], table, index);
}
}
}
return index;
}
}

View file

@ -0,0 +1,223 @@
/**
*
* Copyright 2006 Jerry Huxtable
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import java.awt.*;
import java.util.Random;
/**
* Some more useful math functions for image processing.
* These are becoming obsolete as we move to Java2D. Use MiscComposite instead.
*/
public class PixelUtils {
public final static int REPLACE = 0;
public final static int NORMAL = 1;
public final static int MIN = 2;
public final static int MAX = 3;
public final static int ADD = 4;
public final static int SUBTRACT = 5;
public final static int DIFFERENCE = 6;
public final static int MULTIPLY = 7;
public final static int HUE = 8;
public final static int SATURATION = 9;
public final static int VALUE = 10;
public final static int COLOR = 11;
public final static int SCREEN = 12;
public final static int AVERAGE = 13;
public final static int OVERLAY = 14;
public final static int CLEAR = 15;
public final static int EXCHANGE = 16;
public final static int DISSOLVE = 17;
public final static int DST_IN = 18;
public final static int ALPHA = 19;
public final static int ALPHA_TO_GRAY = 20;
private static Random randomGenerator = new Random();
/**
* Clamp a value to the range 0..255
*/
public static int clamp(int c) {
if (c < 0)
return 0;
if (c > 255)
return 255;
return c;
}
public static int interpolate(int v1, int v2, float f) {
return clamp((int)(v1+f*(v2-v1)));
}
public static int brightness(int rgb) {
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
return (r+g+b)/3;
}
public static boolean nearColors(int rgb1, int rgb2, int tolerance) {
int r1 = (rgb1 >> 16) & 0xff;
int g1 = (rgb1 >> 8) & 0xff;
int b1 = rgb1 & 0xff;
int r2 = (rgb2 >> 16) & 0xff;
int g2 = (rgb2 >> 8) & 0xff;
int b2 = rgb2 & 0xff;
return Math.abs(r1-r2) <= tolerance && Math.abs(g1-g2) <= tolerance && Math.abs(b1-b2) <= tolerance;
}
private final static float hsb1[] = new float[3];//FIXME-not thread safe
private final static float hsb2[] = new float[3];//FIXME-not thread safe
// Return rgb1 painted onto rgb2
public static int combinePixels(int rgb1, int rgb2, int op) {
return combinePixels(rgb1, rgb2, op, 0xff);
}
public static int combinePixels(int rgb1, int rgb2, int op, int extraAlpha, int channelMask) {
return (rgb2 & ~channelMask) | combinePixels(rgb1 & channelMask, rgb2, op, extraAlpha);
}
public static int combinePixels(int rgb1, int rgb2, int op, int extraAlpha) {
if (op == REPLACE)
return rgb1;
int a1 = (rgb1 >> 24) & 0xff;
int r1 = (rgb1 >> 16) & 0xff;
int g1 = (rgb1 >> 8) & 0xff;
int b1 = rgb1 & 0xff;
int a2 = (rgb2 >> 24) & 0xff;
int r2 = (rgb2 >> 16) & 0xff;
int g2 = (rgb2 >> 8) & 0xff;
int b2 = rgb2 & 0xff;
switch (op) {
case NORMAL:
break;
case MIN:
r1 = Math.min(r1, r2);
g1 = Math.min(g1, g2);
b1 = Math.min(b1, b2);
break;
case MAX:
r1 = Math.max(r1, r2);
g1 = Math.max(g1, g2);
b1 = Math.max(b1, b2);
break;
case ADD:
r1 = clamp(r1+r2);
g1 = clamp(g1+g2);
b1 = clamp(b1+b2);
break;
case SUBTRACT:
r1 = clamp(r2-r1);
g1 = clamp(g2-g1);
b1 = clamp(b2-b1);
break;
case DIFFERENCE:
r1 = clamp(Math.abs(r1-r2));
g1 = clamp(Math.abs(g1-g2));
b1 = clamp(Math.abs(b1-b2));
break;
case MULTIPLY:
r1 = clamp(r1*r2/255);
g1 = clamp(g1*g2/255);
b1 = clamp(b1*b2/255);
break;
case DISSOLVE:
if ((randomGenerator.nextInt() & 0xff) <= a1) {
r1 = r2;
g1 = g2;
b1 = b2;
}
break;
case AVERAGE:
r1 = (r1+r2)/2;
g1 = (g1+g2)/2;
b1 = (b1+b2)/2;
break;
case HUE:
case SATURATION:
case VALUE:
case COLOR:
Color.RGBtoHSB(r1, g1, b1, hsb1);
Color.RGBtoHSB(r2, g2, b2, hsb2);
switch (op) {
case HUE:
hsb2[0] = hsb1[0];
break;
case SATURATION:
hsb2[1] = hsb1[1];
break;
case VALUE:
hsb2[2] = hsb1[2];
break;
case COLOR:
hsb2[0] = hsb1[0];
hsb2[1] = hsb1[1];
break;
}
rgb1 = Color.HSBtoRGB(hsb2[0], hsb2[1], hsb2[2]);
r1 = (rgb1 >> 16) & 0xff;
g1 = (rgb1 >> 8) & 0xff;
b1 = rgb1 & 0xff;
break;
case SCREEN:
r1 = 255 - ((255 - r1) * (255 - r2)) / 255;
g1 = 255 - ((255 - g1) * (255 - g2)) / 255;
b1 = 255 - ((255 - b1) * (255 - b2)) / 255;
break;
case OVERLAY:
int m, s;
s = 255 - ((255 - r1) * (255 - r2)) / 255;
m = r1 * r2 / 255;
r1 = (s * r1 + m * (255 - r1)) / 255;
s = 255 - ((255 - g1) * (255 - g2)) / 255;
m = g1 * g2 / 255;
g1 = (s * g1 + m * (255 - g1)) / 255;
s = 255 - ((255 - b1) * (255 - b2)) / 255;
m = b1 * b2 / 255;
b1 = (s * b1 + m * (255 - b1)) / 255;
break;
case CLEAR:
r1 = g1 = b1 = 0xff;
break;
case DST_IN:
r1 = clamp((r2*a1)/255);
g1 = clamp((g2*a1)/255);
b1 = clamp((b2*a1)/255);
a1 = clamp((a2*a1)/255);
return (a1 << 24) | (r1 << 16) | (g1 << 8) | b1;
case ALPHA:
a1 = a1*a2/255;
return (a1 << 24) | (r2 << 16) | (g2 << 8) | b2;
case ALPHA_TO_GRAY:
int na = 255-a1;
return (a1 << 24) | (na << 16) | (na << 8) | na;
}
if (extraAlpha != 0xff || a1 != 0xff) {
a1 = a1*extraAlpha/255;
int a3 = (255-a1)*a2/255;
r1 = clamp((r1*a1+r2*a3)/255);
g1 = clamp((g1*a1+g2*a3)/255);
b1 = clamp((b1*a1+b2*a3)/255);
a1 = clamp(a1+a3);
}
return (a1 << 24) | (r1 << 16) | (g1 << 8) | b1;
}
}

View file

@ -0,0 +1,178 @@
/**
*
* Copyright 2006 Jerry Huxtable
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import java.awt.*;
/**
* A filter which quantizes an image to a set number of colors - useful for producing
* images which are to be encoded using an index color model. The filter can perform
* Floyd-Steinberg error-diffusion dithering if required. At present, the quantization
* is done using an octtree algorithm but I eventually hope to add more quantization
* methods such as median cut. Note: at present, the filter produces an image which
* uses the RGB color model (because the application it was written for required it).
* I hope to extend it to produce an IndexColorModel by request.
*/
public class QuantizeFilter extends WholeImageFilter {
/**
* Floyd-Steinberg dithering matrix.
*/
protected final static int[] matrix = {
0, 0, 0,
0, 0, 7,
3, 5, 1,
};
private int sum = 3+5+7+1;
private boolean dither;
private int numColors = 256;
private boolean serpentine = true;
/**
* Set the number of colors to quantize to.
* @param numColors the number of colors. The default is 256.
*/
public void setNumColors(int numColors) {
this.numColors = Math.min(Math.max(numColors, 8), 256);
}
/**
* Get the number of colors to quantize to.
* @return the number of colors.
*/
public int getNumColors() {
return numColors;
}
/**
* Set whether to use dithering or not. If not, the image is posterized.
* @param dither true to use dithering
*/
public void setDither(boolean dither) {
this.dither = dither;
}
/**
* Return the dithering setting
* @return the current setting
*/
public boolean getDither() {
return dither;
}
/**
* Set whether to use a serpentine pattern for return or not. This can reduce 'avalanche' artifacts in the output.
* @param serpentine true to use serpentine pattern
*/
public void setSerpentine(boolean serpentine) {
this.serpentine = serpentine;
}
/**
* Return the serpentine setting
* @return the current setting
*/
public boolean getSerpentine() {
return serpentine;
}
public void quantize(int[] inPixels, int[] outPixels, int width, int height, int numColors, boolean dither, boolean serpentine) {
int count = width*height;
Quantizer quantizer = new OctTreeQuantizer();
quantizer.setup(numColors);
quantizer.addPixels(inPixels, 0, count);
int[] table = quantizer.buildColorTable();
if (!dither) {
for (int i = 0; i < count; i++)
outPixels[i] = table[quantizer.getIndexForColor(inPixels[i])];
} else {
int index = 0;
for (int y = 0; y < height; y++) {
boolean reverse = serpentine && (y & 1) == 1;
int direction;
if (reverse) {
index = y*width+width-1;
direction = -1;
} else {
index = y*width;
direction = 1;
}
for (int x = 0; x < width; x++) {
int rgb1 = inPixels[index];
int rgb2 = table[quantizer.getIndexForColor(rgb1)];
outPixels[index] = rgb2;
int r1 = (rgb1 >> 16) & 0xff;
int g1 = (rgb1 >> 8) & 0xff;
int b1 = rgb1 & 0xff;
int r2 = (rgb2 >> 16) & 0xff;
int g2 = (rgb2 >> 8) & 0xff;
int b2 = rgb2 & 0xff;
int er = r1-r2;
int eg = g1-g2;
int eb = b1-b2;
for (int i = -1; i <= 1; i++) {
int iy = i+y;
if (0 <= iy && iy < height) {
for (int j = -1; j <= 1; j++) {
int jx = j+x;
if (0 <= jx && jx < width) {
int w;
if (reverse)
w = matrix[(i+1)*3-j+1];
else
w = matrix[(i+1)*3+j+1];
if (w != 0) {
int k = reverse ? index - j : index + j;
rgb1 = inPixels[k];
r1 = (rgb1 >> 16) & 0xff;
g1 = (rgb1 >> 8) & 0xff;
b1 = rgb1 & 0xff;
r1 += er * w/sum;
g1 += eg * w/sum;
b1 += eb * w/sum;
inPixels[k] = (PixelUtils.clamp(r1) << 16) | (PixelUtils.clamp(g1) << 8) | PixelUtils.clamp(b1);
}
}
}
}
}
index += direction;
}
}
}
}
protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
int[] outPixels = new int[width*height];
quantize(inPixels, outPixels, width, height, numColors, dither, serpentine);
return outPixels;
}
public String toString() {
return "Colors/Quantize...";
}
}

View file

@ -0,0 +1,53 @@
/**
*
* Copyright 2006 Jerry Huxtable
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
/**
* The interface for an image quantizer. The addColor method is called (repeatedly
* if necessary) with all the image pixels. A color table can then be returned by
* calling the buildColorTable method.
*/
public interface Quantizer {
/**
* Initialize the quantizer. This should be called before adding any pixels.
* @param numColors the number of colors we're quantizing to.
*/
public void setup(int numColors);
/**
* Add pixels to the quantizer.
* @param pixels the array of ARGB pixels
* @param offset the offset into the array
* @param count the count of pixels
*/
public void addPixels(int[] pixels, int offset, int count);
/**
* Build a color table from the added pixels.
* @return an array of ARGB pixels representing a color table
*/
public int[] buildColorTable();
/**
* Using the previously-built color table, return the index into that table for a pixel.
* This is guaranteed to return a valid index - returning the index of a color closer
* to that requested if necessary.
* @param rgb the pixel to find
* @return the pixel's index in the color table
*/
public int getIndexForColor(int rgb);
}

View file

@ -0,0 +1,86 @@
/**
*
* Copyright 2006 Jerry Huxtable
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.sshare.api;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
/**
* A filter which acts as a superclass for filters which need to have the whole image in memory
* to do their stuff.
*/
public abstract class WholeImageFilter extends AbstractBufferedImageOp {
/**
* The output image bounds.
*/
protected Rectangle transformedSpace;
/**
* The input image bounds.
*/
protected Rectangle originalSpace;
/**
* Construct a WholeImageFilter.
*/
public WholeImageFilter() {
}
public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
int width = src.getWidth();
int height = src.getHeight();
int type = src.getType();
WritableRaster srcRaster = src.getRaster();
originalSpace = new Rectangle(0, 0, width, height);
transformedSpace = new Rectangle(0, 0, width, height);
transformSpace(transformedSpace);
if ( dst == null ) {
ColorModel dstCM = src.getColorModel();
dst = new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(transformedSpace.width, transformedSpace.height), dstCM.isAlphaPremultiplied(), null);
}
WritableRaster dstRaster = dst.getRaster();
int[] inPixels = getRGB( src, 0, 0, width, height, null );
inPixels = filterPixels( width, height, inPixels, transformedSpace );
setRGB( dst, 0, 0, transformedSpace.width, transformedSpace.height, inPixels );
return dst;
}
/**
* Calculate output bounds for given input bounds.
* @param rect input and output rectangle
*/
protected void transformSpace(Rectangle rect) {
}
/**
* Actually filter the pixels.
* @param width the image width
* @param height the image height
* @param inPixels the image pixels
* @param transformedSpace the output bounds
* @return the output pixels
*/
protected abstract int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace );
}

View file

@ -0,0 +1,89 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.test;
import org.jivesoftware.smackx.jingle.media.JingleMediaManager;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.JingleTransportManager;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import org.jivesoftware.smackx.jingle.JingleSession;
import java.util.*;
/**
* Implements a MediaManager for test purposes.
*
* @author Thiago Camargo
*/
public class TestMediaManager extends JingleMediaManager {
public static final String MEDIA_NAME = "TestMedia";
private List<PayloadType> payloads = new ArrayList<PayloadType>();
private PayloadType preferredPayloadType = null;
public TestMediaManager(JingleTransportManager transportManager) {
super(transportManager);
}
/**
* Return all supported Payloads for this Manager.
*
* @return The Payload List
*/
public List<PayloadType> getPayloads() {
return payloads;
}
public void setPayloads(List<PayloadType> payloads) {
this.payloads.addAll(payloads);
}
/**
* Returns a new JingleMediaSession
*
* @param payloadType payloadType
* @param remote remote Candidate
* @param local local Candidate
* @return JingleMediaSession JingleMediaSession
*/
public JingleMediaSession createMediaSession(PayloadType payloadType, final TransportCandidate remote,
final TransportCandidate local, final JingleSession jingleSession) {
TestMediaSession session = null;
session = new TestMediaSession(payloadType, remote, local, "", jingleSession);
return session;
}
public PayloadType getPreferredPayloadType() {
if (preferredPayloadType != null)
return preferredPayloadType;
return super.getPreferredPayloadType();
}
public void setPreferredPayloadType(PayloadType preferredPayloadType) {
this.preferredPayloadType = preferredPayloadType;
}
public String getName() {
return MEDIA_NAME;
}
}

View file

@ -0,0 +1,89 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.mediaimpl.test;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.media.JingleMediaSession;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
/**
* This Class implements a complete JingleMediaSession for unit testing.
*
* @author Thiago Camargo
*/
public class TestMediaSession extends JingleMediaSession {
/**
* Creates a TestMediaSession with defined payload type, remote and local candidates
*
* @param payloadType Payload of the jmf
* @param remote the remote information. The candidate that the jmf will be sent to.
* @param local the local information. The candidate that will receive the jmf
* @param locator media locator
*/
public TestMediaSession(final PayloadType payloadType, final TransportCandidate remote, final TransportCandidate local,
final String locator, JingleSession jingleSession) {
super(payloadType, remote, local, "Test", jingleSession);
initialize();
}
/**
* Initialize the screen share channels.
*/
public void initialize() {
}
/**
* Starts transmission and for NAT Traversal reasons start receiving also.
*/
public void startTrasmit() {
}
/**
* Set transmit activity. If the active is true, the instance should trasmit.
* If it is set to false, the instance should pause transmit.
*
* @param active active state
*/
public void setTrasmit(boolean active) {
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void startReceive() {
// Do nothing
}
/**
* Stops transmission and for NAT Traversal reasons stop receiving also.
*/
public void stopTrasmit() {
}
/**
* For NAT Reasons this method does nothing. Use startTransmit() to start transmit and receive jmf
*/
public void stopReceive() {
}
}

View file

@ -0,0 +1,121 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* Basic Resolver takes all IP addresses of the interfaces and uses the
* first non-loopback address.
* A very simple and easy to use resolver.
*/
public class BasicResolver extends TransportResolver {
/**
* Constructor.
*/
public BasicResolver() {
super();
}
/**
* Resolve the IP address.
* <p/>
* The BasicResolver takes the IP addresses of the interfaces and uses the
* first non-loopback, non-linklocal and non-sitelocal address.
* @throws NotConnectedException
*/
public synchronized void resolve(JingleSession session) throws XMPPException, NotConnectedException {
setResolveInit();
clearCandidates();
Enumeration<NetworkInterface> ifaces = null;
try {
ifaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
e.printStackTrace();
}
while (ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = iaddresses.nextElement();
if (!iaddress.isLoopbackAddress() && !iaddress.isLinkLocalAddress() && !iaddress.isSiteLocalAddress()) {
TransportCandidate tr = new TransportCandidate.Fixed(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName(), getFreePort());
tr.setLocalIp(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName());
addCandidate(tr);
setResolveEnd();
return;
}
}
}
try {
ifaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
e.printStackTrace();
}
while (ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = iaddresses.nextElement();
if (!iaddress.isLoopbackAddress() && !iaddress.isLinkLocalAddress()) {
TransportCandidate tr = new TransportCandidate.Fixed(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName(), getFreePort());
tr.setLocalIp(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName());
addCandidate(tr);
setResolveEnd();
return;
}
}
}
try {
TransportCandidate tr = new TransportCandidate.Fixed(InetAddress.getLocalHost().getHostAddress() != null ? InetAddress.getLocalHost().getHostAddress() : InetAddress.getLocalHost().getHostName(), getFreePort());
tr.setLocalIp(InetAddress.getLocalHost().getHostAddress() != null ? InetAddress.getLocalHost().getHostAddress() : InetAddress.getLocalHost().getHostName());
addCandidate(tr);
} catch (Exception e) {
e.printStackTrace();
}
setResolveEnd();
}
public void initialize() throws XMPPException {
setInitialized();
}
public void cancel() throws XMPPException {
// Nothing to do here
}
}

View file

@ -0,0 +1,30 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smackx.jingle.JingleSession;
/**
* A Basic Jingle Transport Manager implementation.
*
*/
public class BasicTransportManager extends JingleTransportManager{
protected TransportResolver createResolver(JingleSession session) {
return new BasicResolver();
}
}

View file

@ -0,0 +1,151 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smackx.jingle.JingleSession;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Random;
/**
* Bridged Resolver use a RTPBridge Service to add a relayed candidate.
* A very reliable solution for NAT Traversal.
* <p/>
* The resolver verify is the XMPP Server that the client is connected offer this service.
* If the server supports, a candidate is requested from the service.
* The resolver adds this candidate
*/
public class BridgedResolver extends TransportResolver {
XMPPConnection connection;
Random random = new Random();
long sid;
/**
* Constructor.
* A Bridged Resolver need a XMPPConnection to connect to a RTP Bridge.
*/
public BridgedResolver(XMPPConnection connection) {
super();
this.connection = connection;
}
/**
* Resolve Bridged Candidate.
* <p/>
* The BridgedResolver takes the IP addresse and ports of a jmf proxy service.
* @throws NotConnectedException
*/
public synchronized void resolve(JingleSession session) throws XMPPException, NotConnectedException {
setResolveInit();
clearCandidates();
sid = Math.abs(random.nextLong());
RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, String.valueOf(sid));
String localIp = getLocalHost();
TransportCandidate localCandidate = new TransportCandidate.Fixed(
rtpBridge.getIp(), rtpBridge.getPortA());
localCandidate.setLocalIp(localIp);
TransportCandidate remoteCandidate = new TransportCandidate.Fixed(
rtpBridge.getIp(), rtpBridge.getPortB());
remoteCandidate.setLocalIp(localIp);
localCandidate.setSymmetric(remoteCandidate);
remoteCandidate.setSymmetric(localCandidate);
localCandidate.setPassword(rtpBridge.getPass());
remoteCandidate.setPassword(rtpBridge.getPass());
localCandidate.setSessionId(rtpBridge.getSid());
remoteCandidate.setSessionId(rtpBridge.getSid());
localCandidate.setConnection(this.connection);
remoteCandidate.setConnection(this.connection);
addCandidate(localCandidate);
setResolveEnd();
}
public void initialize() throws SmackException, XMPPErrorException {
clearCandidates();
if (!RTPBridge.serviceAvailable(connection)) {
setInitialized();
throw new SmackException("No RTP Bridge service available");
}
setInitialized();
}
public void cancel() throws XMPPException {
// Nothing to do here
}
public static String getLocalHost() {
Enumeration<NetworkInterface> ifaces = null;
try {
ifaces = NetworkInterface.getNetworkInterfaces();
}
catch (SocketException e) {
e.printStackTrace();
}
while (ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = iaddresses.nextElement();
if (!iaddress.isLoopbackAddress() && !iaddress.isLinkLocalAddress() && !iaddress.isSiteLocalAddress() && !(iaddress instanceof Inet6Address)) {
return iaddress.getHostAddress();
}
}
}
try {
return InetAddress.getLocalHost().getHostAddress();
}
catch (Exception e) {
e.printStackTrace();
}
return "127.0.0.1";
}
}

View file

@ -0,0 +1,81 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.listeners.CreatedJingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingle.media.PayloadType;
/**
* A Jingle Transport Manager implementation to be used for NAT Networks.
* This kind of transport needs that the connected XMPP Server provide a Bridge Service. (http://www.jivesoftware.com/protocol/rtpbridge)
* To relay the jmf outside the NAT.
*
* @author Thiago Camargo
*/
public class BridgedTransportManager extends JingleTransportManager implements JingleSessionListener, CreatedJingleSessionListener {
XMPPConnection xmppConnection;
public BridgedTransportManager(XMPPConnection xmppConnection) {
super();
this.xmppConnection = xmppConnection;
}
/**
* Return the correspondent resolver
*
* @param session correspondent Jingle Session
* @return resolver
*/
protected TransportResolver createResolver(JingleSession session) {
BridgedResolver bridgedResolver = new BridgedResolver(this.xmppConnection);
return bridgedResolver;
}
// Implement a Session Listener to relay candidates after establishment
public void sessionEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc, JingleSession jingleSession) throws NotConnectedException {
RTPBridge rtpBridge = RTPBridge.relaySession(lc.getConnection(), lc.getSessionId(), lc.getPassword(), rc, lc);
}
public void sessionDeclined(String reason, JingleSession jingleSession) {
}
public void sessionRedirected(String redirection, JingleSession jingleSession) {
}
public void sessionClosed(String reason, JingleSession jingleSession) {
}
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession) {
}
public void sessionMediaReceived(JingleSession jingleSession, String participant) {
// Do Nothing
}
// Session Created
public void sessionCreated(JingleSession jingleSession) {
jingleSession.addListener(this);
}
}

View file

@ -0,0 +1,37 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.net.DatagramPacket;
/**
* Listener for datagram packets received.
*
* @author Thiago Camargo
*/
public interface DatagramListener {
/**
* Called when a datagram is received. If the method returns false, the
* packet MUST NOT be resent from the received Channel.
*
* @param datagramPacket the datagram packet received.
* @return ?
*/
public boolean datagramReceived(DatagramPacket datagramPacket);
}

View file

@ -0,0 +1,85 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
/**
* The FixedResolver is a resolver where
* the external address and port are previously known when the object is
* initialized.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public class FixedResolver extends TransportResolver {
TransportCandidate fixedCandidate;
/**
* Constructor.
*/
public FixedResolver(String ip, int port) {
super();
setFixedCandidate(ip, port);
}
/**
* Create a basic resolver, where we provide the IP and port.
*
* @param ip an IP address
* @param port a port
*/
public void setFixedCandidate(String ip, int port) {
fixedCandidate = new TransportCandidate.Fixed(ip, port);
}
/**
* Resolve the IP address.
* @throws NotConnectedException
*/
public synchronized void resolve(JingleSession session) throws XMPPException, NotConnectedException {
if (!isResolving()) {
setResolveInit();
clearCandidates();
if (fixedCandidate.getLocalIp() == null)
fixedCandidate.setLocalIp(fixedCandidate.getIp());
if (fixedCandidate != null) {
addCandidate(fixedCandidate);
}
setResolveEnd();
}
}
/**
* Initialize the resolver.
*
* @throws XMPPException
*/
public void initialize() throws XMPPException {
setInitialized();
}
public void cancel() throws XMPPException {
// Nothing to do here
}
}

View file

@ -0,0 +1,63 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.listeners.CreatedJingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingle.media.PayloadType;
/**
* A Fixed Jingle Transport Manager implementation.
*
*/
public class FixedTransportManager extends JingleTransportManager implements JingleSessionListener, CreatedJingleSessionListener {
FixedResolver resolver;
public FixedTransportManager(FixedResolver inResolver) {
resolver = inResolver;
}
protected TransportResolver createResolver(JingleSession session) {
return resolver;
}
public void sessionEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc, JingleSession jingleSession) {
}
public void sessionDeclined(String reason, JingleSession jingleSession) {
}
public void sessionRedirected(String redirection, JingleSession jingleSession) {
}
public void sessionClosed(String reason, JingleSession jingleSession) {
}
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession) {
}
public void sessionMediaReceived(JingleSession jingleSession, String participant) {
// Do Nothing
}
public void sessionCreated(JingleSession jingleSession) {
jingleSession.addListener(this);
}
}

View file

@ -0,0 +1,146 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A very Simple HTTP Server
*/
public class HttpServer {
private static final Logger LOGGER = Logger.getLogger(HttpServer.class.getName());
public HttpServer(int port) {
ServerSocket server_socket;
try {
server_socket = new ServerSocket(port);
LOGGER.fine("httpServer running on port " +
server_socket.getLocalPort());
while (true) {
Socket socket = server_socket.accept();
LOGGER.fine("New connection accepted " +
socket.getInetAddress() +
":" + socket.getPort());
try {
HttpRequestHandler request =
new HttpRequestHandler(socket);
Thread thread = new Thread(request);
thread.start();
}
catch (Exception e) {
LOGGER.log(Level.FINE, "Exception", e);
}
}
}
catch (IOException e) {
LOGGER.log(Level.FINE, "Exception", e);
}
}
public static void main(String args[]) {
HttpServer httpServer = new HttpServer(Integer.parseInt(args[0]));
}
class HttpRequestHandler implements Runnable {
final static String CRLF = "\r\n";
Socket socket;
InputStream input;
OutputStream output;
BufferedReader br;
public HttpRequestHandler(Socket socket) throws Exception {
this.socket = socket;
this.input = socket.getInputStream();
this.output = socket.getOutputStream();
this.br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
public void run() {
try {
processRequest();
}
catch (Exception e) {
e.printStackTrace();
}
}
private void processRequest() throws Exception {
while (true) {
String headerLine = br.readLine();
LOGGER.fine(headerLine);
if (headerLine.equals(CRLF) || headerLine.equals("")) break;
StringTokenizer s = new StringTokenizer(headerLine);
String temp = s.nextToken();
if (temp.equals("GET")) {
String serverLine = "Server: Simple httpServer";
String contentTypeLine = "text";
String entityBody = "";
String contentLengthLine;
String statusLine = "HTTP/1.0 200 OK" + CRLF;
contentLengthLine = "Content-Length: "
+ (new Integer(entityBody.length())).toString() + CRLF;
contentTypeLine = "text/html";
output.write(statusLine.getBytes());
output.write(serverLine.getBytes());
output.write(contentTypeLine.getBytes());
output.write(contentLengthLine.getBytes());
output.write(CRLF.getBytes());
output.write(entityBody.getBytes());
}
}
try {
output.close();
br.close();
socket.close();
}
catch (Exception e) {
// Do Nothing
e.printStackTrace();
}
}
}
}

View file

@ -0,0 +1,415 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.logging.Logger;
/**
* ICE Transport candidate.
* <p/>
* A candidate represents the possible transport for data interchange between
* the two endpoints.
*
* @author Thiago Camargo
*/
public class ICECandidate extends TransportCandidate implements Comparable<ICECandidate> {
private static final Logger LOGGER = Logger.getLogger(ICECandidate.class.getName());
private String id; // An identification
private String username;
private int preference;
private Protocol proto;
private Channel channel;
private int network;
private Type type;
public enum Type {
relay, srflx, prflx, local, host
}
public ICECandidate() {
super();
}
/**
* Constructor with the basic elements of a transport definition.
*
* @param ip the IP address to use as a local address
* @param generation used to keep track of the candidates
* @param network used for diagnostics (used when the machine has
* several NICs)
* @param password user name, as it is used in ICE
* @param port the port at the candidate IP address
* @param username user name, as it is used in ICE
* @param preference preference for this transportElement, as it is used
* in ICE
* @param type type as defined in ICE-12
*/
public ICECandidate(String ip, int generation, int network,
String password, int port, String username,
int preference, Type type) {
super(ip, port, generation);
proto = Protocol.UDP;
channel = Channel.MYRTPVOICE;
this.network = network;
this.password = password;
this.username = username;
this.preference = preference;
this.type = type;
}
/**
* Get the ID
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Set the ID
*
* @param id the id to set
*/
public void setId(String id) {
this.id = id;
}
/**
* Get the protocol used for the transmission
*
* @return the protocol used for transmission
*/
public Protocol getProto() {
return proto;
}
/**
* Set the protocol for the transmission
*
* @param proto the protocol to use
*/
public void setProto(Protocol proto) {
this.proto = proto;
}
/**
* Get the network interface used for this connection
*
* @return the interface number
*/
public int getNetwork() {
return network;
}
/**
* Set the interface for this connection
*
* @param network the interface number
*/
public void setNetwork(int network) {
this.network = network;
}
/**
* Get the username for this transportElement in ICE
*
* @return a username string
*/
public String getUsername() {
return username;
}
/**
* Get the channel
*
* @return the channel associated
*/
public Channel getChannel() {
return channel;
}
/**
* Set the channel for this transportElement
*
* @param channel the new channel
*/
public void setChannel(Channel channel) {
this.channel = channel;
}
/**
* Set the username for this transportElement in ICE
*
* @param username the username used in ICE
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Get the preference number for this transportElement
*
* @return the preference for this transportElement
*/
public int getPreference() {
return preference;
}
/**
* Set the preference order for this transportElement
*
* @param preference a number identifying the preference (as defined in
* ICE)
*/
public void setPreference(int preference) {
this.preference = preference;
}
/**
* Get the Candidate Type
*
* @return candidate Type
*/
public Type getType() {
return type;
}
/**
* Set the Candidate Type
*
* @param type candidate type.
*/
public void setType(Type type) {
this.type = type;
}
/**
* Check if a transport candidate is usable. The transport resolver should
* check if the transport candidate the other endpoint has provided is
* usable.
* <p/>
* ICE Candidate can check connectivity using UDP echo Test.
*/
public void check(final List<TransportCandidate> localCandidates) {
//TODO candidate is being checked trigger
//candidatesChecking.add(cand);
final ICECandidate checkingCandidate = this;
Thread checkThread = new Thread(new Runnable() {
public void run() {
final TestResult result = new TestResult();
// Media Proxy don't have Echo features.
// If its a relayed candidate we assumpt that is NOT Valid while other candidates still being checked.
// The negotiator MUST add then in the correct situations
if (getType().equals("relay")) {
triggerCandidateChecked(false);
return;
}
ResultListener resultListener = new ResultListener() {
public void testFinished(TestResult testResult, TransportCandidate candidate) {
if (testResult.isReachable() && checkingCandidate.equals(candidate)) {
result.setResult(true);
LOGGER.fine("Candidate reachable: " + candidate.getIp() + ":" + candidate.getPort() + " from " + getIp() +":" + getPort());
}
}
};
for (TransportCandidate candidate : localCandidates) {
CandidateEcho echo = candidate.getCandidateEcho();
if (echo != null) {
if (candidate instanceof ICECandidate) {
ICECandidate iceCandidate = (ICECandidate) candidate;
if (iceCandidate.getType().equals(getType())) {
try {
echo.addResultListener(resultListener);
InetAddress address = InetAddress.getByName(getIp());
echo.testASync(checkingCandidate, getPassword());
}
catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
}
}
for (int i = 0; i < 10 && !result.isReachable(); i++)
try {
LOGGER.severe("ICE Candidate retry #" + i);
Thread.sleep(400);
}
catch (InterruptedException e) {
e.printStackTrace();
}
for (TransportCandidate candidate : localCandidates) {
CandidateEcho echo = candidate.getCandidateEcho();
if (echo != null) {
echo.removeResultListener(resultListener);
}
}
triggerCandidateChecked(result.isReachable());
//TODO candidate is being checked trigger
//candidatesChecking.remove(cand);
}
}, "Transport candidate check");
checkThread.setName("Transport candidate test");
checkThread.start();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ICECandidate other = (ICECandidate) obj;
if (getChannel() == null) {
if (other.getChannel() != null) {
return false;
}
}
else if (!getChannel().equals(other.getChannel())) {
return false;
}
if (getId() == null) {
if (other.getId() != null) {
return false;
}
}
else if (!getId().equals(other.getId())) {
return false;
}
if (getNetwork() != other.getNetwork()) {
return false;
}
if (getPassword() == null) {
if (other.getPassword() != null) {
return false;
}
}
else if (!getPassword().equals(other.password)) {
return false;
}
if (getPreference() != other.getPreference()) {
return false;
}
if (getProto() == null) {
if (other.getProto() != null) {
return false;
}
}
else if (!getProto().equals(other.getProto())) {
return false;
}
if (getUsername() == null) {
if (other.getUsername() != null) {
return false;
}
}
else if (!getUsername().equals(other.getUsername())) {
return false;
}
if (getIp() == null) {
if (other.getIp() != null) {
return false;
}
}
else if (!getIp().equals(other.getIp())) {
return false;
}
if (getPort() != other.getPort()) {
return false;
}
if (getType() == null) {
if (other.getType() != null) {
return false;
}
}
else if (!getType().equals(other.getType())) {
return false;
}
return true;
}
public boolean isNull() {
if (super.isNull()) {
return true;
}
else if (getProto().isNull()) {
return true;
}
else if (getChannel().isNull()) {
return true;
}
return false;
}
/**
* Compare the to other Transport candidate.
*
* @param arg another Transport candidate
* @return a negative integer, zero, or a positive integer as this
* object is less than, equal to, or greater than the specified
* object
*/
public int compareTo(ICECandidate arg) {
if (getPreference() < arg.getPreference()) {
return -1;
} else if (getPreference() > arg.getPreference()) {
return 1;
}
return 0;
}
}

View file

@ -0,0 +1,279 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
import de.javawi.jstun.test.demo.ice.Candidate;
import de.javawi.jstun.test.demo.ice.ICENegociator;
import de.javawi.jstun.util.UtilityException;
/**
* ICE Resolver for Jingle transport method that results in sending data between two entities using the Interactive Connectivity Establishment (ICE) methodology. (XEP-0176)
* The goal of this resolver is to make possible to establish and manage out-of-band connections between two XMPP entities, even if they are behind Network Address Translators (NATs) or firewalls.
* To use this resolver you must have a STUN Server and be in a non STUN blocked network. Or use a XMPP server with public IP detection Service.
*
* @author Thiago Camargo
*/
public class ICEResolver extends TransportResolver {
private static final Logger LOGGER = Logger.getLogger(ICEResolver.class.getName());
XMPPConnection connection;
Random random = new Random();
long sid;
String server;
int port;
static Map<String, ICENegociator> negociatorsMap = new HashMap<String, ICENegociator>();
//ICENegociator iceNegociator = null;
public ICEResolver(XMPPConnection connection, String server, int port) {
super();
this.connection = connection;
this.server = server;
this.port = port;
this.setType(Type.ice);
}
public void initialize() throws XMPPException {
if (!isResolving() && !isResolved()) {
LOGGER.fine("Initialized");
// Negotiation with a STUN server for a set of interfaces is quite slow, but the results
// never change over then instance of a JVM. To increase connection performance considerably
// we now cache established/initialized negotiators for each STUN server, so that subsequent uses
// of the STUN server are much, much faster.
if (negociatorsMap.get(server) == null) {
ICENegociator iceNegociator = new ICENegociator(server, port, (short) 1);
negociatorsMap.put(server, iceNegociator);
// gather candidates
iceNegociator.gatherCandidateAddresses();
// priorize candidates
iceNegociator.prioritizeCandidates();
}
}
this.setInitialized();
}
public void cancel() throws XMPPException {
}
/**
* Resolve the IP and obtain a valid transport method.
* @throws SmackException
*/
public synchronized void resolve(JingleSession session) throws XMPPException, SmackException {
this.setResolveInit();
for (TransportCandidate candidate : this.getCandidatesList()) {
if (candidate instanceof ICECandidate) {
ICECandidate iceCandidate = (ICECandidate) candidate;
iceCandidate.removeCandidateEcho();
}
}
this.clear();
// Create a transport candidate for each ICE negotiator candidate we have.
ICENegociator iceNegociator = negociatorsMap.get(server);
for (Candidate candidate : iceNegociator.getSortedCandidates())
try {
Candidate.CandidateType type = candidate.getCandidateType();
ICECandidate.Type iceType = ICECandidate.Type.local;
if (type.equals(Candidate.CandidateType.ServerReflexive))
iceType = ICECandidate.Type.srflx;
else if (type.equals(Candidate.CandidateType.PeerReflexive))
iceType = ICECandidate.Type.prflx;
else if (type.equals(Candidate.CandidateType.Relayed))
iceType = ICECandidate.Type.relay;
else
iceType = ICECandidate.Type.host;
// JBW/GW - 17JUL08: Figure out the zero-based NIC number for this candidate.
short nicNum = 0;
try {
Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
short i = 0;
NetworkInterface nic = NetworkInterface.getByInetAddress(candidate.getAddress().getInetAddress());
while(nics.hasMoreElements()) {
NetworkInterface checkNIC = nics.nextElement();
if (checkNIC.equals(nic)) {
nicNum = i;
break;
}
i++;
}
} catch (SocketException e1) {
e1.printStackTrace();
}
TransportCandidate transportCandidate = new ICECandidate(candidate.getAddress().getInetAddress().getHostAddress(), 1, nicNum, String.valueOf(Math.abs(random.nextLong())), candidate.getPort(), "1", candidate.getPriority(), iceType);
transportCandidate.setLocalIp(candidate.getBase().getAddress().getInetAddress().getHostAddress());
transportCandidate.setPort(getFreePort());
try {
transportCandidate.addCandidateEcho(session);
}
catch (SocketException e) {
e.printStackTrace();
}
this.addCandidate(transportCandidate);
LOGGER.fine("Candidate addr: " + candidate.getAddress().getInetAddress() + "|" + candidate.getBase().getAddress().getInetAddress() + " Priority:" + candidate.getPriority());
}
catch (UtilityException e) {
e.printStackTrace();
}
catch (UnknownHostException e) {
e.printStackTrace();
}
// Get a Relay Candidate from XMPP Server
if (RTPBridge.serviceAvailable(connection)) {
// try {
String localIp;
int network;
// JBW/GW - 17JUL08: ICENegotiator.getPublicCandidate() always returned null in JSTUN 1.7.0, and now the API doesn't exist in JSTUN 1.7.1
// if (iceNegociator.getPublicCandidate() != null) {
// localIp = iceNegociator.getPublicCandidate().getBase().getAddress().getInetAddress().getHostAddress();
// network = iceNegociator.getPublicCandidate().getNetwork();
// }
// else {
{
localIp = BridgedResolver.getLocalHost();
network = 0;
}
sid = Math.abs(random.nextLong());
RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, String.valueOf(sid));
TransportCandidate localCandidate = new ICECandidate(
rtpBridge.getIp(), 1, network, String.valueOf(Math.abs(random.nextLong())), rtpBridge.getPortA(), "1", 0, ICECandidate.Type.relay);
localCandidate.setLocalIp(localIp);
TransportCandidate remoteCandidate = new ICECandidate(
rtpBridge.getIp(), 1, network, String.valueOf(Math.abs(random.nextLong())), rtpBridge.getPortB(), "1", 0, ICECandidate.Type.relay);
remoteCandidate.setLocalIp(localIp);
localCandidate.setSymmetric(remoteCandidate);
remoteCandidate.setSymmetric(localCandidate);
localCandidate.setPassword(rtpBridge.getPass());
remoteCandidate.setPassword(rtpBridge.getPass());
localCandidate.setSessionId(rtpBridge.getSid());
remoteCandidate.setSessionId(rtpBridge.getSid());
localCandidate.setConnection(this.connection);
remoteCandidate.setConnection(this.connection);
addCandidate(localCandidate);
// }
// catch (UtilityException e) {
// e.printStackTrace();
// }
// catch (UnknownHostException e) {
// e.printStackTrace();
// }
// Get Public Candidate From XMPP Server
// JBW/GW - 17JUL08 - ICENegotiator.getPublicCandidate() always returned null in JSTUN 1.7.0, and now it doesn't exist in JSTUN 1.7.1
// if (iceNegociator.getPublicCandidate() == null) {
if (true) {
String publicIp = RTPBridge.getPublicIP(connection);
if (publicIp != null && !publicIp.equals("")) {
Enumeration<NetworkInterface> ifaces = null;
try {
ifaces = NetworkInterface.getNetworkInterfaces();
}
catch (SocketException e) {
e.printStackTrace();
}
// If detect this address in local machine, don't use it.
boolean found = false;
while (ifaces.hasMoreElements() && !false) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = iaddresses.nextElement();
if (iaddress.getHostAddress().indexOf(publicIp) > -1) {
found = true;
break;
}
}
}
if (!found) {
try {
TransportCandidate publicCandidate = new ICECandidate(
publicIp, 1, 0, String.valueOf(Math.abs(random.nextLong())), getFreePort(), "1", 0, ICECandidate.Type.srflx);
publicCandidate.setLocalIp(InetAddress.getLocalHost().getHostAddress());
try {
publicCandidate.addCandidateEcho(session);
}
catch (SocketException e) {
e.printStackTrace();
}
addCandidate(publicCandidate);
}
catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
}
}
this.setResolveEnd();
}
}

View file

@ -0,0 +1,83 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.listeners.CreatedJingleSessionListener;
import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener;
import org.jivesoftware.smackx.jingle.media.PayloadType;
public class ICETransportManager extends JingleTransportManager implements JingleSessionListener, CreatedJingleSessionListener {
ICEResolver iceResolver = null;
public ICETransportManager(XMPPConnection xmppConnection, String server, int port) {
iceResolver = new ICEResolver(xmppConnection, server, port);
try {
iceResolver.initializeAndWait();
}
catch (Exception e) {
e.printStackTrace();
}
}
protected TransportResolver createResolver(JingleSession session) throws SmackException {
try {
iceResolver.resolve(session);
}
catch (XMPPException e) {
e.printStackTrace();
}
return iceResolver;
}
// Implement a Session Listener to relay candidates after establishment
public void sessionEstablished(PayloadType pt, TransportCandidate rc, TransportCandidate lc, JingleSession jingleSession) throws NotConnectedException {
if (lc instanceof ICECandidate) {
if (((ICECandidate) lc).getType().equals("relay")) {
RTPBridge rtpBridge = RTPBridge.relaySession(lc.getConnection(), lc.getSessionId(), lc.getPassword(), rc, lc);
}
}
}
public void sessionDeclined(String reason, JingleSession jingleSession) {
}
public void sessionRedirected(String redirection, JingleSession jingleSession) {
}
public void sessionClosed(String reason, JingleSession jingleSession) {
}
public void sessionClosedOnError(XMPPException e, JingleSession jingleSession) {
}
public void sessionMediaReceived(JingleSession jingleSession, String participant) {
// Do Nothing
}
// Session Created
public void sessionCreated(JingleSession jingleSession) {
jingleSession.addListener(this);
}
}

View file

@ -0,0 +1,72 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
/**
* Transport manager for Jingle.
*
* This class makes easier the use of transport resolvers by presenting a simple
* interface for algorithm selection. The transport manager also keeps the match
* between the resolution method and the &lt;transport&gt; element present in
* Jingle packets.
*
* As Jingle have many transport methods (official and unofficial methods),
* this abstract class helps us to extends the transport support of the API.
*
* This class must be used with a JingleManager instance in the following way:
*
* JingleManager jingleManager = new JingleManager(xmppConnection, new BasicTransportManager());
*
* @author Thiago Camargo
*/
public abstract class JingleTransportManager {
// This class implements the context of a Strategy pattern...
/**
* Deafult contructor.
*/
public JingleTransportManager() {
}
/**
* Get a new Transport Resolver to be used in a Jingle Session
*
* @return the TransportResolver to be used
*/
public TransportResolver getResolver(JingleSession session) throws XMPPException, SmackException {
TransportResolver resolver = createResolver(session);
if (resolver == null) {
resolver = new BasicResolver();
}
resolver.initializeAndWait();
return resolver;
}
/**
* Create a Transport Resolver instance according to the implementation.
*
* @return the TransportResolver
*/
protected abstract TransportResolver createResolver(JingleSession session) throws SmackException;
}

View file

@ -0,0 +1,535 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.xmlpull.v1.XmlPullParser;
/**
* RTPBridge IQ Packet used to request and retrieve a RTPBridge Candidates that can be used for a Jingle Media Transmission between two parties that are behind NAT.
* This Jingle Bridge has all the needed information to establish a full UDP Channel (Send and Receive) between two parties.
* <i>This transport method should be used only if other transport methods are not allowed. Or if you want a more reliable transport.</i>
* <p/>
* High Level Usage Example:
* <p/>
* RTPBridge rtpBridge = RTPBridge.getRTPBridge(connection, sessionID);
*
* @author Thiago Camargo
*/
public class RTPBridge extends IQ {
private static final Logger LOGGER = Logger.getLogger(RTPBridge.class.getName());
private String sid;
private String pass;
private String ip;
private String name;
private int portA = -1;
private int portB = -1;
private String hostA;
private String hostB;
private BridgeAction bridgeAction = BridgeAction.create;
private enum BridgeAction {
create, change, publicip
}
/**
* Element name of the packet extension.
*/
public static final String NAME = "rtpbridge";
/**
* Element name of the packet extension.
*/
public static final String ELEMENT_NAME = "rtpbridge";
/**
* Namespace of the packet extension.
*/
public static final String NAMESPACE = "http://www.jivesoftware.com/protocol/rtpbridge";
static {
ProviderManager.getInstance().addIQProvider(NAME, NAMESPACE, new Provider());
}
/**
* Creates a RTPBridge Instance with defined Session ID
*
* @param sid
*/
public RTPBridge(String sid) {
this.sid = sid;
}
/**
* Creates a RTPBridge Instance with defined Session ID
*
* @param action
*/
public RTPBridge(BridgeAction action) {
this.bridgeAction = action;
}
/**
* Creates a RTPBridge Instance with defined Session ID
*
* @param sid
* @param bridgeAction
*/
public RTPBridge(String sid, BridgeAction bridgeAction) {
this.sid = sid;
this.bridgeAction = bridgeAction;
}
/**
* Creates a RTPBridge Packet without Session ID
*/
public RTPBridge() {
}
/**
* Get the attributes string
*/
public String getAttributes() {
StringBuilder str = new StringBuilder();
if (getSid() != null)
str.append(" sid='").append(getSid()).append("'");
if (getPass() != null)
str.append(" pass='").append(getPass()).append("'");
if (getPortA() != -1)
str.append(" porta='").append(getPortA()).append("'");
if (getPortB() != -1)
str.append(" portb='").append(getPortB()).append("'");
if (getHostA() != null)
str.append(" hosta='").append(getHostA()).append("'");
if (getHostB() != null)
str.append(" hostb='").append(getHostB()).append("'");
return str.toString();
}
/**
* Get the Session ID of the Packet (usually same as Jingle Session ID)
*
* @return the session ID
*/
public String getSid() {
return sid;
}
/**
* Set the Session ID of the Packet (usually same as Jingle Session ID)
*
* @param sid
*/
public void setSid(String sid) {
this.sid = sid;
}
/**
* Get the Host A IP Address
*
* @return the Host A IP Address
*/
public String getHostA() {
return hostA;
}
/**
* Set the Host A IP Address
*
* @param hostA
*/
public void setHostA(String hostA) {
this.hostA = hostA;
}
/**
* Get the Host B IP Address
*
* @return the Host B IP Address
*/
public String getHostB() {
return hostB;
}
/**
* Set the Host B IP Address
*
* @param hostB
*/
public void setHostB(String hostB) {
this.hostB = hostB;
}
/**
* Get Side A receive port
*
* @return the side A receive prot
*/
public int getPortA() {
return portA;
}
/**
* Set Side A receive port
*
* @param portA
*/
public void setPortA(int portA) {
this.portA = portA;
}
/**
* Get Side B receive port
*
* @return the side B receive port
*/
public int getPortB() {
return portB;
}
/**
* Set Side B receive port
*
* @param portB
*/
public void setPortB(int portB) {
this.portB = portB;
}
/**
* Get the RTP Bridge IP
*
* @return the RTP Bridge IP
*/
public String getIp() {
return ip;
}
/**
* Set the RTP Bridge IP
*
* @param ip
*/
public void setIp(String ip) {
this.ip = ip;
}
/**
* Get the RTP Agent Pass
*
* @return the RTP Agent Pass
*/
public String getPass() {
return pass;
}
/**
* Set the RTP Agent Pass
*
* @param pass
*/
public void setPass(String pass) {
this.pass = pass;
}
/**
* Get the name of the Candidate
*
* @return the name of the Candidate
*/
public String getName() {
return name;
}
/**
* Set the name of the Candidate
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the Child Element XML of the Packet
*
* @return the Child Element XML of the Packet
*/
public String getChildElementXML() {
StringBuilder str = new StringBuilder();
str.append("<" + ELEMENT_NAME + " xmlns='" + NAMESPACE + "' sid='").append(sid).append("'>");
if (bridgeAction.equals(BridgeAction.create))
str.append("<candidate/>");
else if (bridgeAction.equals(BridgeAction.change))
str.append("<relay ").append(getAttributes()).append(" />");
else
str.append("<publicip ").append(getAttributes()).append(" />");
str.append("</" + ELEMENT_NAME + ">");
return str.toString();
}
/**
* IQProvider for RTP Bridge packets.
* Parse receive RTPBridge packet to a RTPBridge instance
*
* @author Thiago Rocha
*/
public static class Provider implements IQProvider {
public Provider() {
super();
}
public IQ parseIQ(XmlPullParser parser) throws Exception {
boolean done = false;
int eventType;
String elementName;
if (!parser.getNamespace().equals(RTPBridge.NAMESPACE))
throw new Exception("Not a RTP Bridge packet");
RTPBridge iq = new RTPBridge();
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("sid"))
iq.setSid(parser.getAttributeValue(i));
}
// Start processing sub-elements
while (!done) {
eventType = parser.next();
elementName = parser.getName();
if (eventType == XmlPullParser.START_TAG) {
if (elementName.equals("candidate")) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("ip"))
iq.setIp(parser.getAttributeValue(i));
else if (parser.getAttributeName(i).equals("pass"))
iq.setPass(parser.getAttributeValue(i));
else if (parser.getAttributeName(i).equals("name"))
iq.setName(parser.getAttributeValue(i));
else if (parser.getAttributeName(i).equals("porta"))
iq.setPortA(Integer.parseInt(parser.getAttributeValue(i)));
else if (parser.getAttributeName(i).equals("portb"))
iq.setPortB(Integer.parseInt(parser.getAttributeValue(i)));
}
}
else if (elementName.equals("publicip")) {
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("ip"))
iq.setIp(parser.getAttributeValue(i));
}
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(RTPBridge.ELEMENT_NAME)) {
done = true;
}
}
}
return iq;
}
}
/**
* Get a new RTPBridge Candidate from the server.
* If a error occurs or the server don't support RTPBridge Service, null is returned.
*
* @param connection
* @param sessionID
* @return the new RTPBridge
* @throws NotConnectedException
*/
public static RTPBridge getRTPBridge(XMPPConnection connection, String sessionID) throws NotConnectedException {
if (!connection.isConnected()) {
return null;
}
RTPBridge rtpPacket = new RTPBridge(sessionID);
rtpPacket.setTo(RTPBridge.NAME + "." + connection.getServiceName());
PacketCollector collector = connection.createPacketCollectorAndSend(rtpPacket);
RTPBridge response = (RTPBridge) collector.nextResult();
// Cancel the collector.
collector.cancel();
return response;
}
/**
* Check if the server support RTPBridge Service.
*
* @param connection
* @return true if the server supports the RTPBridge service
* @throws XMPPErrorException
* @throws NoResponseException
* @throws NotConnectedException
*/
public static boolean serviceAvailable(XMPPConnection connection) throws NoResponseException,
XMPPErrorException, NotConnectedException {
if (!connection.isConnected()) {
return false;
}
LOGGER.fine("Service listing");
ServiceDiscoveryManager disco = ServiceDiscoveryManager
.getInstanceFor(connection);
// DiscoverItems items = disco.discoverItems(connection.getServiceName());
// Iterator iter = items.getItems();
// while (iter.hasNext()) {
// DiscoverItems.Item item = (DiscoverItems.Item) iter.next();
// if (item.getEntityID().startsWith("rtpbridge.")) {
// return true;
// }
// }
DiscoverInfo discoInfo = disco.discoverInfo(connection.getServiceName());
for (DiscoverInfo.Identity identity : discoInfo.getIdentities()) {
if ((identity.getName() != null) && (identity.getName().startsWith("rtpbridge"))) {
return true;
}
}
return false;
}
/**
* Check if the server support RTPBridge Service.
*
* @param connection
* @return the RTPBridge
* @throws NotConnectedException
*/
public static RTPBridge relaySession(XMPPConnection connection, String sessionID, String pass, TransportCandidate proxyCandidate, TransportCandidate localCandidate) throws NotConnectedException {
if (!connection.isConnected()) {
return null;
}
RTPBridge rtpPacket = new RTPBridge(sessionID, RTPBridge.BridgeAction.change);
rtpPacket.setTo(RTPBridge.NAME + "." + connection.getServiceName());
rtpPacket.setType(Type.SET);
rtpPacket.setPass(pass);
rtpPacket.setPortA(localCandidate.getPort());
rtpPacket.setPortB(proxyCandidate.getPort());
rtpPacket.setHostA(localCandidate.getIp());
rtpPacket.setHostB(proxyCandidate.getIp());
// LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort());
PacketCollector collector = connection.createPacketCollectorAndSend(rtpPacket);
RTPBridge response = (RTPBridge) collector.nextResult();
// Cancel the collector.
collector.cancel();
return response;
}
/**
* Get Public Address from the Server.
*
* @param xmppConnection
* @return public IP String or null if not found
* @throws NotConnectedException
*/
public static String getPublicIP(XMPPConnection xmppConnection) throws NotConnectedException {
if (!xmppConnection.isConnected()) {
return null;
}
RTPBridge rtpPacket = new RTPBridge(RTPBridge.BridgeAction.publicip);
rtpPacket.setTo(RTPBridge.NAME + "." + xmppConnection.getServiceName());
rtpPacket.setType(Type.SET);
// LOGGER.debug("Relayed to: " + candidate.getIp() + ":" + candidate.getPort());
PacketCollector collector = xmppConnection.createPacketCollectorAndSend(rtpPacket);
RTPBridge response = (RTPBridge) collector.nextResult();
// Cancel the collector.
collector.cancel();
if(response == null) return null;
if (response.getIp() == null || response.getIp().equals("")) return null;
Enumeration<NetworkInterface> ifaces = null;
try {
ifaces = NetworkInterface.getNetworkInterfaces();
}
catch (SocketException e) {
e.printStackTrace();
}
while (ifaces!=null&&ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = iaddresses.nextElement();
if (!iaddress.isLoopbackAddress())
if (iaddress.getHostAddress().indexOf(response.getIp()) >= 0)
return null;
}
}
return response.getIp();
}
}

View file

@ -0,0 +1,29 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
/**
* Listener for ECHO Test Results
*
* @author Thiago Camargo
*/
public interface ResultListener {
public void testFinished(TestResult result, TransportCandidate candidate);
}

View file

@ -0,0 +1,271 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
import org.xmlpull.v1.XmlPullParser;
/**
* STUN IQ Packet used to request and retrieve a STUN server and port to make p2p connections easier. STUN is usually used by Jingle Media Transmission between two parties that are behind NAT.
* <p/>
* High Level Usage Example:
* <p/>
* STUN stun = STUN.getSTUNServer(connection);
*
* @author Thiago Camargo
*/
public class STUN extends IQ {
private static final Logger LOGGER = Logger.getLogger(STUN.class.getName());
private List<StunServerAddress> servers = new ArrayList<StunServerAddress>();
private String publicIp = null;
/**
* Element name of the packet extension.
*/
public static final String DOMAIN = "stun";
/**
* Element name of the packet extension.
*/
public static final String ELEMENT_NAME = "query";
/**
* Namespace of the packet extension.
*/
public static final String NAMESPACE = "google:jingleinfo";
static {
ProviderManager.getInstance().addIQProvider(ELEMENT_NAME, NAMESPACE, new STUN.Provider());
}
/**
* Creates a STUN IQ
*/
public STUN() {
}
/**
* Get a list of STUN Servers recommended by the Server
*
* @return the list of STUN servers
*/
public List<StunServerAddress> getServers() {
return servers;
}
/**
* Get Public Ip returned from the XMPP server
*
* @return the public IP
*/
public String getPublicIp() {
return publicIp;
}
/**
* Set Public Ip returned from the XMPP server
*
* @param publicIp
*/
private void setPublicIp(String publicIp) {
this.publicIp = publicIp;
}
/**
* Get the Child Element XML of the Packet
*
* @return the child element XML
*/
public String getChildElementXML() {
StringBuilder str = new StringBuilder();
str.append("<" + ELEMENT_NAME + " xmlns='" + NAMESPACE + "'/>");
return str.toString();
}
/**
* IQProvider for RTP Bridge packets.
* Parse receive RTPBridge packet to a RTPBridge instance
*
* @author Thiago Rocha
*/
public static class Provider implements IQProvider {
public Provider() {
super();
}
public IQ parseIQ(XmlPullParser parser) throws Exception {
boolean done = false;
int eventType;
String elementName;
if (!parser.getNamespace().equals(NAMESPACE))
throw new Exception("Not a STUN packet");
STUN iq = new STUN();
// Start processing sub-elements
while (!done) {
eventType = parser.next();
elementName = parser.getName();
if (eventType == XmlPullParser.START_TAG) {
if (elementName.equals("server")) {
String host = null;
String port = null;
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("host"))
host = parser.getAttributeValue(i);
else if (parser.getAttributeName(i).equals("udp"))
port = parser.getAttributeValue(i);
}
if (host != null && port != null)
iq.servers.add(new StunServerAddress(host, port));
}
else if (elementName.equals("publicip")) {
String host = null;
for (int i = 0; i < parser.getAttributeCount(); i++) {
if (parser.getAttributeName(i).equals("ip"))
host = parser.getAttributeValue(i);
}
if (host != null && !host.equals(""))
iq.setPublicIp(host);
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals(ELEMENT_NAME)) {
done = true;
}
}
}
return iq;
}
}
/**
* Get a new STUN Server Address and port from the server.
* If a error occurs or the server don't support STUN Service, null is returned.
*
* @param connection
* @return the STUN server address
* @throws NotConnectedException
*/
public static STUN getSTUNServer(XMPPConnection connection) throws NotConnectedException {
if (!connection.isConnected()) {
return null;
}
STUN stunPacket = new STUN();
stunPacket.setTo(DOMAIN + "." + connection.getServiceName());
PacketCollector collector = connection.createPacketCollectorAndSend(stunPacket);
STUN response = (STUN) collector.nextResult();
// Cancel the collector.
collector.cancel();
return response;
}
/**
* Check if the server support STUN Service.
*
* @param connection the connection
* @return true if the server support STUN
* @throws SmackException
* @throws XMPPException
*/
public static boolean serviceAvailable(XMPPConnection connection) throws XMPPException, SmackException {
if (!connection.isConnected()) {
return false;
}
LOGGER.fine("Service listing");
ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(connection);
DiscoverItems items = disco.discoverItems(connection.getServiceName());
for (DiscoverItems.Item item : items.getItems()) {
DiscoverInfo info = disco.discoverInfo(item.getEntityID());
for (DiscoverInfo.Identity identity : info.getIdentities()) {
if (identity.getCategory().equals("proxy") && identity.getType().equals("stun"))
if (info.containsFeature(NAMESPACE))
return true;
}
LOGGER.fine(item.getName() + "-" + info.getType());
}
return false;
}
/**
* Provides easy abstract to store STUN Server Addresses and Ports
*/
public static class StunServerAddress {
private String server;
private String port;
public StunServerAddress(String server, String port) {
this.server = server;
this.port = port;
}
/**
* Get the Host Address
*
* @return the host address
*/
public String getServer() {
return server;
}
/**
* Get the Server Port
*
* @return the server port
*/
public String getPort() {
return port;
}
}
}

View file

@ -0,0 +1,520 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import de.javawi.jstun.test.BindingLifetimeTest;
import de.javawi.jstun.test.DiscoveryInfo;
import de.javawi.jstun.test.DiscoveryTest;
/**
* Transport resolver using the JSTUN library, to discover public IP and use it as a candidate.
*
* The goal of this resolver is to take possible to establish and manage out-of-band connections between two XMPP entities, even if they are behind Network Address Translators (NATs) or firewalls.
*
* @author Thiago Camargo
*/
public class STUNResolver extends TransportResolver {
private static final Logger LOGGER = Logger.getLogger(STUNResolver.class.getName());
// The filename where the STUN servers are stored.
public final static String STUNSERVERS_FILENAME = "META-INF/stun-config.xml";
// Current STUN server we are using
protected STUNService currentServer;
protected Thread resolverThread;
protected int defaultPort;
protected String resolvedPublicIP;
protected String resolvedLocalIP;
/**
* Constructor with default STUN server.
*/
public STUNResolver() {
super();
this.defaultPort = 0;
this.currentServer = new STUNService();
}
/**
* Constructor with a default port.
*
* @param defaultPort Port to use by default.
*/
public STUNResolver(int defaultPort) {
this();
this.defaultPort = defaultPort;
}
/**
* Return true if the service is working.
*
* @see TransportResolver#isResolving()
*/
public boolean isResolving() {
return super.isResolving() && resolverThread != null;
}
/**
* Set the STUN server name and port
*
* @param ip the STUN server name
* @param port the STUN server port
*/
public void setSTUNService(String ip, int port) {
currentServer = new STUNService(ip, port);
}
/**
* Get the name of the current STUN server.
*
* @return the name of the STUN server
*/
public String getCurrentServerName() {
if (!currentServer.isNull()) {
return currentServer.getHostname();
} else {
return null;
}
}
/**
* Get the port of the current STUN server.
*
* @return the port of the STUN server
*/
public int getCurrentServerPort() {
if (!currentServer.isNull()) {
return currentServer.getPort();
} else {
return 0;
}
}
/**
* Load the STUN configuration from a stream.
*
* @param stunConfigStream An InputStream with the configuration file.
* @return A list of loaded servers
*/
public ArrayList<STUNService> loadSTUNServers(java.io.InputStream stunConfigStream) {
ArrayList<STUNService> serversList = new ArrayList<STUNService>();
String serverName;
int serverPort;
try {
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(stunConfigStream, "UTF-8");
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
// Parse a STUN server definition
if (parser.getName().equals("stunServer")) {
serverName = null;
serverPort = -1;
// Parse the hostname
parser.next();
parser.next();
serverName = parser.nextText();
// Parse the port
parser.next();
parser.next();
try {
serverPort = Integer.parseInt(parser.nextText());
}
catch (Exception e) {
}
// If we have a valid hostname and port, add
// it to the list.
if (serverName != null && serverPort != -1) {
STUNService service = new STUNService(serverName, serverPort);
serversList.add(service);
}
}
}
eventType = parser.next();
}
while (eventType != XmlPullParser.END_DOCUMENT);
}
catch (XmlPullParserException e) {
LOGGER.log(Level.SEVERE, "Exception", e);
}
catch (IOException e) {
LOGGER.log(Level.SEVERE, "Exception", e);
}
currentServer = bestSTUNServer(serversList);
return serversList;
}
/**
* Load a list of services: STUN servers and ports. Some public STUN servers
* are:
* <p/>
* <pre>
* iphone-stun.freenet.de:3478
* larry.gloo.net:3478
* stun.xten.net:3478
* stun.fwdnet.net
* stun.fwd.org (no DNS SRV record)
* stun01.sipphone.com (no DNS SRV record)
* stun.softjoys.com (no DNS SRV record)
* stun.voipbuster.com (no DNS SRV record)
* stun.voxgratia.org (no DNS SRV record)
* stun.noc.ams-ix.net
* </pre>
* <p/>
* This list should be contained in a file in the "META-INF" directory
*
* @return a list of services
*/
public ArrayList<STUNService> loadSTUNServers() {
ArrayList<STUNService> serversList = new ArrayList<STUNService>();
// Load the STUN configuration
try {
// Get an array of class loaders to try loading the config from.
ClassLoader[] classLoaders = new ClassLoader[2];
classLoaders[0] = new STUNResolver() {
}.getClass().getClassLoader();
classLoaders[1] = Thread.currentThread().getContextClassLoader();
for (int i = 0; i < classLoaders.length; i++) {
Enumeration<URL> stunConfigEnum = classLoaders[i]
.getResources(STUNSERVERS_FILENAME);
while (stunConfigEnum.hasMoreElements() && serversList.isEmpty()) {
URL url = stunConfigEnum.nextElement();
java.io.InputStream stunConfigStream = null;
stunConfigStream = url.openStream();
serversList.addAll(loadSTUNServers(stunConfigStream));
stunConfigStream.close();
}
}
}
catch (Exception e) {
LOGGER.log(Level.SEVERE, "Exception", e);
}
return serversList;
}
/**
* Get the best usable STUN server from a list.
*
* @return the best STUN server that can be used.
*/
private STUNService bestSTUNServer(ArrayList<STUNService> listServers) {
if (listServers.isEmpty()) {
return null;
} else {
// TODO: this should use some more advanced criteria...
return listServers.get(0);
}
}
/**
* Resolve the IP and obtain a valid transport method.
* @throws NotConnectedException
*/
public synchronized void resolve(JingleSession session) throws XMPPException, NotConnectedException {
setResolveInit();
clearCandidates();
TransportCandidate candidate = new TransportCandidate.Fixed(
resolvedPublicIP, getFreePort());
candidate.setLocalIp(resolvedLocalIP);
LOGGER.fine("RESOLVING : " + resolvedPublicIP + ":" + candidate.getPort());
addCandidate(candidate);
setResolveEnd();
}
/**
* Initialize the resolver.
*
* @throws XMPPException
*/
public void initialize() throws XMPPException {
LOGGER.fine("Initialized");
if (!isResolving()&&!isResolved()) {
// Get the best STUN server available
if (currentServer.isNull()) {
loadSTUNServers();
}
// We should have a valid STUN server by now...
if (!currentServer.isNull()) {
clearCandidates();
resolverThread = new Thread(new Runnable() {
public void run() {
// Iterate through the list of interfaces, and ask
// to the STUN server for our address.
try {
Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
String candAddress;
int candPort;
while (ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> iaddresses = iface.getInetAddresses();
while (iaddresses.hasMoreElements()) {
InetAddress iaddress = iaddresses.nextElement();
if (!iaddress.isLoopbackAddress()
&& !iaddress.isLinkLocalAddress()) {
// Reset the candidate
candAddress = null;
candPort = -1;
DiscoveryTest test = new DiscoveryTest(iaddress,
currentServer.getHostname(),
currentServer.getPort());
try {
// Run the tests and get the
// discovery
// information, where all the
// info is stored...
DiscoveryInfo di = test.test();
candAddress = di.getPublicIP() != null ?
di.getPublicIP().getHostAddress() : null;
// Get a valid port
if (defaultPort == 0) {
candPort = getFreePort();
} else {
candPort = defaultPort;
}
// If we have a valid candidate,
// add it to the list.
if (candAddress != null && candPort >= 0) {
TransportCandidate candidate = new TransportCandidate.Fixed(
candAddress, candPort);
candidate.setLocalIp(iaddress.getHostAddress() != null ? iaddress.getHostAddress() : iaddress.getHostName());
addCandidate(candidate);
resolvedPublicIP = candidate.getIp();
resolvedLocalIP = candidate.getLocalIp();
return;
}
}
catch (Exception e) {
LOGGER.log(Level.SEVERE, "Exception", e);
}
}
}
}
}
catch (SocketException e) {
LOGGER.log(Level.SEVERE, "Exception", e);
}
finally {
setInitialized();
}
}
}, "Waiting for all the transport candidates checks...");
resolverThread.setName("STUN resolver");
resolverThread.start();
} else {
throw new IllegalStateException("No valid STUN server found.");
}
}
}
/**
* Cancel any operation.
*
* @see TransportResolver#cancel()
*/
public synchronized void cancel() throws XMPPException {
if (isResolving()) {
resolverThread.interrupt();
setResolveEnd();
}
}
/**
* Clear the list of candidates and start the resolution again.
*
* @see TransportResolver#clear()
*/
public synchronized void clear() throws XMPPException {
this.defaultPort = 0;
super.clear();
}
/**
* STUN service definition.
*/
protected class STUNService {
private String hostname; // The hostname of the service
private int port; // The port number
/**
* Basic constructor, with the hostname and port
*
* @param hostname The hostname
* @param port The port
*/
public STUNService(String hostname, int port) {
super();
this.hostname = hostname;
this.port = port;
}
/**
* Default constructor, without name and port.
*/
public STUNService() {
this(null, -1);
}
/**
* Get the host name of the STUN service.
*
* @return The host name
*/
public String getHostname() {
return hostname;
}
/**
* Set the hostname of the STUN service.
*
* @param hostname The host name of the service.
*/
public void setHostname(String hostname) {
this.hostname = hostname;
}
/**
* Get the port of the STUN service
*
* @return The port number where the STUN server is waiting.
*/
public int getPort() {
return port;
}
/**
* Set the port number for the STUN service.
*
* @param port The port number.
*/
public void setPort(int port) {
this.port = port;
}
/**
* Basic format test: the service is not null.
*
* @return true if the hostname and port are null
*/
public boolean isNull() {
if (hostname == null) {
return true;
} else if (hostname.length() == 0) {
return true;
} else if (port < 0) {
return true;
} else {
return false;
}
}
/**
* Check a binding with the STUN currentServer.
* <p/>
* Note: this function blocks for some time, waiting for a response.
*
* @return true if the currentServer is usable.
*/
public boolean checkBinding() {
boolean result = false;
try {
BindingLifetimeTest binding = new BindingLifetimeTest(hostname, port);
binding.test();
while (true) {
Thread.sleep(5000);
if (binding.getLifetime() != -1) {
if (binding.isCompleted()) {
return true;
}
} else {
break;
}
}
}
catch (Exception e) {
LOGGER.log(Level.SEVERE, "Exception in checkBinding", e);
}
return result;
}
}
}

View file

@ -0,0 +1,47 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smackx.jingle.JingleSession;
/**
* A Jingle Transport Manager implementation to be used on NAT networks with STUN Service NOT Blocked.
*
* @author Thiago Camargo
*/
public class STUNTransportManager extends JingleTransportManager {
STUNResolver stunResolver = null;
public STUNTransportManager() {
stunResolver = new STUNResolver() {
};
try {
stunResolver.initializeAndWait();
} catch (Exception e) {
e.printStackTrace();
}
}
protected TransportResolver createResolver(JingleSession session) {
try {
stunResolver.resolve(session);
} catch (Exception e) {
e.printStackTrace();
}
return stunResolver;
}
}

View file

@ -0,0 +1,139 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.util.logging.Logger;
/**
* A Simple and Experimental Bridge.
* It Creates a TCP Socket That Connects to another TCP Socket Listener and forwards every packets received to an UDP Listener.
* And forwards every packets received in UDP Socket, to the TCP Server
*/
public class TcpUdpBridgeClient {
private static final Logger LOGGER = Logger.getLogger(TcpUdpBridgeClient.class.getName());
private String remoteTcpHost = null;
private String remoteUdpHost = null;
private int remoteTcpPort = -1;
private int remoteUdpPort = -1;
private int localUdpPort = -1;
private DatagramSocket localUdpSocket;
private Socket localTcpSocket;
public TcpUdpBridgeClient(String remoteTcpHost, String remoteUdpHost, int remoteTcpPort, int remoteUdpPort) {
this.remoteTcpHost = remoteTcpHost;
this.remoteUdpHost = remoteUdpHost;
this.remoteTcpPort = remoteTcpPort;
this.remoteUdpPort = remoteUdpPort;
try {
localTcpSocket = new Socket(remoteTcpHost, remoteTcpPort);
localUdpSocket = new DatagramSocket(0);
localUdpPort = localUdpSocket.getLocalPort();
LOGGER.fine("UDP: " + localUdpSocket.getLocalPort());
}
catch (IOException e) {
e.printStackTrace();
}
startBridge();
}
public void startBridge() {
final Thread process = new Thread(new Runnable() {
public void run() {
try {
OutputStream out = localTcpSocket.getOutputStream();
while (true) {
byte b[] = new byte[500];
DatagramPacket p = new DatagramPacket(b, 500);
localUdpSocket.receive(p);
if (p.getLength() == 0) continue;
LOGGER.fine("UDP Client Received and Sending to TCP Server:"+new String(p.getData(),0,p.getLength(),"UTF-8"));
out.write(p.getData(), 0, p.getLength());
out.flush();
LOGGER.fine("Client Flush");
}
}
catch (IOException e) {
e.printStackTrace();
}
}
});
new Thread(new Runnable() {
public void run() {
try {
InputStream in = localTcpSocket.getInputStream();
InetAddress remoteHost = InetAddress.getByName(remoteUdpHost);
process.start();
while (true) {
byte b[] = new byte[500];
int s = in.read(b);
//if (s == -1) continue;
LOGGER.fine("TCP Client:" +new String(b,0,s,"UTF-8"));
DatagramPacket udpPacket = new DatagramPacket(b, s);
udpPacket.setAddress(remoteHost);
udpPacket.setPort(remoteUdpPort);
localUdpSocket.send(udpPacket);
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public Socket getLocalTcpSocket() {
return localTcpSocket;
}
public DatagramSocket getLocalUdpSocket() {
return localUdpSocket;
}
}

View file

@ -0,0 +1,141 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Logger;
/**
* A Simple and Experimental Bridge.
* It Creates a TCP Socket Listeners for Connections and forwards every packets received to an UDP Listener.
* And forwards every packets received in UDP Socket, to the TCP Client
*/
public class TcpUdpBridgeServer {
private static final Logger LOGGER = Logger.getLogger(TcpUdpBridgeServer.class.getName());
private String remoteTcpHost = null;
private String remoteUdpHost = null;
private int remoteTcpPort = -1;
private int remoteUdpPort = -1;
private int localUdpPort = -1;
private DatagramSocket localUdpSocket;
private Socket localTcpSocket;
private ServerSocket serverTcpSocket;
public TcpUdpBridgeServer(String remoteTcpHost, String remoteUdpHost, int remoteTcpPort, int remoteUdpPort) {
this.remoteTcpHost = remoteTcpHost;
this.remoteUdpHost = remoteUdpHost;
this.remoteTcpPort = remoteTcpPort;
this.remoteUdpPort = remoteUdpPort;
try {
serverTcpSocket = new ServerSocket(remoteTcpPort);
localUdpSocket = new DatagramSocket(0);
localUdpPort = localUdpSocket.getLocalPort();
LOGGER.fine("UDP: " + localUdpSocket.getLocalPort());
}
catch (IOException e) {
e.printStackTrace();
}
startBridge();
}
public void startBridge() {
final Thread process = new Thread(new Runnable() {
public void run() {
try {
OutputStream out = localTcpSocket.getOutputStream();
while (true) {
byte b[] = new byte[500];
DatagramPacket p = new DatagramPacket(b, 500);
localUdpSocket.receive(p);
if (p.getLength() == 0) continue;
LOGGER.fine("UDP Server Received and Sending to TCP Client:" + new String(p.getData(), 0, p.getLength(), "UTF-8"));
out.write(p.getData(), 0, p.getLength());
out.flush();
LOGGER.fine("Server Flush");
}
}
catch (IOException e) {
e.printStackTrace();
}
}
});
new Thread(new Runnable() {
public void run() {
try {
localTcpSocket = serverTcpSocket.accept();
process.start();
InputStream in = localTcpSocket.getInputStream();
InetAddress remoteHost = InetAddress.getByName(remoteUdpHost);
while (true) {
byte b[] = new byte[500];
int s = in.read(b);
//if (s == -1) continue;
LOGGER.fine("TCP Server:" + new String(b, 0, s, "UTF-8"));
DatagramPacket udpPacket = new DatagramPacket(b, s);
udpPacket.setAddress(remoteHost);
udpPacket.setPort(remoteUdpPort);
localUdpSocket.send(udpPacket);
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public Socket getLocalTcpSocket() {
return localTcpSocket;
}
public DatagramSocket getLocalUdpSocket() {
return localUdpSocket;
}
}

View file

@ -0,0 +1,54 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
/**
* Result of an ECHO Test
*
* @author Thiago Camargo
*/
public class TestResult {
private boolean result = false;
private String ip = null;
private int port = 0;
public boolean isReachable() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}

View file

@ -0,0 +1,849 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.logging.Logger;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.nat.TransportResolverListener.Checker;
/**
* Transport candidate.
* <p/>
* A candidate represents the possible transport for data interchange between
* the two endpoints.
*
* @author Thiago Camargo
* @author Alvaro Saurin
*/
public abstract class TransportCandidate {
private static final Logger LOGGER = Logger.getLogger(TransportCandidate.class.getName());
private String name;
private String ip; // IP address
private int port; // Port to use, or 0 for any port
private String localIp;
private int generation;
protected String password;
private String sessionId;
private XMPPConnection connection;
private TransportCandidate symmetric;
private CandidateEcho candidateEcho = null;
private Thread echoThread = null;
// Listeners for events
private final List<TransportResolverListener.Checker> listeners = new ArrayList<Checker>();
public void addCandidateEcho(JingleSession session) throws SocketException, UnknownHostException {
candidateEcho = new CandidateEcho(this, session);
echoThread = new Thread(candidateEcho);
echoThread.start();
}
public void removeCandidateEcho() {
if (candidateEcho != null)
candidateEcho.cancel();
candidateEcho = null;
echoThread = null;
}
public CandidateEcho getCandidateEcho() {
return candidateEcho;
}
public String getIp() {
return ip;
}
/**
* Set the IP address.
*
* @param ip the IP address
*/
public void setIp(String ip) {
this.ip = ip;
}
/**
* Get local IP to bind to this candidate
*
* @return the local IP
*/
public String getLocalIp() {
return localIp == null ? ip : localIp;
}
/**
* Set local IP to bind to this candidate
*
* @param localIp
*/
public void setLocalIp(String localIp) {
this.localIp = localIp;
}
/**
* Get the symmetric candidate for this candidate if it exists.
*
* @return the symmetric candidate
*/
public TransportCandidate getSymmetric() {
return symmetric;
}
/**
* Set the symetric candidate for this candidate.
*
* @param symetric
*/
public void setSymmetric(TransportCandidate symetric) {
this.symmetric = symetric;
}
/**
* Get the password used by ICE or relayed candidate
*
* @return a password
*/
public String getPassword() {
return password;
}
/**
* Set the password used by ICE or relayed candidate
*
* @param password a password
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Get the XMPPConnection use to send or receive this candidate
*
* @return the connection
*/
public XMPPConnection getConnection() {
return connection;
}
/**
* Set the XMPPConnection use to send or receive this candidate
*
* @param connection
*/
public void setConnection(XMPPConnection connection) {
this.connection = connection;
}
/**
* Get the jingle's sessionId that is using this candidate
*
* @return the session ID
*/
public String getSessionId() {
return sessionId;
}
/**
* Set the jingle's sessionId that is using this candidate
*
* @param sessionId
*/
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
/**
* Empty constructor
*/
public TransportCandidate() {
this(null, 0, 0);
}
/**
* Constructor with IP address and port
*
* @param ip The IP address.
* @param port The port number.
*/
public TransportCandidate(String ip, int port) {
this(ip, port, 0);
}
/**
* Constructor with IP address and port
*
* @param ip The IP address.
* @param port The port number.
* @param generation The generation
*/
public TransportCandidate(String ip, int port, int generation) {
this.ip = ip;
this.port = port;
this.generation = generation;
}
/**
* Return true if the candidate is not valid.
*
* @return true if the candidate is null.
*/
public boolean isNull() {
if (ip == null) {
return true;
} else if (ip.length() == 0) {
return true;
} else if (port < 0) {
return true;
} else {
return false;
}
}
/**
* Get the port, or 0 for any port.
*
* @return the port or 0
*/
public int getPort() {
return port;
}
/**
* Set the port, using 0 for any port
*
* @param port the port
*/
public void setPort(int port) {
this.port = port;
}
/**
* Get the generation for a transportElement definition
*
* @return the generation
*/
public int getGeneration() {
return generation;
}
/**
* Set the generation for a transportElement definition.
*
* @param generation the generation number
*/
public void setGeneration(int generation) {
this.generation = generation;
}
/**
* Get the name used for identifying this transportElement method (optional)
*
* @return a name used for identifying this transportElement (ie,
* "myrtpvoice1")
*/
public String getName() {
return name;
}
/**
* Set a name for identifying this transportElement.
*
* @param name the name used for the transportElement
*/
public void setName(String name) {
this.name = name;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TransportCandidate other = (TransportCandidate) obj;
if (generation != other.generation) {
return false;
}
if (getIp() == null) {
if (other.getIp() != null) {
return false;
}
} else if (!getIp().equals(other.getIp())) {
return false;
}
if (getPort() != other.getPort()) {
return false;
}
if (getName() == null) {
if (other.getName() != null) {
return false;
}
} else if (!getName().equals(other.getName())) {
return false;
}
if (getPort() != other.getPort()) {
return false;
}
return true;
}
/**
* Check if a transport candidate is usable. The transport resolver should
* check if the transport candidate the other endpoint has provided is
* usable.
* <p/>
* Subclasses should provide better methods if they can...
*/
public void check(final List<TransportCandidate> localCandidates) {
//TODO candidate is being checked trigger
//candidatesChecking.add(cand);
Thread checkThread = new Thread(new Runnable() {
public void run() {
boolean isUsable;
try {
InetAddress candAddress = InetAddress.getByName(getIp());
isUsable = true;//candAddress.isReachable(TransportResolver.CHECK_TIMEOUT);
}
catch (Exception e) {
isUsable = false;
}
triggerCandidateChecked(isUsable);
//TODO candidate is being checked trigger
//candidatesChecking.remove(cand);
}
}, "Transport candidate check");
checkThread.setName("Transport candidate test");
checkThread.start();
}
/**
* Trigger a new candidate checked event.
*
* @param result The result.
*/
void triggerCandidateChecked(boolean result) {
for (TransportResolverListener.Checker trl : getListenersList()) {
trl.candidateChecked(this, result);
}
}
/**
* Get the list of listeners
*
* @return the list of listeners
*/
public List<TransportResolverListener.Checker> getListenersList() {
synchronized (listeners) {
return new ArrayList<Checker>(listeners);
}
}
/**
* Add a transport resolver listener.
*
* @param li The transport resolver listener to be added.
*/
public void addListener(TransportResolverListener.Checker li) {
synchronized (listeners) {
listeners.add(li);
}
}
/**
* Fixed transport candidate
*/
public static class Fixed extends TransportCandidate {
public Fixed() {
super();
}
/**
* Constructor with IP address and port
*
* @param ip The IP address.
* @param port The port number.
*/
public Fixed(String ip, int port) {
super(ip, port);
}
/**
* Constructor with IP address and port
*
* @param ip The IP address.
* @param port The port number.
* @param generation The generation
*/
public Fixed(String ip, int port, int generation) {
super(ip, port, generation);
}
}
/**
* Type-safe enum for the transportElement protocol
*/
public static class Protocol {
public static final Protocol UDP = new Protocol("udp");
public static final Protocol TCP = new Protocol("tcp");
public static final Protocol TCPACT = new Protocol("tcp-act");
public static final Protocol TCPPASS = new Protocol("tcp-pass");
public static final Protocol SSLTCP = new Protocol("ssltcp");
private String value;
public Protocol(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* Returns the Protocol constant associated with the String value.
*/
public static Protocol fromString(String value) {
if (value == null) {
return UDP;
}
value = value.toLowerCase(Locale.US);
if (value.equals("udp")) {
return UDP;
} else if (value.equals("tcp")) {
return TCP;
} else if (value.equals("tcp-act")) {
return TCPACT;
} else if (value.equals("tcp-pass")) {
return TCPPASS;
} else if (value.equals("ssltcp")) {
return SSLTCP;
} else {
return UDP;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Protocol other = (Protocol) obj;
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
return true;
}
/**
* Return true if the protocol is not valid.
*
* @return true if the protocol is null
*/
public boolean isNull() {
if (value == null) {
return true;
} else if (value.length() == 0) {
return true;
} else {
return false;
}
}
}
/**
* Type-safe enum for the transportElement channel
*/
public static class Channel {
public static final Channel MYRTPVOICE = new Channel("myrtpvoice");
public static final Channel MYRTCPVOICE = new Channel("myrtcpvoice");
private String value;
public Channel(String value) {
this.value = value;
}
public String toString() {
return value;
}
/**
* Returns the MediaChannel constant associated with the String value.
*/
public static Channel fromString(String value) {
if (value == null) {
return MYRTPVOICE;
}
value = value.toLowerCase(Locale.US);
if (value.equals("myrtpvoice")) {
return MYRTPVOICE;
} else if (value.equals("tcp")) {
return MYRTCPVOICE;
} else {
return MYRTPVOICE;
}
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Channel other = (Channel) obj;
if (value == null) {
if (other.value != null) {
return false;
}
} else if (!value.equals(other.value)) {
return false;
}
return true;
}
/**
* Return true if the channel is not valid.
*
* @return true if the channel is null
*/
public boolean isNull() {
if (value == null) {
return true;
} else if (value.length() == 0) {
return true;
} else {
return false;
}
}
}
public class CandidateEcho implements Runnable {
DatagramSocket socket = null;
String localUser = null;
String remoteUser = null;
String id = null;
byte send[] = null;
byte receive[] = null;
DatagramPacket sendPacket = null;
List<DatagramListener> listeners = new ArrayList<DatagramListener>();
List<ResultListener> resultListeners = new ArrayList<ResultListener>();
boolean enabled = true;
boolean ended = false;
long replyTries = 2;
long tries = 10;
TransportCandidate candidate = null;
public CandidateEcho(TransportCandidate candidate, JingleSession session) throws UnknownHostException, SocketException {
this.socket = new DatagramSocket(candidate.getPort(), InetAddress.getByName(candidate.getLocalIp()));
this.localUser = session.getInitiator();
this.remoteUser = session.getResponder();
this.id = session.getSid();
this.candidate = candidate;
int keySplitIndex = ((int) Math.ceil(((float) id.length()) / 2));
String local = id.substring(0, keySplitIndex) + ";" + localUser;
String remote = id.substring(keySplitIndex) + ";" + remoteUser;
try {
if (session.getConnection().getUser().equals(session.getInitiator())) {
this.send = local.getBytes("UTF-8");
this.receive = remote.getBytes("UTF-8");
} else {
this.receive = local.getBytes("UTF-8");
this.send = remote.getBytes("UTF-8");
}
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public void run() {
try {
LOGGER.fine("Listening for ECHO: " + socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort());
while (true) {
DatagramPacket packet = new DatagramPacket(new byte[150], 150);
socket.receive(packet);
//LOGGER.fine("ECHO Packet Received in: " + socket.getLocalAddress().getHostAddress() + ":" + socket.getLocalPort() + " From: " + packet.getAddress().getHostAddress() + ":" + packet.getPort());
boolean accept = false;
ByteBuffer buf = ByteBuffer.wrap(packet.getData());
byte[] content = new byte[packet.getLength()];
buf = buf.get(content, 0, packet.getLength());
packet.setData(content);
for (DatagramListener listener : listeners) {
accept = listener.datagramReceived(packet);
if (accept) break;
}
long delay = 100 / replyTries;
String str[] = new String(packet.getData(), "UTF-8").split(";");
String pass = str[0];
String address[] = str[1].split(":");
String ip = address[0];
String port = address[1];
if (pass.equals(candidate.getPassword()) && !accept) {
byte[] cont = null;
try {
cont = (password + ";" + candidate.getIp() + ":" + candidate.getPort()).getBytes("UTF-8");
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
packet.setData(cont);
packet.setLength(cont.length);
packet.setAddress(InetAddress.getByName(ip));
packet.setPort(Integer.parseInt(port));
for (int i = 0; i < replyTries; i++) {
socket.send(packet);
if (!enabled) break;
try {
Thread.sleep(delay);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
catch (UnknownHostException uhe) {
if (enabled) {
}
}
catch (SocketException se) {
if (enabled) {
}
}
catch (IOException ioe) {
if (enabled) {
}
}
catch (Exception e) {
if (enabled) {
}
}
}
public void cancel() {
this.enabled = false;
socket.close();
}
private void fireTestResult(TestResult testResult, TransportCandidate candidate) {
for (ResultListener resultListener : resultListeners)
resultListener.testFinished(testResult, candidate);
}
public void testASync(final TransportCandidate transportCandidate, final String password) {
Thread thread = new Thread(new Runnable() {
public void run() {
DatagramListener listener = new DatagramListener() {
public boolean datagramReceived(DatagramPacket datagramPacket) {
try {
LOGGER.fine("ECHO Received to: " + candidate.getIp() + ":" + candidate.getPort() + " data: " + new String(datagramPacket.getData(), "UTF-8"));
String str[] = new String(datagramPacket.getData(), "UTF-8").split(";");
String pass = str[0];
String addr[] = str[1].split(":");
String ip = addr[0];
String pt = addr[1];
if (pass.equals(password)
&& transportCandidate.getIp().indexOf(ip) != -1
&& transportCandidate.getPort() == Integer.parseInt(pt)) {
LOGGER.fine("ECHO OK: " + candidate.getIp() + ":" + candidate.getPort() + " <-> " + transportCandidate.getIp() + ":" + transportCandidate.getPort());
TestResult testResult = new TestResult();
testResult.setResult(true);
ended = true;
fireTestResult(testResult, transportCandidate);
return true;
}
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
LOGGER.fine("ECHO Wrong Data: " + datagramPacket.getAddress().getHostAddress() + ":" + datagramPacket.getPort());
return false;
}
};
addListener(listener);
byte[] content = null;
try {
content = new String(password + ";" + getIp() + ":" + getPort()).getBytes("UTF-8");
}
catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
DatagramPacket packet = new DatagramPacket(content, content.length);
try {
packet.setAddress(InetAddress.getByName(transportCandidate.getIp()));
}
catch (UnknownHostException e) {
e.printStackTrace();
}
packet.setPort(transportCandidate.getPort());
long delay = 200;
try {
for (int i = 0; i < tries; i++) {
socket.send(packet);
if (ended) break;
try {
Thread.sleep(delay);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
catch (IOException e) {
// Do Nothing
}
try {
Thread.sleep(2000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
removeListener(listener);
}
});
thread.start();
}
public void addListener(DatagramListener listener) {
listeners.add(listener);
}
public void removeListener(DatagramListener listener) {
listeners.remove(listener);
}
public void addResultListener(ResultListener resultListener) {
resultListeners.add(resultListener);
}
public void removeResultListener(ResultListener resultListener) {
resultListeners.remove(resultListener);
}
}
}

View file

@ -0,0 +1,931 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.ContentNegotiator;
import org.jivesoftware.smackx.jingle.JingleActionEnum;
import org.jivesoftware.smackx.jingle.JingleException;
import org.jivesoftware.smackx.jingle.JingleNegotiator;
import org.jivesoftware.smackx.jingle.JingleNegotiatorState;
import org.jivesoftware.smackx.jingle.JingleSession;
import org.jivesoftware.smackx.jingle.listeners.JingleListener;
import org.jivesoftware.smackx.jingle.listeners.JingleTransportListener;
import org.jivesoftware.smackx.jingle.packet.Jingle;
import org.jivesoftware.smackx.jingle.packet.JingleContent;
import org.jivesoftware.smackx.jingle.packet.JingleTransport;
import org.jivesoftware.smackx.jingle.packet.JingleTransport.JingleTransportCandidate;
/**
* Transport negotiator.
* <p/>
* <p/>
* This class is responsible for managing the transport negotiation process,
* handling all the packet interchange and the stage control.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class TransportNegotiator extends JingleNegotiator {
private static final Logger LOGGER = Logger.getLogger(TransportNegotiator.class.getName());
// The time we give to the candidates check before we accept or decline the
// transport (in milliseconds)
public final static int CANDIDATES_ACCEPT_PERIOD = 4000;
// The session this nenotiator belongs to
//private final JingleSession session;
// The transport manager
private final TransportResolver resolver;
// Transport candidates we have offered
private final List<TransportCandidate> offeredCandidates = new ArrayList<TransportCandidate>();
// List of remote transport candidates
private final List<TransportCandidate> remoteCandidates = new ArrayList<TransportCandidate>();
// Valid remote candidates
private final List<TransportCandidate> validRemoteCandidates = new ArrayList<TransportCandidate>();
// Accepted Remote Candidates
private final List<TransportCandidate> acceptedRemoteCandidates = new ArrayList<TransportCandidate>();
// The best local candidate we have offered (and accepted by the other part)
private TransportCandidate acceptedLocalCandidate;
// The thread that will report the result to the other end
private Thread resultThread;
// Listener for the resolver
private TransportResolverListener.Resolver resolverListener;
private ContentNegotiator parentNegotiator;
/**
* Default constructor.
*
* @param session The Jingle session
* @param transResolver The JingleTransportManager to use
*/
public TransportNegotiator(JingleSession session, TransportResolver transResolver, ContentNegotiator parentNegotiator) {
super(session);
resolver = transResolver;
this.parentNegotiator = parentNegotiator;
resultThread = null;
}
/**
* Get a new instance of the right TransportNegotiator class with this
* candidate.
*
* @return A TransportNegotiator instance
*/
public abstract JingleTransport getJingleTransport(TransportCandidate cand);
/**
* Return true if the transport candidate is acceptable for the current
* negotiator.
*
* @return true if the transport candidate is acceptable
*/
public abstract boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates);
/**
* Obtain the best local candidate we want to offer.
*
* @return the best local candidate
*/
public final TransportCandidate getBestLocalCandidate() {
return resolver.getPreferredCandidate();
}
/**
* Set the best local transport candidate we have offered and accepted by
* the other endpoint.
*
* @param bestLocalCandidate the acceptedLocalCandidate to set
*/
private void setAcceptedLocalCandidate(TransportCandidate bestLocalCandidate) throws XMPPException {
for (int i = 0; i < resolver.getCandidateCount(); i++) {
//TODO FIX The EQUAL Sentence
if (resolver.getCandidate(i).getIp().equals(bestLocalCandidate.getIp())
&& resolver.getCandidate(i).getPort() == bestLocalCandidate.getPort()) {
acceptedLocalCandidate = resolver.getCandidate(i);
return;
}
}
LOGGER.fine("BEST: ip=" + bestLocalCandidate.getIp() + " port=" + bestLocalCandidate.getPort() + " has not been offered.");
//throw new XMPPException("Local transport candidate has not be offered.");
}
/**
* Get the best accepted local candidate we have offered.
*
* @return a transport candidate we have offered.
*/
public TransportCandidate getAcceptedLocalCandidate() {
return acceptedLocalCandidate;
}
/**
* Called from above to start the negotiator during a session-initiate.
*/
protected void doStart() {
try {
sendTransportCandidatesOffer();
setNegotiatorState(JingleNegotiatorState.PENDING);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Called from above to session-terminate.
*/
public void close() {
super.close();
}
/**
* Return a JingleTransport that best reflects this transport negotiator.
*/
public JingleTransport getJingleTransport() {
return getJingleTransport(getBestRemoteCandidate());
}
public List<TransportCandidate> getOfferedCandidates() {
return offeredCandidates;
}
/**
* Obtain the best common transport candidate obtained in the negotiation.
*
* @return the bestRemoteCandidate
*/
public abstract TransportCandidate getBestRemoteCandidate();
/**
* Get the list of remote candidates.
*
* @return the remoteCandidates
*/
private List<TransportCandidate> getRemoteCandidates() {
return remoteCandidates;
}
/**
* Add a remote candidate to the list. The candidate will be checked in
* order to verify if it is usable.
*
* @param rc a remote candidate to add and check.
*/
private void addRemoteCandidate(TransportCandidate rc) {
// Add the candidate to the list
if (rc != null) {
if (acceptableTransportCandidate(rc, offeredCandidates)) {
synchronized (remoteCandidates) {
remoteCandidates.add(rc);
}
// Check if the new candidate can be used.
checkRemoteCandidate(rc);
}
}
}
/**
* Add a offered candidate to the list.
*
* @param rc a remote candidate we have offered.
*/
private void addOfferedCandidate(TransportCandidate rc) {
// Add the candidate to the list
if (rc != null) {
synchronized (offeredCandidates) {
offeredCandidates.add(rc);
}
}
}
/**
* Check asynchronously the new transport candidate.
*
* @param offeredCandidate a transport candidates to check
*/
private void checkRemoteCandidate(final TransportCandidate offeredCandidate) {
offeredCandidate.addListener(new TransportResolverListener.Checker() {
public void candidateChecked(TransportCandidate cand, final boolean validCandidate) {
if (validCandidate) {
if (getNegotiatorState() == JingleNegotiatorState.PENDING)
addValidRemoteCandidate(offeredCandidate);
}
}
public void candidateChecking(TransportCandidate cand) {
}
});
offeredCandidate.check(resolver.getCandidatesList());
}
/**
* Return true if the transport is established.
*
* @return true if the transport is established.
*/
private boolean isEstablished() {
return getBestRemoteCandidate() != null && getAcceptedLocalCandidate() != null;
}
/**
* Return true if the transport is fully established.
*
* @return true if the transport is fully established.
*/
public final boolean isFullyEstablished() {
return (isEstablished() && ((getNegotiatorState() == JingleNegotiatorState.SUCCEEDED) || (getNegotiatorState() == JingleNegotiatorState.FAILED)));
}
/**
* Launch a thread that checks, after some time, if any of the candidates
* offered by the other endpoint is usable. The thread does not check the
* candidates: it just checks if we have got a valid one and sends an Accept
* in that case.
*/
private void delayedCheckBestCandidate(final JingleSession js, final Jingle jin) {
//
// If this is the first insertion in the list, start the thread that
// will send the result of our checks...
//
if (resultThread == null && !getRemoteCandidates().isEmpty()) {
resultThread = new Thread(new Runnable() {
public void run() {
// Sleep for some time, waiting for the candidates checks
int totalTime = (CANDIDATES_ACCEPT_PERIOD + TransportResolver.CHECK_TIMEOUT);
int tries = (int) Math.ceil(totalTime / 1000);
for (int i = 0; i < tries - 1; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Once we are in pending state, look for any valid remote
// candidate, and send an "accept" if we have one...
TransportCandidate bestRemote = getBestRemoteCandidate();
//State state = getState();
if ((bestRemote != null)
&& ((getNegotiatorState() == JingleNegotiatorState.PENDING))) {
// Accepting the remote candidate
if (!acceptedRemoteCandidates.contains(bestRemote)) {
Jingle jout = new Jingle(JingleActionEnum.CONTENT_ACCEPT);
JingleContent content = parentNegotiator.getJingleContent();
content.addJingleTransport(getJingleTransport(bestRemote));
jout.addContent(content);
// Send the packet
try {
js.sendFormattedJingle(jin, jout);
}
catch (NotConnectedException e) {
throw new IllegalStateException(e);
}
acceptedRemoteCandidates.add(bestRemote);
}
if ((isEstablished()) && (getNegotiatorState() == JingleNegotiatorState.PENDING)) {
setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
try {
triggerTransportEstablished(getAcceptedLocalCandidate(), bestRemote);
}
catch (NotConnectedException e) {
throw new IllegalStateException(e);
}
break;
}
}
}
// Once we are in pending state, look for any valid remote
// candidate, and send an "accept" if we have one...
TransportCandidate bestRemote = getBestRemoteCandidate();
if (bestRemote == null) {
boolean foundRemoteRelay = false;
for (TransportCandidate candidate : remoteCandidates) {
if (candidate instanceof ICECandidate) {
ICECandidate iceCandidate = (ICECandidate) candidate;
if (iceCandidate.getType().equals("relay")) {
//TODO Check if the relay is reacheable
addValidRemoteCandidate(iceCandidate);
foundRemoteRelay = true;
}
}
}
// If not found, check if we offered a relay. If yes, we should accept any remote candidate.
// We should accept the Public One if we received it, otherwise, accepts any.
if (!foundRemoteRelay) {
boolean foundLocalRelay = false;
for (TransportCandidate candidate : offeredCandidates) {
if (candidate instanceof ICECandidate) {
ICECandidate iceCandidate = (ICECandidate) candidate;
if (iceCandidate.getType().equals("relay")) {
foundLocalRelay = true;
}
}
}
if (foundLocalRelay) {
boolean foundRemotePublic = false;
for (TransportCandidate candidate : remoteCandidates) {
if (candidate instanceof ICECandidate) {
ICECandidate iceCandidate = (ICECandidate) candidate;
if (iceCandidate.getType().equals(ICECandidate.Type.srflx)) {
addValidRemoteCandidate(iceCandidate);
foundRemotePublic = true;
}
}
}
if (!foundRemotePublic) {
for (TransportCandidate candidate : remoteCandidates) {
if (candidate instanceof ICECandidate) {
ICECandidate iceCandidate = (ICECandidate) candidate;
addValidRemoteCandidate(iceCandidate);
}
}
}
}
}
}
for (int i = 0; i < 6; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
bestRemote = getBestRemoteCandidate();
//State state = getState();
if ((bestRemote != null)
&& ((getNegotiatorState() == JingleNegotiatorState.PENDING))) {
if (!acceptedRemoteCandidates.contains(bestRemote)) {
Jingle jout = new Jingle(JingleActionEnum.CONTENT_ACCEPT);
JingleContent content = parentNegotiator.getJingleContent();
content.addJingleTransport(getJingleTransport(bestRemote));
jout.addContent(content);
// Send the packet
try {
js.sendFormattedJingle(jin, jout);
}
catch (NotConnectedException e) {
throw new IllegalStateException(e);
}
acceptedRemoteCandidates.add(bestRemote);
}
if (isEstablished()) {
setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
break;
}
}
}
if (getNegotiatorState() != JingleNegotiatorState.SUCCEEDED) {
try {
session
.terminate("Unable to negotiate session. This may be caused by firewall configuration problems.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "Waiting for all the transport candidates checks...");
resultThread.setName("Transport Resolver Result");
resultThread.start();
}
}
/**
* Add a valid remote candidate to the list. The remote candidate has been
* checked, and the remote
*
* @param remoteCandidate a remote candidate to add
*/
private void addValidRemoteCandidate(TransportCandidate remoteCandidate) {
// Add the candidate to the list
if (remoteCandidate != null) {
synchronized (validRemoteCandidates) {
LOGGER.fine("Added valid candidate: " + remoteCandidate.getIp() + ":" + remoteCandidate.getPort());
validRemoteCandidates.add(remoteCandidate);
}
}
}
/**
* Get the list of valid (ie, checked) remote candidates.
*
* @return The list of valid (ie, already checked) remote candidates.
*/
final ArrayList<TransportCandidate> getValidRemoteCandidatesList() {
synchronized (validRemoteCandidates) {
return new ArrayList<TransportCandidate>(validRemoteCandidates);
}
}
/**
* Get an iterator for the list of valid (ie, checked) remote candidates.
*
* @return The iterator for the list of valid (ie, already checked) remote
* candidates.
*/
public final Iterator<TransportCandidate> getValidRemoteCandidates() {
return Collections.unmodifiableList(getRemoteCandidates()).iterator();
}
/**
* Add an offered remote candidate. The transport candidate can be unusable:
* we must check if we can use it.
*
* @param rc the remote candidate to add.
*/
private void addRemoteCandidates(List<TransportCandidate> rc) {
if (rc != null) {
if (rc.size() > 0) {
for (TransportCandidate aRc : rc) {
addRemoteCandidate(aRc);
}
}
}
}
/**
* Parse the list of transport candidates from a Jingle packet.
*
* @param jin The input jingle packet
*/
private List<TransportCandidate> obtainCandidatesList(Jingle jingle) {
List<TransportCandidate> result = new ArrayList<TransportCandidate>();
if (jingle != null) {
// Get the list of candidates from the packet
for (JingleContent jingleContent : jingle.getContentsList()) {
if (jingleContent.getName().equals(parentNegotiator.getName())) {
for (JingleTransport jingleTransport : jingleContent.getJingleTransportsList()) {
for (JingleTransportCandidate jingleTransportCandidate : jingleTransport.getCandidatesList()) {
TransportCandidate transCand = jingleTransportCandidate.getMediaTransport();
result.add(transCand);
}
}
}
}
}
return result;
}
/**
* Send an offer for a transport candidate
*
* @param cand
* @throws NotConnectedException
*/
private synchronized void sendTransportCandidateOffer(TransportCandidate cand) throws NotConnectedException {
if (!cand.isNull()) {
// Offer our new candidate...
addOfferedCandidate(cand);
JingleContent content = parentNegotiator.getJingleContent();
content.addJingleTransport(getJingleTransport(cand));
Jingle jingle = new Jingle(JingleActionEnum.TRANSPORT_INFO);
jingle.addContent(content);
// We SHOULD NOT be sending packets directly.
// This circumvents the state machinery.
// TODO - work this into the state machinery.
session.sendFormattedJingle(jingle);
}
}
/**
* Create a Jingle packet where we announce our transport candidates.
*
* @throws XMPPException
* @throws SmackException
*/
private void sendTransportCandidatesOffer() throws XMPPException, SmackException {
List<TransportCandidate> notOffered = resolver.getCandidatesList();
notOffered.removeAll(offeredCandidates);
// Send any unset candidate
for (Object aNotOffered : notOffered) {
sendTransportCandidateOffer((TransportCandidate) aNotOffered);
}
// .. and start a listener that will send any future candidate
if (resolverListener == null) {
// Add a listener that sends the offer when the resolver finishes...
resolverListener = new TransportResolverListener.Resolver() {
public void candidateAdded(TransportCandidate cand) throws NotConnectedException {
sendTransportCandidateOffer(cand);
}
public void end() {
}
public void init() {
}
};
resolver.addListener(resolverListener);
}
if (!(resolver.isResolving() || resolver.isResolved())) {
// Resolve our IP and port
LOGGER.fine("RESOLVER CALLED");
resolver.resolve(session);
}
}
/**
* Dispatch an incoming packet. The method is responsible for recognizing
* the packet type and, depending on the current state, deliverying the
* packet to the right event handler and wait for a response.
*
* @param iq the packet received
* @return the new Jingle packet to send.
* @throws XMPPException
* @throws SmackException
*/
public final List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException {
List<IQ> responses = new ArrayList<IQ>();
IQ response = null;
if (iq != null) {
if (iq.getType().equals(IQ.Type.ERROR)) {
// Process errors
setNegotiatorState(JingleNegotiatorState.FAILED);
triggerTransportClosed(null);
// This next line seems wrong, and may subvert the normal closing process.
throw new JingleException(iq.getError().getMessage());
} else if (iq.getType().equals(IQ.Type.RESULT)) {
// Process ACKs
if (isExpectedId(iq.getPacketID())) {
response = receiveResult(iq);
removeExpectedId(iq.getPacketID());
}
} else if (iq instanceof Jingle) {
// Get the action from the Jingle packet
Jingle jingle = (Jingle) iq;
JingleActionEnum action = jingle.getAction();
switch (action) {
case CONTENT_ACCEPT:
response = receiveContentAcceptAction(jingle);
break;
case CONTENT_MODIFY:
break;
case CONTENT_REMOVE:
break;
case SESSION_INFO:
break;
case SESSION_INITIATE:
response = receiveSessionInitiateAction(jingle);
break;
case SESSION_ACCEPT:
response = receiveSessionAcceptAction(jingle);
break;
case TRANSPORT_INFO:
response = receiveTransportInfoAction(jingle);
break;
default:
break;
}
}
}
if (response != null) {
addExpectedId(response.getPacketID());
responses.add(response);
}
return responses;
}
/**
* The other endpoint has partially accepted our invitation: start
* offering a list of candidates.
*
* @return an IQ packet
* @throws XMPPException
* @throws SmackException
*/
private Jingle receiveResult(IQ iq) throws XMPPException, SmackException {
Jingle response = null;
sendTransportCandidatesOffer();
setNegotiatorState(JingleNegotiatorState.PENDING);
return response;
}
/**
* @param jingle
* @param jingleTransport
* @return the iq
* @throws SmackException
*/
private IQ receiveSessionInitiateAction(Jingle jingle) throws XMPPException, SmackException {
IQ response = null;
// Parse the Jingle and get any proposed transport candidates
//addRemoteCandidates(obtainCandidatesList(jin));
// Start offering candidates
sendTransportCandidatesOffer();
// All these candidates will be checked asyncronously. Wait for some
// time and check if we have a valid candidate to use...
delayedCheckBestCandidate(session, jingle);
// Set the next state
setNegotiatorState(JingleNegotiatorState.PENDING);
return response;
}
/**
* @param jingle
* @param jingleTransport
* @return the iq
*/
private IQ receiveTransportInfoAction(Jingle jingle) throws XMPPException {
IQ response = null;
// Parse the Jingle and get any proposed transport candidates
//addRemoteCandidates(obtainCandidatesList(jin));
// // Start offering candidates
// sendTransportCandidatesOffer();
//
// // All these candidates will be checked asyncronously. Wait for some
// // time and check if we have a valid candidate to use...
// delayedCheckBestCandidate(session, jingle);
//
// // Set the next state
// setNegotiatorState(JingleNegotiatorState.PENDING);
// Parse the Jingle and get any proposed transport candidates
addRemoteCandidates(obtainCandidatesList(jingle));
// Wait for some time and check if we have a valid candidate to
// use...
delayedCheckBestCandidate(session, jingle);
response = session.createAck(jingle);
return response;
}
/**
* One of our transport candidates has been accepted.
*
* @param jin The input packet
* @return a Jingle packet
* @throws XMPPException an exception
* @see org.jivesoftware.smackx.jingle.JingleNegotiator.State#eventAccept(org.jivesoftware.smackx.jingle.packet.Jingle)
*/
private IQ receiveContentAcceptAction(Jingle jingle) throws XMPPException {
IQ response = null;
// Parse the Jingle and get the accepted candidate
List<TransportCandidate> accepted = obtainCandidatesList(jingle);
if (!accepted.isEmpty()) {
for (TransportCandidate cand : accepted) {
LOGGER.fine("Remote acccepted candidate addr: " + cand.getIp());
}
TransportCandidate cand = (TransportCandidate) accepted.get(0);
setAcceptedLocalCandidate(cand);
if (isEstablished()) {
LOGGER.fine(cand.getIp() + " is set active");
//setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
}
}
return response;
}
/**
* @param jingle
* @return the iq
*/
private IQ receiveSessionAcceptAction(Jingle jingle) {
IQ response = null;
LOGGER.fine("Transport stabilished");
//triggerTransportEstablished(getAcceptedLocalCandidate(), getBestRemoteCandidate());
//setNegotiatorState(JingleNegotiatorState.SUCCEEDED);
return response;
}
/**
* Trigger a Transport session established event.
*
* @param local TransportCandidate that has been agreed.
* @param remote TransportCandidate that has been agreed.
* @throws NotConnectedException
*/
private void triggerTransportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException {
List<JingleListener> listeners = getListenersList();
for (JingleListener li : listeners) {
if (li instanceof JingleTransportListener) {
JingleTransportListener mli = (JingleTransportListener) li;
LOGGER.fine("triggerTransportEstablished " + local.getLocalIp() + ":" + local.getPort() + " <-> "
+ remote.getIp() + ":" + remote.getPort());
mli.transportEstablished(local, remote);
}
}
}
/**
* Trigger a Transport closed event.
*
* @param cand current TransportCandidate that is cancelled.
*/
private void triggerTransportClosed(TransportCandidate cand) {
List<JingleListener> listeners = getListenersList();
for (JingleListener li : listeners) {
if (li instanceof JingleTransportListener) {
JingleTransportListener mli = (JingleTransportListener) li;
mli.transportClosed(cand);
}
}
}
// Subclasses
/**
* Raw-UDP transport negotiator
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public static final class RawUdp extends TransportNegotiator {
/**
* Default constructor, with a JingleSession and transport manager.
*
* @param js The Jingle session this negotiation belongs to.
* @param res The transport resolver to use.
*/
public RawUdp(JingleSession js, final TransportResolver res, ContentNegotiator parentNegotiator) {
super(js, res, parentNegotiator);
}
/**
* Get a TransportNegotiator instance.
*/
public org.jivesoftware.smackx.jingle.packet.JingleTransport getJingleTransport(TransportCandidate bestRemote) {
org.jivesoftware.smackx.jingle.packet.JingleTransport.RawUdp jt = new org.jivesoftware.smackx.jingle.packet.JingleTransport.RawUdp();
jt.addCandidate(new org.jivesoftware.smackx.jingle.packet.JingleTransport.RawUdp.Candidate(bestRemote));
return jt;
}
/**
* Obtain the best common transport candidate obtained in the
* negotiation.
*
* @return the bestRemoteCandidate
*/
public TransportCandidate getBestRemoteCandidate() {
// Hopefully, we only have one validRemoteCandidate
ArrayList<TransportCandidate> cands = getValidRemoteCandidatesList();
if (!cands.isEmpty()) {
LOGGER.fine("RAW CAND");
return (TransportCandidate) cands.get(0);
} else {
LOGGER.fine("No Remote Candidate");
return null;
}
}
/**
* Return true for fixed candidates.
*/
public boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates) {
return tc instanceof TransportCandidate.Fixed;
}
}
/**
* Ice transport negotiator.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public static final class Ice extends TransportNegotiator {
/**
* Default constructor, with a JingleSession and transport manager.
*
* @param js The Jingle session this negotiation belongs to.
* @param res The transport manager to use.
*/
public Ice(JingleSession js, final TransportResolver res, ContentNegotiator parentNegotiator) {
super(js, res, parentNegotiator);
}
/**
* Get a TransportNegotiator instance.
*
* @param candidate
*/
public org.jivesoftware.smackx.jingle.packet.JingleTransport getJingleTransport(TransportCandidate candidate) {
org.jivesoftware.smackx.jingle.packet.JingleTransport.Ice jt = new org.jivesoftware.smackx.jingle.packet.JingleTransport.Ice();
jt.addCandidate(new org.jivesoftware.smackx.jingle.packet.JingleTransport.Ice.Candidate(candidate));
return jt;
}
/**
* Obtain the best remote candidate obtained in the negotiation so far.
*
* @return the bestRemoteCandidate
*/
public TransportCandidate getBestRemoteCandidate() {
ICECandidate result = null;
ArrayList<TransportCandidate> cands = getValidRemoteCandidatesList();
if (!cands.isEmpty()) {
int highest = -1;
ICECandidate chose = null;
for (TransportCandidate transportCandidate : cands) {
if (transportCandidate instanceof ICECandidate) {
ICECandidate icecandidate = (ICECandidate) transportCandidate;
if (icecandidate.getPreference() > highest) {
chose = icecandidate;
highest = icecandidate.getPreference();
}
}
}
result = chose;
}
if (result != null && result.getType().equals("relay"))
LOGGER.fine("Relay Type");
return result;
}
/**
* Return true for ICE candidates.
*/
public boolean acceptableTransportCandidate(TransportCandidate tc, List<TransportCandidate> localCandidates) {
return tc instanceof ICECandidate;
}
}
}

View file

@ -0,0 +1,408 @@
/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.jingle.JingleSession;
/**
* A TransportResolver is used for obtaining a list of valid transport
* candidates. A transport candidate is composed by an IP address and a port number.
* It is called candidate, because it can be elected or not.
*
* @author Thiago Camargo
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class TransportResolver {
private static final Logger LOGGER = Logger.getLogger(TransportResolver.class.getName());
public enum Type {
rawupd, ice
}
;
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public Type type = Type.rawupd;
// the time, in milliseconds, before a check aborts
public static final int CHECK_TIMEOUT = 3000;
// Listeners for events
private final ArrayList<TransportResolverListener> listeners = new ArrayList<TransportResolverListener>();
// TRue if the resolver is working
private boolean resolving;
// This will be true when all the transport candidates have been gathered...
private boolean resolved;
// This indicates that a transport resolver is initialized
private boolean initialized = false;
// We store a list of candidates internally, just in case there are several
// possibilities. When the user asks for a transport, we return the best
// one.
protected final List<TransportCandidate> candidates = new ArrayList<TransportCandidate>();
/**
* Default constructor.
*/
protected TransportResolver() {
super();
resolving = false;
resolved = false;
}
/**
* Initialize the Resolver
*/
public abstract void initialize() throws XMPPException, SmackException;
/**
* Start a the resolution.
*/
public abstract void resolve(JingleSession session) throws XMPPException, SmackException;
/**
* Clear the list of candidates and start a new resolution process.
*
* @throws XMPPException
*/
public void clear() throws XMPPException {
cancel();
candidates.clear();
}
/**
* Cancel any asynchronous resolution operation.
*/
public abstract void cancel() throws XMPPException;
/**
* Return true if the resolver is working.
*
* @return true if the resolver is working.
*/
public boolean isResolving() {
return resolving;
}
/**
* Return true if the resolver has finished the search for transport
* candidates.
*
* @return true if the search has finished
*/
public boolean isResolved() {
return resolved;
}
/**
* Set the Transport Resolver as initialized.
*/
public synchronized void setInitialized() {
initialized = true;
}
/**
* Chack if the Transport Resolver is initialized
*
* @return true if initialized
*/
public synchronized boolean isInitialized() {
return initialized;
}
/**
* Indicate the beggining of the resolution process. This method must be
* used by subclasses at the begining of their resolve() method.
*/
protected synchronized void setResolveInit() {
resolved = false;
resolving = true;
triggerResolveInit();
}
/**
* Indicate the end of the resolution process. This method must be used by
* subclasses at the begining of their resolve() method.
*/
protected synchronized void setResolveEnd() {
resolved = true;
resolving = false;
triggerResolveEnd();
}
// Listeners management
/**
* Add a transport resolver listener.
*
* @param li The transport resolver listener to be added.
*/
public void addListener(TransportResolverListener li) {
synchronized (listeners) {
listeners.add(li);
}
}
/**
* Removes a transport resolver listener.
*
* @param li The transport resolver listener to be removed
*/
public void removeListener(TransportResolverListener li) {
synchronized (listeners) {
listeners.remove(li);
}
}
/**
* Get the list of listeners
*
* @return the list of listeners
*/
public ArrayList<TransportResolverListener> getListenersList() {
synchronized (listeners) {
return new ArrayList<TransportResolverListener>(listeners);
}
}
/**
* Trigger a new candidate added event.
*
* @param cand The candidate added to the list of candidates.
* @throws NotConnectedException
*/
protected void triggerCandidateAdded(TransportCandidate cand) throws NotConnectedException {
Iterator<TransportResolverListener> iter = getListenersList().iterator();
while (iter.hasNext()) {
TransportResolverListener trl = iter.next();
if (trl instanceof TransportResolverListener.Resolver) {
TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
LOGGER.fine("triggerCandidateAdded : " + cand.getLocalIp());
li.candidateAdded(cand);
}
}
}
/**
* Trigger a event notifying the initialization of the resolution process.
*/
private void triggerResolveInit() {
Iterator<TransportResolverListener> iter = getListenersList().iterator();
while (iter.hasNext()) {
TransportResolverListener trl = iter.next();
if (trl instanceof TransportResolverListener.Resolver) {
TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
li.init();
}
}
}
/**
* Trigger a event notifying the obtention of all the candidates.
*/
private void triggerResolveEnd() {
Iterator<TransportResolverListener> iter = getListenersList().iterator();
while (iter.hasNext()) {
TransportResolverListener trl = iter.next();
if (trl instanceof TransportResolverListener.Resolver) {
TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
li.end();
}
}
}
// Candidates management
/**
* Clear the list of candidate
*/
protected void clearCandidates() {
synchronized (candidates) {
candidates.clear();
}
}
/**
* Add a new transport candidate
*
* @param cand The candidate to add
* @throws NotConnectedException
*/
protected void addCandidate(TransportCandidate cand) throws NotConnectedException {
synchronized (candidates) {
if (!candidates.contains(cand))
candidates.add(cand);
}
// Notify the listeners
triggerCandidateAdded(cand);
}
/**
* Get an iterator for the list of candidates
*
* @return an iterator
*/
public Iterator<TransportCandidate> getCandidates() {
synchronized (candidates) {
return Collections.unmodifiableList(new ArrayList<TransportCandidate>(candidates)).iterator();
}
}
/**
* Get the candididate with the highest preference.
*
* @return The best candidate, according to the preference order.
*/
public TransportCandidate getPreferredCandidate() {
TransportCandidate result = null;
ArrayList<ICECandidate> cands = new ArrayList<ICECandidate>();
for (TransportCandidate tpcan : getCandidatesList()) {
if (tpcan instanceof ICECandidate)
cands.add((ICECandidate) tpcan);
}
// (ArrayList<ICECandidate>) getCandidatesList();
if (cands.size() > 0) {
Collections.sort(cands);
// Return the last candidate
result = (TransportCandidate) cands.get(cands.size() - 1);
LOGGER.fine("Result: " + result.getIp());
}
return result;
}
/**
* Get the numer of transport candidates.
*
* @return The length of the transport candidates list.
*/
public int getCandidateCount() {
synchronized (candidates) {
return candidates.size();
}
}
/**
* Get the list of candidates
*
* @return the list of transport candidates
*/
public List<TransportCandidate> getCandidatesList() {
List<TransportCandidate> result = null;
synchronized (candidates) {
result = new ArrayList<TransportCandidate>(candidates);
}
return result;
}
/**
* Get the n-th candidate
*
* @return a transport candidate
*/
public TransportCandidate getCandidate(int i) {
TransportCandidate cand;
synchronized (candidates) {
cand = (TransportCandidate) candidates.get(i);
}
return cand;
}
/**
* Initialize Transport Resolver and wait until it is complete unitialized.
* @throws SmackException
*/
public void initializeAndWait() throws XMPPException, SmackException {
this.initialize();
try {
LOGGER.fine("Initializing transport resolver...");
while (!this.isInitialized()) {
LOGGER.fine("Resolver init still pending");
Thread.sleep(1000);
}
LOGGER.fine("Transport resolved");
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Obtain a free port we can use.
*
* @return A free port number.
*/
protected int getFreePort() {
ServerSocket ss;
int freePort = 0;
for (int i = 0; i < 10; i++) {
freePort = (int) (10000 + Math.round(Math.random() * 10000));
freePort = freePort % 2 == 0 ? freePort : freePort + 1;
try {
ss = new ServerSocket(freePort);
freePort = ss.getLocalPort();
ss.close();
return freePort;
}
catch (IOException e) {
e.printStackTrace();
}
}
try {
ss = new ServerSocket(0);
freePort = ss.getLocalPort();
ss.close();
}
catch (IOException e) {
e.printStackTrace();
}
return freePort;
}
}

View file

@ -0,0 +1,67 @@
/**
*
* Copyright 2003-2006 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.nat;
import org.jivesoftware.smack.SmackException.NotConnectedException;
/**
* Transport resolver Interface
*/
public abstract interface TransportResolverListener {
/**
* Resolver listener.
*/
public interface Resolver extends TransportResolverListener {
/**
* The resolution process has been started.
*/
public void init();
/**
* A transport candidate has been added
*
* @param cand The transport candidate.
* @throws NotConnectedException
*/
public void candidateAdded(TransportCandidate cand) throws NotConnectedException;
/**
* All the transport candidates have been obtained.
*/
public void end();
}
/**
* Resolver checker.
*/
public interface Checker extends TransportResolverListener {
/**
* A transport candidate has been checked.
*
* @param cand The transport candidate that has been checked.
* @param result True if the candidate is usable.
*/
public void candidateChecked(TransportCandidate cand, boolean result);
/**
* A transport candidate is being checked.
*
* @param cand The transport candidate that is being checked.
*/
public void candidateChecking(TransportCandidate cand);
}
}

View file

@ -0,0 +1,374 @@
/**
*
* Copyright 2003-2007 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.packet;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.jingle.JingleActionEnum;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* An Jingle sub-packet, which is used by XMPP clients to exchange info like
* descriptions and transports. <p/> The following link summarizes the
* requirements of Jingle IM: <a
* href="http://www.jabber.org/jeps/jep-0166.html">Valid tags</a>.
* <p/>
* <p/> Warning: this is an non-standard protocol documented by <a
* href="http://www.jabber.org/jeps/jep-0166.html">JEP-166</a>. Because this is
* a non-standard protocol, it is subject to change.
*
* @author Alvaro Saurin
*/
public class Jingle extends IQ {
// static
public static final String NAMESPACE = "urn:xmpp:tmp:jingle";
public static final String NODENAME = "jingle";
// non-static
private String sid; // The session id
private JingleActionEnum action; // The action associated to the Jingle
private String initiator; // The initiator as a "user@host/resource"
private String responder; // The responder
// Sub-elements of a Jingle object.
private final List<JingleContent> contents = new ArrayList<JingleContent>();
private JingleContentInfo contentInfo;
/**
* A constructor where the main components can be initialized.
*/
public Jingle(final List<JingleContent> contents, final JingleContentInfo mi,
final String sid) {
super();
if (contents != null) {
contents.addAll(contents);
}
setContentInfo(mi);
setSid(sid);
// Set null all other fields in the packet
initiator = null;
responder = null;
action = null;
}
/**
* Constructor with a contents.
*
* @param content a content
*/
public Jingle(final JingleContent content) {
super();
addContent(content);
// Set null all other fields in the packet
initiator = null;
responder = null;
// Some default values for the most common situation...
action = JingleActionEnum.UNKNOWN;
this.setType(IQ.Type.SET);
}
/**
* Constructor with a content info.
*
* @param info The content info
*/
public Jingle(final JingleContentInfo info) {
super();
setContentInfo(info);
// Set null all other fields in the packet
initiator = null;
responder = null;
// Some default values for the most common situation...
action = JingleActionEnum.UNKNOWN;
this.setType(IQ.Type.SET);
}
/**
* A constructor where the action can be specified.
*
* @param action The action.
*/
public Jingle(final JingleActionEnum action) {
this(null, null, null);
this.action = action;
// In general, a Jingle with an action is used in a SET packet...
this.setType(IQ.Type.SET);
}
/**
* A constructor where the session ID can be specified.
*
* @param sid The session ID related to the negotiation.
* @see #setSid(String)
*/
public Jingle(final String sid) {
this(null, null, sid);
}
/**
* The default constructor
*/
public Jingle() {
super();
}
/**
* Set the session ID related to this session. The session ID is a unique
* identifier generated by the initiator. This should match the XML Nmtoken
* production so that XML character escaping is not needed for characters
* such as &.
*
* @param sid the session ID
*/
public final void setSid(final String sid) {
this.sid = sid;
}
/**
* Returns the session ID related to the session. The session ID is a unique
* identifier generated by the initiator. This should match the XML Nmtoken
* production so that XML character escaping is not needed for characters
* such as &.
*
* @return Returns the session ID related to the session.
* @see #setSid(String)
*/
public String getSid() {
return sid;
}
/**
* Returns the XML element name of the extension sub-packet root element.
* Always returns "jingle"
*
* @return the XML element name of the packet extension.
*/
public static String getElementName() {
return NODENAME;
}
/**
* Returns the XML namespace of the extension sub-packet root element.
*
* @return the XML namespace of the packet extension.
*/
public static String getNamespace() {
return NAMESPACE;
}
/**
* @return the audioInfo
*/
public JingleContentInfo getContentInfo() {
return contentInfo;
}
/**
* @param contentInfo the audioInfo to set
*/
public void setContentInfo(final JingleContentInfo contentInfo) {
this.contentInfo = contentInfo;
}
/**
* Get an iterator for the contents
*
* @return the contents
*/
public Iterator<JingleContent> getContents() {
synchronized (contents) {
return Collections.unmodifiableList(new ArrayList<JingleContent>(contents)).iterator();
}
}
/**
* Get an iterator for the content
*
* @return the contents
*/
public List<JingleContent> getContentsList() {
synchronized (contents) {
return new ArrayList<JingleContent>(contents);
}
}
/**
* Add a new content.
*
* @param content the content to add
*/
public void addContent(final JingleContent content) {
if (content != null) {
synchronized (contents) {
contents.add(content);
}
}
}
/**
* Add a list of JingleContent elements
*
* @param contentList the list of contents to add
*/
public void addContents(final List<JingleContent> contentList) {
if (contentList != null) {
synchronized (contents) {
contents.addAll(contentList);
}
}
}
/**
* Get the action specified in the packet
*
* @return the action
*/
public JingleActionEnum getAction() {
return action;
}
/**
* Set the action in the packet
*
* @param action the action to set
*/
public void setAction(final JingleActionEnum action) {
this.action = action;
}
/**
* Get the initiator. The initiator will be the full JID of the entity that
* has initiated the flow (which may be different to the "from" address in
* the IQ)
*
* @return the initiator
*/
public String getInitiator() {
return initiator;
}
/**
* Set the initiator. The initiator must be the full JID of the entity that
* has initiated the flow (which may be different to the "from" address in
* the IQ)
*
* @param initiator the initiator to set
*/
public void setInitiator(final String initiator) {
this.initiator = initiator;
}
/**
* Get the responder. The responder is the full JID of the entity that has
* replied to the initiation (which may be different to the "to" addresss in
* the IQ).
*
* @return the responder
*/
public String getResponder() {
return responder;
}
/**
* Set the responder. The responder must be the full JID of the entity that
* has replied to the initiation (which may be different to the "to"
* addresss in the IQ).
*
* @param resp the responder to set
*/
public void setResponder(final String resp) {
responder = resp;
}
/**
* Get a hash key for the session this packet belongs to.
*
* @param sid The session id
* @param initiator The initiator
* @return A hash key
*/
public static int getSessionHash(final String sid, final String initiator) {
final int PRIME = 31;
int result = 1;
result = PRIME * result + (initiator == null ? 0 : initiator.hashCode());
result = PRIME * result + (sid == null ? 0 : sid.hashCode());
return result;
}
/**
* Return the XML representation of the packet.
*
* @return the XML string
*/
public String getChildElementXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName());
buf.append(" xmlns=\"").append(getNamespace()).append("\"");
if (getInitiator() != null) {
buf.append(" initiator=\"").append(getInitiator()).append("\"");
}
if (getResponder() != null) {
buf.append(" responder=\"").append(getResponder()).append("\"");
}
if (getAction() != null) {
buf.append(" action=\"").append(getAction()).append("\"");
}
if (getSid() != null) {
buf.append(" sid=\"").append(getSid()).append("\"");
}
buf.append(">");
synchronized (contents) {
for (JingleContent content : contents) {
buf.append(content.toXML());
}
}
// and the same for audio jmf info
if (contentInfo != null) {
buf.append(contentInfo.toXML());
}
buf.append("</").append(getElementName()).append(">");
return buf.toString();
}
}

View file

@ -0,0 +1,183 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.packet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.jivesoftware.smack.packet.PacketExtension;
/**
* Jingle content.
*
* @author Jeff Williams
*/
public class JingleContent implements PacketExtension {
public static final String NODENAME = "content";
public static final String CREATOR = "creator";
public static final String NAME = "name";
private String creator;
private String name;
private JingleDescription description;
private final List<JingleTransport> transports = new ArrayList<JingleTransport>();
/**
* Creates a content description..
*/
public JingleContent(String creator, String name) {
super();
this.creator = creator;
this.name = name;
}
public String getCreator() {
return creator;
}
public String getName() {
return name;
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public String getElementName() {
return NODENAME;
}
/**
* Return the namespace.
*
* @return The namespace
*/
public String getNamespace() {
// There is no namespace for <content>
return "";
}
/**
* Sets the description for this Jingle content.
*
* @param description
* The description
*/
public void setDescription(JingleDescription description) {
this.description = description;
}
/**
* Gets the description for this Jingle content.
*
* @return The description.
*/
public JingleDescription getDescription() {
return description;
}
/**
* Adds a JingleTransport type to the packet.
*
* @param transport
* the JignleTransport to add.
*/
public void addJingleTransport(final JingleTransport transport) {
synchronized (transports) {
transports.add(transport);
}
}
/**
* Adds a list of transports to add to the packet.
*
* @param transports
* the transports to add.
*/
public void addTransports(final List<JingleTransport> transports) {
synchronized (transports) {
for (JingleTransport transport : transports) {
addJingleTransport(transport);
}
}
}
/**
* Returns an Iterator for the JingleTransports in the packet.
*
* @return an Iterator for the JingleTransports in the packet.
*/
public Iterator<JingleTransport> getJingleTransports() {
return Collections.unmodifiableList(getJingleTransportsList()).iterator();
}
/**
* Returns a list for the JingleTransports in the packet.
*
* @return a list for the JingleTransports in the packet.
*/
public List<JingleTransport> getJingleTransportsList() {
synchronized (transports) {
return new ArrayList<JingleTransport>(transports);
}
}
/**
* Returns a count of the JingleTransports in the Jingle packet.
*
* @return the number of the JingleTransports in the Jingle packet.
*/
public int getJingleTransportsCount() {
synchronized (transports) {
return transports.size();
}
}
/**
* Convert a Jingle description to XML.
*
* @return a string with the XML representation
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
synchronized (transports) {
buf.append("<").append(getElementName());
buf.append(" creator='" + creator + "' name='" + name + "'>");
// Add the description.
if (description != null) {
buf.append(description.toXML());
}
// Add all of the transports.
for (JingleTransport transport : transports) {
buf.append(transport.toXML());
}
buf.append("</").append(getElementName()).append(">");
}
return buf.toString();
}
}

View file

@ -0,0 +1,294 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* Jingle content description.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class JingleContentDescription implements PacketExtension {
// static
public static final String NODENAME = "description";
// non-static
private final List<JinglePayloadType> payloads = new ArrayList<JinglePayloadType>();
/**
* Creates a content description..
*/
public JingleContentDescription() {
super();
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public String getElementName() {
return NODENAME;
}
/**
* Return the namespace.
*
* @return The namespace
*/
public abstract String getNamespace();
/**
* Adds a audio payload type to the packet.
*
* @param pt the audio payload type to add.
*/
public void addJinglePayloadType(final JinglePayloadType pt) {
synchronized (payloads) {
payloads.add(pt);
}
}
/**
* Adds a list of payloads to the packet.
*
* @param pts the payloads to add.
*/
public void addAudioPayloadTypes(final List<PayloadType.Audio> pts) {
synchronized (payloads) {
Iterator<PayloadType.Audio> ptIter = pts.iterator();
while (ptIter.hasNext()) {
PayloadType.Audio pt = ptIter.next();
addJinglePayloadType(new JinglePayloadType.Audio(pt));
}
}
}
/**
* Returns an Iterator for the audio payloads in the packet.
*
* @return an Iterator for the audio payloads in the packet.
*/
public Iterator<JinglePayloadType> getJinglePayloadTypes() {
return Collections.unmodifiableList(getJinglePayloadTypesList()).iterator();
}
/**
* Returns a list for the audio payloads in the packet.
*
* @return a list for the audio payloads in the packet.
*/
public ArrayList<JinglePayloadType> getJinglePayloadTypesList() {
synchronized (payloads) {
return new ArrayList<JinglePayloadType>(payloads);
}
}
/**
* Return the list of Payload types contained in the description.
*
* @return a list of PayloadType.Audio
*/
public ArrayList<PayloadType.Audio> getAudioPayloadTypesList() {
ArrayList<PayloadType.Audio> result = new ArrayList<PayloadType.Audio>();
Iterator<JinglePayloadType> jinglePtsIter = getJinglePayloadTypes();
while (jinglePtsIter.hasNext()) {
JinglePayloadType jpt = jinglePtsIter.next();
if (jpt instanceof JinglePayloadType.Audio) {
JinglePayloadType.Audio jpta = (JinglePayloadType.Audio) jpt;
result.add((PayloadType.Audio)jpta.getPayloadType());
}
}
return result;
}
/**
* Returns a count of the audio payloads in the Jingle packet.
*
* @return the number of audio payloads in the Jingle packet.
*/
public int getJinglePayloadTypesCount() {
synchronized (payloads) {
return payloads.size();
}
}
/**
* Convert a Jingle description to XML.
*
* @return a string with the XML representation
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
synchronized (payloads) {
if (payloads.size() > 0) {
buf.append("<").append(getElementName());
buf.append(" xmlns=\"").append(getNamespace()).append("\" >");
Iterator<JinglePayloadType> pt = payloads.listIterator();
while (pt.hasNext()) {
JinglePayloadType pte = pt.next();
buf.append(pte.toXML());
}
buf.append("</").append(getElementName()).append(">");
}
}
return buf.toString();
}
/**
* Jingle audio description
*/
public static class Audio extends JingleContentDescription {
public static final String NAMESPACE = "urn:xmpp:tmp:jingle:apps:rtp";
public Audio() {
super();
}
/**
* Utility constructor, with a JinglePayloadType
*/
public Audio(final JinglePayloadType pt) {
super();
addJinglePayloadType(pt);
}
public String getNamespace() {
return NAMESPACE;
}
}
/**
* A payload type, contained in a descriptor.
*
* @author Alvaro Saurin
*/
public static class JinglePayloadType {
public static final String NODENAME = "payload-type";
private PayloadType payload;
/**
* Create a payload type.
*
* @param payload the payload
*/
public JinglePayloadType(final PayloadType payload) {
super();
this.payload = payload;
}
/**
* Create an empty payload type.
*/
public JinglePayloadType() {
this(null);
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public static String getElementName() {
return NODENAME;
}
/**
* Get the payload represented.
*
* @return the payload
*/
public PayloadType getPayloadType() {
return payload;
}
/**
* Set the payload represented.
*
* @param payload the payload to set
*/
public void setPayload(final PayloadType payload) {
this.payload = payload;
}
protected String getChildAttributes() {
return null;
}
public String toXML() {
StringBuilder buf = new StringBuilder();
if (payload != null) {
buf.append("<").append(getElementName()).append(" ");
// We covert here the payload type to XML
if (payload.getId() != PayloadType.INVALID_PT) {
buf.append(" id=\"").append(payload.getId()).append("\"");
}
if (payload.getName() != null) {
buf.append(" name=\"").append(payload.getName()).append("\"");
}
if (payload.getChannels() != 0) {
buf.append(" channels=\"").append(payload.getChannels()).append("\"");
}
if (getChildAttributes() != null) {
buf.append(getChildAttributes());
}
buf.append("/>");
}
return buf.toString();
}
/**
* Audio payload type element
*/
public static class Audio extends JinglePayloadType {
public Audio(final PayloadType.Audio audio) {
super(audio);
}
protected String getChildAttributes() {
StringBuilder buf = new StringBuilder();
PayloadType pt = getPayloadType();
if (pt instanceof PayloadType.Audio) {
PayloadType.Audio pta = (PayloadType.Audio) pt;
buf.append(" clockrate=\"").append(pta.getClockRate()).append("\" ");
}
return buf.toString();
}
}
}
}

View file

@ -0,0 +1,154 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.jingle.media.ContentInfo;
/**
* Jingle content info
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public class JingleContentInfo implements PacketExtension {
protected ContentInfo mediaInfoElement;
private String namespace;
/**
* Empty constructor, with no jmf info.
*/
public JingleContentInfo() {
this(null);
}
/**
* Constructor with a jmf info
*
* @param mediaInfoElement MediaInfo element
*/
public JingleContentInfo(final ContentInfo mediaInfoElement) {
super();
this.mediaInfoElement = mediaInfoElement;
}
/**
* Get the jmf info element.
*
* @return the mediaInfoElement
*/
public ContentInfo getMediaInfo() {
return mediaInfoElement;
}
/**
* Get the element name
*/
public String getElementName() {
// Media info is supposed to be just a single-word command...
return getMediaInfo().toString();
}
/**
* Set the name space.
*
* @param ns the namespace
*/
protected void setNamespace(final String ns) {
namespace = ns;
}
/**
* Get the publilc namespace
*/
public String getNamespace() {
return namespace;
}
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName()).append(" xmlns=\"");
buf.append(getNamespace()).append("\" ");
buf.append("/>");
return buf.toString();
}
/**
* Transport part of a Jingle packet.
*/
public static class Audio extends JingleContentInfo {
public static final String NAMESPACE = "urn:xmpp:tmp:jingle:apps:rtp";
public Audio(final ContentInfo mi) {
super(mi);
setNamespace(NAMESPACE);
}
public String getNamespace() {
return NAMESPACE;
}
// Subclasses: specialize the Audio jmf info...
/**
* Busy jmf info.
*/
public static class Busy extends Audio {
public Busy() {
super(ContentInfo.Audio.BUSY);
}
}
/**
* Hold jmf info.
*/
public static class Hold extends Audio {
public Hold() {
super(ContentInfo.Audio.HOLD);
}
}
/**
* Mute jmf info.
*/
public static class Mute extends Audio {
public Mute() {
super(ContentInfo.Audio.MUTE);
}
}
/**
* Queued jmf info.
*/
public static class Queued extends Audio {
public Queued() {
super(ContentInfo.Audio.QUEUED);
}
}
/**
* Ringing jmf info.
*/
public static class Ringing extends Audio {
public Ringing() {
super(ContentInfo.Audio.RINGING);
}
}
}
}

View file

@ -0,0 +1,197 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.packet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.jingle.media.PayloadType;
/**
* Jingle content description.
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class JingleDescription implements PacketExtension {
private static final Logger LOGGER = Logger.getLogger(JingleDescription.class.getName());
// static
public static final String NODENAME = "description";
// non-static
private final List<PayloadType> payloads = new ArrayList<PayloadType>();
/**
* Creates a content description..
*/
public JingleDescription() {
super();
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public String getElementName() {
return NODENAME;
}
/**
* Return the namespace.
*
* @return The namespace
*/
public abstract String getNamespace();
/**
* Adds a audio payload type to the packet.
*
* @param pt the audio payload type to add.
*/
public void addPayloadType(final PayloadType pt) {
synchronized (payloads) {
if (pt == null) {
LOGGER.severe("Null payload type");
} else {
payloads.add(pt);
}
}
}
/**
* Adds a list of payloads to the packet.
*
* @param pts the payloads to add.
*/
public void addAudioPayloadTypes(final List<PayloadType> pts) {
synchronized (payloads) {
Iterator<PayloadType> ptIter = pts.iterator();
while (ptIter.hasNext()) {
PayloadType.Audio pt = (PayloadType.Audio) ptIter.next();
addPayloadType(new PayloadType.Audio(pt));
}
}
}
/**
* Returns an Iterator for the audio payloads in the packet.
*
* @return an Iterator for the audio payloads in the packet.
*/
public Iterator<PayloadType> getPayloadTypes() {
return Collections.unmodifiableList(getPayloadTypesList()).iterator();
}
/**
* Returns a list for the audio payloads in the packet.
*
* @return a list for the audio payloads in the packet.
*/
public List<PayloadType> getPayloadTypesList() {
synchronized (payloads) {
return new ArrayList<PayloadType>(payloads);
}
}
/**
* Return the list of Payload types contained in the description.
*
* @return a list of PayloadType.Audio
*/
public List<PayloadType> getAudioPayloadTypesList() {
ArrayList<PayloadType> result = new ArrayList<PayloadType>();
Iterator<PayloadType> jinglePtsIter = getPayloadTypes();
while (jinglePtsIter.hasNext()) {
PayloadType jpt = (PayloadType) jinglePtsIter.next();
if (jpt instanceof PayloadType.Audio) {
PayloadType.Audio jpta = (PayloadType.Audio) jpt;
result.add(jpta);
}
}
return result;
}
/**
* Returns a count of the audio payloads in the Jingle packet.
*
* @return the number of audio payloads in the Jingle packet.
*/
public int getPayloadTypesCount() {
synchronized (payloads) {
return payloads.size();
}
}
/**
* Convert a Jingle description to XML.
*
* @return a string with the XML representation
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
synchronized (payloads) {
if (payloads.size() > 0) {
buf.append("<").append(getElementName());
buf.append(" xmlns=\"").append(getNamespace()).append("\" >");
for (PayloadType payloadType : payloads) {
if (payloadType != null) {
buf.append(payloadType.toXML());
}
}
buf.append("</").append(getElementName()).append(">");
}
}
return buf.toString();
}
/**
* Jingle audio description
*/
public static class Audio extends JingleDescription {
public static final String NAMESPACE = "urn:xmpp:tmp:jingle:apps:rtp";
public Audio() {
super();
}
/**
* Utility constructor, with a PayloadType
*/
public Audio(final PayloadType pt) {
super();
addPayloadType(pt);
}
public String getNamespace() {
return NAMESPACE;
}
}
}

View file

@ -0,0 +1,156 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.packet;
import java.util.Locale;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smackx.jingle.media.ContentInfo;
import org.xmlpull.v1.XmlPullParser;
public class JingleError implements PacketExtension {
public static String NAMESPACE = "urn:xmpp:tmp:jingle:errors";
public static final JingleError OUT_OF_ORDER = new JingleError("out-of-order");
public static final JingleError UNKNOWN_SESSION = new JingleError("unknown-session");
public static final JingleError UNSUPPORTED_CONTENT = new JingleError(
"unsupported-content");
public static final JingleError UNSUPPORTED_TRANSPORTS = new JingleError(
"unsupported-transports");
// Non standard error messages
public static final JingleError NO_COMMON_PAYLOAD = new JingleError(
"unsupported-codecs");
public static final JingleError NEGOTIATION_ERROR = new JingleError(
"negotiation-error");
public static final JingleError MALFORMED_STANZA = new JingleError("malformed-stanza");
private String message;
/**
* Creates a new error with the specified code and message.
*
* @param message a message describing the error.
*/
public JingleError(final String message) {
this.message = message;
}
/**
* Returns the message describing the error, or null if there is no message.
*
* @return the message describing the error, or null if there is no message.
*/
public String getMessage() {
return message;
}
/**
* Returns the error as XML.
*
* @return the error as XML.
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
if (message != null) {
buf.append("<error type=\"cancel\">");
buf.append("<").append(message).append(" xmlns=\"").append(NAMESPACE).append(
"\"/>");
buf.append("</error>");
}
return buf.toString();
}
/**
* Returns a Action instance associated with the String value.
*/
public static JingleError fromString(String value) {
if (value != null) {
value = value.toLowerCase(Locale.US);
if (value.equals("out-of-order")) {
return OUT_OF_ORDER;
} else if (value.equals("unknown-session")) {
return UNKNOWN_SESSION;
} else if (value.equals("unsupported-content")) {
return UNSUPPORTED_CONTENT;
} else if (value.equals("unsupported-transports")) {
return UNSUPPORTED_TRANSPORTS;
} else if (value.equals("unsupported-codecs")) {
return NO_COMMON_PAYLOAD;
} else if (value.equals("negotiation-error")) {
return NEGOTIATION_ERROR;
} else if (value.equals("malformed-stanza")) {
return MALFORMED_STANZA;
}
}
return null;
}
public String toString() {
return getMessage();
}
public String getElementName() {
return message;
}
public String getNamespace() {
return NAMESPACE;
}
public static class Provider implements PacketExtensionProvider {
private PacketExtension audioInfo;
/**
* Empty constructor.
*/
public Provider() {
}
/**
* Parse a JingleDescription.Audio extension.
*/
public PacketExtension parseExtension(final XmlPullParser parser)
throws Exception {
PacketExtension result = null;
if (audioInfo != null) {
result = audioInfo;
} else {
String elementName = parser.getName();
// Try to get an Audio content info
ContentInfo mi = ContentInfo.Audio.fromString(elementName);
if (mi != null) {
result = new JingleContentInfo.Audio(mi);
}
}
return result;
}
}
}

View file

@ -0,0 +1,426 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.jingle.nat.ICECandidate;
import org.jivesoftware.smackx.jingle.nat.TransportCandidate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* A jingle transport extension
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public class JingleTransport implements PacketExtension {
// static
public static final String NODENAME = "transport";
// non-static
protected String namespace;
protected final List<JingleTransportCandidate> candidates = new ArrayList<JingleTransportCandidate>();
/**
* Default constructor.
*/
public JingleTransport() {
super();
}
/**
* Utility constructor, with a transport candidate element.
*
* @param candidate A transport candidate element to add.
*/
public JingleTransport(final JingleTransportCandidate candidate) {
super();
addCandidate(candidate);
}
/**
* Copy constructor.
*
* @param tr the other jingle transport.
*/
public JingleTransport(final JingleTransport tr) {
if (tr != null) {
namespace = tr.namespace;
if (tr.candidates.size() > 0) {
candidates.addAll(tr.candidates);
}
}
}
/**
* Adds a transport candidate.
*
* @param candidate the candidate
*/
public void addCandidate(final JingleTransportCandidate candidate) {
if (candidate != null) {
synchronized (candidates) {
candidates.add(candidate);
}
}
}
/**
* Get an iterator for the candidates
*
* @return an iterator
*/
public Iterator<JingleTransportCandidate> getCandidates() {
return Collections.unmodifiableList(getCandidatesList()).iterator();
}
/**
* Get the list of candidates.
*
* @return The candidates list.
*/
public List<JingleTransportCandidate> getCandidatesList() {
ArrayList<JingleTransportCandidate> res = null;
synchronized (candidates) {
res = new ArrayList<JingleTransportCandidate>(candidates);
}
return res;
}
/**
* Get the number of transport candidates.
*
* @return The number of transport candidates contained.
*/
public int getCandidatesCount() {
return getCandidatesList().size();
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public String getElementName() {
return NODENAME;
}
/**
* Set the namespace.
*
* @param ns The namespace
*/
protected void setNamespace(final String ns) {
namespace = ns;
}
/**
* Get the namespace.
*
* @return The namespace
*/
public String getNamespace() {
return namespace;
}
/**
* Return the XML representation for this element.
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getElementName()).append(" xmlns=\"");
buf.append(getNamespace()).append("\" ");
synchronized (candidates) {
if (getCandidatesCount() > 0) {
buf.append(">");
Iterator<JingleTransportCandidate> iter = getCandidates();
while (iter.hasNext()) {
JingleTransportCandidate candidate = iter.next();
buf.append(candidate.toXML());
}
buf.append("</").append(getElementName()).append(">");
} else {
buf.append("/>");
}
}
return buf.toString();
}
/**
* Candidate element in the transport. This class acts as a view of the
* "TransportCandidate" in the Jingle space.
*
* @author Alvaro Saurin
* @see TransportCandidate
*/
public static abstract class JingleTransportCandidate {
public static final String NODENAME = "candidate";
// The transport candidate contained in the element.
protected TransportCandidate transportCandidate;
/**
* Creates a new TransportNegotiator child.
*/
public JingleTransportCandidate() {
super();
}
/**
* Creates a new TransportNegotiator child.
*
* @param candidate the jmf transport candidate
*/
public JingleTransportCandidate(final TransportCandidate candidate) {
super();
setMediaTransport(candidate);
}
/**
* Returns the XML element name of the element.
*
* @return the XML element name of the element.
*/
public static String getElementName() {
return NODENAME;
}
/**
* Get the current transportElement candidate.
*
* @return the transportElement candidate
*/
public TransportCandidate getMediaTransport() {
return transportCandidate;
}
/**
* Set the transportElement candidate.
*
* @param cand the transportElement candidate
*/
public void setMediaTransport(final TransportCandidate cand) {
if (cand != null) {
transportCandidate = cand;
}
}
/**
* Get the list of attributes.
*
* @return a string with the list of attributes.
*/
protected String getChildElements() {
return null;
}
/**
* Obtain a valid XML representation of a trancport candidate
*
* @return A string containing the XML dump of the transport candidate.
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
String childElements = getChildElements();
if (transportCandidate != null && childElements != null) {
buf.append("<").append(getElementName()).append(" ");
buf.append(childElements);
buf.append("/>");
}
return buf.toString();
}
}
// Subclasses
/**
* RTP-ICE profile
*/
public static class Ice extends JingleTransport {
public static final String NAMESPACE = "urn:xmpp:tmp:jingle:transports:ice-udp";
public Ice() {
super();
setNamespace(NAMESPACE);
}
/**
* Add a transport candidate
*
* @see org.jivesoftware.smackx.jingle.packet.JingleTransport#addCandidate(org.jivesoftware.smackx.jingle.packet.JingleTransport.JingleTransportCandidate)
*/
public void addCandidate(final JingleTransportCandidate candidate) {
super.addCandidate(candidate);
}
/**
* Get the list of candidates. As a "raw-udp" transport can only contain
* one candidate, we use the first in the list...
*
* @see org.jivesoftware.smackx.jingle.packet.JingleTransport#getCandidates()
*/
public List<JingleTransportCandidate> getCandidatesList() {
List<JingleTransportCandidate> copy = new ArrayList<JingleTransportCandidate>();
List<JingleTransportCandidate> superCandidatesList = super.getCandidatesList();
for (int i = 0; i < superCandidatesList.size(); i++) {
copy.add(superCandidatesList.get(i));
}
return copy;
}
public static class Candidate extends JingleTransportCandidate {
/**
* Default constructor
*/
public Candidate() {
super();
}
/**
* Constructor with a transport candidate.
*/
public Candidate(final TransportCandidate tc) {
super(tc);
}
/**
* Get the elements of this candidate.
*/
protected String getChildElements() {
StringBuilder buf = new StringBuilder();
if (transportCandidate != null) {// && transportCandidate instanceof ICECandidate) {
ICECandidate tci = (ICECandidate) transportCandidate;
// We convert the transportElement candidate to XML here...
buf.append(" generation=\"").append(tci.getGeneration()).append("\"");
buf.append(" ip=\"").append(tci.getIp()).append("\"");
buf.append(" port=\"").append(tci.getPort()).append("\"");
buf.append(" network=\"").append(tci.getNetwork()).append("\"");
buf.append(" username=\"").append(tci.getUsername()).append("\"");
buf.append(" password=\"").append(tci.getPassword()).append("\"");
buf.append(" preference=\"").append(tci.getPreference()).append("\"");
buf.append(" type=\"").append(tci.getType()).append("\"");
// Optional elements
if (transportCandidate.getName() != null) {
buf.append(" name=\"").append(tci.getName()).append("\"");
}
}
return buf.toString();
}
}
}
/**
* Raw UDP profile.
*/
public static class RawUdp extends JingleTransport {
public static final String NAMESPACE = "http://www.xmpp.org/extensions/xep-0177.html#ns";
public RawUdp() {
super();
setNamespace(NAMESPACE);
}
/**
* Add a transport candidate
*
* @see org.jivesoftware.smackx.jingle.packet.JingleTransport#addCandidate(org.jivesoftware.smackx.jingle.packet.JingleTransport.JingleTransportCandidate)
*/
public void addCandidate(final JingleTransportCandidate candidate) {
candidates.clear();
super.addCandidate(candidate);
}
/**
* Get the list of candidates. As a "raw-udp" transport can only contain
* one candidate, we use the first in the list...
*
* @see org.jivesoftware.smackx.jingle.packet.JingleTransport#getCandidates()
*/
public List<JingleTransportCandidate> getCandidatesList() {
List<JingleTransportCandidate> copy = new ArrayList<JingleTransportCandidate>();
List<JingleTransportCandidate> superCandidatesList = super.getCandidatesList();
if (superCandidatesList.size() > 0) {
copy.add(superCandidatesList.get(0));
}
return copy;
}
/**
* Raw-udp transport candidate.
*/
public static class Candidate extends JingleTransportCandidate {
/**
* Default constructor
*/
public Candidate() {
super();
}
/**
* Constructor with a transport candidate.
*/
public Candidate(final TransportCandidate tc) {
super(tc);
}
/**
* Get the elements of this candidate.
*/
protected String getChildElements() {
StringBuilder buf = new StringBuilder();
if (transportCandidate != null && transportCandidate instanceof TransportCandidate.Fixed) {
TransportCandidate.Fixed tcf = (TransportCandidate.Fixed) transportCandidate;
buf.append(" generation=\"").append(tcf.getGeneration()).append("\"");
buf.append(" ip=\"").append(tcf.getIp()).append("\"");
buf.append(" port=\"").append(tcf.getPort()).append("\"");
// Optional parameters
String name = tcf.getName();
if (name != null) {
buf.append(" name=\"").append(name).append("\"");
}
}
return buf.toString();
}
}
}
}

View file

@ -0,0 +1,141 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.packet.JingleContentDescription;
import org.jivesoftware.smackx.jingle.packet.JingleContentDescription.JinglePayloadType;
import org.xmlpull.v1.XmlPullParser;
/**
* Parser for a Jingle description
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class JingleContentDescriptionProvider implements PacketExtensionProvider {
/**
* Default constructor
*/
public JingleContentDescriptionProvider() {
super();
}
/**
* Parse a iq/jingle/description/payload-type element.
*
* @param parser the input to parse
* @return a payload type element
* @throws Exception
*/
protected JinglePayloadType parsePayload(final XmlPullParser parser)
throws Exception {
int ptId = 0;
String ptName;
int ptChannels = 0;
try {
ptId = Integer.parseInt(parser.getAttributeValue("", "id"));
} catch (Exception e) {
}
ptName = parser.getAttributeValue("", "name");
try {
ptChannels = Integer.parseInt(parser.getAttributeValue("", "channels"));
} catch (Exception e) {
}
return new JinglePayloadType(new PayloadType(ptId, ptName, ptChannels));
}
/**
* Parse a iq/jingle/description element.
*
* @param parser the input to parse
* @return a description element
* @throws Exception
*/
public PacketExtension parseExtension(final XmlPullParser parser) throws Exception {
boolean done = false;
JingleContentDescription desc = getInstance();
while (!done) {
int eventType = parser.next();
String name = parser.getName();
if (eventType == XmlPullParser.START_TAG) {
if (name.equals(JingleContentDescription.JinglePayloadType.NODENAME)) {
desc.addJinglePayloadType(parsePayload(parser));
} else {
throw new Exception("Unknow element \"" + name + "\" in content.");
}
} else if (eventType == XmlPullParser.END_TAG) {
if (name.equals(JingleContentDescription.NODENAME)) {
done = true;
}
}
}
return desc;
}
/**
* Return a new instance of this class. Subclasses must overwrite this
* method.
*/
protected abstract JingleContentDescription getInstance();
/**
* Jingle audio
*/
public static class Audio extends JingleContentDescriptionProvider {
/**
* Default constructor
*/
public Audio() {
super();
}
/**
* Parse an audio payload type.
*/
public JinglePayloadType parsePayload(final XmlPullParser parser)
throws Exception {
JinglePayloadType pte = super.parsePayload(parser);
PayloadType.Audio pt = new PayloadType.Audio(pte.getPayloadType());
int ptClockRate = 0;
try {
ptClockRate = Integer.parseInt(parser.getAttributeValue("", "clockrate"));
} catch (Exception e) {
}
pt.setClockRate(ptClockRate);
return new JinglePayloadType.Audio(pt);
}
/**
* Get a new instance of this object.
*/
protected JingleContentDescription getInstance() {
return new JingleContentDescription.Audio();
}
}
}

View file

@ -0,0 +1,122 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smackx.jingle.media.ContentInfo;
import org.jivesoftware.smackx.jingle.packet.JingleContentInfo;
import org.xmlpull.v1.XmlPullParser;
/**
* Jingle Audio jmf-info provider
*
* @author Alvaro Saurin
*/
public class JingleContentInfoProvider implements PacketExtensionProvider {
/**
* Creates a new provider. ProviderManager requires that every
* PacketExtensionProvider has a public, no-argument constructor
*/
public JingleContentInfoProvider() {
super();
}
public PacketExtension parseExtension(final XmlPullParser parser) throws Exception {
// This method must be overwritten by subclasses...
return null;
}
/**
* JingleDescription.Audio info provider
*/
public static class Audio extends JingleContentInfoProvider {
private PacketExtension audioInfo;
/**
* Empty constructor.
*/
public Audio() {
this(null);
}
/**
* Constructor with an audio info.
*
* @param audioInfo the jmf info
*/
public Audio(final PacketExtension audioInfo) {
super();
this.audioInfo = audioInfo;
}
/**
* Parse a JingleDescription.Audio extension.
*/
public PacketExtension parseExtension(final XmlPullParser parser)
throws Exception {
PacketExtension result = null;
if (audioInfo != null) {
result = audioInfo;
} else {
String elementName = parser.getName();
// Try to get an Audio content info
ContentInfo mi = ContentInfo.Audio.fromString(elementName);
if (mi != null) {
result = new JingleContentInfo.Audio(mi);
}
}
return result;
}
// Sub-elements
public static class Busy extends Audio {
public Busy() {
super(new JingleContentInfo.Audio.Busy());
}
}
public static class Hold extends Audio {
public Hold() {
super(new JingleContentInfo.Audio.Hold());
}
}
public static class Mute extends Audio {
public Mute() {
super(new JingleContentInfo.Audio.Mute());
}
}
public static class Queued extends Audio {
public Queued() {
super(new JingleContentInfo.Audio.Queued());
}
}
public static class Ringing extends Audio {
public Ringing() {
super(new JingleContentInfo.Audio.Ringing());
}
}
}
}

View file

@ -0,0 +1,55 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smackx.jingle.packet.JingleContent;
import org.xmlpull.v1.XmlPullParser;
/**
* Jingle <content> provider
*
* @author Jeff Williams
*/
public class JingleContentProvider implements PacketExtensionProvider {
/**
* Creates a new provider. ProviderManager requires that every
* PacketExtensionProvider has a public, no-argument constructor
*/
public JingleContentProvider() {
super();
}
/**
* Parse a JingleContent extension.
*/
public PacketExtension parseExtension(final XmlPullParser parser) throws Exception {
PacketExtension result = null;
String elementName = parser.getName();
String creator = parser.getAttributeValue("", JingleContent.CREATOR);
String name = parser.getAttributeValue("", JingleContent.NAME);
// Try to get an Audio content info
result = new JingleContent(creator, name);
return result;
}
}

View file

@ -0,0 +1,140 @@
/**
*
* Copyright 2003-2005 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle.provider;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smackx.jingle.media.PayloadType;
import org.jivesoftware.smackx.jingle.packet.JingleDescription;
import org.xmlpull.v1.XmlPullParser;
/**
* Parser for a Jingle description
*
* @author Alvaro Saurin <alvaro.saurin@gmail.com>
*/
public abstract class JingleDescriptionProvider implements PacketExtensionProvider {
/**
* Default constructor
*/
public JingleDescriptionProvider() {
super();
}
/**
* Parse a iq/jingle/description/payload-type element.
*
* @param parser
* the input to parse
* @return a payload type element
* @throws Exception
*/
protected PayloadType parsePayload(final XmlPullParser parser) throws Exception {
int ptId = 0;
String ptName;
int ptChannels = 0;
try {
ptId = Integer.parseInt(parser.getAttributeValue("", "id"));
} catch (Exception e) {
}
ptName = parser.getAttributeValue("", "name");
try {
ptChannels = Integer.parseInt(parser.getAttributeValue("", "channels"));
} catch (Exception e) {
}
return new PayloadType(ptId, ptName, ptChannels);
}
/**
* Parse a iq/jingle/description element.
*
* @param parser
* the input to parse
* @return a description element
* @throws Exception
*/
public PacketExtension parseExtension(final XmlPullParser parser) throws Exception {
boolean done = false;
JingleDescription desc = getInstance();
while (!done) {
int eventType = parser.next();
String name = parser.getName();
if (eventType == XmlPullParser.START_TAG) {
if (name.equals(PayloadType.NODENAME)) {
desc.addPayloadType(parsePayload(parser));
} else {
throw new Exception("Unknow element \"" + name + "\" in content.");
}
} else if (eventType == XmlPullParser.END_TAG) {
if (name.equals(JingleDescription.NODENAME)) {
done = true;
}
}
}
return desc;
}
/**
* Return a new instance of this class. Subclasses must overwrite this
* method.
*/
protected abstract JingleDescription getInstance();
/**
* Jingle audio
*/
public static class Audio extends JingleDescriptionProvider {
/**
* Default constructor
*/
public Audio() {
super();
}
/**
* Parse an audio payload type.
*/
public PayloadType parsePayload(final XmlPullParser parser) throws Exception {
PayloadType pte = super.parsePayload(parser);
PayloadType.Audio pt = new PayloadType.Audio(pte);
int ptClockRate = 0;
try {
ptClockRate = Integer.parseInt(parser.getAttributeValue("", "clockrate"));
} catch (Exception e) {
}
pt.setClockRate(ptClockRate);
return pt;
}
/**
* Get a new instance of this object.
*/
protected JingleDescription getInstance() {
return new JingleDescription.Audio();
}
}
}

Some files were not shown because too many files have changed in this diff Show more