Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpperret2006-10-17 15:13:09 -0400
committerpperret2006-10-17 15:13:09 -0400
commit74b36391b5cd2cc712303392645f01971b6dabcc (patch)
tree950e36f74b6a90535de8a3aaf33a4d226279d956 /protocols
parent8deb3e7166aa8524fa2b53da623a558f7683d2bb (diff)
downloadorg.eclipse.ecf-74b36391b5cd2cc712303392645f01971b6dabcc.tar.gz
org.eclipse.ecf-74b36391b5cd2cc712303392645f01971b6dabcc.tar.xz
org.eclipse.ecf-74b36391b5cd2cc712303392645f01971b6dabcc.zip
xmpp jingle prov.
Diffstat (limited to 'protocols')
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/DecodingJMFMediaEngine.java131
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/DecodingJSMediaEngine.java134
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/EncodingJMFMediaEngine.java194
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/EncodingJSMediaEngine.java111
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JMFMediaTransport.java152
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaListener.java25
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaManager.java539
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaSession.java248
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/MediaEngine.java287
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/MediaTransport.java172
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/AISDataSource.java71
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/AISPushBufferStream.java127
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/DSInputStream.java118
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/DefaultFormatTranslationProvider.java268
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/FormatTranslationProvider.java12
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/FormatTranslator.java91
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/PTIDAssigner.java59
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/SpeexFormatTranslationProvider.java126
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/StreamConverter.java60
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/BasicResolver.java61
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/FixedResolver.java51
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/STUNResolver.java485
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportCandidate.java703
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportResolver.java390
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportResolverListener.java38
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/Jingle.java589
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleContentDescription.java279
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleContentInfo.java138
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleError.java102
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleTransport.java384
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleContentDescriptionProvider.java125
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleContentInfoProvider.java106
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleProvider.java126
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleProviderTest.java117
-rw-r--r--protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleTransportProvider.java226
35 files changed, 6845 insertions, 0 deletions
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/DecodingJMFMediaEngine.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/DecodingJMFMediaEngine.java
new file mode 100644
index 000000000..9f8776d61
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/DecodingJMFMediaEngine.java
@@ -0,0 +1,131 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.media.Controller;
+import javax.media.Manager;
+import javax.media.NoProcessorException;
+import javax.media.Processor;
+import javax.media.control.TrackControl;
+import javax.media.format.AudioFormat;
+import javax.media.protocol.DataSource;
+import javax.sound.sampled.AudioInputStream;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+import org.jivesoftware.smackx.jingle.media.JingleMediaManager.ContentType;
+import org.jivesoftware.smackx.jingle.media.util.FormatTranslator;
+import org.jivesoftware.smackx.jingle.media.util.StreamConverter;
+
+public class DecodingJMFMediaEngine extends MediaEngine.Decoding {
+
+ private InputStream input;
+ private AudioInputStream aisOutput;
+
+ private Processor processor;
+
+ public DecodingJMFMediaEngine() {
+ super(null);
+ }
+
+ public DecodingJMFMediaEngine(PayloadType inputFormat) {
+ super(inputFormat);
+ }
+
+ public Set getSupportedPayloadTypes(ContentType contentType) {
+ if(contentType == ContentType.AUDIO) {
+ Set result = new HashSet();
+
+ for(int i = 0; i < EncodingJMFMediaEngine.supported.length; i++) {
+
+ PayloadType pt = FormatTranslator.toPayloadType(EncodingJMFMediaEngine.supported[i]);
+ if(pt!=null) result.add(pt);
+ }
+
+ return result;
+ }
+ return null;
+ }
+
+ public void start() {
+ processor.start();
+
+ }
+
+ public void stop() {
+ processor.stop();
+ }
+
+ public void configure() {
+
+ if(input == null) throw new InputNotSetError("Input must be set before configuring.");
+ DataSource inputDS = StreamConverter.toDataSource(input);
+
+ try {
+ processor = Manager.createProcessor(inputDS);
+
+ processor.configure();
+ waitForState(Processor.Configured, processor);
+
+ TrackControl[] tracks = processor.getTrackControls();
+
+ tracks[0].setFormat(new AudioFormat(AudioFormat.LINEAR, 44100.0, 16, 2));
+
+ processor.realize();
+
+ } catch (NoProcessorException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ public int getState() {
+ if(processor == null) return INSTANTIATED;
+ else if(processor.getState() == Processor.Configuring ||
+ processor.getState() == Processor.Configured ||
+ processor.getState() == Controller.Realizing) return CONFIGURING;
+ else if(processor.getState() == Controller.Realized) return CONFIGURED;
+ else if(processor.getState() == Controller.Started) return STARTED;
+ else return INSTANTIATED;
+ }
+
+ public void close() {
+ processor.close();
+ processor = null;
+ try {
+ getOutput().close();
+ } catch (IOException e) {}
+ }
+
+ public InputStream getOutput() {
+ if(processor == null || (processor.getState() != Processor.Realized && processor.getState() != Processor.Started)) aisOutput = null;
+ else if(aisOutput == null) aisOutput = StreamConverter.toAudioInputStream(processor.getDataOutput());
+
+ return aisOutput;
+ }
+
+ /**
+ * Block until processor p enters the given state.
+ */
+ private void waitForState(int state, Processor p){
+ while(p.getState() != state) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ public InputStream getInput() {
+ return input;
+ }
+
+ public void setInput(InputStream input) {
+ if(processor == null) this.input = input;
+ }
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/DecodingJSMediaEngine.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/DecodingJSMediaEngine.java
new file mode 100644
index 000000000..131907d77
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/DecodingJSMediaEngine.java
@@ -0,0 +1,134 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+import org.jivesoftware.smackx.jingle.media.JingleMediaManager.ContentType;
+import org.jivesoftware.smackx.jingle.media.util.FormatTranslator;
+
+public class DecodingJSMediaEngine extends MediaEngine.Decoding {
+
+ private static AudioFormat[] defaultOutputs = {new AudioFormat(8000, 8, 1, true, false),
+ new AudioFormat(16000, 8, 1, true, false),
+ new AudioFormat(44100, 8, 1, true, false),
+ new AudioFormat(8000, 8, 2, true, false),
+ new AudioFormat(16000, 8, 2, true, false),
+ new AudioFormat(44100, 8, 2, true, false),
+ new AudioFormat(8000, 16, 1, true, false),
+ new AudioFormat(16000, 16, 1, true, false),
+ new AudioFormat(44100, 16, 1, true, false),
+ new AudioFormat(8000, 16, 2, true, false),
+ new AudioFormat(16000, 16, 2, true, false),
+ new AudioFormat(44100, 16, 2, true, false)};
+
+ private AudioInputStream input, output;
+ private AudioFormat format;
+ private int state;
+
+
+ /**
+ * Zero argument constructor demanded by the SPI. Should not normally be used.
+ */
+ public DecodingJSMediaEngine() {
+ super(null);
+ }
+
+ public DecodingJSMediaEngine(PayloadType inputFormat) {
+ super(inputFormat);
+ format = FormatTranslator.toJSAudioFormat(inputFormat);
+ state = INSTANTIATED;
+ }
+
+ public synchronized InputStream getInput() {
+ return input;
+ }
+
+ public synchronized void setInput(InputStream input) {
+ try {
+ AudioInputStream aIS;
+ if(input instanceof AudioInputStream) aIS = (AudioInputStream) input;
+ else aIS = AudioSystem.getAudioInputStream(input);
+
+ if(!FormatTranslator.equals(aIS.getFormat(),format)) throw new IllegalArgumentException("Incoming " +
+ "audio stream does not have the correct format. Expected "+ format +" and got " +
+ aIS.getFormat());
+ this.input = aIS;
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ }
+
+ public synchronized InputStream getOutput() {
+ return output;
+ }
+
+ public synchronized Set getSupportedPayloadTypes(ContentType contentType) {
+ Set result = new HashSet();
+
+ if(contentType.equals(ContentType.AUDIO)) {
+
+ for(int i = 0; i < defaultOutputs.length; i++) {
+ AudioFormat outputFormat = defaultOutputs[i];
+
+ AudioFormat.Encoding[] encodings = AudioSystem.getTargetEncodings(outputFormat);
+
+ for(int j = 0; j < encodings.length; j++) {
+
+ AudioFormat[] formats = AudioSystem.getTargetFormats(encodings[j], outputFormat);
+ for(int k = 0; k < formats.length; k++) {
+
+ if(AudioSystem.isConversionSupported(AudioFormat.Encoding.PCM_SIGNED, formats[k]) && FormatTranslator.toPayloadType(formats[k]) != null)
+ result.add(FormatTranslator.toPayloadType(formats[k]));
+ }
+ }
+ }
+
+ }
+
+ return result;
+ }
+
+ public synchronized int getState() {
+ return state;
+ }
+
+ public synchronized void configure() throws UnableToConfigureMediaEngine {
+ if(state != INSTANTIATED) throw new StateOrderViolationError("MediaEngine should be in the instantiated state when configure is called.");
+ if(input == null) throw new InputNotSetError("Must set input before configuring MediaEngine.");
+ output = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, input);
+ state = CONFIGURED;
+ }
+
+ public synchronized void start() {
+ if(state == CONFIGURED) state = STARTED;
+ else throw new StateOrderViolationError("Tried to start MediaEngine without configuring first.");
+ }
+
+ public synchronized void stop() {
+ if(state == STARTED) state = CONFIGURED;
+ else throw new StateOrderViolationError("Tried to stop MediaEngine when it hasn't been started.");
+ }
+
+ public synchronized void close() {
+ try {
+ if(output != null) {
+ output.close();
+ output = null;
+ }
+ state = CLOSED;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/EncodingJMFMediaEngine.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/EncodingJMFMediaEngine.java
new file mode 100644
index 000000000..a05df1207
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/EncodingJMFMediaEngine.java
@@ -0,0 +1,194 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.media.Controller;
+import javax.media.Manager;
+import javax.media.NoProcessorException;
+import javax.media.Processor;
+import javax.media.control.TrackControl;
+import javax.media.format.AudioFormat;
+import javax.media.protocol.DataSource;
+import javax.sound.sampled.AudioInputStream;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+import org.jivesoftware.smackx.jingle.media.JingleMediaManager.ContentType;
+import org.jivesoftware.smackx.jingle.media.util.FormatTranslator;
+import org.jivesoftware.smackx.jingle.media.util.StreamConverter;
+
+public class EncodingJMFMediaEngine extends MediaEngine.Encoding {
+
+ protected static final AudioFormat[] supported = {
+ new AudioFormat(AudioFormat.LINEAR, 44100.0, 16, 2, AudioFormat.BIG_ENDIAN, AudioFormat.SIGNED, AudioFormat.NOT_SPECIFIED, AudioFormat.NOT_SPECIFIED, byte[].class),
+ new AudioFormat(AudioFormat.LINEAR, 44100.0, 16, 1, AudioFormat.BIG_ENDIAN, AudioFormat.SIGNED, AudioFormat.NOT_SPECIFIED, AudioFormat.NOT_SPECIFIED, byte[].class)
+ };
+
+ private Processor processor;
+ private InputStream input;
+ private AudioInputStream aisOutput;
+
+ public EncodingJMFMediaEngine() {
+ super(null);
+ }
+
+ public EncodingJMFMediaEngine(PayloadType outputFormat) {
+ super(outputFormat);
+ }
+
+ public Set getSupportedPayloadTypes(ContentType contentType) {
+ if(contentType == ContentType.AUDIO) {
+ Set result = new HashSet();
+
+ for(int i = 0; i < supported.length; i++) {
+
+ PayloadType pt = FormatTranslator.toPayloadType(supported[i]);
+ if(pt!=null) result.add(pt);
+ }
+
+ return result;
+ }
+ return null;
+// //first check the cache
+// Map cachedResultByDS = (Map) supportedPTs.get(StreamConverter.toDataSource(getInput()));
+// if(cachedResultByDS != null) {
+// Set cachedResultByContentType = (Set) cachedResultByDS.get(contentType);
+// if(cachedResultByContentType != null) return cachedResultByContentType;
+// }
+//
+//
+// // currently only supports audio
+// if(contentType.equals(ContentType.AUDIO)) {
+// if(getInput() == null) throw new MediaEngine.InputNotSetError("Must set input before" +
+// " asking which formats it supports.");
+//
+// DataSource inputDS = StreamConverter.toDataSource(input);
+//
+//
+// //this is a little bit of a hack, but it beats hard coding it.
+// Processor p;
+// try {
+// p = Manager.createProcessor(inputDS);
+// p.configure();
+//
+// waitForState(Processor.Configured, p);
+//
+// TrackControl[] tracks = p.getTrackControls();
+//
+// ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.CONTENT_UNKNOWN);
+// p.setContentDescriptor(cd);
+//
+// Format[] formats = tracks[0].getSupportedFormats();
+// Set result = new HashSet();
+//
+// for(int i = 0; i < formats.length; i++) {
+// PayloadType pt = FormatTranslator.toPayloadType(formats[i]);
+// if(pt!=null)System.out.println(formats[i] + " - " + pt.getName() + ", " + pt.getChannels() + ", " + pt.getId());
+// if(pt!=null) result.add(pt);
+// }
+//
+// //add the result to the cache
+// if(cachedResultByDS != null) cachedResultByDS.put(contentType, result);
+// else {
+// Map newCTMap = new HashMap();
+// newCTMap.put(contentType, result);
+// supportedPTs.put(inputDS, newCTMap);
+// }
+//
+// return result;
+//
+// } catch (Exception e) {
+// //not sure if much more can be done here.
+// e.printStackTrace();
+// }
+//
+// }
+// return null;
+ }
+
+ public void configure() throws UnableToConfigureMediaEngine {
+
+ DataSource inputDS = StreamConverter.toDataSource(input);
+
+ if(inputDS == null) throw new InputNotSetError("Input must be set before configuring.");
+
+ try {
+ processor = Manager.createProcessor(inputDS);
+
+ processor.configure();
+ waitForState(Processor.Configured, processor);
+
+ TrackControl[] tracks = processor.getTrackControls();
+ try {
+ tracks[0].setFormat(FormatTranslator.toJMFFormat(getPayloadType()));
+ } catch (Exception e) {
+ throw new UnableToConfigureMediaEngine(e);
+ }
+
+ processor.realize();
+
+ } catch (NoProcessorException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ public void start() {
+ processor.start();
+ }
+
+ public void stop() {
+ processor.stop();
+
+ }
+
+ public int getState() {
+ if(processor == null) return INSTANTIATED;
+ else if(processor.getState() == Processor.Configuring ||
+ processor.getState() == Processor.Configured ||
+ processor.getState() == Controller.Realizing) return CONFIGURING;
+ else if(processor.getState() == Controller.Realized) return CONFIGURED;
+ else if(processor.getState() == Controller.Started) return STARTED;
+ else return INSTANTIATED;
+ }
+
+ public void close() {
+ processor.close();
+ processor = null;
+ try {
+ getOutput().close();
+ } catch (IOException e) {}
+ }
+
+ /**
+ * Block until processor p enters the given state.
+ */
+ private void waitForState(int state, Processor p){
+ while(p.getState() != state) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {}
+ }
+ }
+
+ public InputStream getOutput() {
+ if(processor == null || (processor.getState() != Processor.Realized && processor.getState() != Processor.Started)) aisOutput = null;
+ else if(aisOutput == null) aisOutput = StreamConverter.toAudioInputStream(processor.getDataOutput());
+
+ return aisOutput;
+ }
+
+ public InputStream getInput() {
+ return input;
+ }
+
+ public void setInput(InputStream input) {
+ if(processor == null) this.input = input;
+ }
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/EncodingJSMediaEngine.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/EncodingJSMediaEngine.java
new file mode 100644
index 000000000..c765e09ba
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/EncodingJSMediaEngine.java
@@ -0,0 +1,111 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+import org.jivesoftware.smackx.jingle.media.JingleMediaManager.ContentType;
+import org.jivesoftware.smackx.jingle.media.util.FormatTranslator;
+
+public class EncodingJSMediaEngine extends MediaEngine.Encoding {
+
+ private AudioInputStream input, output;
+ private AudioFormat format;
+ private int state;
+
+ /**
+ * Zero argument constructor demanded by the SPI. Should not normally be used.
+ */
+ public EncodingJSMediaEngine() {
+ super(null);
+ }
+
+ public EncodingJSMediaEngine(PayloadType outputFormat) {
+ super(outputFormat);
+ format = FormatTranslator.toJSAudioFormat(outputFormat);
+ state = INSTANTIATED;
+ }
+
+ public synchronized InputStream getInput() {
+ return input;
+ }
+
+ public synchronized void setInput(InputStream input) {
+ try {
+
+ AudioInputStream aIS;
+ if(input instanceof AudioInputStream) aIS = (AudioInputStream) input;
+ else aIS = AudioSystem.getAudioInputStream(input);
+
+ if((aIS.getFormat().getEncoding().equals(AudioFormat.Encoding.PCM_SIGNED))
+ || (aIS.getFormat().getEncoding().equals(AudioFormat.Encoding.PCM_UNSIGNED)))
+ this.input = aIS;
+
+ else throw new IllegalArgumentException("Input to EncodingJSMediaEngine should be PCM.");
+
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public synchronized InputStream getOutput() {
+ return output;
+ }
+
+ public synchronized Set getSupportedPayloadTypes(ContentType contentType) {
+ if(input == null) throw new InputNotSetError("Input must be set before calling getSupportedPayloadTypes.");
+ Set result = new HashSet();
+
+ if(contentType.equals(ContentType.AUDIO)) {
+ AudioFormat.Encoding[] encodings = AudioSystem.getTargetEncodings(input.getFormat());
+
+ for(int i = 0; i < encodings.length; i++) {
+ AudioFormat[] formats = AudioSystem.getTargetFormats(encodings[i], input.getFormat());
+ for(int j = 0; j < formats.length; j++)
+ if(FormatTranslator.toPayloadType(formats[j]) != null)
+ result.add(FormatTranslator.toPayloadType(formats[j]));
+ }
+ }
+ return result;
+ }
+
+ public synchronized int getState() {
+ return state;
+ }
+
+ public synchronized void configure() throws UnableToConfigureMediaEngine {
+ if(input == null) throw new InputNotSetError("Must set input before configuring MediaEngine.");
+ if(state != INSTANTIATED) throw new StateOrderViolationError("MediaEngine should be in the instantiated state when configure is called.");
+ output = AudioSystem.getAudioInputStream(format, input);
+ state = CONFIGURED;
+ }
+
+ public synchronized void start() {
+ if(state == CONFIGURED) state = STARTED;
+ else throw new StateOrderViolationError("Tried to start MediaEngine without configuring first.");
+ }
+
+ public synchronized void stop() {
+ if(state == STARTED) state = CONFIGURED;
+ else throw new StateOrderViolationError("Tried to stop MediaEngine when it hasn't been started.");
+ }
+
+ public synchronized void close() {
+ try {
+ if(output != null) {
+ output.close();
+ output = null;
+ }
+ state = CLOSED;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JMFMediaTransport.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JMFMediaTransport.java
new file mode 100644
index 000000000..20510873d
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JMFMediaTransport.java
@@ -0,0 +1,152 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.media.protocol.DataSource;
+import javax.media.rtp.RTPManager;
+import javax.media.rtp.ReceiveStream;
+import javax.media.rtp.ReceiveStreamListener;
+import javax.media.rtp.SendStream;
+import javax.media.rtp.SessionAddress;
+import javax.media.rtp.event.NewReceiveStreamEvent;
+import javax.media.rtp.event.ReceiveStreamEvent;
+
+import org.jivesoftware.smackx.jingle.media.util.StreamConverter;
+import org.jivesoftware.smackx.nat.TransportCandidate;
+import org.jivesoftware.smackx.nat.TransportCandidate.Protocol;
+
+/**
+ * JMF based subclass of the abstract MediaTransport class. This class presently only supports
+ * sending via UDP.
+ *
+ * @author Alasdair North
+ *
+ */
+public class JMFMediaTransport extends MediaTransport implements ReceiveStreamListener {
+
+ private RTPManager rtpManager;
+ private int receivingState, transmittingState;
+ private SendStream sendStream;
+ private ReceiveStream receiveStream;
+ private InputStream inputStream, outputStream;
+ private DataSource input;
+
+ public JMFMediaTransport(TransportCandidate remoteCandidate, TransportCandidate localCandidate) {
+ super(remoteCandidate, localCandidate);
+ rtpManager = RTPManager.newInstance();
+ transmittingState = MediaTransport.TRANSMITTER_INSTANTIATED;
+ receivingState = MediaTransport.RECEIVER_INSTANTIATED;
+ }
+
+ public void configure() {
+ if(getInput() == null) throw new UnableToConfigure("Tried to configure with input not set.");
+
+ try {
+ SessionAddress localAddr = new SessionAddress(InetAddress.getByName(
+ getLocalCandidate().getIP()), getLocalCandidate().getPort());
+ rtpManager.initialize(localAddr);
+ rtpManager.addTarget(new SessionAddress(InetAddress.getByName(
+ getRemoteCandidate().getIP()), getRemoteCandidate().getPort()));
+
+ transmittingState = MediaTransport.TRANSMITTER_CONFIGURED;
+ receivingState = MediaTransport.RECEIVER_CONFIGURED;
+ } catch (Exception e) {
+ throw new UnableToConfigure(e);
+ }
+ }
+
+ public void start() {
+ //start listening for incoming streams
+ rtpManager.addReceiveStreamListener(this);
+ receivingState = MediaTransport.RECEIVER_WAITING;
+
+ //and start sending
+ try {
+ sendStream = rtpManager.createSendStream(input, 0);
+ sendStream.start();
+ transmittingState = MediaTransport.TRANSMITTER_SENDING;
+
+ } catch (Exception e) {
+ throw new UnableToStart(e);
+ }
+ }
+
+ public void stop() {
+ try {
+ sendStream.stop();
+ transmittingState = MediaTransport.TRANSMITTER_CONFIGURED;
+ rtpManager.removeReceiveStreamListener(this);
+ receivingState = MediaTransport.RECEIVER_CONFIGURED;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ public void close() {
+ try {
+ sendStream.stop();
+ sendStream = null;
+ receiveStream = null;
+ rtpManager.dispose();
+ rtpManager = null;
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public int getTransmitterState() {
+ return transmittingState;
+ }
+
+ public int getReceiverState() {
+ return receivingState;
+ }
+
+ public void update(ReceiveStreamEvent evt) {
+ if (evt instanceof NewReceiveStreamEvent && receiveStream ==null) {
+
+ try {
+ receiveStream = ((NewReceiveStreamEvent)evt).getReceiveStream();
+ receivingState = MediaTransport.RECEIVER_RECEIVING;
+ notifyOutputListenersOutputReady(getOutput());
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ //TODO deal with disconnection
+
+ }
+
+ public InputStream getOutput() {
+ if(receiveStream == null) return null;
+ if(outputStream == null) outputStream = StreamConverter.toAudioInputStream(receiveStream.getDataSource());
+
+ return outputStream;
+ }
+
+ public Set getSupportedProtocols() {
+ Set result = new HashSet();
+
+ result.add(Protocol.UDP);
+
+ return null;
+ }
+
+ public InputStream getInput() {
+ return inputStream;
+ }
+
+ public void setInput(InputStream input) {
+ this.input = StreamConverter.toDataSource(input);
+ inputStream = input;
+
+ }
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaListener.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaListener.java
new file mode 100644
index 000000000..3cd463bf6
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaListener.java
@@ -0,0 +1,25 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.io.InputStream;
+
+/**
+ * Jingle media listeners.
+ *
+ * @author Alasdair North
+ */
+public interface JingleMediaListener {
+
+ /**
+ * Listen for when an objects output DataSource is ready to be used.
+ * @author Alasdair North
+ */
+ public static interface Output {
+
+ /**
+ * Called when the objects output has changed (but not when it changes to null).
+ * @param output the new output DataSource of the listened to object.
+ */
+ public void outputChanged(InputStream output);
+ }
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaManager.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaManager.java
new file mode 100644
index 000000000..dd3bc66de
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaManager.java
@@ -0,0 +1,539 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.sound.sampled.AudioSystem;
+
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smackx.jingle.JingleListener;
+import org.jivesoftware.smackx.jingle.JingleManager;
+import org.jivesoftware.smackx.jingle.JingleSession;
+import org.jivesoftware.smackx.jingle.JingleSessionRequest;
+import org.jivesoftware.smackx.jingle.PayloadType;
+import org.jivesoftware.smackx.jingle.media.util.FormatTranslator;
+import org.jivesoftware.smackx.nat.STUNResolver;
+import org.jivesoftware.smackx.nat.TransportCandidate;
+import org.jivesoftware.smackx.nat.TransportResolver;
+import org.jivesoftware.smackx.nat.TransportCandidate.Protocol;
+
+import sun.misc.Service;
+
+/**
+ * The JingleMediaManager is used to initiate peer-to-peer connections
+ * using the Jingle protocol (JEP-0166).
+ *
+ * @author Alasdair North
+ *
+ */
+public class JingleMediaManager {
+
+ private JingleManager jingleManager;
+ private TransportSupportStore supportedTransports;
+
+ private Class[] encodingMediaEngines, decodingMediaEngines, mediaTransports;
+
+ //default lists to be used if none are found via the SPI
+ private static final Class[] defaultEME = {EncodingJMFMediaEngine.class, EncodingJSMediaEngine.class};
+ private static final Class[] defaultDME = {DecodingJMFMediaEngine.class, DecodingJSMediaEngine.class};
+ private static final Class[] defaultMT = {JMFMediaTransport.class};
+
+ private Comparator ptComparator;
+
+
+ /**
+ * Create a new JingleMediaManager.
+ * @param xmppConnection XMPPConnection that sessions will be negotiated over.
+ * @param tr TransportResolver to be used.
+ * @param ptComparator Comparator to be used when sorting PayloadTypes into order of preference.
+ */
+ public JingleMediaManager(XMPPConnection xmppConnection, TransportResolver tr, Comparator ptComparator) {
+ jingleManager = new JingleManager(xmppConnection, tr);
+ JingleManager.setServiceEnabled(xmppConnection, true);
+
+ this.ptComparator = ptComparator;
+
+ //load the Classes for the media engines and media transports in the class path
+ Iterator itEnc = Service.providers(MediaEngine.Encoding.class);
+ encodingMediaEngines = getClassArray(itEnc);
+ if(encodingMediaEngines.length == 0) encodingMediaEngines = defaultEME;
+
+ Iterator itDec = Service.providers(MediaEngine.Decoding.class);
+ decodingMediaEngines = getClassArray(itDec);
+ if(decodingMediaEngines.length == 0) decodingMediaEngines = defaultDME;
+
+ Iterator itTran = Service.providers(MediaTransport.class);
+ mediaTransports = getClassArray(itTran);
+ if(mediaTransports.length == 0) mediaTransports = defaultMT;
+
+ initializeSupportedTransports();
+ }
+
+ /**
+ * Create a new JingleMediaManager using the default PayloadType comparator (which prefers low
+ * bandwidth solutions).
+ * @param xmppConnection XMPPConnection that sessions will be negotiated over.
+ * @param tr TransportResolver to be used.
+ */
+ public JingleMediaManager(XMPPConnection xmppConnection, TransportResolver tr) {
+ this(xmppConnection, tr, null);
+ setPTComparator(new DefaultPTC());
+ }
+
+ /**
+ * Create a new JingleMediaManager using the default PayloadType comparator (which prefers low
+ * bandwidth solutions) and TransportManager (which only negotiates UDP sessions).
+ * @param xmppConnection XMPPConnection that sessions will be negotiated over.
+ */
+ public JingleMediaManager(XMPPConnection xmppConnection) {
+ this(xmppConnection, new STUNResolver());
+ }
+
+ /**
+ * Creates a JingleMediaSession to start communication with another user.
+ * @param JID the identity of the other user.
+ * @param contentType the desired content type of the session.
+ * @param input the source of data for the session.
+ * @return a JingleMediaSession object which will negotiate and implement a Jingle session.
+ */
+ public JingleMediaSession createOutgoingJingleSession(String JID, ContentType contentType,
+ InputStream input) {
+
+ if(contentType.equals(ContentType.AUDIO)) {
+
+ JingleSession newSession = jingleManager.createOutgoingJingleSession(JID,
+ getSupportedPTs(contentType, input));
+ try {
+ newSession.start(null);
+ return new JingleMediaSession(AudioSystem.getAudioInputStream(input), newSession, this);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ public JingleMediaSession createIncomingJingleSession(JingleSessionRequest request, ContentType contentType, InputStream input) {
+
+ if(contentType.equals(ContentType.AUDIO)) {
+
+ JingleSession js = jingleManager.createIncomingJingleSession(request, getSupportedPTs(contentType, input));
+
+ try {
+ js.start(null);
+ return new JingleMediaSession(AudioSystem.getAudioInputStream(input), js, this);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get a decoding MediaEngine for the specified PayloadType.
+ * @param pt type of input.
+ * @param input The InputStream containing the input for the MediaEngine.
+ * @return MediaEngine for decoding the given input.
+ */
+ public MediaEngine.Decoding getDecodingMediaEngine(PayloadType pt, InputStream input) {
+
+ return (MediaEngine.Decoding) getMediaEngine(false, pt, input);
+ }
+
+ /**
+ * Get an encoding MediaEngine for the specified PayloadType.
+ * @param pt type of output desired.
+ * @param input The InputStream containing the input for the MediaEngine.
+ * @return MediaEngine for encoding the given input.
+ */
+ public MediaEngine.Encoding getEncodingMediaEngine(PayloadType pt, InputStream input) {
+
+ return (MediaEngine.Encoding) getMediaEngine(true, pt, input);
+ }
+
+ private MediaEngine getMediaEngine(boolean encoding, PayloadType pt, InputStream input) {
+
+ Class meClass;
+ EncodingSupportStore supportedPTs = generateSupportedAudioPTs(input);
+
+ if(encoding) meClass = supportedPTs.getEncodingClassForPT(pt);
+ else meClass = supportedPTs.getDecodingClassForPT(pt);
+
+ if(meClass != null) {
+ try {
+
+ Object[] args = {pt};
+ MediaEngine me = (MediaEngine) getInstance(meClass, args);
+ me.setInput(input);
+ return me;
+
+ } catch (Exception e) {
+ // in this case there was something wrong with the instantiation
+ e.printStackTrace();
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public List getSupportedPTs(ContentType contentType, InputStream input) {
+
+ if(contentType.equals(ContentType.AUDIO)) {
+
+ Set setPTs = generateSupportedAudioPTs(input)
+ .getSupportedPTsForContentType(contentType);
+ return order(setPTs);
+ }
+ return null;
+ }
+
+ private List order(Set setPTs) {
+ List result = new ArrayList(setPTs);
+ Collections.sort(result, ptComparator);
+ return result;
+ }
+
+ private EncodingSupportStore generateSupportedAudioPTs(InputStream input) {
+ EncodingSupportStore result = new EncodingSupportStore();
+ addSupportedAudioPTs(true, input, result);
+ addSupportedAudioPTs(false, input, result);
+ return result;
+ }
+
+ private void addSupportedAudioPTs(boolean encoding,
+ InputStream input, EncodingSupportStore store) {
+
+ Class[] mediaEngineArray;
+
+ if(encoding) mediaEngineArray = encodingMediaEngines;
+ else mediaEngineArray = decodingMediaEngines;
+
+ for(int i = 0; i < mediaEngineArray.length; i++) {
+// use reflection to instantiate a member of the class
+ try {
+
+ Object[] args = {new PayloadType(PayloadType.INVALID_PT, "invalid"), this};
+ MediaEngine me = (MediaEngine) getInstance(mediaEngineArray[i], args);
+
+ if(encoding) me.setInput(input);
+ Set supported = me.getSupportedPayloadTypes(ContentType.AUDIO);
+
+ Iterator it = supported.iterator();
+
+ while (it.hasNext()) {
+ if(encoding) store.setEncodingSupport(ContentType.AUDIO,
+ (PayloadType) it.next(), mediaEngineArray[i]);
+ else store.setDecodingSupport(ContentType.AUDIO,
+ (PayloadType) it.next(), mediaEngineArray[i]);
+ }
+
+ } catch (Exception e) {
+// Instantiation went wrong in some way. This can safely be ignored as the support
+// elements will not have been added to the array.
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void initializeSupportedTransports() {
+ supportedTransports = new TransportSupportStore();
+
+ for(int i = 0; i < mediaTransports.length; i++) {
+ try {
+ Object[] args = {new TransportCandidate.Fixed()};
+ MediaTransport mt = (MediaTransport) getInstance(mediaTransports[i], args);
+
+ Set supported = mt.getSupportedProtocols();
+
+ Iterator it = supported.iterator();
+
+ while(it.hasNext()) supportedTransports.addElement(new TransportSupportElement(
+ (Protocol)it.next(), mediaTransports[i]));
+
+
+ } catch(Exception e) {
+// Instantiation went wrong in some way. This can safely be ignored as the support
+// elements will not have been added to the array.
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Get a MediaTransport object that implements the given transport type. Returns null if none
+ * is availbale.
+ * @param remoteCandidate The remote candidate to use for connecting to the remote service.
+ * @param localCandidate The local candidate where we must listen for connections.
+ * @return MediaTransport object implementing transport of the given type.
+ */
+ public MediaTransport getMediaTransport(TransportCandidate remoteCandidate,
+ TransportCandidate localCandidate) {
+
+ Class mtClass = supportedTransports.getClassForProtocol(MediaTransport.getProtocol(remoteCandidate));
+
+ if(mtClass != null) {
+ try {
+
+ Object[] args = {remoteCandidate, localCandidate};
+ return (MediaTransport) getInstance(mtClass, args);
+
+ } catch (Exception e) {
+ // instantiating the class did not work, so remove its pairing with this
+ // protocol from the list
+ supportedTransports.remove(MediaTransport.getProtocol(remoteCandidate), mtClass);
+ }
+ }
+ return null;
+
+ }
+
+ private Object getInstance(Class c, Object[] args) throws SecurityException,
+ NoSuchMethodException, IllegalArgumentException, InstantiationException,
+ IllegalAccessException, InvocationTargetException {
+
+ Class[] argTypes = new Class[args.length];
+ for(int i = 0; i < args.length; i++) argTypes[i] = args[i].getClass();
+ Constructor con = c.getConstructor(argTypes);
+
+ return con.newInstance(args);
+
+ }
+
+ /**
+ * Add a new session request listener. This listener will be notified when new session requests
+ * are received.
+ * @param listener the listener to be added.
+ */
+ public void addSessionRequestListener(JingleListener.SessionRequest listener) {
+ jingleManager.addJingleSessionRequestListener(listener);
+ }
+
+ /**
+ * Remove a session request listener. This listener will no longer be notified of new session
+ * requests.
+ * @param listener the listener to be added.
+ */
+ public void removeSessionRequestListener(JingleListener.SessionRequest listener) {
+ jingleManager.removeJingleSessionRequestListener(listener);
+ }
+
+ /**
+ * @param comp Comparator to be used when sorting PayloadTypes into order of preference.
+ */
+ public void setPTComparator(Comparator comp) {
+ ptComparator = comp;
+ }
+
+ private Class[] getClassArray(Iterator it) {
+ Set classes = new HashSet();
+ while(it.hasNext()) classes.add(it.next().getClass());
+
+ Class[] result = new Class[classes.size()];
+ Iterator itClasses = classes.iterator();
+ for(int i = 0; i < result.length; i++) result[i] = (Class) itClasses.next();
+ return result;
+ }
+
+// Inner classes -------------------------------------------------------------------
+
+ private class EncodingSupportStore {
+
+ private Set elements = new HashSet();
+
+ private EncodingSupportElement getElementForPT(PayloadType pt) {
+ Iterator it = elements.iterator();
+ while(it.hasNext()) {
+ EncodingSupportElement current = (EncodingSupportElement) it.next();
+ if(current.getPayloadType().equals(pt)) return current;
+ }
+ return null;
+ }
+
+ public void setEncodingSupport(ContentType ct, PayloadType pt, Class encClass) {
+ EncodingSupportElement toChange = getElementForPT(pt);
+ if(toChange == null) {
+ toChange = new EncodingSupportElement(ct, pt);
+ toChange.setEncodingClass(encClass);
+ elements.add(toChange);
+ }
+ else toChange.setEncodingClass(encClass);
+ }
+
+ public void setDecodingSupport(ContentType ct, PayloadType pt, Class decClass) {
+ EncodingSupportElement toChange = getElementForPT(pt);
+ if(toChange == null) {
+ toChange = new EncodingSupportElement(ct, pt);
+ toChange.setDecodingClass(decClass);
+ elements.add(toChange);
+ }
+ else toChange.setDecodingClass(decClass);
+ }
+
+ public void remove(PayloadType pt, Class meClass) {
+
+ EncodingSupportElement toRemove = getElementForPT(pt);
+ if(toRemove.getEncodingClass().equals(meClass)) toRemove.setEncodingClass(null);
+ if(toRemove.getDecodingClass().equals(meClass)) toRemove.setDecodingClass(null);
+ if(toRemove.getDecodingClass() == null && toRemove.getEncodingClass() == null)
+ elements.remove(toRemove);
+ }
+
+ public Set getSupportedPTsForContentType(ContentType contentType) {
+ Set result = new HashSet();
+
+ Iterator it = elements.iterator();
+
+ while (it.hasNext()) {
+ EncodingSupportElement current = (EncodingSupportElement) it.next();
+ //only reurn PTs that we can encode and decode
+ if(current.getContentType().equals(contentType)
+ && current.getEncodingClass() != null
+ && current.getDecodingClass() != null)
+ result.add(current.getPayloadType());
+ }
+
+ return result;
+ }
+
+ public Class getEncodingClassForPT(PayloadType pt) {
+ EncodingSupportElement toReturn = getElementForPT(pt);
+ if(toReturn == null) return null;
+ return toReturn.getEncodingClass();
+ }
+
+ public Class getDecodingClassForPT(PayloadType pt) {
+ EncodingSupportElement toReturn = getElementForPT(pt);
+ if(toReturn == null) return null;
+ return toReturn.getEncodingClass();
+ }
+
+ }
+
+ private class EncodingSupportElement {
+
+ private ContentType contentType;
+ private PayloadType payloadType;
+ private Class encodingClass, decodingClass;
+
+ public EncodingSupportElement(ContentType contentType, PayloadType payloadType) {
+ this.contentType = contentType;
+ this.payloadType = payloadType;
+ }
+
+ public Class getDecodingClass() {
+ return decodingClass;
+ }
+
+ public void setDecodingClass(Class decodingClass) {
+ this.decodingClass = decodingClass;
+ }
+
+ public Class getEncodingClass() {
+ return encodingClass;
+ }
+
+ public void setEncodingClass(Class encodingClass) {
+ this.encodingClass = encodingClass;
+ }
+
+ public ContentType getContentType() {
+ return contentType;
+ }
+
+ public PayloadType getPayloadType() {
+ return payloadType;
+ }
+ }
+
+ private class TransportSupportStore {
+ private Set elements;
+
+ public void addElement(TransportSupportElement element) {
+ elements.add(element);
+ }
+
+ public void remove(Protocol proto, Class mtClass) {
+ Iterator it = elements.iterator();
+ while(it.hasNext()) {
+ TransportSupportElement current = (TransportSupportElement) it.next();
+ if(current.getProtocol().equals(proto) && current.getImplementingClass().equals(mtClass))
+ elements.remove(current);
+ }
+ }
+
+ public Class getClassForProtocol(TransportCandidate.Protocol protocol) {
+ Iterator it = elements.iterator();
+ while(it.hasNext()) {
+ TransportSupportElement current = (TransportSupportElement) it.next();
+ if(current.getProtocol().equals(protocol)) return current.getImplementingClass();
+ }
+ return null;
+ }
+ }
+
+ private class TransportSupportElement {
+ private TransportCandidate.Protocol protocol;
+ private Class implementingClass;
+
+ public TransportSupportElement(Protocol protocol, Class implementingClass) {
+ this.protocol = protocol;
+ this.implementingClass = implementingClass;
+ }
+
+ public Class getImplementingClass() {
+ return implementingClass;
+ }
+
+ public TransportCandidate.Protocol getProtocol() {
+ return protocol;
+ }
+
+ }
+
+ public static class ContentType {
+
+// Media type constants. Used to differentiate between the different types of connections
+// that can be negotiated using Jingle.
+
+ public static final ContentType AUDIO = new ContentType();
+ public static final ContentType VIDEO = new ContentType();
+ public static final ContentType AUDIO_AND_VIDEO = new ContentType();
+ public static final ContentType FILE_SHARING = new ContentType();
+
+ public boolean equals(Object o) {
+ return o == this;
+ }
+ }
+
+ public static class SessionCreationError extends Error{
+ private static final long serialVersionUID = 1L;
+ public SessionCreationError(String text) {
+ super(text);
+ }
+ }
+
+ //Payload Type comparators------------------------------------------------------------------
+
+ /**
+ * PayloadType comparator that orders PayloadTypes by preference level.
+ */
+ public static class DefaultPTC implements Comparator {
+
+ public int compare(Object arg0, Object arg1) {
+ PayloadType pt0 = (PayloadType) arg0;
+ PayloadType pt1 = (PayloadType) arg1;
+ if(FormatTranslator.preferenceLevel(pt0) < FormatTranslator.preferenceLevel(pt1)) return 1;
+ if(FormatTranslator.preferenceLevel(pt0) > FormatTranslator.preferenceLevel(pt1)) return -1;
+ else return 0;
+ }
+ }
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaSession.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaSession.java
new file mode 100644
index 000000000..f18fab3de
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/JingleMediaSession.java
@@ -0,0 +1,248 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.sound.sampled.AudioInputStream;
+
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smackx.jingle.JingleListener;
+import org.jivesoftware.smackx.jingle.JingleSession;
+import org.jivesoftware.smackx.jingle.PayloadType;
+import org.jivesoftware.smackx.jingle.media.JingleMediaListener.Output;
+import org.jivesoftware.smackx.jingle.media.MediaEngine.UnableToConfigureMediaEngine;
+import org.jivesoftware.smackx.nat.TransportCandidate;
+
+/**
+ * Class representing ongoing peer-to-peer connections negotiated via Jingle.
+ *
+ * @author Alasdair North
+ *
+ */
+public class JingleMediaSession implements JingleListener.Session {
+
+ private JingleSession jingleSession;
+ private AudioInputStream input;
+ private JingleMediaManager mediaManager;
+ private Set outputListeners;
+
+ private MediaEngine.Encoding encodingMediaEngine;
+ private MediaEngine.Decoding decodingMediaEngine;
+ private MediaTransport mediaTransport;
+
+ /**
+ * Create a new JingleMediaSession from the given JingleSession.
+ * @param input the DataSource from which data will be taken.
+ * @param jingleSession the JingleSession object used to negotiate session parameters.
+ * @param mediaManager the JingleMediaManager that created this JingleMediaSession.
+ */
+ public JingleMediaSession(AudioInputStream input, JingleSession jingleSession, JingleMediaManager mediaManager) {
+ this.input = input;
+ this.jingleSession = jingleSession;
+ this.mediaManager = mediaManager;
+
+ outputListeners = new HashSet();
+
+ jingleSession.addListener(this);
+ }
+
+ /**
+ * This method will return null until data starts being received.
+ * @return data received and decoded by the JingleMediaSession.
+ */
+ public InputStream getOutput() {
+ if(decodingMediaEngine == null) return null;
+ return decodingMediaEngine.getOutput();
+ }
+
+ /**
+ * Release all resources held by the JingleMediaSession and cease operation.
+ */
+ public void close() {
+ jingleSession.close();
+ jingleSession = null;
+ encodingMediaEngine.close();
+ encodingMediaEngine = null;
+ decodingMediaEngine.close();
+ decodingMediaEngine = null;
+ mediaTransport.close();
+ mediaTransport = null;
+ }
+
+ private void notifyOutputListeners(InputStream output) {
+ Iterator it = outputListeners.iterator();
+ while(it.hasNext()) ((JingleMediaListener.Output) it.next()).outputChanged(output);
+ }
+
+ /**
+ * Add an output listener to be notified when the JingleMediaSessions output changes.
+ * @param listener the listener to be added.
+ */
+ public void addOutputListener(Output listener) {
+ outputListeners.add(listener);
+ }
+
+ /**
+ * Remove an output listener. This listener will no longer be notified when the
+ * JingleMediaSessions output changes.
+ * @param listener the listner to be removed.
+ */
+ public void removeOutputListener(Output listener) {
+ outputListeners.remove(listener);
+ }
+
+ /**
+ * Add a JingleListener to be notified of JingleSession events.
+ * @param listener the listener to be added.
+ */
+ public void addJingleListener(JingleListener listener) {
+ if(listener instanceof Session) jingleSession.addListener(listener);
+ if(listener instanceof Media) jingleSession.addMediaListener((Media)listener);
+ if(listener instanceof Transport)
+ jingleSession.addTransportListener((Transport)listener);
+ }
+
+ /**
+ * Remove a JingleListener. This listener will no longer be notified of JingleSession events.
+ * @param listener the listner to be removed.
+ */
+ public void removeJingleListener(JingleListener listener) {
+ if(listener instanceof Session) jingleSession.removeListener(listener);
+ if(listener instanceof Media) jingleSession.removeMediaListener((Media)listener);
+ if(listener instanceof Transport)
+ jingleSession.removeTransportListener((Transport)listener);
+ }
+
+// JingleListener.Session methods --------------------------------------------------------
+
+ public void sessionEstablished() {
+ // TODO Should anything be done here?
+ }
+
+ public void sessionDeclined() {
+ // TODO Should anything be done here?
+ }
+
+ public void sessionRedirected(String redirection) {
+ // TODO Start a new JingleMediaSession?
+ }
+
+ public void sessionClosed() {
+ close();
+ }
+
+ public void sessionClosedOnError(Exception e) {
+ close();
+ }
+
+ public void sessionDeclined(String reason) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void sessionClosed(String reason) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void sessionClosedOnError(XMPPException e) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void sessionEstablished(final PayloadType pt, final TransportCandidate rc, final TransportCandidate lc) {
+
+ Thread mediaConnectionCreationThread = new Thread() {
+ public void run() {
+
+ encodingMediaEngine = mediaManager.getEncodingMediaEngine(pt, input);
+ encodingMediaEngine.addOutputListener(new EncOutListener());
+ try {
+ encodingMediaEngine.configure();
+ } catch (UnableToConfigureMediaEngine e1) {
+ e1.printStackTrace();
+ }
+
+ //wait for the encoding engine to configure itself
+ while(encodingMediaEngine.getState() != MediaEngine.CONFIGURED) {
+ try {
+ wait(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ encodingMediaEngine.start();
+
+ mediaTransport = mediaManager.getMediaTransport(rc, lc);
+ mediaTransport.addOutputListener(new TranOutListener());
+
+ mediaTransport.setInput(encodingMediaEngine.getOutput());
+ mediaTransport.configure();
+
+// wait for the transport to configure itself
+ while(mediaTransport.getTransmitterState() != MediaTransport.TRANSMITTER_CONFIGURED ||
+ mediaTransport.getReceiverState() != MediaTransport.RECEIVER_CONFIGURED) {
+ try {
+ wait(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ mediaTransport.start();
+ }
+ };
+
+ mediaConnectionCreationThread.start();
+
+ }
+
+ private class EncOutListener implements JingleMediaListener.Output {
+
+ public void outputChanged(InputStream output) {
+
+
+ }
+
+ }
+
+ private class TranOutListener implements JingleMediaListener.Output {
+
+ public void outputChanged(InputStream output) {
+
+// hook it up to a decoding media engine
+ decodingMediaEngine = mediaManager.getDecodingMediaEngine(encodingMediaEngine.getPayloadType(), output);
+ decodingMediaEngine.addOutputListener(new DecOutListener());
+
+ try {
+ decodingMediaEngine.configure();
+ } catch (UnableToConfigureMediaEngine e1) {
+ e1.printStackTrace();
+ }
+
+ while(decodingMediaEngine.getState() != MediaEngine.CONFIGURED) {
+ try {
+ wait(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ decodingMediaEngine.start();
+ }
+
+ }
+
+ private class DecOutListener implements JingleMediaListener.Output {
+
+ public void outputChanged(InputStream output) {
+ notifyOutputListeners(output);
+ }
+
+ }
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/MediaEngine.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/MediaEngine.java
new file mode 100644
index 000000000..89bdf60e8
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/MediaEngine.java
@@ -0,0 +1,287 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+import org.jivesoftware.smackx.jingle.media.JingleMediaManager.ContentType;
+
+/**
+ * MediaEngines encode and decode data.
+ *
+ * @author Alasdair North
+ */
+public abstract class MediaEngine {
+
+ public static final int INSTANTIATED = 0;
+ public static final int CONFIGURING = 1;
+ public static final int CONFIGURED = 2;
+ public static final int STARTED = 3;
+ public static final int CLOSED = 4;
+
+ private static final OutputMonitoringThread monitorThread = new OutputMonitoringThread();
+
+ private PayloadType encodingFormat;
+ private Set outputListeners;
+
+
+ /**
+ * This empty constructor is needed by the SPI, it doesn't do anything other than create an
+ * object.
+ */
+ public MediaEngine() {}
+
+ /**
+ * Create a new MediaEngine.
+ * @param encodedFormat The Format that encoded data dealt with by this MediaEngine is in.
+ */
+ public MediaEngine(PayloadType encodedFormat) {
+ this.encodingFormat = encodedFormat;
+ outputListeners = new HashSet();
+ }
+
+ /**
+ * @return The InputStream from which this MediaEngine gets its data.
+ */
+ public abstract InputStream getInput();
+
+ /**
+ * Set the MediaEngine's input, this should be done before configuring the MediaEngine or
+ * calling one of the getSupportedPayloadTypes methods.
+ * @param input Set the DataSource from which this MediaEngine gets its data.
+ */
+ public abstract void setInput(InputStream input);
+
+ /**
+ * This method does not need to return anything other than null until the MediaEngine is
+ * STARTED. When it changes output listeners will be notified of the new output DataSource.
+ * @return the DataSource representing the output of this MediaEngine.
+ */
+ public abstract InputStream getOutput();
+
+ /**
+ * @return the PayloadType of encoded data.
+ */
+ public synchronized PayloadType getPayloadType() {
+ return encodingFormat;
+ }
+
+ /**
+ * Get the current state of the MediaEngine. This will be one of the constants specified in
+ * MediaEngine.
+ * @return the MediaEngine's current state.
+ */
+ public abstract int getState();
+
+ /**
+ * Called to intitiate the transition from the INSTANTIATED state to the CONFIGURING state, and
+ * then finally to CONFIGURED.
+ * @throws UnableToConfigureMediaEngine when unable to configure.
+ */
+ public abstract void configure() throws UnableToConfigureMediaEngine;
+
+ /**
+ * Starts the transition from CONFIGURED to STARTED. When this is called the MediaEngine
+ * should start to process data and output it.
+ */
+ public abstract void start();
+
+ /**
+ * Starts the transition from STARTED back to CONFIGURED. When this is called the MediaEngine
+ * should stop sending data to the output.
+ */
+ public abstract void stop();
+
+ /**
+ * Releases all the resources held by the MediaEngine and ceases it's activity. This will put
+ * it back to the INSTANTIATED state. In order to be used again it will have to be configured
+ * again.
+ */
+ public abstract void close();
+
+ public abstract Set getSupportedPayloadTypes(ContentType contentType);
+
+ /**
+ * Add an output listener to the MediaEngine. This listener will be notified when the output
+ * changes to anything other than null.
+ * @param listener the listener to be added.
+ */
+ public void addOutputListener(JingleMediaListener.Output listener) {
+ outputListeners.add(listener);
+ if(!monitorThread.isStarted()) monitorThread.setStarted(true);
+ if(!monitorThread.isInMonitoredList(this)) monitorThread.addToMonitoredList(this);
+ }
+
+ /**
+ * Remove an output listener from the MediaEngine. This listener will no longer receive
+ * notifications of changes in output.
+ * @param listener the listener to be removed.
+ */
+ public void removeOutputListener(JingleMediaListener.Output listener) {
+ outputListeners.remove(listener);
+ if(outputListeners.isEmpty()) monitorThread.removeFromMonitoredList(this);
+ if(monitorThread.isMonitoredEmpty()) monitorThread.setStarted(false);
+ }
+
+ private void notifyOutputListenersOutputReady(InputStream output) {
+ Iterator it = outputListeners.iterator();
+ while(it.hasNext()) ((JingleMediaListener.Output) it.next()).outputChanged(output);
+ }
+
+ /**
+ * Subclasses of MediaEngine.Encoding are responsible for encoding raw data to be transmitted
+ * by MediaTransport classes. In order to be used by the jingle media code they must have
+ * entries in the META-INF/services directory of a jar file in the class path. They must also
+ * have a zero argument constructor.
+ *
+ * @author Alasdair North
+ */
+ public static abstract class Encoding extends MediaEngine {
+
+ public Encoding(PayloadType outputFormat) {
+ super(outputFormat);
+ }
+
+ /**
+ * Return a set of the PayloadTypes that this class can encode data to for the given ContentType.
+ * These ContentTypes are specified using the constants in JingleMediaManager. The MediaEngine's
+ * input must be specified before calling this method.
+ * @param contentType
+ * @return Set of supported PayloadTypes
+ */
+ public abstract Set getSupportedPayloadTypes(ContentType contentType);
+ }
+
+ /**
+ * Subclasses of MediaEngine.Decoding are responsible for decoding encoded data received
+ * by MediaTransport classes. In order to be used by the jingle media code they must have
+ * entries in the META-INF/services directory of a jar file in the class path. They must also
+ * have a zero argument constructor.
+ *
+ * @author Alasdair North
+ */
+ public static abstract class Decoding extends MediaEngine {
+
+ public Decoding(PayloadType inputFormat) {
+ super(inputFormat);
+ }
+
+ /**
+ * Return a set of the PayloadTypes that this class can decode data from for the given ContentType.
+ * These ContentTypes are specified using the constants in JingleMediaManager. The MediaEngine's
+ * input does not need to be specified before calling this method.
+ * @param contentType
+ * @return Set of supported PayloadTypes
+ */
+ public abstract Set getSupportedPayloadTypes(ContentType contentType);
+ }
+
+ public static class StateOrderViolationError extends Error {
+ private static final long serialVersionUID = 1L;
+
+ public StateOrderViolationError(String text) {
+ super(text);
+ }
+
+ }
+
+ public static class InputNotSetError extends Error {
+ private static final long serialVersionUID = 1L;
+
+ public InputNotSetError(String text) {
+ super(text);
+ }
+ }
+
+ public static class UnableToConfigureMediaEngine extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public UnableToConfigureMediaEngine(Throwable cause) {
+ super(cause);
+ }
+
+ public UnableToConfigureMediaEngine(String cause) {
+ super(cause);
+ }
+ }
+
+ /**
+ * A thread to monitor the outputs of MediaEngines and notify output listeners of changes.
+ * @author Alasdair North
+ */
+ private static class OutputMonitoringThread extends Thread {
+
+ private volatile boolean started = false;
+// Map from MediaEngines to DataSources used to monitor for changes in output
+ private volatile Map monitored = new HashMap();
+
+ public boolean isStarted() {
+ return started;
+ }
+
+ public void setStarted(boolean started) {
+ this.started = started;
+ if(started) super.start();
+ }
+
+ public void addToMonitoredList(MediaEngine me) {
+ synchronized(monitored) {
+ monitored.put(me, null);
+ }
+ }
+
+ public void removeFromMonitoredList(MediaEngine me) {
+ synchronized(monitored) {
+ monitored.remove(me);
+ }
+ }
+
+ public boolean isInMonitoredList(MediaEngine me) {
+ synchronized(monitored) {
+ return monitored.containsKey(me);
+ }
+ }
+
+ public boolean isMonitoredEmpty() {
+ synchronized(monitored) {
+ return monitored.isEmpty();
+ }
+ }
+
+ public void run() {
+
+ while(started) {
+ synchronized(monitored) {
+ Set keySet = new HashSet(monitored.keySet());
+ Iterator it = keySet.iterator();
+ while(it.hasNext()) {
+
+ MediaEngine currentKey = (MediaEngine) it.next();
+ InputStream keysOutput;
+ try {
+ keysOutput = currentKey.getOutput();
+ } catch (Exception e) {
+ keysOutput = null;
+ }
+
+// should listener be changed to notify of changes in output and notify of null
+// as well?
+ if(keysOutput != monitored.get(currentKey) && keysOutput != null)
+ currentKey.notifyOutputListenersOutputReady(keysOutput);
+
+ monitored.put(currentKey, keysOutput);
+ }
+ }
+ }
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {}
+ }
+ }
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/MediaTransport.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/MediaTransport.java
new file mode 100644
index 000000000..14407faae
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/MediaTransport.java
@@ -0,0 +1,172 @@
+package org.jivesoftware.smackx.jingle.media;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import java.io.InputStream;
+
+import org.jivesoftware.smackx.nat.TransportCandidate;
+
+/**
+ * Subclasses of MediaTransport are responisble for the transmission of data, implementing
+ * TransportCandidates negotiated by the TransportNegotiator.
+ *
+ * @author Alasdair North
+ *
+ */
+public abstract class MediaTransport {
+
+ public static final int TRANSMITTER_INSTANTIATED = 0;
+ public static final int TRANSMITTER_CONFIGURED = 1;
+ public static final int TRANSMITTER_SENDING = 2;
+ public static final int TRANSMITTER_CLOSED = 3;
+
+
+ public static final int RECEIVER_INSTANTIATED = 7;
+ public static final int RECEIVER_CONFIGURED = 8;
+ public static final int RECEIVER_WAITING = 9;
+ public static final int RECEIVER_RECEIVING = 10;
+ public static final int RECEIVER_CLOSED = 11;
+
+ private TransportCandidate remoteCandidate, localCandidate;
+ private Set outputListeners;
+
+ /**
+ * This empty constructor is needed by the SPI, it shouldn't do anything other than create an
+ * object.
+ */
+ public MediaTransport(){}
+
+ /**
+ * Create a new MediaTransport to use the transport method specified by the successful
+ * TransportCandidate.
+ * @param remoteCandidate The remote candidate to use for connecting to the remote service.
+ * @param localCandidate The local candidate where we must listen for connections
+ */
+ public MediaTransport(TransportCandidate remoteCandidate, TransportCandidate localCandidate) {
+ this.localCandidate = localCandidate;
+ this.remoteCandidate = remoteCandidate;
+ outputListeners = new HashSet();
+ }
+
+ /**
+ * Called to initiate the transition from the INSTANTIATED states to CONFIGURED states.
+ */
+ public abstract void configure();
+
+ /**
+ * Initiates transition from TRANSMITTER_CONFIGURED to TRANSMITTER_SENDING and
+ * RECEIVER_CONFIGURED to RECEIVER_WAITING. The MediaTransport will start transmitting data
+ * from the input and will begin waiting for data transmitted from the other end.
+ */
+ public abstract void start();
+
+ /**
+ * Initiates transition from TRANSMITTER_SENDING to TRANSMITTER_CONFIGURED and
+ * RECEIVER_WAITING to RECEIVER_CONFIGURED. The transmitter stops transmitting and the
+ * receiver stops receiving and also stops listening for data transmitted from the other end.
+ */
+ public abstract void stop();
+
+ /**
+ * Releases all resources held by the MediaTransport and cease's its activity. Will return it
+ * to the INSTANTIATED states (i.e. it must be reconfigured before being used again).
+ */
+ public abstract void close();
+
+ /**
+ * @return the state of the transmission side of the MediaTransport.
+ */
+ public abstract int getTransmitterState();
+
+ /**
+ * @return the state of the receiving side of the MediaTransport.
+ */
+ public abstract int getReceiverState();
+
+ /**
+ * @return a Set of the TransportCandidate.Protocols supported by this MediaTransport.
+ */
+ public abstract Set getSupportedProtocols();
+
+ public TransportCandidate getLocalCandidate() {
+ return localCandidate;
+ }
+
+ public TransportCandidate getRemoteCandidate() {
+ return remoteCandidate;
+ }
+
+ /**
+ * @return the DataSource from which this MediaTransport gets its data.
+ */
+ public abstract InputStream getInput();
+
+ /**
+ * This method must be called before the MediaTransport is started.
+ * @param input the DataSource from which this MediaTransport will get its data.
+ */
+ public abstract void setInput(InputStream input);
+
+ /**
+ * This method may return nothing but null until the MediaTransport is in the RECEIVER_RECEIVING
+ * state.
+ * @return the data received bt the MediaTransport.
+ */
+ public abstract InputStream getOutput();
+
+ /**
+ * Add an output listener to the MediaTransport. This listener will be notified when the
+ * MediaTransport receives a new data stream.
+ * @param listener the listener to be added.
+ */
+ public void addOutputListener(JingleMediaListener.Output listener) {
+ outputListeners.add(listener);
+ }
+
+ /**
+ * Add an output listener to the MediaTransport. This listener will no longer be notified
+ * of new received data streams.
+ * @param listener the listener to be removed.
+ */
+ public void removeOutputListener(JingleMediaListener.Output listener) {
+ outputListeners.remove(listener);
+ }
+
+ /**
+ * Notify the output listeners that a new output is available.
+ * @param output new output DataSource.
+ */
+ protected void notifyOutputListenersOutputReady(InputStream output) {
+ Iterator it = outputListeners.iterator();
+ while(it.hasNext()) ((JingleMediaListener.Output) it.next()).outputChanged(output);
+ }
+
+ /**
+ * @param tc
+ * @return the Protocol to be used for implementing the given TransportCandidate.
+ */
+ public static TransportCandidate.Protocol getProtocol(TransportCandidate tc) {
+ if(tc instanceof TransportCandidate.Ice) return ((TransportCandidate.Ice) tc).getProto();
+ else return TransportCandidate.Protocol.UDP;
+ }
+
+ public static class UnableToConfigure extends Error {
+ private static final long serialVersionUID = 1L;
+ public UnableToConfigure(Throwable cause) {
+ super(cause);
+ }
+ public UnableToConfigure(String cause) {
+ super(cause);
+ }
+ }
+
+ public static class UnableToStart extends Error {
+ private static final long serialVersionUID = 1L;
+ public UnableToStart(Throwable cause) {
+ super(cause);
+ }
+ }
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/AISDataSource.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/AISDataSource.java
new file mode 100644
index 000000000..62608fb8b
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/AISDataSource.java
@@ -0,0 +1,71 @@
+package org.jivesoftware.smackx.jingle.media.util;
+
+import java.io.IOException;
+
+import javax.media.Time;
+import javax.media.protocol.PushBufferDataSource;
+import javax.media.protocol.PushBufferStream;
+import javax.sound.sampled.AudioInputStream;
+
+public class AISDataSource extends PushBufferDataSource {
+
+ private AISPushBufferStream[] streams = new AISPushBufferStream[1];
+ private boolean connected = false;
+ private boolean started = false;
+
+ public AISDataSource(AudioInputStream ais) {
+ streams[0] = new AISPushBufferStream(ais);
+ }
+
+ public PushBufferStream[] getStreams() {
+ return streams;
+ }
+
+ public String getContentType() {
+ if(!connected) {
+ System.err.println("Error: DataSource not connected");
+ return null;
+ }
+ return "raw";
+ }
+
+ public void connect() throws IOException {
+ connected = true;
+ }
+
+ public void disconnect() {
+ try {
+ if(started) stop();
+ } catch (IOException e) {}
+ connected = false;
+ }
+
+ public void start() throws IOException {
+ if(!connected) throw new Error("DataSource must be connected before it can be started");
+ if(!started) {
+ started = true;
+ streams[0].start(true);
+ }
+
+ }
+
+ public void stop() throws IOException {
+ if(connected && started) {
+ started = false;
+ streams[0].start(false);
+ }
+ }
+
+ public Object getControl(String arg0) {
+ return null;
+ }
+
+ public Object[] getControls() {
+ return null;
+ }
+
+ public Time getDuration() {
+ return DURATION_UNKNOWN;
+ }
+
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/AISPushBufferStream.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/AISPushBufferStream.java
new file mode 100644
index 000000000..6b3012d76
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/AISPushBufferStream.java
@@ -0,0 +1,127 @@
+package org.jivesoftware.smackx.jingle.media.util;
+
+import java.io.IOException;
+
+import javax.media.Buffer;
+import javax.media.Format;
+import javax.media.protocol.BufferTransferHandler;
+import javax.media.protocol.ContentDescriptor;
+import javax.media.protocol.PushBufferStream;
+import javax.sound.sampled.AudioInputStream;
+
+
+public class AISPushBufferStream implements PushBufferStream, Runnable {
+
+ private Format format;
+ private AudioInputStream data;
+ private ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW);
+
+ private BufferTransferHandler transferHandler;
+ private int seqNo;
+
+ private Thread thread;
+ private boolean threadStarted;
+
+ public AISPushBufferStream(AudioInputStream ais) {
+ data = ais;
+ format = FormatTranslator.toJMFFormat(ais.getFormat());
+ thread = new Thread(this);
+ seqNo = 0;
+ }
+
+ public Format getFormat() {
+ return format;
+ }
+
+ public synchronized void read(Buffer buff) throws IOException {
+
+
+ int dataAvailable = 0;
+ try {
+ dataAvailable = data.available();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if(dataAvailable > 0) {
+ byte[] outdata = new byte[dataAvailable];
+ int dataRead = data.read(outdata);
+
+ buff.setFormat(getFormat());
+ buff.setData(outdata);
+
+ buff.setLength(dataRead);
+ buff.setSequenceNumber(seqNo);
+ buff.setHeader(null);
+ buff.setFlags(0);
+ }
+
+
+ }
+
+ public synchronized void setTransferHandler(BufferTransferHandler transferHandler) {
+ this.transferHandler = transferHandler;
+ notifyAll();
+ }
+
+ public ContentDescriptor getContentDescriptor() {
+ return cd;
+ }
+
+ public long getContentLength() {
+ return LENGTH_UNKNOWN;
+ }
+
+ public boolean endOfStream() {
+ return false;
+ }
+
+ public Object[] getControls() {
+ return null;
+ }
+
+ public Object getControl(String arg0) {
+ //no controls
+ return null;
+ }
+
+ public synchronized void start(boolean started) {
+ threadStarted = started;
+ if(started && !thread.isAlive()) {
+ thread = new Thread(this);
+ thread.start();
+ }
+ notifyAll();
+ }
+
+ public void run() {
+ while(threadStarted) {
+ synchronized(this) {
+ while(transferHandler == null && threadStarted) {
+ try {
+ wait(1000);
+ } catch(InterruptedException e) {}
+ }
+ }
+
+ int dataAvailable = 0;
+ try {
+ dataAvailable = data.available();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ //loop until there is some data to be read
+ if(threadStarted && transferHandler != null) {
+ if(dataAvailable > 0) {
+ transferHandler.transferData(this);
+ }
+ else try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {}
+ }
+ }
+ }
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/DSInputStream.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/DSInputStream.java
new file mode 100644
index 000000000..c8be4af11
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/DSInputStream.java
@@ -0,0 +1,118 @@
+package org.jivesoftware.smackx.jingle.media.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.media.Buffer;
+import javax.media.protocol.BufferTransferHandler;
+import javax.media.protocol.PushBufferDataSource;
+import javax.media.protocol.PushBufferStream;
+
+public class DSInputStream extends InputStream implements BufferTransferHandler {
+
+ private byte[] circularByteBuffer;
+ private int dataStart;
+ private int dataEnd;
+
+ private PushBufferDataSource input;
+
+ public DSInputStream (PushBufferDataSource input, int bufferSize) {
+ input.getStreams()[0].setTransferHandler(this);
+ this.input = input;
+ dataStart = 0;
+ dataEnd = 0;
+ circularByteBuffer = new byte[bufferSize];
+ }
+
+ public int read() throws IOException {
+ while(true) {
+ if(dataStart == dataEnd) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {}
+ }
+ else synchronized(circularByteBuffer) {
+ int returnValue = circularByteBuffer[dataStart];
+ if(dataStart == circularByteBuffer.length - 1) dataStart = 0;
+ else dataStart ++;
+ return returnValue;
+ }
+ }
+ }
+
+ public int available() {
+ synchronized(circularByteBuffer) {
+ if(dataStart <= dataEnd) return dataEnd - dataStart;
+ else return dataEnd - dataStart + circularByteBuffer.length + 1;
+ }
+ }
+
+ private void addData(byte[] data, int offset, int length) {
+ if(data.length > 0 && offset + length <= data.length && offset >= 0 && length > 0) {
+ synchronized(circularByteBuffer) {
+ for(int i = offset; i < offset + length; i++) {
+ if(dataEnd == circularByteBuffer.length - 1) {
+// wrap around
+ circularByteBuffer[0] = data[i];
+ dataEnd = 0;
+ } else {
+ circularByteBuffer[dataEnd + 1] = data[i];
+ dataEnd++;
+ }
+
+// if data has been overwritten then move on the start marker
+ if(dataStart == dataEnd) dataStart++;
+ if(dataStart == circularByteBuffer.length) dataStart = 0;
+ }
+ }
+ }
+ }
+
+ public int read(byte[] b, int off, int len) {
+ if(b == null) throw new NullPointerException();
+ if(off < 0 || len < 0 || off + len > b.length) throw new IndexOutOfBoundsException();
+ if(len > 0) {
+ synchronized(circularByteBuffer) {
+ int bytesRead = 0;
+
+ for(int i = off; i < off+len; i++) {
+ if(dataStart == dataEnd) break;
+ b[i] = circularByteBuffer[dataStart];
+ bytesRead++;
+ if(dataStart == circularByteBuffer.length - 1) dataStart = 0;
+ else dataStart ++;
+ }
+ return bytesRead;
+ }
+ }
+ return 0;
+ }
+
+ public int read(byte[] b) {
+ return read(b, 0, b.length);
+ }
+
+ public void close() {
+ synchronized(circularByteBuffer) {
+ circularByteBuffer = null;
+ input.getStreams()[0].setTransferHandler(null);
+ }
+ }
+
+ public void transferData(PushBufferStream pbs) {
+ Buffer buff = new Buffer();
+ try {
+ pbs.read(buff);
+ byte[] data = (byte[]) buff.getData();
+ addData(data, buff.getOffset(), buff.getLength());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ public boolean markSupported() {
+ return false;
+ }
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/DefaultFormatTranslationProvider.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/DefaultFormatTranslationProvider.java
new file mode 100644
index 000000000..ac5d5367c
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/DefaultFormatTranslationProvider.java
@@ -0,0 +1,268 @@
+package org.jivesoftware.smackx.jingle.media.util;
+
+import javax.media.Format;
+import javax.media.format.AudioFormat;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+
+public class DefaultFormatTranslationProvider implements FormatTranslationProvider {
+
+ private static final AudioFormat JMF_STATIC_PT_0 = new AudioFormat(AudioFormat.ULAW_RTP, 8000.0, 8, 1,
+ AudioFormat.LITTLE_ENDIAN, AudioFormat.NOT_SPECIFIED, 8,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ private static final AudioFormat JMF_STATIC_PT_3 = new AudioFormat(AudioFormat.GSM_RTP, 8000.0, AudioFormat.NOT_SPECIFIED,
+ 1, AudioFormat.NOT_SPECIFIED, AudioFormat.NOT_SPECIFIED, 264,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ private static final AudioFormat JMF_STATIC_PT_4 = new AudioFormat(AudioFormat.G723_RTP, 8000.0, AudioFormat.NOT_SPECIFIED,
+ 1, AudioFormat.NOT_SPECIFIED, AudioFormat.NOT_SPECIFIED, 192,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ private static final AudioFormat JMF_STATIC_PT_5 = new AudioFormat(AudioFormat.DVI_RTP, 8000.0, 4, 1);
+
+ private static final AudioFormat JMF_STATIC_PT_6 = new AudioFormat(AudioFormat.DVI_RTP, 16000.0, 4, 1);
+
+ private static final AudioFormat JMF_STATIC_PT_8 = new AudioFormat(AudioFormat.ALAW, 8000.0, 8, 1,
+ AudioFormat.LITTLE_ENDIAN, AudioFormat.NOT_SPECIFIED, 8,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ private static final AudioFormat JMF_STATIC_PT_10 = new AudioFormat(AudioFormat.LINEAR, 44100.0, 16, 2,
+ AudioFormat.BIG_ENDIAN, AudioFormat.SIGNED, AudioFormat.NOT_SPECIFIED,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ private static final AudioFormat JMF_STATIC_PT_11 = new AudioFormat(AudioFormat.LINEAR, 44100.0, 16, 1,
+ AudioFormat.BIG_ENDIAN, AudioFormat.SIGNED, AudioFormat.NOT_SPECIFIED,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ private static final AudioFormat JMF_STATIC_PT_14 = new AudioFormat(AudioFormat.MPEG_RTP, 44100.0, 16, 1);
+
+ private static final AudioFormat JMF_STATIC_PT_16 = new AudioFormat(AudioFormat.DVI_RTP, 11025.0, 4, 1);
+
+ private static final AudioFormat JMF_STATIC_PT_17 = new AudioFormat(AudioFormat.DVI_RTP, 22050.0, 4, 1);
+
+
+ private static final javax.sound.sampled.AudioFormat JS_STATIC_PT_0 = new javax.sound.sampled.AudioFormat(javax.sound.sampled.AudioFormat.Encoding.ULAW, 8000f, 8, 1, 1, 8000f, false);
+
+ private static final javax.sound.sampled.AudioFormat JS_STATIC_PT_8 = new javax.sound.sampled.AudioFormat(javax.sound.sampled.AudioFormat.Encoding.ALAW, 8000f, 8, 1, 1, 8000f, false);
+
+ private static final javax.sound.sampled.AudioFormat JS_STATIC_PT_10 = new javax.sound.sampled.AudioFormat(44100f, 16, 2, true, true);
+
+ private static final javax.sound.sampled.AudioFormat JS_STATIC_PT_11 = new javax.sound.sampled.AudioFormat(44100f, 16, 1, true, true);
+
+
+ public Object translate(Object sourceFormat, Class targetClass) {
+ if(targetClass == AudioFormat.class || targetClass == Format.class) {
+ if(sourceFormat instanceof PayloadType) return toJMFAudioFormat((PayloadType) sourceFormat);
+ else if(sourceFormat instanceof javax.sound.sampled.AudioFormat) return toJMFAudioFormat((javax.sound.sampled.AudioFormat) sourceFormat);
+ else if(sourceFormat instanceof AudioFormat) return sourceFormat;
+
+ } else if(targetClass == javax.sound.sampled.AudioFormat.class) {
+ if(sourceFormat instanceof PayloadType) return toJSAudioFormat((PayloadType) sourceFormat);
+ else if(sourceFormat instanceof AudioFormat) return toJSAudioFormat((AudioFormat) sourceFormat);
+ else if(sourceFormat instanceof javax.sound.sampled.AudioFormat) return sourceFormat;
+
+ } else if(targetClass == PayloadType.class || targetClass == PayloadType.Audio.class) {
+ if(sourceFormat instanceof javax.sound.sampled.AudioFormat) return toPayloadType((javax.sound.sampled.AudioFormat) sourceFormat);
+ else if(sourceFormat instanceof AudioFormat) return toPayloadType((AudioFormat) sourceFormat);
+ else if(sourceFormat instanceof PayloadType) return sourceFormat;
+ }
+
+ return null;
+ }
+
+ private AudioFormat toJMFAudioFormat(PayloadType pt) {
+ AudioFormat result = null;
+
+ //first the static payload types
+ switch(pt.getId()) {
+ case 0: result = JMF_STATIC_PT_0; break;
+ case 3: result = JMF_STATIC_PT_3; break;
+ case 4: result = JMF_STATIC_PT_4; break;
+ case 5: result = JMF_STATIC_PT_5; break;
+ case 6: result = JMF_STATIC_PT_6; break;
+ case 8: result = JMF_STATIC_PT_8; break;
+ case 10: result = JMF_STATIC_PT_10; break;
+ case 11: result = JMF_STATIC_PT_11; break;
+ case 14: result = JMF_STATIC_PT_14; break;
+ case 16: result = JMF_STATIC_PT_16; break;
+ case 17: result = JMF_STATIC_PT_17; break;
+ }
+
+ //then the dynamic ones
+ if(result == null) {
+ if(!(pt instanceof PayloadType.Audio)) throw new Error("PayloadType had dynamic ID but was" +
+ " not an instance of PayloadType.Audio and so did not contain enough" +
+ " information.");
+ PayloadType.Audio pta = (PayloadType.Audio) pt;
+
+ if(pta.getClockRate() == 0) throw new Error("Sampling rate was" +
+ " not set in the given PayloadType.");
+ if(pta.getName() == null) throw new Error("Codec name was" +
+ " not set in the given PayloadType");
+
+ if(pta.getName().equals("PCMA")) {
+ result = new AudioFormat(AudioFormat.ALAW, pta.getClockRate(), 8, pta.getChannels(),
+ AudioFormat.LITTLE_ENDIAN, AudioFormat.NOT_SPECIFIED, 8,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ } else if(pta.getName().equals("PCMU")) {
+ result = new AudioFormat(AudioFormat.ULAW_RTP, pta.getClockRate(), 8, pta.getChannels(),
+ AudioFormat.LITTLE_ENDIAN, AudioFormat.NOT_SPECIFIED, 8,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ } else if(pta.getName().equals("L16")) {
+ result = new AudioFormat(AudioFormat.LINEAR, pta.getClockRate(), 16, pta.getChannels(),
+ AudioFormat.BIG_ENDIAN, AudioFormat.SIGNED, AudioFormat.NOT_SPECIFIED,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ } else if(pta.getName().equals("L8")) {
+ result = new AudioFormat(AudioFormat.LINEAR, pta.getClockRate(), 8, pta.getChannels(),
+ AudioFormat.BIG_ENDIAN, AudioFormat.SIGNED, AudioFormat.NOT_SPECIFIED,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ } else if(pta.getName().equals("MPA")) {
+ result = new AudioFormat(AudioFormat.MPEG_RTP, pta.getClockRate(), 16, pta.getChannels());
+
+ } else if(pta.getName().equals("GSM")) {
+ result = new AudioFormat(AudioFormat.GSM_RTP, pta.getClockRate(), AudioFormat.NOT_SPECIFIED,
+ pta.getChannels(), AudioFormat.NOT_SPECIFIED, AudioFormat.NOT_SPECIFIED, 264,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ } else if(pta.getName().equals("G723")) {
+ result = new AudioFormat(AudioFormat.G723_RTP, pta.getClockRate(), AudioFormat.NOT_SPECIFIED,
+ pta.getChannels(), AudioFormat.NOT_SPECIFIED, AudioFormat.NOT_SPECIFIED, 192,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ } else if(pta.getName().equals("DVI4")) {
+ result = new AudioFormat(AudioFormat.DVI_RTP, pta.getClockRate(), 4, pta.getChannels());
+ }
+ }
+
+ return result;
+ }
+
+ private AudioFormat toJMFAudioFormat(javax.sound.sampled.AudioFormat af) {
+
+ if(af.getEncoding().equals(javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED)) {
+ int endianness = af.isBigEndian() ? AudioFormat.BIG_ENDIAN : AudioFormat.LITTLE_ENDIAN;
+
+ return new AudioFormat(AudioFormat.LINEAR, af.getSampleRate(), af.getSampleSizeInBits(), af.getChannels(),
+ endianness, AudioFormat.SIGNED, AudioFormat.NOT_SPECIFIED,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ } else if(af.getEncoding().equals(javax.sound.sampled.AudioFormat.Encoding.PCM_UNSIGNED)) {
+ int endianness = af.isBigEndian() ? AudioFormat.BIG_ENDIAN : AudioFormat.LITTLE_ENDIAN;
+
+ return new AudioFormat(AudioFormat.LINEAR, af.getSampleRate(), af.getSampleSizeInBits(), af.getChannels(),
+ endianness, AudioFormat.UNSIGNED, AudioFormat.NOT_SPECIFIED,
+ AudioFormat.NOT_SPECIFIED, byte[].class);
+
+ } else {
+ PayloadType pt = toPayloadType(af);
+ if(pt != null) return toJMFAudioFormat(pt);
+ return null;
+ }
+ }
+
+ private javax.sound.sampled.AudioFormat toJSAudioFormat(PayloadType pt) {
+ //Static payload types
+ switch(pt.getId()) {
+ case 0: return JS_STATIC_PT_0;
+ case 8: return JS_STATIC_PT_8;
+ case 10: return JS_STATIC_PT_10;
+ case 11: return JS_STATIC_PT_11;
+ }
+
+ //Dynamic payload types
+ if(pt instanceof PayloadType.Audio) {
+ PayloadType.Audio pta = (PayloadType.Audio) pt;
+ if(pta.getName() != null && pta.getChannels() != 0 && pta.getClockRate() != 0f) {
+ if(pta.getName().equals("PCMU")) {
+ return new javax.sound.sampled.AudioFormat(javax.sound.sampled.AudioFormat.Encoding.ULAW, pta.getClockRate(), 8, pta.getChannels(), pta.getChannels(), pta.getClockRate(), false);
+ } else if(pta.getName().equals("PCMA")) {
+ return new javax.sound.sampled.AudioFormat(javax.sound.sampled.AudioFormat.Encoding.ALAW, pta.getClockRate(), 8, pta.getChannels(), pta.getChannels(), pta.getClockRate(), false);
+ } else if(pta.getName().equals("L16")) {
+ return new javax.sound.sampled.AudioFormat(pta.getClockRate(), 16, pta.getChannels(), true, true);
+ } else if(pta.getName().equals("L8")) {
+ return new javax.sound.sampled.AudioFormat(pta.getClockRate(), 8, pta.getChannels(), true, true);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private javax.sound.sampled.AudioFormat toJSAudioFormat(AudioFormat af) {
+ if(af.getEncoding().equals(AudioFormat.LINEAR)) {
+ return new javax.sound.sampled.AudioFormat((float) af.getSampleRate(), af.getSampleSizeInBits(), af.getChannels(),
+ af.getSigned() == AudioFormat.SIGNED, af.getEndian() == AudioFormat.BIG_ENDIAN);
+ } else {
+ PayloadType pt = toPayloadType(af);
+ if(pt != null) return toJSAudioFormat(pt);
+ return null;
+ }
+ }
+
+ private PayloadType toPayloadType(AudioFormat af) {
+
+ //static payload types
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_0))) return new PayloadType(0, "PCMU");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_3))) return new PayloadType(3, "GSM");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_4))) return new PayloadType(4, "G723");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_5))) return new PayloadType(5, "DVI4");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_6))) return new PayloadType(6, "DVI4");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_8))) return new PayloadType(8, "PCMA");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_10))) return new PayloadType(10, "L16");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_11))) return new PayloadType(11, "L16");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_14))) return new PayloadType(14, "MPA");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_16))) return new PayloadType(16, "DVI4");
+ if((FormatTranslator.equals(af, JMF_STATIC_PT_17))) return new PayloadType(17, "DVI4");
+
+ //dynamic payload types
+ String name = "";
+ if(af.getEncoding().equals(AudioFormat.ALAW) && af.getSampleSizeInBits() == 8) name = "PCMA";
+ else if(af.getEncoding().equals(AudioFormat.GSM_RTP) && af.getFrameSizeInBits() == 264) name = "GSM";
+ else if(af.getEncoding().equals(AudioFormat.G723_RTP) && af.getFrameSizeInBits() == 192) name = "G723";
+ else if((af.getEncoding().equals(AudioFormat.DVI) || af.getEncoding().equals(AudioFormat.DVI_RTP)) && af.getSampleSizeInBits() == 4) name = "DVI4";
+ else if((af.getEncoding().equals(AudioFormat.ULAW) || af.getEncoding().equals(AudioFormat.ULAW_RTP)) && af.getSampleSizeInBits() == 8) name = "PCMU";
+ else if(af.getEncoding().equals(AudioFormat.LINEAR) && af.getSampleSizeInBits() == 16 && af.getEndian() == AudioFormat.BIG_ENDIAN && af.getSigned() == AudioFormat.SIGNED) name = "L16";
+ else if(af.getEncoding().equals(AudioFormat.LINEAR) && af.getSampleSizeInBits() == 8 && af.getEndian() == AudioFormat.BIG_ENDIAN && af.getSigned() == AudioFormat.SIGNED) name = "L8";
+ else if((af.getEncoding().equals(AudioFormat.MPEG) || af.getEncoding().equals(AudioFormat.MPEG_RTP)) && af.getSampleSizeInBits() == 16) name = "MPA";
+
+ if(name != "") {
+ int id = PTIDAssigner.getDynamicID(name, af.getChannels(), (float) af.getSampleRate());
+ return new PayloadType.Audio(id, name, af.getChannels(), (int) Math.round(af.getSampleRate()));
+ }
+
+ return null;
+ }
+
+ private PayloadType toPayloadType(javax.sound.sampled.AudioFormat af) {
+// Static payload types
+ if(FormatTranslator.equals(af, JS_STATIC_PT_0)) return new PayloadType(0, "PCMU");
+ if(FormatTranslator.equals(af, JS_STATIC_PT_8)) return new PayloadType(8, "PCMA");
+ if(FormatTranslator.equals(af, JS_STATIC_PT_10)) return new PayloadType(10, "L16");
+ if(FormatTranslator.equals(af, JS_STATIC_PT_11)) return new PayloadType(11, "L16");
+
+ //Dynamic payload types
+ String name = "";
+ if(af.getEncoding().equals(javax.sound.sampled.AudioFormat.Encoding.ULAW) && af.getSampleSizeInBits() == 8) name = "PCMU";
+ else if(af.getEncoding().equals(javax.sound.sampled.AudioFormat.Encoding.ALAW) && af.getSampleSizeInBits() == 8) name = "PCMA";
+ else if(af.getEncoding().equals(javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED) && af.getSampleSizeInBits() == 16 && af.isBigEndian()) name = "L16";
+ else if(af.getEncoding().equals(javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED) && af.getSampleSizeInBits() == 8 && af.isBigEndian()) name = "L8";
+
+ if(name != "") {
+ int id = PTIDAssigner.getDynamicID(name, af.getChannels(), af.getSampleRate());
+ return new PayloadType.Audio(id, name, af.getChannels(), Math.round(af.getSampleRate()));
+ }
+
+ return null;
+ }
+
+ public float preferenceLevel(PayloadType pt) {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/FormatTranslationProvider.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/FormatTranslationProvider.java
new file mode 100644
index 000000000..fe434cd25
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/FormatTranslationProvider.java
@@ -0,0 +1,12 @@
+package org.jivesoftware.smackx.jingle.media.util;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+
+public interface FormatTranslationProvider {
+
+ public Object translate(Object sourceFormat, Class targetClass);
+
+ public float preferenceLevel(PayloadType format);
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/FormatTranslator.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/FormatTranslator.java
new file mode 100644
index 000000000..6c4b11e86
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/FormatTranslator.java
@@ -0,0 +1,91 @@
+package org.jivesoftware.smackx.jingle.media.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.media.Format;
+import javax.sound.sampled.AudioFormat;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+
+import sun.misc.Service;
+
+public class FormatTranslator {
+
+ public static final float UNDEFINED_PREFERENCE_LEVEL = -1.0f;
+
+ public static Object translate(Object sourceFormat, Class targetClass) {
+
+ FormatTranslationProvider[] providers = getProviderArray();
+
+ for(int i = 0; i < providers.length; i++) {
+ Object result = providers[i].translate(sourceFormat, targetClass);
+ if(result != null) return result;
+ }
+
+ return null;
+ }
+
+ public static float preferenceLevel(PayloadType pt) {
+
+ FormatTranslationProvider[] providers = getProviderArray();
+
+ for(int i = 0; i < providers.length; i++) {
+ float result = providers[i].preferenceLevel(pt);
+ if(result != UNDEFINED_PREFERENCE_LEVEL) return result;
+ }
+
+ return UNDEFINED_PREFERENCE_LEVEL;
+ }
+
+ public static AudioFormat toJSAudioFormat(Object sourceFormat) {
+ return (AudioFormat) translate(sourceFormat, AudioFormat.class);
+ }
+
+ public static Format toJMFFormat(Object sourceFormat) {
+ return (Format) translate(sourceFormat, Format.class);
+ }
+
+ public static PayloadType toPayloadType(Object sourceFormat) {
+ return (PayloadType) translate(sourceFormat, PayloadType.class);
+ }
+
+ public static boolean equals(AudioFormat af1, AudioFormat af2) {
+ if(!af1.getEncoding().equals(af2.getEncoding())) return false;
+ if(af1.getChannels() != af2.getChannels()) return false;
+ if(!af1.getClass().equals(af2.getClass())) return false;
+ if(af1.getSampleSizeInBits() != af2.getSampleSizeInBits()) return false;
+ if(af1.getSampleRate() != af2.getSampleRate()) return false;
+ if(af1.isBigEndian() != af2.isBigEndian()) return false;
+ return true;
+ }
+
+ public static boolean equals(javax.media.format.AudioFormat af1, javax.media.format.AudioFormat af2) {
+ if(!af1.isSameEncoding(af2)) return false;
+ if(af1.getChannels() != af2.getChannels()) return false;
+ if(!af1.getClass().equals(af2.getClass())) return false;
+ if(af1.getSampleSizeInBits() != af2.getSampleSizeInBits()) return false;
+ if(af1.getSampleRate() != af2.getSampleRate()) return false;
+ if(af1.getEndian() != af2.getEndian()) return false;
+ if(af1.getSigned() != af2.getSigned()) return false;
+ return true;
+ }
+
+ private static FormatTranslationProvider[] getProviderArray() {
+ FormatTranslationProvider[] result = null;
+
+ Iterator it = Service.providers(FormatTranslationProvider.class);
+ ArrayList al = new ArrayList();
+ while(it.hasNext()) al.add(it.next());
+ result = (FormatTranslationProvider[]) al.toArray(new FormatTranslationProvider[0]);
+
+ //if none were found use default list
+ if(result.length == 0) {
+ FormatTranslationProvider[] defaultList = {new DefaultFormatTranslationProvider(), new SpeexFormatTranslationProvider()};
+ return defaultList;
+ }
+
+ return result;
+ }
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/PTIDAssigner.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/PTIDAssigner.java
new file mode 100644
index 000000000..065ac5a0a
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/PTIDAssigner.java
@@ -0,0 +1,59 @@
+package org.jivesoftware.smackx.jingle.media.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+
+public class PTIDAssigner {
+
+ private static Map dynamicIDs;
+ private static Set unassignedIDs;
+
+ static {
+ dynamicIDs = new HashMap();
+ unassignedIDs = new HashSet();
+
+// initialise the Set of unassigned IDs, these are IDs in the static range that can be used
+// for dynamic payload types. Values taken from table 4 and 5 in section 6 of RFC3551
+ int[] ids = {20,21,22,23,24,27,29,30,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,
+ 52,53,54,55,56,57,58,59,60,61,62,63,64,65,67,68,69,70,71,77,78,79,80,81,82,83,84,
+ 85,86,87,88,89,90,91,92,93,94,95};
+ for(int i = 0; i < ids.length; i++) unassignedIDs.add(new Integer(ids[i]));
+ }
+
+ public synchronized static int getDynamicID(String name, int channels, float rate) {
+
+ String lookupString = name + ", " + channels + ", " + rate;
+
+// has this pt already got an ID?
+ Integer result = (Integer) dynamicIDs.get(lookupString);
+ if(result != null) return result.intValue();
+
+// If not then generate a new one. Make the new ID one higher than the highest one
+// previously, or 97 if there are no previously assigned values.
+ int highest = 96;
+ Iterator it = dynamicIDs.values().iterator();
+ while (it.hasNext()) {
+ Integer current = (Integer) it.next();
+ if (current.intValue() > highest) highest = current.intValue();
+ }
+ if(highest != 127) {
+ dynamicIDs.put(lookupString, new Integer(highest + 1));
+ return highest + 1;
+ } else if(unassignedIDs.size() > 0) {
+// if the highest PT ID was 127 then we have exhausted all the available IDs in the
+// dynamic range and must start using unassigned IDs in the static range
+ Integer intID = (Integer) unassignedIDs.iterator().next();
+ unassignedIDs.remove(intID);
+ dynamicIDs.put(lookupString, intID);
+
+ return intID.intValue();
+ } else return PayloadType.INVALID_PT;
+ }
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/SpeexFormatTranslationProvider.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/SpeexFormatTranslationProvider.java
new file mode 100644
index 000000000..0917780df
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/SpeexFormatTranslationProvider.java
@@ -0,0 +1,126 @@
+package org.jivesoftware.smackx.jingle.media.util;
+
+//import javax.media.Format;
+//import javax.media.format.AudioFormat;
+//import javax.sound.sampled.AudioSystem;
+
+import org.jivesoftware.smackx.jingle.PayloadType;
+//import org.xiph.speex.spi.SpeexEncoding;
+
+public class SpeexFormatTranslationProvider implements FormatTranslationProvider {
+
+// This class is mostly commented out to disable Speex in Jingle media. This is done because
+// at the moment Speex decoding and encoding doesn't quite work. Once this is fixed then this
+// code can all be uncommented to reenable Speex support.
+
+// 0 1 2 3 4 5 6 7 8 9 10
+// private static final boolean[] non_vbr_qualities = {false, false, false, true, false, false, false, true, false, false, true};
+// private static final boolean[] vbr_qualities = {false, false, false, true, false, false, false, true, false, false, true};
+
+ public Object translate(Object sourceFormat, Class targetClass) {
+/* if(targetClass == AudioFormat.class || targetClass == Format.class) {
+ if(sourceFormat instanceof javax.sound.sampled.AudioFormat)
+ return toJMFAudioFormat((javax.sound.sampled.AudioFormat) sourceFormat);
+
+ } else if(targetClass == javax.sound.sampled.AudioFormat.class) {
+ if(sourceFormat instanceof PayloadType.Audio)
+ return toJSAudioFormat((PayloadType.Audio) sourceFormat);
+
+ else if(sourceFormat instanceof AudioFormat)
+ return toJSAudioFormat((AudioFormat) sourceFormat);
+
+ } else if(targetClass == PayloadType.class || targetClass == PayloadType.Audio.class) {
+ if(sourceFormat instanceof javax.sound.sampled.AudioFormat)
+ return toPayloadType((javax.sound.sampled.AudioFormat) sourceFormat);
+ }
+
+*/ return null;
+ }
+
+/* private AudioFormat toJMFAudioFormat(javax.sound.sampled.AudioFormat af) {
+ if(af.getEncoding() instanceof SpeexEncoding && allowed(af.getEncoding().toString())) {
+ return new AudioFormat(af.getEncoding().toString(), af.getSampleRate(), AudioFormat.NOT_SPECIFIED, af.getChannels());
+ }
+ return null;
+ }
+
+ private javax.sound.sampled.AudioFormat toJSAudioFormat(PayloadType.Audio pta) {
+ if(pta.getName().startsWith("SPEEX") && allowed(pta.getName())) {
+ SpeexEncoding encoding = getEncoding(pta.getName());
+ return new javax.sound.sampled.AudioFormat(encoding, pta.getClockRate(), AudioSystem.NOT_SPECIFIED,
+ pta.getChannels(), AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, false);
+ }
+ return null;
+ }
+
+ private javax.sound.sampled.AudioFormat toJSAudioFormat(AudioFormat af) {
+ if(af.getEncoding().startsWith("SPEEX") && allowed(af.getEncoding())) {
+ SpeexEncoding encoding = getEncoding(af.getEncoding());
+ if(encoding != null) return new javax.sound.sampled.AudioFormat(encoding, (float) af.getSampleRate(), AudioSystem.NOT_SPECIFIED,
+ af.getChannels(), AudioSystem.NOT_SPECIFIED, AudioSystem.NOT_SPECIFIED, false);
+ }
+ return null;
+ }
+
+ private PayloadType toPayloadType(javax.sound.sampled.AudioFormat af) {
+ if(af.getEncoding() instanceof SpeexEncoding && allowed(af.getEncoding().toString())) {
+ int id = PTIDAssigner.getDynamicID(af.getEncoding().toString(), af.getChannels(), af.getSampleRate());
+ return new PayloadType.Audio(id, af.getEncoding().toString(), af.getChannels(), Math.round(af.getSampleRate()));
+ }
+ return null;
+ }*/
+
+ public float preferenceLevel(PayloadType pt) {
+ if(!pt.getName().startsWith("SPEEX"))
+ return FormatTranslator.UNDEFINED_PREFERENCE_LEVEL;
+ else {
+
+ }
+ return FormatTranslator.UNDEFINED_PREFERENCE_LEVEL;
+ }
+
+/* private static SpeexEncoding getEncoding(String speexString) {
+ SpeexEncoding speexEnc = null;
+
+ if(allowed(speexString)) {
+ if(speexString.equals("SPEEX_quality_0")) speexEnc = SpeexEncoding.SPEEX_Q0;
+ else if(speexString.equals("SPEEX_quality_1")) speexEnc = SpeexEncoding.SPEEX_Q1;
+ else if(speexString.equals("SPEEX_quality_2")) speexEnc = SpeexEncoding.SPEEX_Q2;
+ else if(speexString.equals("SPEEX_quality_3")) speexEnc = SpeexEncoding.SPEEX_Q3;
+ else if(speexString.equals("SPEEX_quality_4")) speexEnc = SpeexEncoding.SPEEX_Q4;
+ else if(speexString.equals("SPEEX_quality_5")) speexEnc = SpeexEncoding.SPEEX_Q5;
+ else if(speexString.equals("SPEEX_quality_6")) speexEnc = SpeexEncoding.SPEEX_Q6;
+ else if(speexString.equals("SPEEX_quality_7")) speexEnc = SpeexEncoding.SPEEX_Q7;
+ else if(speexString.equals("SPEEX_quality_8")) speexEnc = SpeexEncoding.SPEEX_Q8;
+ else if(speexString.equals("SPEEX_quality_9")) speexEnc = SpeexEncoding.SPEEX_Q9;
+ else if(speexString.equals("SPEEX_quality_10")) speexEnc = SpeexEncoding.SPEEX_Q10;
+ else if(speexString.equals("SPEEX_VBR_quality_0")) speexEnc = SpeexEncoding.SPEEX_VBR0;
+ else if(speexString.equals("SPEEX_VBR_quality_1")) speexEnc = SpeexEncoding.SPEEX_VBR1;
+ else if(speexString.equals("SPEEX_VBR_quality_2")) speexEnc = SpeexEncoding.SPEEX_VBR2;
+ else if(speexString.equals("SPEEX_VBR_quality_3")) speexEnc = SpeexEncoding.SPEEX_VBR3;
+ else if(speexString.equals("SPEEX_VBR_quality_4")) speexEnc = SpeexEncoding.SPEEX_VBR4;
+ else if(speexString.equals("SPEEX_VBR_quality_5")) speexEnc = SpeexEncoding.SPEEX_VBR5;
+ else if(speexString.equals("SPEEX_VBR_quality_6")) speexEnc = SpeexEncoding.SPEEX_VBR6;
+ else if(speexString.equals("SPEEX_VBR_quality_7")) speexEnc = SpeexEncoding.SPEEX_VBR7;
+ else if(speexString.equals("SPEEX_VBR_quality_8")) speexEnc = SpeexEncoding.SPEEX_VBR8;
+ else if(speexString.equals("SPEEX_VBR_quality_9")) speexEnc = SpeexEncoding.SPEEX_VBR9;
+ else if(speexString.equals("SPEEX_VBR_quality_10")) speexEnc = SpeexEncoding.SPEEX_VBR10;
+ }
+
+ return speexEnc;
+ }
+
+ private static boolean allowed(String speexString) {
+ String[] split = speexString.split("_");
+ if(split.length == 3) {
+ int index = Integer.parseInt(split[2]);
+ return non_vbr_qualities[index];
+ } else if (split.length == 4) {
+ int index = Integer.parseInt(split[3]);
+ return vbr_qualities[index];
+ } else return false;
+
+ }
+*/}
+
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/StreamConverter.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/StreamConverter.java
new file mode 100644
index 000000000..237d1ea38
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/jingle/media/util/StreamConverter.java
@@ -0,0 +1,60 @@
+package org.jivesoftware.smackx.jingle.media.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.media.format.AudioFormat;
+import javax.media.protocol.DataSource;
+import javax.media.protocol.PushBufferDataSource;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+
+public class StreamConverter {
+
+ private static Map ais2ds = new HashMap();
+ private static Map ds2ais = new HashMap();
+
+ public static DataSource toDataSource(AudioInputStream ais) {
+ DataSource result = (DataSource) ais2ds.get(ais);
+ if(result == null) {
+ result = new AISDataSource(ais);
+ try {
+ result.connect();
+ result.start();
+ } catch (IOException e) {}
+ ais2ds.put(ais, result);
+ }
+ return result;
+ }
+
+ public static DataSource toDataSource(InputStream is) {
+ if(is instanceof AudioInputStream)
+ return toDataSource((AudioInputStream) is);
+ return null;
+ }
+
+ public static AudioInputStream toAudioInputStream(DataSource ds) {
+ AudioInputStream result = (AudioInputStream) ds2ais.get(ds);
+ if(result == null) {
+ try {
+ AudioFormat af = (AudioFormat) ((PushBufferDataSource) ds).getStreams()[0].getFormat();
+ result = new AudioInputStream(toInputStream(ds), FormatTranslator.toJSAudioFormat(af), AudioSystem.NOT_SPECIFIED);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ ais2ds.put(ds, result);
+ }
+ return result;
+ }
+
+ public static InputStream toInputStream(DataSource ds) {
+ if (ds instanceof PushBufferDataSource) {
+ return new DSInputStream((PushBufferDataSource) ds, 40*1024);
+ } else throw new Error("DataSource must be of class PushBufferDataSource");
+ }
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/BasicResolver.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/BasicResolver.java
new file mode 100644
index 000000000..984517d60
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/BasicResolver.java
@@ -0,0 +1,61 @@
+package org.jivesoftware.smackx.nat;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
+import org.jivesoftware.smack.XMPPException;
+
+/**
+ * Simple resolver.
+ */
+public class BasicResolver extends TransportResolver {
+
+ /**
+ * Constructor.
+ */
+ public BasicResolver() {
+ super();
+ }
+
+ /**
+ * Resolve the IP address.
+ *
+ * The BasicResolver takes the IP addresses of the interfaces and uses the
+ * first non-loopback address.
+ */
+ public synchronized void resolve() throws XMPPException {
+
+ setResolveInit();
+
+ clearCandidates();
+
+ Enumeration ifaces = null;
+
+ try {
+ ifaces = NetworkInterface.getNetworkInterfaces();
+ } catch (SocketException e) {
+ e.printStackTrace();
+ }
+
+ while (ifaces.hasMoreElements()) {
+
+ NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
+ Enumeration iaddresses = iface.getInetAddresses();
+
+ while (iaddresses.hasMoreElements()) {
+ InetAddress iaddress = (InetAddress) iaddresses.nextElement();
+ if (!iaddress.isLoopbackAddress() && !iaddress.isLinkLocalAddress()) {
+ addCandidate(new TransportCandidate.Fixed(iaddress.getHostName(), 0));
+ }
+ }
+ }
+
+ setResolveEnd();
+ }
+
+ public void cancel() throws XMPPException {
+ // Nothing to do here
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/FixedResolver.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/FixedResolver.java
new file mode 100644
index 000000000..c4fdc2d58
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/FixedResolver.java
@@ -0,0 +1,51 @@
+package org.jivesoftware.smackx.nat;
+
+import org.jivesoftware.smack.XMPPException;
+
+/**
+ * Specialization of the BasicResolver. 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 BasicResolver {
+
+ TransportCandidate fixedCandidate;
+
+ /**
+ * Constructor.
+ */
+ public FixedResolver(final String ip, final 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(final String ip, final int port) {
+ fixedCandidate = new TransportCandidate.Fixed(ip, port);
+ }
+
+ /**
+ * Resolve the IP address.
+ */
+ public synchronized void resolve() throws XMPPException {
+
+ if (!isResolving()) {
+ setResolveInit();
+
+ clearCandidates();
+
+ if (fixedCandidate != null) {
+ addCandidate(fixedCandidate);
+ }
+
+ setResolveEnd();
+ }
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/STUNResolver.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/STUNResolver.java
new file mode 100644
index 000000000..47ef7e1eb
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/STUNResolver.java
@@ -0,0 +1,485 @@
+package org.jivesoftware.smackx.nat;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.ServerSocket;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import org.jivesoftware.smack.XMPPException;
+import org.xmlpull.v1.*;
+import org.xmlpull.mxp1.MXParser;
+
+import java.net.URL;
+
+import de.javawi.jstun.test.BindingLifetimeTest;
+import de.javawi.jstun.test.DiscoveryInfo;
+import de.javawi.jstun.test.DiscoveryTest;
+
+/**
+ * Transport resolver using the STUN library.
+ *
+ * @author Alvaro Saurin
+ */
+public class STUNResolver extends TransportResolver {
+
+ // The filename where the STUN servers are stored.
+ public final static String STUNSERVERS_FILENAME = "META-INF/stun-config.xml";
+
+ // Fallback values when we don't have any STUN server to use...
+ private final static String FALLBACKHOSTNAME = "stun.xten.net";
+
+ private final static int FALLBACKHOSTPORT = 3478;
+
+ // Current STUN server we are using
+ private STUNService currentServer;
+
+ private Thread resolverThread;
+
+ private int defaultPort;
+
+ /**
+ * 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 org.jivesoftware.smackx.nat.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(final String ip, final 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 loadSTUNServers(final java.io.InputStream stunConfigStream) {
+ ArrayList serversList = new ArrayList();
+ String serverName;
+ int serverPort;
+
+ try {
+ XmlPullParser parser = new MXParser();
+ 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) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ currentServer = bestSTUNServer(serversList);
+
+ return serversList;
+ }
+
+ /**
+ * Load a list of services: STUN servers and ports. Some public STUN servers
+ * are:
+ *
+ * <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>
+ *
+ * This list should be contained in a file in the "META-INF" directory
+ *
+ * @return a list of services
+ */
+ public ArrayList loadSTUNServers() {
+ ArrayList serversList = new ArrayList();
+
+ // 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 stunConfigEnum = classLoaders[i]
+ .getResources(STUNSERVERS_FILENAME);
+
+ while (stunConfigEnum.hasMoreElements() && serversList.isEmpty()) {
+ URL url = (URL) stunConfigEnum.nextElement();
+ java.io.InputStream stunConfigStream = null;
+
+ stunConfigStream = url.openStream();
+ serversList.addAll(loadSTUNServers(stunConfigStream));
+ stunConfigStream.close();
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // If the list of candidates is empty, add at least one default server
+ if (serversList.isEmpty()) {
+ serversList.add(new STUNService(FALLBACKHOSTNAME, FALLBACKHOSTPORT));
+ }
+
+ return serversList;
+ }
+
+ /**
+ * Get the best usable STUN server from a list.
+ *
+ * @return the best STUN server that can be used.
+ */
+ private STUNService bestSTUNServer(final ArrayList listServers) {
+ if (listServers.isEmpty()) {
+ return null;
+ } else {
+ // TODO: this should use some more advanced criteria...
+ return (STUNService) listServers.get(0);
+ }
+ }
+
+ /**
+ * Obtain a free port we can use.
+ *
+ * @return A free port number.
+ */
+ private int getFreePort() {
+ ServerSocket ss;
+ int freePort = 0;
+ try {
+ ss = new ServerSocket(0);
+ freePort = ss.getLocalPort();
+ ss.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return freePort;
+ }
+
+ /**
+ * Resolve the IP and obtain a valid transport method.
+ */
+ public synchronized void resolve() throws XMPPException {
+ if (!isResolving()) {
+ // Get the best STUN server available
+ if (currentServer.isNull()) {
+ loadSTUNServers();
+ }
+
+ // We should have a valid STUN server by now...
+ if (!currentServer.isNull()) {
+
+ setResolveInit();
+
+ 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 ifaces = NetworkInterface.getNetworkInterfaces();
+ String candAddress;
+ int candPort;
+
+ while (ifaces.hasMoreElements()) {
+
+ NetworkInterface iface = (NetworkInterface) ifaces
+ .nextElement();
+ Enumeration iaddresses = iface.getInetAddresses();
+
+ while (iaddresses.hasMoreElements()) {
+ InetAddress iaddress = (InetAddress) 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()
+ .getHostAddress();
+
+ // 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) {
+ addCandidate(new TransportCandidate.Fixed(
+ candAddress, candPort));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ } catch (SocketException e) {
+ e.printStackTrace();
+ } finally {
+ setResolveEnd();
+ }
+ }
+ }, "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 org.jivesoftware.smackx.nat.TransportResolver#cancel()
+ */
+ public synchronized void cancel() throws XMPPException {
+ if (isResolving()) {
+ resolverThread.interrupt();
+ setResolveEnd();
+ }
+ }
+
+ /**
+ * Clear the list of candidates and start the resolution again.
+ *
+ * @see org.jivesoftware.smackx.nat.TransportResolver#clear()
+ */
+ public synchronized void clear() throws XMPPException {
+ this.defaultPort = 0;
+ super.clear();
+ }
+
+ /**
+ * STUN service definition.
+ */
+ private 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(final String hostname, final 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(final 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(final 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.
+ *
+ * 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) {
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportCandidate.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportCandidate.java
new file mode 100644
index 000000000..46c558945
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportCandidate.java
@@ -0,0 +1,703 @@
+/**
+ * $RCSfile: TransportCandidate.java,v $
+ * $Revision: 1.1 $
+ * $Date: 2006/10/17 19:13:55 $
+ *
+ * Copyright (C) 2002-2006 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by
+ * Jive Software (http://www.jivesoftware.com)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please
+ * contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ * nor may "Smack" appear in their name, without prior written
+ * permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx.nat;
+
+/**
+ * Transport candidate.
+ *
+ * </p>
+ *
+ * A candidate represents the possible transport for data interchange between
+ * the two endpoints.
+ *
+ * </p>
+ *
+ * @author Alvaro Saurin
+ */
+public abstract class TransportCandidate {
+
+ private String name;
+
+ private String ip; // IP address
+
+ private int port; // Port to use, or 0 for any port
+
+ private int generation;
+
+ /**
+ * 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(final String ip, final int port) {
+ this(ip, port, 0);
+ }
+
+ /**
+ * Constructor with IP address and port
+ *
+ * @param ip The IP address.
+ * @param port The port number.
+ * @parame generation The generation
+ */
+ public TransportCandidate(final String ip, final int port, final 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 IP
+ *
+ * @return the IP address
+ */
+ public String getIP() {
+ return ip;
+ }
+
+ /**
+ * Set the IP address.
+ *
+ * @param ip the IP address
+ */
+ public void setIP(final String ip) {
+ this.ip = ip;
+ }
+
+ /**
+ * 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(final 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(final 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(final String name) {
+ this.name = name;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(final 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 (getName() == null) {
+ if (other.getName() != null) {
+ return false;
+ }
+ } else if (!getName().equals(other.getName())) {
+ return false;
+ }
+ if (getPort() != other.getPort()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 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(final String ip, final int port) {
+ super(ip, port);
+ }
+
+ /**
+ * Constructor with IP address and port
+ *
+ * @param ip The IP address.
+ * @param port The port number.
+ * @parame generation The generation
+ */
+ public Fixed(final String ip, final int port, final int generation) {
+ super(ip, port, generation);
+ }
+ }
+
+ /**
+ * Ice candidate.
+ */
+ public static class Ice extends TransportCandidate implements Comparable {
+
+ private String id; // An identification
+
+ private String password;
+
+ private String username;
+
+ private int preference;
+
+ private Protocol proto;
+
+ private Channel channel;
+
+ private int network;
+
+ public Ice() {
+ 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
+ */
+ public Ice(final String ip, final int generation, final int network,
+ final String password, final int port, final String username,
+ final int preference) {
+ super(ip, port, generation);
+
+ proto = new Protocol("");
+ channel = new Channel("");
+
+ this.network = network;
+ this.password = password;
+ this.username = username;
+ this.preference = preference;
+ }
+
+ /**
+ * Get the ID
+ *
+ * @return the id
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Set the ID
+ *
+ * @param id the id to set
+ */
+ public void setId(final 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(final 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(final int network) {
+ this.network = network;
+ }
+
+ /**
+ * Get the password used by ICE
+ *
+ * @return a password
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Set the password used by ICE
+ *
+ * @param password a password
+ */
+ public void setPassword(final String password) {
+ this.password = password;
+ }
+
+ /**
+ * 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(final Channel channel) {
+ this.channel = channel;
+ }
+
+ /**
+ * Set the username for this transportElement in ICE
+ *
+ * @param username the username used in ICE
+ */
+ public void setUsername(final 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(final int preference) {
+ this.preference = preference;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ final Ice other = (Ice) 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;
+ }
+ 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(final Object arg) {
+ if (arg instanceof TransportCandidate.Ice) {
+ TransportCandidate.Ice tc = (TransportCandidate.Ice) arg;
+ if (getPreference() < tc.getPreference()) {
+ return -1;
+ } else if (getPreference() > tc.getPreference()) {
+ return 1;
+ }
+ }
+ return 0;
+ }
+ }
+
+ /**
+ * 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(final 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();
+ 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(final 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(final 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();
+ 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(final 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;
+ }
+ }
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportResolver.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportResolver.java
new file mode 100644
index 000000000..726e6363e
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportResolver.java
@@ -0,0 +1,390 @@
+/**
+ * $RCSfile: TransportResolver.java,v $
+ * $Revision: 1.1 $
+ * $Date: 2006/10/17 19:13:55 $
+ *
+ * Copyright (C) 2002-2006 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by
+ * Jive Software (http://www.jivesoftware.com)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ * endorse or promote products derived from this software without
+ * prior written permission. For written permission, please
+ * contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ * nor may "Smack" appear in their name, without prior written
+ * permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx.nat;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jivesoftware.smack.XMPPException;
+
+/**
+ * A TransportResolver is used for obtaining a list of valid transport
+ * candidates.
+ *
+ * @author Alvaro Saurin <alvaro.saurin@gmail.com>
+ */
+public abstract class TransportResolver {
+
+ // the time, in milliseconds, before a check aborts
+ public static final int CHECK_TIMEOUT = 2000;
+
+ // Listeners for events
+ private final ArrayList listeners = new ArrayList();
+
+ // TRue if the resolver is working
+ private boolean resolving;
+
+ // This will be true when all the transport candidates have been gathered...
+ private boolean resolved;
+
+ // 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 candidates = new ArrayList();
+
+ // Remote candidates that are being checked
+ private final static ArrayList candidatesChecking = new ArrayList();
+
+ /**
+ * Default constructor.
+ */
+ protected TransportResolver() {
+ super();
+
+ resolving = false;
+ resolved = false;
+ }
+
+ /**
+ * Start the resolution.
+ */
+ public abstract void resolve() throws XMPPException;
+
+ /**
+ * Clear the list of candidates and start a new resolution process.
+ *
+ * @throws XMPPException
+ */
+ public void clear() throws XMPPException {
+ cancel();
+ candidates.clear();
+ resolve();
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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();
+ }
+
+ /**
+ * Check if a transport candidate is usable. The transport resolver should
+ * check if the transport candidate the other endpoint has provided is
+ * usable.
+ *
+ * This method provides a basic check where it sends a "ping" to the remote
+ * address provided in the candidate. If the "ping" succedess, the candidate
+ * is accepted. Subclasses should provide better methods if they can...
+ *
+ * @param cand The transport candidate to test.
+ */
+ public void check(final TransportCandidate cand) {
+ if (!candidatesChecking.contains(cand)) {
+ candidatesChecking.add(cand);
+
+ Thread checkThread = new Thread(new Runnable() {
+ public void run() {
+ boolean isUsable;
+
+ InetAddress candAddress;
+ try {
+ candAddress = InetAddress.getByName(cand.getIP());
+ isUsable = candAddress.isReachable(CHECK_TIMEOUT);
+ } catch (Exception e) {
+ isUsable = false;
+ }
+ triggerCandidateChecked(cand, isUsable);
+
+ candidatesChecking.remove(cand);
+ }
+ }, "Transport candidate check");
+
+ checkThread.setName("Transport candidate test");
+ checkThread.start();
+ }
+ }
+
+ // Listeners management
+
+ /**
+ * Add a transport resolver listener.
+ *
+ * @param li The transport resolver listener to be added.
+ */
+ public void addListener(final TransportResolverListener li) {
+ synchronized (listeners) {
+ listeners.add(li);
+ }
+ }
+
+ /**
+ * Removes a transport resolver listener.
+ *
+ * @param li The transport resolver listener to be removed
+ */
+ public void removeListener(final TransportResolverListener li) {
+ synchronized (listeners) {
+ listeners.remove(li);
+ }
+ }
+
+ /**
+ * Get the list of listeners
+ *
+ * @return the list of listeners
+ */
+ public ArrayList getListenersList() {
+ synchronized (listeners) {
+ return new ArrayList(listeners);
+ }
+ }
+
+ /**
+ * Trigger a new candidate added event.
+ *
+ * @param cand The candidate added to the list of candidates.
+ */
+ protected void triggerCandidateAdded(final TransportCandidate cand) {
+ Iterator iter = getListenersList().iterator();
+ while (iter.hasNext()) {
+ TransportResolverListener trl = (TransportResolverListener) iter.next();
+ if (trl instanceof TransportResolverListener.Resolver) {
+ TransportResolverListener.Resolver li = (TransportResolverListener.Resolver) trl;
+ li.candidateAdded(cand);
+ }
+ }
+ }
+
+ /**
+ * Trigger a new candidate checked event.
+ *
+ * @param cand The checked candidate.
+ * @param result The result.
+ */
+ protected void triggerCandidateChecked(final TransportCandidate cand,
+ final boolean result) {
+ Iterator iter = getListenersList().iterator();
+ while (iter.hasNext()) {
+ TransportResolverListener trl = (TransportResolverListener) iter.next();
+ if (trl instanceof TransportResolverListener.Checker) {
+ TransportResolverListener.Checker li = (TransportResolverListener.Checker) trl;
+ li.candidateChecked(cand, result);
+ }
+ }
+ }
+
+ /**
+ * Trigger a event notifying the initialization of the resolution process.
+ */
+ private void triggerResolveInit() {
+ Iterator iter = getListenersList().iterator();
+ while (iter.hasNext()) {
+ TransportResolverListener trl = (TransportResolverListener) 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 iter = getListenersList().iterator();
+ while (iter.hasNext()) {
+ TransportResolverListener trl = (TransportResolverListener) 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
+ */
+ protected void addCandidate(final TransportCandidate cand) {
+ synchronized (candidates) {
+ candidates.add(cand);
+ }
+
+ // Notify the listeners
+ triggerCandidateAdded(cand);
+ }
+
+ /**
+ * Get an iterator for the list of candidates
+ *
+ * @return an iterator
+ */
+ public Iterator getCandidates() {
+ synchronized (candidates) {
+ return Collections.unmodifiableList(new ArrayList(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 cands = (ArrayList) getCandidatesList();
+ if (cands.size() > 0) {
+ Collections.sort(cands);
+ // Return the last candidate
+ result = (TransportCandidate) cands.get(cands.size() - 1);
+ }
+
+ 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 getCandidatesList() {
+ ArrayList result = null;
+
+ synchronized (candidates) {
+ result = new ArrayList(candidates);
+ }
+
+ return result;
+ }
+
+ /**
+ * Get the n-th candidate
+ *
+ * @return a transport candidate
+ */
+ public TransportCandidate getCandidate(final int i) {
+ TransportCandidate cand;
+
+ synchronized (candidates) {
+ cand = (TransportCandidate) candidates.get(i);
+ }
+ return cand;
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportResolverListener.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportResolverListener.java
new file mode 100644
index 000000000..367acc887
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/nat/TransportResolverListener.java
@@ -0,0 +1,38 @@
+package org.jivesoftware.smackx.nat;
+
+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.
+ */
+ public void candidateAdded(TransportCandidate cand);
+
+ /**
+ * 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.
+ * @oaram result True if the candidate is usable.
+ */
+ public void candidateChecked(TransportCandidate cand, boolean result);
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/Jingle.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/Jingle.java
new file mode 100644
index 000000000..800e42898
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/Jingle.java
@@ -0,0 +1,589 @@
+/**
+ * $RCSfile: Jingle.java,v $
+ * $Revision: 1.1 $
+ * $Date: 2006/10/17 19:14:14 $
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. 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.packet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * 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 = "http://jabber.org/protocol/jingle";
+
+ public static final String NODENAME = "jingle";
+
+ // non-static
+
+ private String sid; // The session id
+
+ private Action 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 descriptions = new ArrayList();
+
+ private final List transports = new ArrayList();
+
+ private JingleContentInfo contentInfo;
+
+ /**
+ * A constructor where the main components can be initialized.
+ */
+ public Jingle(final List descs, final List trans, final JingleContentInfo mi,
+ final String sid) {
+ super();
+
+ if (descs != null) {
+ descriptions.addAll(descs);
+ }
+
+ if (trans != null) {
+ transports.addAll(trans);
+ }
+
+ setContentInfo(mi);
+ setSid(sid);
+
+ // Set null all other fields in the packet
+ initiator = null;
+ responder = null;
+ action = null;
+ }
+
+ /**
+ * Constructor with a description.
+ *
+ * @param descr a description
+ */
+ public Jingle(final JingleContentDescription descr) {
+ super();
+
+ addDescription(descr);
+
+ // Set null all other fields in the packet
+ initiator = null;
+ responder = null;
+
+ // Some default values for the most common situation...
+ action = Jingle.Action.CONTENTINFO;
+ this.setType(IQ.Type.SET);
+ }
+
+ /**
+ * Constructor with a transport.
+ *
+ * @param trans a transport
+ */
+ public Jingle(final JingleTransport trans) {
+ super();
+
+ addTransport(trans);
+
+ // Set null all other fields in the packet
+ initiator = null;
+ responder = null;
+
+ // Some default values for the most common situation...
+ action = Jingle.Action.TRANSPORTINFO;
+ 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 = Jingle.Action.CONTENTINFO;
+ this.setType(IQ.Type.SET);
+ }
+
+ /**
+ * A constructor where the action can be specified.
+ *
+ * @param action The action.
+ */
+ public Jingle(final Jingle.Action action) {
+ this(null, 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, 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.
+ * According the specification the namespace is always
+ * "http://jabber.org/protocol/jingle"
+ *
+ * @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 content descriptions
+ *
+ * @return the descriptions
+ */
+ public Iterator getDescriptions() {
+ synchronized (descriptions) {
+ return Collections.unmodifiableList(new ArrayList(descriptions)).iterator();
+ }
+ }
+
+ /**
+ * Get an iterator for the content descriptions
+ *
+ * @return the descriptions
+ */
+ public ArrayList getDescriptionsList() {
+ synchronized (descriptions) {
+ return new ArrayList(descriptions);
+ }
+ }
+
+ /**
+ * Add a new content description.
+ *
+ * @param desc the descriptions to add
+ */
+ public void addDescription(final JingleContentDescription desc) {
+ if (desc != null) {
+ synchronized (descriptions) {
+ descriptions.add(desc);
+ }
+ }
+ }
+
+ /**
+ * Add a list of JingleContentDescription elements
+ *
+ * @param descsList the list of transports to add
+ */
+ public void addDescriptions(final List descsList) {
+ if (descsList != null) {
+ synchronized (descriptions) {
+ descriptions.addAll(descsList);
+ }
+ }
+ }
+
+ /**
+ * Get an iterator for the transport.
+ *
+ * @return the transports
+ */
+ public Iterator getTransports() {
+ synchronized (transports) {
+ return Collections.unmodifiableList(new ArrayList(transports)).iterator();
+ }
+ }
+
+ /**
+ * Get the list of transports.
+ *
+ * @return the transports list.
+ */
+ public ArrayList getTransportsList() {
+ synchronized (transports) {
+ return new ArrayList(transports);
+ }
+ }
+
+ /**
+ * Add a new JingleTransport element
+ *
+ * @param trans the transports to add
+ */
+ public void addTransport(final JingleTransport trans) {
+ if (trans != null) {
+ synchronized (transports) {
+ transports.add(trans);
+ }
+ }
+ }
+
+ /**
+ * Add a list of JingleTransport elements
+ *
+ * @param transList the list of transports to add
+ */
+ public void addTransports(final List transList) {
+ if (transList != null) {
+ synchronized (transports) {
+ transports.addAll(transList);
+ }
+ }
+ }
+
+ /**
+ * Get the action specified in the packet
+ *
+ * @return the action
+ */
+ public Action getAction() {
+ return action;
+ }
+
+ /**
+ * Set the action in the packet
+ *
+ * @param action the action to set
+ */
+ public void setAction(final Action 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() {
+ StringBuffer buf = new StringBuffer();
+
+ 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(">");
+
+ // Look for possible payload types, and dump them.
+ synchronized (descriptions) {
+ for (int i = 0; i < descriptions.size(); i++) {
+ JingleContentDescription desc = (JingleContentDescription) descriptions
+ .get(i);
+ buf.append(desc.toXML());
+ }
+ }
+
+ // If the packet has transports, dump them.
+ synchronized (transports) {
+ for (int i = 0; i < transports.size(); i++) {
+ JingleTransport trans = (JingleTransport) transports.get(i);
+ buf.append(trans.toXML());
+ }
+ }
+
+ // and the same for audio media info
+ if (contentInfo != null) {
+ buf.append(contentInfo.toXML());
+ }
+
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+ /**
+ * The "action" in the jingle packet, as an enum.
+ */
+ public static class Action {
+
+ public static final Action CONTENTACCEPT = new Action("description-accept");
+
+ public static final Action CONTENTDECLINE = new Action("description-decline");
+
+ public static final Action CONTENTINFO = new Action("description-info");
+
+ public static final Action CONTENTMODIFY = new Action("description-modify");
+
+ public static final Action SESSIONACCEPT = new Action("session-accept");
+
+ public static final Action SESSIONINFO = new Action("session-info");
+
+ public static final Action SESSIONINITIATE = new Action("session-initiate");
+
+ public static final Action SESSIONREDIRECT = new Action("session-redirect");
+
+ public static final Action SESSIONEXTEND = new Action("session-extend");
+
+ public static final Action SESSIONREDUCE = new Action("session-reduce");
+
+ public static final Action SESSIONTERMINATE = new Action("session-terminate");
+
+ public static final Action TRANSPORTACCEPT = new Action("transport-accept");
+
+ public static final Action TRANSPORTDECLINE = new Action("transport-decline");
+
+ public static final Action TRANSPORTINFO = new Action("transport-info");
+
+ public static final Action TRANSPORTMODIFY = new Action("transport-modify");
+
+ private String value;
+
+ public Action(final String value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the String value for an Action.
+ */
+ public String toString() {
+ return value;
+ }
+
+ /**
+ * Returns a Action instance associated with the String value.
+ */
+ public static Action fromString(String value) {
+ if (value != null) {
+ value = value.toLowerCase();
+ if (value.equals("description-accept")) {
+ return CONTENTACCEPT;
+ } else if (value.equals("description-decline")) {
+ return CONTENTDECLINE;
+ } else if (value.equals("description-info")) {
+ return CONTENTINFO;
+ } else if (value.equals("description-modify")) {
+ return CONTENTMODIFY;
+ } else if (value.equals("session-accept")) {
+ return SESSIONACCEPT;
+ } else if (value.equals("session-info")) {
+ return SESSIONINFO;
+ } else if (value.equals("session-initiate")) {
+ return SESSIONINITIATE;
+ } else if (value.equals("session-redirect")) {
+ return SESSIONACCEPT;
+ } else if (value.equals("session-extend")) {
+ return SESSIONEXTEND;
+ } else if (value.equals("session-reduce")) {
+ return SESSIONREDUCE;
+ } else if (value.equals("session-terminate")) {
+ return SESSIONTERMINATE;
+ } else if (value.equals("transport-accept")) {
+ return TRANSPORTACCEPT;
+ } else if (value.equals("transport-decline")) {
+ return TRANSPORTDECLINE;
+ } else if (value.equals("transport-info")) {
+ return TRANSPORTINFO;
+ } else if (value.equals("transport-modify")) {
+ return TRANSPORTMODIFY;
+ }
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ final int PRIME = 31;
+ int result = 1;
+ result = PRIME * result + (value == null ? 0 : value.hashCode());
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Action other = (Action) obj;
+ if (value == null) {
+ if (other.value != null) {
+ return false;
+ }
+ } else if (!value.equals(other.value)) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleContentDescription.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleContentDescription.java
new file mode 100644
index 000000000..7e74620e8
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleContentDescription.java
@@ -0,0 +1,279 @@
+package org.jivesoftware.smackx.packet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.jingle.PayloadType;
+
+/**
+ * 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 payloads = new ArrayList();
+
+ /**
+ * 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 pts) {
+ synchronized (payloads) {
+ Iterator ptIter = pts.iterator();
+ while (ptIter.hasNext()) {
+ PayloadType.Audio pt = (PayloadType.Audio) 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 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 getJinglePayloadTypesList() {
+ synchronized (payloads) {
+ return new ArrayList(payloads);
+ }
+ }
+
+ /**
+ * Return the list of Payload types contained in the description.
+ *
+ * @return a list of PayloadType.Audio
+ */
+ public ArrayList getAudioPayloadTypesList() {
+ ArrayList result = new ArrayList();
+ Iterator jinglePtsIter = getJinglePayloadTypes();
+
+ while(jinglePtsIter.hasNext()) {
+ JinglePayloadType jpt = (JinglePayloadType) jinglePtsIter.next();
+ if (jpt instanceof JinglePayloadType.Audio) {
+ JinglePayloadType.Audio jpta = (JinglePayloadType.Audio) jpt;
+ result.add(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() {
+ StringBuffer buf = new StringBuffer();
+
+ synchronized (payloads) {
+ if (payloads.size() > 0) {
+ buf.append("<").append(getElementName());
+ buf.append(" xmlns=\"").append(getNamespace()).append("\" >");
+
+ Iterator pt = payloads.listIterator();
+ while (pt.hasNext()) {
+ JinglePayloadType pte = (JinglePayloadType) 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 = "http://jabber.org/protocol/jingle/description/audio";
+
+ 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() {
+ StringBuffer buf = new StringBuffer();
+
+ 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() {
+ StringBuffer buf = new StringBuffer();
+ PayloadType pt = getPayloadType();
+ if (pt instanceof PayloadType.Audio) {
+ PayloadType.Audio pta = (PayloadType.Audio) pt;
+
+ buf.append(" clockrate=\"").append(pta.getClockRate()).append("\" ");
+ }
+ return buf.toString();
+ }
+ }
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleContentInfo.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleContentInfo.java
new file mode 100644
index 000000000..44f62121b
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleContentInfo.java
@@ -0,0 +1,138 @@
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.jingle.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 media info.
+ */
+ public JingleContentInfo() {
+ this(null);
+ }
+
+ /**
+ * Constructor with a media info
+ *
+ * @param mediaInfoElement MediaInfo element
+ */
+ public JingleContentInfo(final ContentInfo mediaInfoElement) {
+ super();
+ this.mediaInfoElement = mediaInfoElement;
+ }
+
+ /**
+ * Get the media 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() {
+ StringBuffer buf = new StringBuffer();
+ 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 = "http://jabber.org/protocol/jingle/info/audio";
+
+ public Audio(final ContentInfo mi) {
+ super(mi);
+ setNamespace(NAMESPACE);
+ }
+
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ // Subclasses: specialize the Audio media info...
+
+ /**
+ * Busy media info.
+ */
+ public static class Busy extends Audio {
+ public Busy() {
+ super(ContentInfo.Audio.BUSY);
+ }
+ }
+
+ /**
+ * Hold media info.
+ */
+ public static class Hold extends Audio {
+ public Hold() {
+ super(ContentInfo.Audio.HOLD);
+ }
+ }
+
+ /**
+ * Mute media info.
+ */
+ public static class Mute extends Audio {
+ public Mute() {
+ super(ContentInfo.Audio.MUTE);
+ }
+ }
+
+ /**
+ * Queued media info.
+ */
+ public static class Queued extends Audio {
+ public Queued() {
+ super(ContentInfo.Audio.QUEUED);
+ }
+ }
+
+ /**
+ * Ringing media info.
+ */
+ public static class Ringing extends Audio {
+ public Ringing() {
+ super(ContentInfo.Audio.RINGING);
+ }
+ }
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleError.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleError.java
new file mode 100644
index 000000000..1a4b2d298
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleError.java
@@ -0,0 +1,102 @@
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+public class JingleError implements PacketExtension {
+
+ public static String NAMESPACE = "http://jabber.org/protocol/jingle#error";
+
+ 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(
+ "no-common-payload");
+
+ 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() {
+ StringBuffer buf = new StringBuffer();
+ 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();
+ 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("no-common-payload")) {
+ 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;
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleTransport.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleTransport.java
new file mode 100644
index 000000000..3586f9a8d
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/packet/JingleTransport.java
@@ -0,0 +1,384 @@
+package org.jivesoftware.smackx.packet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.nat.TransportCandidate;
+
+/**
+ * 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 candidates = new ArrayList();
+
+ /**
+ * 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 getCandidates() {
+ return Collections.unmodifiableList(getCandidatesList()).iterator();
+ }
+
+ /**
+ * Get the list of candidates.
+ *
+ * @return The candidates list.
+ */
+ public ArrayList getCandidatesList() {
+ ArrayList res = null;
+ synchronized (candidates) {
+ res = new ArrayList(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() {
+ StringBuffer buf = new StringBuffer();
+
+ buf.append("<").append(getElementName()).append(" xmlns=\"");
+ buf.append(getNamespace()).append("\" ");
+
+ synchronized (candidates) {
+ if (getCandidatesCount() > 0) {
+ buf.append(">");
+ Iterator iter = getCandidates();
+ while (iter.hasNext()) {
+ JingleTransportCandidate candidate = (JingleTransportCandidate) 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.
+ *
+ * @see TransportCandidate
+ * @author Alvaro Saurin
+ */
+ public static abstract class JingleTransportCandidate {
+
+ public static final String NODENAME = "candidate";
+
+ // The transport candidate contained in the element.
+ protected TransportCandidate transportCandidate;
+
+ /**
+ * Creates a new JingleTransport child.
+ */
+ public JingleTransportCandidate() {
+ super();
+ }
+
+ /**
+ * Creates a new JingleTransport child.
+ *
+ * @param candidate the media 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() {
+ StringBuffer buf = new StringBuffer();
+ 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 = "http://jabber.org/protocol/jingle/transport/ice";
+
+ public Ice() {
+ super();
+ setNamespace(NAMESPACE);
+ }
+
+ 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() {
+ StringBuffer buf = new StringBuffer();
+
+ if (transportCandidate != null && transportCandidate instanceof TransportCandidate.Ice) {
+ TransportCandidate.Ice tci = (TransportCandidate.Ice) 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("\"");
+
+ // 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://jabber.org/protocol/jingle/transport/raw-udp";
+
+ public RawUdp() {
+ super();
+ setNamespace(NAMESPACE);
+ }
+
+ /**
+ * Add a transport candidate
+ *
+ * @see org.jivesoftware.smackx.packet.JingleTransport#addCandidate(org.jivesoftware.smackx.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.packet.JingleTransport#getCandidates()
+ */
+ public ArrayList getCandidatesList() {
+ ArrayList copy = new ArrayList();
+ ArrayList 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() {
+ StringBuffer buf = new StringBuffer();
+
+ 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();
+ }
+
+ }
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleContentDescriptionProvider.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleContentDescriptionProvider.java
new file mode 100644
index 000000000..b47175fc5
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleContentDescriptionProvider.java
@@ -0,0 +1,125 @@
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.jingle.PayloadType;
+import org.jivesoftware.smackx.packet.JingleContentDescription;
+import org.jivesoftware.smackx.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();
+ }
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleContentInfoProvider.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleContentInfoProvider.java
new file mode 100644
index 000000000..90d0cadb9
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleContentInfoProvider.java
@@ -0,0 +1,106 @@
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.jingle.ContentInfo;
+import org.jivesoftware.smackx.packet.JingleContentInfo;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Jingle Audio media-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;
+ }
+
+ /**
+ * JingleContentDescription.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 media info
+ */
+ public Audio(final PacketExtension audioInfo) {
+ super();
+ this.audioInfo = audioInfo;
+ }
+
+ /**
+ * Parse a JingleContentDescription.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());
+ }
+ }
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleProvider.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleProvider.java
new file mode 100644
index 000000000..57d285c72
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleProvider.java
@@ -0,0 +1,126 @@
+/**
+ * $RCSfile: JingleProvider.java,v $
+ * $Revision: 1.1 $
+ * $Date: 2006/10/17 19:14:28 $
+ *
+ * Copyright 2003-2005 Jive Software.
+ *
+ * All rights reserved. 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.provider;
+
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smackx.packet.Jingle;
+import org.jivesoftware.smackx.packet.JingleContentDescription;
+import org.jivesoftware.smackx.packet.JingleContentInfo;
+import org.jivesoftware.smackx.packet.JingleTransport;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * The JingleProvider parses Jingle packets.
+ *
+ * @author Alvaro Saurin
+ */
+public class JingleProvider implements IQProvider {
+
+ /**
+ * Creates a new provider. ProviderManager requires that every
+ * PacketExtensionProvider has a public, no-argument constructor
+ */
+ public JingleProvider() {
+ super();
+ }
+
+ /**
+ * Parse a iq/jingle element.
+ */
+ public IQ parseIQ(final XmlPullParser parser) throws Exception {
+
+ Jingle jingle = new Jingle();
+ String sid = "";
+ Jingle.Action action;
+ String initiator = "";
+ String responder = "";
+ boolean done = false;
+
+ // Sub-elements providers
+ JingleContentDescriptionProvider jdpAudio = new JingleContentDescriptionProvider.Audio();
+ JingleTransportProvider jtpRawUdp = new JingleTransportProvider.RawUdp();
+ JingleTransportProvider jtpIce = new JingleTransportProvider.Ice();
+ JingleContentInfoProvider jmipAudio = new JingleContentInfoProvider.Audio();
+
+ int eventType;
+ String elementName;
+ String namespace;
+
+ // Get some attributes for the <jingle> element
+ sid = parser.getAttributeValue("", "sid");
+ action = Jingle.Action.fromString(parser.getAttributeValue("", "action"));
+ initiator = parser.getAttributeValue("", "initiator");
+ responder = parser.getAttributeValue("", "responder");
+
+ jingle.setSid(sid);
+ jingle.setAction(action);
+ jingle.setInitiator(initiator);
+ jingle.setResponder(responder);
+
+ // Start processing sub-elements
+ while (!done) {
+ eventType = parser.next();
+ elementName = parser.getName();
+ namespace = parser.getNamespace();
+
+ if (eventType == XmlPullParser.START_TAG) {
+
+ // Parse some well know subelements, depending on the namespaces
+ // and element names...
+
+ if (elementName.equals(JingleContentDescription.NODENAME)
+ && namespace.equals(JingleContentDescription.Audio.NAMESPACE)) {
+ jingle.addDescription((JingleContentDescription) jdpAudio
+ .parseExtension(parser));
+ } else if (elementName.equals(JingleTransport.NODENAME)) {
+
+ // Parse the possible transport namespaces
+ if (namespace.equals(JingleTransport.RawUdp.NAMESPACE)) {
+ jingle.addTransport((JingleTransport) jtpRawUdp
+ .parseExtension(parser));
+ } else if (namespace.equals(JingleTransport.Ice.NAMESPACE)) {
+ jingle.addTransport((JingleTransport) jtpIce
+ .parseExtension(parser));
+ } else {
+ throw new XMPPException("Unknown transport namespace \""
+ + namespace + "\" in Jingle packet.");
+ }
+ } else if (namespace.equals(JingleContentInfo.Audio.NAMESPACE)) {
+ jingle.setContentInfo((JingleContentInfo) jmipAudio
+ .parseExtension(parser));
+ } else {
+ throw new XMPPException("Unknown combination of namespace \""
+ + namespace + "\" and element name \"" + elementName
+ + "\" in Jingle packet.");
+ }
+
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(Jingle.getElementName())) {
+ done = true;
+ }
+ }
+ }
+
+ return jingle;
+ }
+}
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleProviderTest.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleProviderTest.java
new file mode 100644
index 000000000..bbbc0a340
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleProviderTest.java
@@ -0,0 +1,117 @@
+package org.jivesoftware.smackx.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.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.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='http://jabber.org/protocol/jingle'" +
+ " initiator=\"gorrino@viejo.com\"" +
+ " responder=\"colico@hepatico.com\"" +
+ " action=\"transport-info\" sid=\"\">" +
+ " <description xmlns='http://jabber.org/protocol/jingle/content/audio'>" +
+ " <payload-type id=\"34\" name=\"supercodec-34\"/>" +
+ " <payload-type id=\"23\" name=\"supercodec-23\"/>" +
+ " </description>" +
+ " <transport xmlns='http://jabber.org/protocol/jingle/transport/ice'>" +
+ " <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() {
+ StringBuffer buf = new StringBuffer();
+ buf.append(s);
+ return buf.toString();
+ }
+ }
+
+
+ protected int getMaxConnections() {
+ return 2;
+ }
+
+}
+
diff --git a/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleTransportProvider.java b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleTransportProvider.java
new file mode 100644
index 000000000..7af2edba4
--- /dev/null
+++ b/protocols/bundles/org.jivesoftware.smack/src/org/jivesoftware/smackx/provider/JingleTransportProvider.java
@@ -0,0 +1,226 @@
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.nat.TransportCandidate;
+import org.jivesoftware.smackx.packet.JingleTransport;
+import org.jivesoftware.smackx.packet.JingleTransport.JingleTransportCandidate;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Provider for a Jingle transport element
+ *
+ * @author Alvaro Saurin <alvaro.saurin@gmail.com>
+ */
+public abstract class JingleTransportProvider implements PacketExtensionProvider {
+
+ /**
+ * Creates a new provider. ProviderManager requires that every
+ * PacketExtensionProvider has a public, no-argument constructor
+ */
+ public JingleTransportProvider() {
+ super();
+ }
+
+ /**
+ * Obtain the corresponding JingleTransport instance.
+ *
+ * @return a new JingleTransport instance
+ */
+ protected JingleTransport getInstance() {
+ return new JingleTransport();
+ }
+
+ /**
+ * Parse a iq/jingle/transport element.
+ *
+ * @param parser the structure to parse
+ * @return a transport element.
+ * @throws Exception
+ */
+ public PacketExtension parseExtension(final XmlPullParser parser) throws Exception {
+ boolean done = false;
+ JingleTransport trans = getInstance();
+
+ while (!done) {
+ int eventType = parser.next();
+ String name = parser.getName();
+
+ if (eventType == XmlPullParser.START_TAG) {
+ if (name.equals(JingleTransportCandidate.NODENAME)) {
+ trans.addCandidate(parseCandidate(parser));
+ } else {
+ throw new Exception("Unknown tag \"" + name + "\" in transport element.");
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (name.equals(JingleTransport.NODENAME)) {
+ done = true;
+ }
+ }
+ }
+
+ return trans;
+ }
+
+ protected abstract JingleTransportCandidate parseCandidate(final XmlPullParser parser)
+ throws Exception;
+
+ /**
+ * RTP-ICE profile
+ */
+ public static class Ice extends JingleTransportProvider {
+
+ /**
+ * Defauls constructor.
+ */
+ public Ice() {
+ super();
+ }
+
+ /**
+ * Obtain the corresponding JingleTransport.Ice instance.
+ *
+ * @return a new JingleTransport.Ice instance
+ */
+ protected JingleTransport getInstance() {
+ return new JingleTransport.Ice();
+ }
+
+ /**
+ * Parse a iq/jingle/transport/candidate element.
+ *
+ * @param parser the structure to parse
+ * @return a candidate element
+ * @throws Exception
+ */
+ protected JingleTransportCandidate parseCandidate(XmlPullParser parser) throws Exception {
+ TransportCandidate.Ice mt = new TransportCandidate.Ice();
+
+ String channel = parser.getAttributeValue("", "channel");
+ String generation = parser.getAttributeValue("", "generation");
+ String ip = parser.getAttributeValue("", "ip");
+ String name = parser.getAttributeValue("", "name");
+ String network = parser.getAttributeValue("", "network");
+ String username = parser.getAttributeValue("", "username");
+ String password = parser.getAttributeValue("", "password");
+ String port = parser.getAttributeValue("", "port");
+ String preference = parser.getAttributeValue("", "preference");
+ String proto = parser.getAttributeValue("", "proto");
+
+ if (channel != null) {
+ mt.setChannel(new TransportCandidate.Channel(channel));
+ }
+
+ if (generation != null) {
+ try {
+ mt.setGeneration(Integer.parseInt(generation));
+ } catch (Exception e) {
+ }
+ }
+
+ if (ip != null) {
+ mt.setIP(ip);
+ }
+
+ if (name != null) {
+ mt.setName(name);
+ }
+
+ if (network != null) {
+ try {
+ mt.setNetwork(Integer.parseInt(network));
+ } catch (Exception e) {
+ }
+ }
+
+ if (username != null) {
+ mt.setUsername(username);
+ }
+
+ if (password != null) {
+ mt.setPassword(password);
+ }
+
+ if (port != null) {
+ try {
+ mt.setPort(Integer.parseInt(port));
+ } catch (Exception e) {
+ }
+ }
+
+ if (preference != null) {
+ try {
+ mt.setPreference(Integer.parseInt(preference));
+ } catch (Exception e) {
+ }
+ }
+
+ if (proto != null) {
+ mt.setProto(new TransportCandidate.Protocol(proto));
+ }
+
+ return new JingleTransport.Ice.Candidate(mt);
+ }
+ }
+
+ /**
+ * Raw UDP profile
+ */
+ public static class RawUdp extends JingleTransportProvider {
+
+ /**
+ * Defauls constructor.
+ */
+ public RawUdp() {
+ super();
+ }
+
+ /**
+ * Obtain the corresponding JingleTransport.RawUdp instance.
+ *
+ * @return a new JingleTransport.RawUdp instance
+ */
+ protected JingleTransport getInstance() {
+ return new JingleTransport.RawUdp();
+ }
+
+ /**
+ * Parse a iq/jingle/transport/candidate element.
+ *
+ * @param parser the structure to parse
+ * @return a candidate element
+ * @throws Exception
+ */
+ protected JingleTransportCandidate parseCandidate(XmlPullParser parser) throws Exception {
+ TransportCandidate.Fixed mt = new TransportCandidate.Fixed();
+
+ String generation = parser.getAttributeValue("", "generation");
+ String ip = parser.getAttributeValue("", "ip");
+ String name = parser.getAttributeValue("", "name");
+ String port = parser.getAttributeValue("", "port");
+
+ if (generation != null) {
+ try {
+ mt.setGeneration(Integer.parseInt(generation));
+ } catch (Exception e) {
+ }
+ }
+
+ if (ip != null) {
+ mt.setIP(ip);
+ }
+
+ if (name != null) {
+ mt.setName(name);
+ }
+
+ if (port != null) {
+ try {
+ mt.setPort(Integer.parseInt(port));
+ } catch (Exception e) {
+ }
+ }
+ return new JingleTransport.RawUdp.Candidate(mt);
+ }
+ }
+}

Back to the top