From 62f54e12354e208c8931e069fce235c4ca14d205 Mon Sep 17 00:00:00 2001
From: dknn <yoann.blein@free.fr>
Date: Sat, 7 Jul 2012 12:08:42 +0200
Subject: Add raw UDP transport payload: element, serializer, parser and tests


diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index e6f3f60..167f5c1 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -69,6 +69,7 @@
 #include <Swiften/Parser/PayloadParsers/WhiteboardParser.h>
 #include <Swiften/Parser/PayloadParsers/RTPPayloadTypeParser.h>
 #include <Swiften/Parser/PayloadParsers/JingleRTPDescriptionParserFactory.h>
+#include <Swiften/Parser/PayloadParsers/JingleRawUDPTransportPayloadParser.h>
 
 using namespace boost;
 
@@ -132,6 +133,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(boost::make_shared<DeliveryReceiptRequestParserFactory>());
 	factories_.push_back(boost::make_shared<JingleRTPDescriptionParserFactory>(this));
 	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<RTPPayloadTypeParser> >("payload-type"));
+	factories_.push_back(boost::make_shared<GenericPayloadParserFactory<JingleRawUDPTransportPayloadParser> >("transport", "urn:xmpp:jingle:transports:raw-udp:1"));
 
 	foreach(shared_ptr<PayloadParserFactory> factory, factories_) {
 		addFactory(factory.get());
diff --git a/Swiften/Parser/PayloadParsers/JingleRawUDPTransportPayloadParser.cpp b/Swiften/Parser/PayloadParsers/JingleRawUDPTransportPayloadParser.cpp
new file mode 100644
index 0000000..aa6aaf7
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/JingleRawUDPTransportPayloadParser.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2012 Yoann Blein
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Parser/PayloadParsers/JingleRawUDPTransportPayloadParser.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/optional.hpp>
+
+namespace Swift {
+
+JingleRawUDPTransportPayloadParser::JingleRawUDPTransportPayloadParser() : level(0) {
+
+}
+
+void JingleRawUDPTransportPayloadParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) {
+	if (level == 1 && element == "candidate") {
+		JingleRawUDPTransportPayload::Candidate candidate;
+
+		// component
+		try {
+			candidate.component = boost::numeric_cast<boost::uint8_t>(boost::lexical_cast<int>(attributes.getAttributeValue("component").get_value_or("0")));
+		} catch(boost::bad_lexical_cast &) { }
+		// generation
+		try {
+			candidate.generation = boost::numeric_cast<boost::uint8_t>(boost::lexical_cast<int>(attributes.getAttributeValue("generation").get_value_or("0")));
+		} catch(boost::bad_lexical_cast &) { }
+		// id
+		candidate.cid = attributes.getAttributeValue("id").get_value_or("");
+		// ip/port
+		int port = -1;
+		try {
+			port = boost::lexical_cast<int>(attributes.getAttributeValue("port").get_value_or("0"));
+		} catch(boost::bad_lexical_cast &) { }
+		candidate.hostAddressPort = HostAddressPort(HostAddress(attributes.getAttributeValue("ip").get_value_or("")), port);
+		// type
+		candidate.type = stringToType(attributes.getAttributeValue("type").get_value_or(""));
+
+		getPayloadInternal()->addCandidate(candidate);
+	}
+
+	++level;
+}
+
+void JingleRawUDPTransportPayloadParser::handleEndElement(const std::string&, const std::string&) {
+	--level;
+}
+
+void JingleRawUDPTransportPayloadParser::handleCharacterData(const std::string&) {
+
+}
+
+JingleRawUDPTransportPayload::Candidate::Type JingleRawUDPTransportPayloadParser::stringToType(const std::string &str) const {
+	if (str == "host") {
+		return JingleRawUDPTransportPayload::Candidate::Host;
+	} else if (str == "prflx") {
+		return JingleRawUDPTransportPayload::Candidate::PeerReflected;
+	} else if (str == "relay") {
+		return JingleRawUDPTransportPayload::Candidate::Relayed;
+	} else if (str == "srflx") {
+		return JingleRawUDPTransportPayload::Candidate::ServerReflected;
+	} else {
+		return JingleRawUDPTransportPayload::Candidate::Unknown;
+	}
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/JingleRawUDPTransportPayloadParser.h b/Swiften/Parser/PayloadParsers/JingleRawUDPTransportPayloadParser.h
new file mode 100644
index 0000000..859fe88
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/JingleRawUDPTransportPayloadParser.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011 Yoann Blein
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Elements/JingleRawUDPTransportPayload.h>
+#include <Swiften/Parser/GenericPayloadParser.h>
+
+namespace Swift {
+
+class JingleRawUDPTransportPayloadParser : public GenericPayloadParser<JingleRawUDPTransportPayload> {
+	public:
+		JingleRawUDPTransportPayloadParser();
+
+		virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes);
+		virtual void handleEndElement(const std::string& element, const std::string&);
+		virtual void handleCharacterData(const std::string& data);
+
+	private:
+		JingleRawUDPTransportPayload::Candidate::Type stringToType(const std::string &str) const;
+
+	private:
+		int level;
+};
+
+}
diff --git a/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp
index 18f38b2..2dd301f 100644
--- a/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp
+++ b/Swiften/Parser/PayloadParsers/UnitTest/JingleParserTest.cpp
@@ -15,8 +15,8 @@
 #include <Swiften/Elements/StreamInitiationFileInfo.h>
 #include <Swiften/Elements/JingleFileTransferReceived.h>
 #include <Swiften/Elements/JingleFileTransferHash.h>
-//#include <Swiften/Elements/RTPPayloadType.h>
 #include <Swiften/Elements/JingleRTPDescription.h>
+#include <Swiften/Elements/JingleRawUDPTransportPayload.h>
 #include <Swiften/Base/DateTime.h>
 
 #include <Swiften/Base/Log.h>
@@ -47,6 +47,8 @@ class JingleParserTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST(testParse_Xep0167_Example1);
 		CPPUNIT_TEST(testParse_Xep0167_Example48);
 
+		CPPUNIT_TEST(testParse_Xep0177_Example3);
+
 		CPPUNIT_TEST_SUITE_END();
 
 	public:
@@ -819,6 +821,77 @@ class JingleParserTest : public CppUnit::TestFixture {
 			CPPUNIT_ASSERT_EQUAL(std::string("AS"), type);
 			CPPUNIT_ASSERT_EQUAL(std::string("128"), value);
 		}
+
+		// http://xmpp.org/extensions/xep-0177.html#example-3
+		void testParse_Xep0177_Example3() {
+			PayloadsParserTester parser;
+			CPPUNIT_ASSERT(parser.parse(
+			   "<jingle action=\"session-accept\""
+					   " initiator=\"romeo@montague.lit/orchard\""
+					   " responder=\"juliet@capulet.lit/balcony\""
+					   " sid=\"a73sjjvkla37jfea\""
+					   " xmlns=\"urn:xmpp:jingle:1\">"
+				   "<content creator=\"initiator\" name=\"voice\">"
+					   "<description media=\"audio\" xmlns=\"urn:xmpp:jingle:apps:rtp:1\">"
+						   "<payload-type id=\"18\" name=\"G729\"/>"
+					   "</description>"
+					   "<transport xmlns=\"urn:xmpp:jingle:transports:raw-udp:1\">"
+						   "<candidate component=\"1\""
+								   " generation=\"0\""
+								   " id=\"z7sdjb01hf\""
+								   " ip=\"208.68.163.214\""
+								   " port=\"9876\"/>"
+						   "<candidate component=\"2\""
+								   " generation=\"0\""
+								   " id=\"hg92lsn10b\""
+								   " ip=\"208.68.163.214\""
+								   " port=\"9877\"/>"
+					   "</transport>"
+				   "</content>"
+			   "</jingle>"
+			));
+
+			JinglePayload::ref jingle = parser.getPayload<JinglePayload>();
+			CPPUNIT_ASSERT(jingle);
+			CPPUNIT_ASSERT_EQUAL(JinglePayload::SessionAccept, jingle->getAction());
+			CPPUNIT_ASSERT_EQUAL(JID("romeo@montague.lit/orchard"), jingle->getInitiator());
+			CPPUNIT_ASSERT_EQUAL(JID("juliet@capulet.lit/balcony"), jingle->getResponder());
+			CPPUNIT_ASSERT_EQUAL(std::string("a73sjjvkla37jfea"), jingle->getSessionID());
+
+			JingleContentPayload::ref content = jingle->getPayload<JingleContentPayload>();
+			CPPUNIT_ASSERT(content);
+			CPPUNIT_ASSERT_EQUAL(std::string("voice"), content->getName());
+
+			JingleRTPDescription::ref desc = content->getDescription<JingleRTPDescription>();
+			CPPUNIT_ASSERT(desc);
+			CPPUNIT_ASSERT_EQUAL(JingleRTPDescription::Audio, desc->getMedia());
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), desc->getPayloadTypes().size());
+
+			const RTPPayloadType& payloadType = desc->getPayloadTypes()[0];
+			CPPUNIT_ASSERT_EQUAL(static_cast<boost::uint8_t>(18), payloadType.getID());
+			CPPUNIT_ASSERT_EQUAL(std::string("G729"), payloadType.getName());
+
+			JingleRawUDPTransportPayload::ref transport = content->getTransport<JingleRawUDPTransportPayload>();
+			CPPUNIT_ASSERT(transport);
+
+			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), transport->getCandidates().size());
+
+			const JingleRawUDPTransportPayload::Candidate& candidate1 = transport->getCandidates()[0];
+			boost::uint8_t expected = 1;
+			boost::uint8_t actual = candidate1.component;
+
+			CPPUNIT_ASSERT_EQUAL(expected, actual);
+			CPPUNIT_ASSERT_EQUAL(static_cast<boost::uint8_t>(0), candidate1.generation);
+			CPPUNIT_ASSERT_EQUAL(std::string("z7sdjb01hf"), candidate1.cid);
+			CPPUNIT_ASSERT(HostAddressPort(HostAddress("208.68.163.214"), 9876) == candidate1.hostAddressPort);
+
+			const JingleRawUDPTransportPayload::Candidate& candidate2 = transport->getCandidates()[1];
+			CPPUNIT_ASSERT_EQUAL(static_cast<boost::uint8_t>(2), candidate2.component);
+			CPPUNIT_ASSERT_EQUAL(static_cast<boost::uint8_t>(0), candidate2.generation);
+			CPPUNIT_ASSERT_EQUAL(std::string("hg92lsn10b"), candidate2.cid);
+			CPPUNIT_ASSERT(HostAddressPort(HostAddress("208.68.163.214"), 9877) == candidate2.hostAddressPort);
+		}
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(JingleParserTest);
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index 1ce84b9..43d50f0 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -75,6 +75,7 @@ sources = [
 		"PayloadParsers/WhiteboardParser.cpp",
 		"PayloadParsers/RTPPayloadTypeParser.cpp",
 		"PayloadParsers/JingleRTPDescriptionParser.cpp",
+		"PayloadParsers/JingleRawUDPTransportPayloadParser.cpp",
 		"PlatformXMLParserFactory.cpp",
 		"PresenceParser.cpp",
 		"SerializingParser.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index a08cb6e..78c20e5 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -1,4 +1,4 @@
-import os, re, Version, os.path, time
+import os, re, Version, os.path
 
 Import("env")
 
@@ -6,7 +6,7 @@ Import("env")
 # Flags
 ################################################################################
 
-swiften_dep_modules = ["BOOST", "GCONF", "ICU", "LIBIDN", "ZLIB", "OPENSSL", "LIBXML", "EXPAT", "AVAHI", "LIBMINIUPNPC", "LIBNATPMP", "SQLITE", "SQLITE_ASYNC"]
+swiften_dep_modules = ["BOOST", "GCONF", "ICU", "LIBIDN", "ZLIB", "OPENSSL", "LIBXML", "EXPAT", "AVAHI", "LIBMINIUPNPC", "LIBNATPMP", "SQLITE", "SQLITE_ASYNC", "JRTPLIB"]
 external_swiften_dep_modules = ["BOOST"]
 
 if env["SCONS_STAGE"] == "flags" :
@@ -206,6 +206,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Serializer/PayloadSerializers/WhiteboardSerializer.cpp",
 			"Serializer/PayloadSerializers/RTPPayloadTypeSerializer.cpp",			
 			"Serializer/PayloadSerializers/JingleRTPDescriptionSerializer.cpp",
+			"Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.cpp",
 			"Serializer/PresenceSerializer.cpp",
 			"Serializer/StanzaSerializer.cpp",
 			"Serializer/StreamErrorSerializer.cpp",
diff --git a/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.cpp
index c36d574..f2ba1a7 100644
--- a/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/JingleContentPayloadSerializer.cpp
@@ -20,6 +20,7 @@
 
 #include <Swiften/Serializer/PayloadSerializers/JingleIBBTransportPayloadSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/JingleS5BTransportPayloadSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.h>
 
 #include "Swiften/FileTransfer/JingleTransport.h"
 
@@ -58,11 +59,17 @@ std::string JingleContentPayloadSerializer::serializePayload(boost::shared_ptr<J
 		JingleS5BTransportPayloadSerializer s5bSerializer;
 		JingleS5BTransportPayload::ref s5b;
 
+		// JingleRawUDPTransportPayload
+		JingleRawUDPTransportPayloadSerializer rawUdpSerializer;
+		JingleRawUDPTransportPayload::ref rawUDP;
+
 		foreach(JingleTransportPayload::ref transport, payload->getTransports()) {
 			if ((ibb = boost::dynamic_pointer_cast<JingleIBBTransportPayload>(transport))) {
 				payloadXML.addNode(boost::make_shared<XMLRawTextNode>(ibbSerializer.serializePayload(ibb)));
 			} else if ((s5b = boost::dynamic_pointer_cast<JingleS5BTransportPayload>(transport))) {
 				payloadXML.addNode(boost::make_shared<XMLRawTextNode>(s5bSerializer.serializePayload(s5b)));
+			} else if ((rawUDP = boost::dynamic_pointer_cast<JingleRawUDPTransportPayload>(transport))) {
+				payloadXML.addNode(boost::make_shared<XMLRawTextNode>(rawUdpSerializer.serializePayload(rawUDP)));
 			}
 		}
 	}
diff --git a/Swiften/Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.cpp
new file mode 100644
index 0000000..af13003
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2012 Yoann Blein
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.h>
+
+#include <boost/smart_ptr/make_shared.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Serializer/XML/XMLElement.h>
+
+namespace Swift {
+
+JingleRawUDPTransportPayloadSerializer::JingleRawUDPTransportPayloadSerializer() {
+}
+
+std::string JingleRawUDPTransportPayloadSerializer::serializePayload(boost::shared_ptr<JingleRawUDPTransportPayload> payload) const {
+	XMLElement payloadXML("transport", "urn:xmpp:jingle:transports:raw-udp:1");
+
+	foreach (JingleRawUDPTransportPayload::Candidate candidate, payload->getCandidates()) {
+		boost::shared_ptr<XMLElement> candidateXML = boost::make_shared<XMLElement>("candidate");
+		candidateXML->setAttribute("component", boost::lexical_cast<std::string>(static_cast<int>(candidate.component)));
+		candidateXML->setAttribute("generation", boost::lexical_cast<std::string>(static_cast<int>(candidate.generation)));
+		candidateXML->setAttribute("id", candidate.cid);
+		candidateXML->setAttribute("ip", candidate.hostAddressPort.getAddress().toString());
+		candidateXML->setAttribute("port", boost::lexical_cast<std::string>(candidate.hostAddressPort.getPort()));
+		if (candidate.type != JingleRawUDPTransportPayload::Candidate::Unknown)
+			candidateXML->setAttribute("type", typeToString(candidate.type));
+		payloadXML.addNode(candidateXML);
+	}
+
+	return payloadXML.serialize();
+}
+
+std::string JingleRawUDPTransportPayloadSerializer::typeToString(JingleRawUDPTransportPayload::Candidate::Type type) const {
+	switch (type) {
+		case JingleRawUDPTransportPayload::Candidate::Unknown:
+			return "";
+		case JingleRawUDPTransportPayload::Candidate::Host:
+			return "host";
+		case JingleRawUDPTransportPayload::Candidate::PeerReflected:
+			return "prflx";
+		case JingleRawUDPTransportPayload::Candidate::Relayed:
+			return "relay";
+		case JingleRawUDPTransportPayload::Candidate::ServerReflected:
+			return "srflx";
+	}
+	assert(false);
+	return "";
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.h
new file mode 100644
index 0000000..5479d1d
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2012 Yoann Blein
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Serializer/GenericPayloadSerializer.h>
+#include <Swiften/Elements/JingleRawUDPTransportPayload.h>
+
+namespace Swift {
+	class JingleRawUDPTransportPayloadSerializer : public GenericPayloadSerializer<JingleRawUDPTransportPayload> {
+		public:
+			JingleRawUDPTransportPayloadSerializer();
+
+			virtual std::string serializePayload(boost::shared_ptr<JingleRawUDPTransportPayload>)  const;
+
+		private:
+			std::string typeToString(JingleRawUDPTransportPayload::Candidate::Type) const;
+
+	};
+}
diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp
index a89c91c..4a74a24 100644
--- a/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp
+++ b/Swiften/Serializer/PayloadSerializers/UnitTest/JingleSerializersTest.cpp
@@ -16,6 +16,7 @@
 #include <Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h>
 #include <Swiften/Serializer/PayloadSerializers/RTPPayloadTypeSerializer.h>
 #include <Swiften/Serializer/PayloadSerializers/JingleRTPDescriptionSerializer.h>
+#include <Swiften/Serializer/PayloadSerializers/JingleRawUDPTransportPayloadSerializer.h>
 #include <Swiften/Elements/JingleFileTransferDescription.h>
 #include <Swiften/Elements/StreamInitiationFileInfo.h>
 #include <Swiften/Elements/JingleIBBTransportPayload.h>
@@ -25,6 +26,7 @@
 #include <Swiften/Elements/JingleFileTransferReceived.h>
 #include <Swiften/Elements/RTPPayloadType.h>
 #include <Swiften/Elements/JingleRTPDescription.h>
+#include <Swiften/Elements/JingleRawUDPTransportPayload.h>
 #include <Swiften/Base/DateTime.h>
 
 using namespace Swift;
@@ -56,6 +58,10 @@ class JingleSerializersTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST(testSerialize_Xep0167_Example1);
 		CPPUNIT_TEST(testSerialize_Xep0167_Example48);
 
+		CPPUNIT_TEST(testSerialize_RawUDPTransportPayload);
+
+		CPPUNIT_TEST(testSerialize_Xep0177_Example3);
+
 		CPPUNIT_TEST_SUITE_END();
 		
 		 boost::shared_ptr<JinglePayloadSerializer> createTestling() {
@@ -660,6 +666,95 @@ class JingleSerializersTest : public CppUnit::TestFixture {
 			CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload));
 		}
 
+		void testSerialize_RawUDPTransportPayload() {
+			std::string expected =
+				"<transport xmlns=\"urn:xmpp:jingle:transports:raw-udp:1\">"
+					"<candidate component=\"1\""
+						" generation=\"0\""
+						" id=\"a9j3mnbtu1\""
+						" ip=\"10.1.1.104\""
+						" port=\"13540\""
+						" type=\"srflx\"/>"
+				"</transport>";
+
+			JingleRawUDPTransportPayload::ref transport = boost::make_shared<JingleRawUDPTransportPayload>();
+
+			JingleRawUDPTransportPayload::Candidate candidate;
+			candidate.component = 1;
+			candidate.generation = 0;
+			candidate.cid = "a9j3mnbtu1";
+			candidate.hostAddressPort = HostAddressPort(HostAddress("10.1.1.104"), 13540);
+			candidate.type = JingleRawUDPTransportPayload::Candidate::ServerReflected;
+			transport->addCandidate(candidate);
+
+			boost::shared_ptr<JingleRawUDPTransportPayloadSerializer> serializer = boost::make_shared<JingleRawUDPTransportPayloadSerializer>();
+			CPPUNIT_ASSERT_EQUAL(expected, serializer->serialize(transport));
+		}
+
+		// http://xmpp.org/extensions/xep-0177.html#example-3
+		void testSerialize_Xep0177_Example3() {
+			std::string expected =
+				"<jingle action=\"session-accept\""
+						" initiator=\"romeo@montague.lit/orchard\""
+						" responder=\"juliet@capulet.lit/balcony\""
+						" sid=\"a73sjjvkla37jfea\""
+						" xmlns=\"urn:xmpp:jingle:1\">"
+					"<content creator=\"initiator\" name=\"voice\">"
+						"<description media=\"audio\" xmlns=\"urn:xmpp:jingle:apps:rtp:1\">"
+							"<payload-type id=\"18\" name=\"G729\"/>"
+						"</description>"
+						"<transport xmlns=\"urn:xmpp:jingle:transports:raw-udp:1\">"
+							"<candidate component=\"1\""
+									" generation=\"0\""
+									" id=\"z7sdjb01hf\""
+									" ip=\"208.68.163.214\""
+									" port=\"9876\"/>"
+							"<candidate component=\"2\""
+									" generation=\"0\""
+									" id=\"hg92lsn10b\""
+									" ip=\"208.68.163.214\""
+									" port=\"9877\"/>"
+						"</transport>"
+					"</content>"
+				"</jingle>";
+
+			JinglePayload::ref payload = boost::make_shared<JinglePayload>();
+			payload->setAction(JinglePayload::SessionAccept);
+			payload->setInitiator(JID("romeo@montague.lit/orchard"));
+			payload->setResponder(JID("juliet@capulet.lit/balcony"));
+			payload->setSessionID("a73sjjvkla37jfea");
+
+			JingleRTPDescription::ref desc = boost::make_shared<JingleRTPDescription>(JingleRTPDescription::Audio);
+			RTPPayloadType payloadType(18, "G729");
+			desc->addPayloadType(payloadType);
+
+			JingleRawUDPTransportPayload::ref transport = boost::make_shared<JingleRawUDPTransportPayload>();
+
+			JingleRawUDPTransportPayload::Candidate candidate1;
+			candidate1.component = 1;
+			candidate1.generation = 0;
+			candidate1.cid = "z7sdjb01hf";
+			candidate1.hostAddressPort = HostAddressPort(HostAddress("208.68.163.214"), 9876);
+			transport->addCandidate(candidate1);
+
+			JingleRawUDPTransportPayload::Candidate candidate2;
+			candidate2.component = 2;
+			candidate2.generation = 0;
+			candidate2.cid = "hg92lsn10b";
+			candidate2.hostAddressPort = HostAddressPort(HostAddress("208.68.163.214"), 9877);
+			transport->addCandidate(candidate2);
+
+			JingleContentPayload::ref content = boost::make_shared<JingleContentPayload>();
+			content->setCreator(JingleContentPayload::InitiatorCreator);
+			content->setName("voice");
+			content->addDescription(desc);
+			content->addTransport(transport);
+
+			payload->addPayload(content);
+
+			CPPUNIT_ASSERT_EQUAL(expected, createTestling()->serialize(payload));
+		}
+
 	private:
 		FullPayloadSerializerCollection collection;
 };
-- 
cgit v0.10.2-6-g49f6