Implemented old jingle protocol support.

git-svn-id: http://voip.null.ro/svn/yate@2666 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
marian 2009-05-31 15:22:08 +00:00
parent 7c21fa4f63
commit 283dd92294
8 changed files with 732 additions and 80 deletions

View File

@ -3,6 +3,12 @@
[general]
; Global settings of Jabber and Jingle engines
; jingleversion: integer: Jingle session version to use for outgoing calls
; Defaults to the newest version if missing or invalid
; Allowed values are 0 or 1
; This parameter can be overridden when routing by an 'ojingleversion' parameter
;jingleversion=
; secure_rtp: boolean: Offer SRTP when negotiating Jingle sessions
; Defaults to disable
;secure_rtp=disable
@ -152,7 +158,7 @@
default=false
; mulaw: bool: Companded-only G711 mu-law (PCMU/8000)
mulaw=default
mulaw=true
; alaw: bool: Companded-only G711 a-law (PCMU/8000)
alaw=true

View File

@ -84,17 +84,6 @@ inline bool checkValidXmlns(XMLElement* e, XMPPNamespace::Type ns,
return false;
}
// Fix some element name conflicts: change type according to their namespace
// Some elements may have the same name in different namespaces
inline void fixXmlType(XMLElement* xml)
{
if (!xml)
return;
if (xml->type() == XMLElement::Session &&
xml->hasAttribute("xmlns",s_ns[XMPPNamespace::Jingle]))
xml->changeType(XMLElement::Jingle);
}
#define DROP_AND_EXIT { dropXML(xml); return; }
#define INVALIDXML_AND_EXIT(code,reason) { invalidStreamXML(xml,code,reason); return; }
#define ERRORXML_AND_EXIT { errorStreamXML(xml); return; }
@ -1352,8 +1341,6 @@ JBEvent* JBStream::getIqEvent(XMLElement* xml, int iqType, XMPPError::Type& erro
}
error = XMPPError::NoError;
XMLElement* child = xml->findFirstChild();
if (child)
fixXmlType(child);
// Request (type is set or get): check the child (MUST exists)
// Result: check it only if it has a child
@ -1369,6 +1356,10 @@ JBEvent* JBStream::getIqEvent(XMLElement* xml, int iqType, XMPPError::Type& erro
if (checkValidXmlns(child,XMPPNamespace::Jingle,error))
IQEVENT_SET_REQ(JBEvent::IqJingleGet,JBEvent::IqJingleSet);
break;
case XMLElement::Session:
if (checkValidXmlns(child,XMPPNamespace::JingleSession,error))
IQEVENT_SET_REQ(JBEvent::IqJingleGet,JBEvent::IqJingleSet);
break;
case XMLElement::Query:
if (XMPPUtils::hasXmlns(*child,XMPPNamespace::DiscoInfo))
IQEVENT_SET_REQ(JBEvent::IqDiscoInfoGet,JBEvent::IqDiscoInfoSet);
@ -1394,6 +1385,10 @@ JBEvent* JBStream::getIqEvent(XMLElement* xml, int iqType, XMPPError::Type& erro
if (XMPPUtils::hasXmlns(*child,XMPPNamespace::Jingle))
IQEVENT_SET_RSP(JBEvent::IqJingleRes,JBEvent::IqJingleErr);
break;
case XMLElement::Session:
if (XMPPUtils::hasXmlns(*child,XMPPNamespace::JingleSession))
IQEVENT_SET_RSP(JBEvent::IqJingleRes,JBEvent::IqJingleErr);
break;
case XMLElement::Query:
if (XMPPUtils::hasXmlns(*child,XMPPNamespace::DiscoInfo))
IQEVENT_SET_RSP(JBEvent::IqDiscoInfoRes,JBEvent::IqDiscoInfoErr);

View File

@ -93,7 +93,7 @@ void JGEngine::initialize(const NamedList& params)
}
// Make an outgoing call
JGSession* JGEngine::call(const String& localJID, const String& remoteJID,
JGSession* JGEngine::call(JGSession::Version ver, const String& localJID, const String& remoteJID,
const ObjList& contents, XMLElement* extra, const char* message,
const char* subject)
{
@ -113,10 +113,19 @@ JGSession* JGEngine::call(const String& localJID, const String& remoteJID,
// Create outgoing session
bool hasStream = (0 != stream);
if (hasStream) {
JGSession* session = new JGSession1(this,stream,localJID,remoteJID,
contents,extra,message,subject);
JGSession* session = 0;
switch (ver) {
case JGSession::Version1:
session = new JGSession1(this,stream,localJID,remoteJID,message);
break;
case JGSession::Version0:
session = new JGSession0(this,stream,localJID,remoteJID,message);
break;
case JGSession::VersionUnknown:
;
}
TelEngine::destruct(stream);
if (session->state() != JGSession::Destroy) {
if (session && session->initiate(contents,extra,subject)) {
Lock lock(this);
m_sessions.append(session);
return (session && session->ref()) ? session : 0;
@ -200,6 +209,10 @@ bool JGEngine::accept(JBEvent* event, bool& processed, bool& insert)
ver = JGSession::Version1;
sid = child->getAttribute("sid");
}
else if (XMPPUtils::hasXmlns(*child,XMPPNamespace::JingleSession)) {
ver = JGSession::Version0;
sid = child->getAttribute("id");
}
DDebug(this,DebugAll,"Accepting event=%s child=%s sid=%s version=%d",
event->name(),child->name(),sid.c_str(),ver);
if (sid.null()) {
@ -229,6 +242,9 @@ bool JGEngine::accept(JBEvent* event, bool& processed, bool& insert)
case JGSession::Version1:
m_sessions.append(new JGSession1(this,event,sid));
break;
case JGSession::Version0:
m_sessions.append(new JGSession0(this,event,sid));
break;
default:
Debug(this,DebugStub,
"JGEngine::accept(): unhandled session version %d",ver);
@ -336,7 +352,17 @@ void JGEvent::init(JGSession* session)
m_session = session;
if (m_element) {
m_id = m_element->getAttribute("id");
m_jingle = m_element->findFirstChild(XMLElement::Jingle);
if (m_session)
switch (m_session->version()) {
case JGSession::Version1:
m_jingle = m_element->findFirstChild(XMLElement::Jingle);
break;
case JGSession::Version0:
m_jingle = m_element->findFirstChild(XMLElement::Session);
break;
case JGSession::VersionUnknown:
;
}
}
}

View File

@ -41,15 +41,82 @@ static void addJingleContents(XMLElement* xml, const ObjList& contents, bool min
TelEngine::destruct(jingle);
}
// Utility: add session content(s) to an already created stanza's jingle child
// This method is used by the version 0 of the session
static void addJingleContents0(String& name, XMLElement* xml, const ObjList& contents, bool minimal,
bool addDesc, bool addTrans)
{
if (!xml)
return;
XMLElement* jingle = xml->findFirstChild(XMLElement::Session);
if (!jingle)
return;
for (ObjList* o = contents.skipNull(); o; o = o->skipNext()) {
JGSessionContent* c = static_cast<JGSessionContent*>(o->get());
if (c->type() != JGSessionContent::RtpIceUdp)
continue;
name = c->toString();
if (addDesc) {
XMLElement* desc = XMPPUtils::createElement(XMLElement::Description,
XMPPNamespace::JingleAudio);
for (ObjList* o = c->m_rtpMedia.skipNull(); o; o = o->skipNext()) {
JGRtpMedia* a = static_cast<JGRtpMedia*>(o->get());
desc->addChild(a->toXML());
}
JGRtpMedia* te = new JGRtpMedia("106","telephone-event","8000","","");
desc->addChild(te->toXML());
TelEngine::destruct(te);
jingle->addChild(desc);
}
if (addTrans) {
XMLElement* trans = XMPPUtils::createElement(XMLElement::Transport,
XMPPNamespace::JingleTransport);
if (!minimal) {
for (ObjList* o = c->m_rtpLocalCandidates.skipNull(); o; o = o->skipNext()) {
JGRtpCandidate* rc = static_cast<JGRtpCandidate*>(o->get());
XMLElement* xml = new XMLElement(XMLElement::Candidate);
xml->setAttribute("name","rtp");
xml->setAttributeValid("generation",rc->m_generation);
xml->setAttributeValid("address",rc->m_address);
xml->setAttributeValid("port",rc->m_port);
xml->setAttributeValid("network","0");
xml->setAttributeValid("protocol",rc->m_protocol);
xml->setAttribute("username",c->m_rtpLocalCandidates.m_ufrag);
xml->setAttribute("password",c->m_rtpLocalCandidates.m_password);
xml->setAttributeValid("type","local");
xml->setAttributeValid("preference","1");
trans->addChild(xml);
}
}
jingle->addChild(trans);
}
}
TelEngine::destruct(jingle);
}
// Utility: add xml element child to an already created stanza's jingle child
static void addJingleChild(XMLElement* xml, XMLElement* child)
{
if (!(xml && child))
return;
XMLElement* jingle = xml->findFirstChild(XMLElement::Jingle);
if (!jingle)
if (jingle)
jingle->addChild(child);
else
TelEngine::destruct(child);
TelEngine::destruct(jingle);
}
// Utility: add xml element child to an already created stanza's jingle child
static void addJingleChild0(XMLElement* xml, XMLElement* child)
{
if (!(xml && child))
return;
jingle->addChild(child);
XMLElement* jingle = xml->findFirstChild(XMLElement::Session);
if (jingle)
jingle->addChild(child);
else
TelEngine::destruct(child);
TelEngine::destruct(jingle);
}
@ -338,7 +405,7 @@ JGRtpCandidate* JGRtpCandidates::findByComponent(unsigned int component)
return 0;
}
// Generate a random password to be used with ICE-UDP transport
// Generate a random password or username to be used with ICE-UDP transport
// Maximum number of characters. The maxmimum value is 256.
// The minimum value is 22 for password and 4 for username
void JGRtpCandidates::generateIceToken(String& dest, bool pwd, unsigned int max)
@ -357,6 +424,15 @@ void JGRtpCandidates::generateIceToken(String& dest, bool pwd, unsigned int max)
dest = dest.substr(0,max);
}
// Generate a random password or username to be used with old ICE-UDP transport
void JGRtpCandidates::generateOldIceToken(String& dest)
{
dest = "";
while (dest.length() < 16)
dest << (int)random();
dest = dest.substr(0,16);
}
/**
* JGSessionContent
@ -610,6 +686,12 @@ XMLElement* JGStreamHost::buildRsp(const char* jid)
* JGSession
*/
TokenDict JGSession::s_versions[] = {
{"0", Version0},
{"1", Version1},
{0,0}
};
TokenDict JGSession::s_states[] = {
{"Idle", Idle},
{"Pending", Pending},
@ -634,6 +716,20 @@ TokenDict JGSession::s_reasons[] = {
{0,0}
};
TokenDict JGSession::s_actions0[] = {
{"accept", ActAccept},
{"initiate", ActInitiate},
{"terminate", ActTerminate},
{"info", ActInfo},
{"transport-info", ActTransportInfo},
{"transport-accept", ActTransportAccept},
{"content-info", ActContentInfo},
{"DTMF", ActDtmf},
{"ringing", ActRinging},
{"mute", ActMute},
{0,0}
};
TokenDict JGSession::s_actions1[] = {
{"session-accept", ActAccept},
{"session-initiate", ActInitiate},
@ -821,7 +917,7 @@ bool JGSession::sendDtmf(const char* dtmf, unsigned int msDuration, String* stan
if (!(dtmf && *dtmf))
return false;
XMLElement* iq = createJingle(ActInfo);
XMLElement* iq = createJingle(version() != Version0 ? ActInfo : ActContentInfo);
XMLElement* sess = iq->findFirstChild();
if (!sess) {
TelEngine::destruct(iq);
@ -1347,7 +1443,9 @@ const char* JGSession::lookupAction(int act, Version ver)
switch (ver) {
case Version1:
return lookup(act,s_actions1);
default:
case Version0:
return lookup(act,s_actions0);
case VersionUnknown:
;
}
return 0;
@ -1359,33 +1457,302 @@ JGSession::Action JGSession::lookupAction(const char* str, Version ver)
switch (ver) {
case Version1:
return (Action)lookup(str,s_actions1,ActCount);
default:
case Version0:
return (Action)lookup(str,s_actions0,ActCount);
case VersionUnknown:
;
}
return ActCount;
}
/**
* JGSession0
*/
// Create an outgoing session
JGSession0::JGSession0(JGEngine* engine, JBStream* stream,
const String& callerJID, const String& calledJID, const char* msg)
: JGSession(Version0,engine,stream,callerJID,calledJID,msg)
{
}
// Create an incoming session
JGSession0::JGSession0(JGEngine* engine, JBEvent* event, const String& id)
: JGSession(Version0,engine,event,id)
{
m_sessContentName = m_localSid + "_content";
}
// Destructor
JGSession0::~JGSession0()
{
}
// Check if a given XML element is valid jingle one
XMLElement* JGSession0::checkJingle(XMLElement* xml)
{
if (xml && xml->type() == XMLElement::Session &&
XMPPUtils::hasXmlns(*xml,XMPPNamespace::JingleSession))
return xml;
return 0;
}
// Accept a Pending incoming session
bool JGSession0::accept(const ObjList& contents, String* stanzaId)
{
Lock lock(this);
if (outgoing() || state() != Pending)
return false;
XMLElement* xml = createJingle(ActAccept);
addJingleContents0(m_sessContentName,xml,contents,true,true,true);
if (!sendStanza(xml,stanzaId))
return false;
changeState(Active);
return true;
}
// Send a stanza with session content(s)
bool JGSession0::sendContent(Action action, const ObjList& contents, String* stanzaId)
{
Lock lock(this);
if (state() != Pending && state() != Active)
return false;
bool minimal = false;
bool addDesc = true;
bool addTrans = true;
switch (action) {
case ActTransportInfo:
addDesc = false;
break;
case ActTransportAccept:
minimal = true;
addDesc = false;
addTrans = true;
break;
default:
return false;
};
// Make sure we dont't terminate the session on failure
String tmp;
if (!stanzaId) {
tmp = "Content" + String(Time::secNow());
stanzaId = &tmp;
}
XMLElement* xml = createJingle(action);
addJingleContents0(m_sessContentName,xml,contents,minimal,addDesc,addTrans);
return sendStanza(xml,stanzaId);
}
// Build and send the initial message on an outgoing session
bool JGSession0::initiate(const ObjList& contents, XMLElement* extra, const char* subject)
{
XMLElement* xml = createJingle(ActInitiate);
addJingleContents0(m_sessContentName,xml,contents,true,true,true);
addJingleChild0(xml,extra);
if (!null(subject))
addJingleChild0(xml,new XMLElement(XMLElement::Subject,0,subject));
if (sendStanza(xml)) {
changeState(Pending);
return true;
}
changeState(Destroy);
return false;
}
// Decode a valid jingle set event. Set the event's data on success
JGEvent* JGSession0::decodeJingle(JBEvent* jbev)
{
XMLElement* jingle = jbev->child();
if (!jingle) {
confirm(jbev->releaseXML(),XMPPError::SBadRequest);
return 0;
}
Action act = getAction(jingle);
if (act == ActCount) {
confirm(jbev->releaseXML(),XMPPError::SServiceUnavailable,
"Unknown session action");
return 0;
}
// *** ActTerminate
if (act == ActTerminate) {
// Confirm here: this is a final event,
// stanza won't be confirmed in getEvent()
m_recvTerminate = true;
const char* reason = 0;
const char* text = 0;
XMLElement* res = jingle->findFirstChild(XMLElement::Reason);
if (res) {
XMLElement* tmp = res->findFirstChild();
if (tmp && tmp->type() != XMLElement::Text)
reason = tmp->name();
TelEngine::destruct(tmp);
tmp = res->findFirstChild(XMLElement::Text);
if (tmp)
text = tmp->getText();
TelEngine::destruct(tmp);
TelEngine::destruct(res);
}
JGEvent* ev = new JGEvent(JGEvent::Terminated,this,jbev->releaseXML(),reason,text);
ev->setAction(act);
ev->confirmElement();
return ev;
}
// *** ActContentInfo --> ActDtmf
if (act == ActContentInfo) {
// Check dtmf
// Expect more then 1 'dtmf' child
XMLElement* tmp = jingle->findFirstChild(XMLElement::Dtmf);
String text;
for (; tmp; tmp = jingle->findNextChild(tmp,XMLElement::Dtmf)) {
String reason = tmp->getAttribute("action");
if (reason == "button-up")
text << tmp->getAttribute("code");
}
if (text)
return new JGEvent(ActDtmf,this,jbev->releaseXML(),0,text);
confirm(jbev->releaseXML(),XMPPError::SServiceUnavailable);
return 0;
}
// *** ActInfo
if (act == ActInfo) {
// Check info element
// Return ActInfo event to signal ping (XEP-0166 6.8)
XMLElement* child = jingle->findFirstChild();
if (!child)
return new JGEvent(ActInfo,this,jbev->releaseXML());
JGEvent* event = 0;
Action a = ActCount;
XMPPNamespace::Type ns = XMPPNamespace::Count;
// Check namespace and build event
switch (child->type()) {
case XMLElement::Ringing:
a = ActRinging;
ns = XMPPNamespace::JingleRtpInfoOld;
break;
case XMLElement::Mute:
a = ActMute;
ns = XMPPNamespace::JingleRtpInfoOld;
break;
default: ;
}
if (a != ActCount && XMPPUtils::hasXmlns(*child,ns))
event = new JGEvent(a,this,jbev->releaseXML());
else
confirm(jbev->releaseXML(),XMPPError::SFeatureNotImpl);
TelEngine::destruct(child);
return event;
}
if (act == ActTransportAccept) {
confirm(jbev->element());
return 0;
}
// Get transport
// Get media description
// Create event, update transport and media
XMLElement* media = 0;
XMLElement* trans = jingle;
JGSessionContent* c = 0;
JGEvent* event = 0;
while (true) {
c = new JGSessionContent(JGSessionContent::RtpIceUdp,m_sessContentName,
JGSessionContent::SendBoth,JGSessionContent::CreatorInitiator);
c->m_rtpRemoteCandidates.m_type = JGRtpCandidates::RtpIceUdp;
// Build media
if (act == ActInitiate || act == ActAccept) {
media = jingle->findFirstChild(XMLElement::Description);
if (media && XMPPUtils::hasXmlns(*media,XMPPNamespace::JingleAudio)) {
c->m_rtpMedia.fromXML(media);
c->m_rtpMedia.m_media = JGRtpMediaList::Audio;
}
else
break;
}
// Build transport
trans = trans->findFirstChild(XMLElement::Transport);
if (trans && !XMPPUtils::hasXmlns(*trans,XMPPNamespace::JingleTransport)) {
if (trans != jingle)
TelEngine::destruct(trans);
else
trans = 0;
}
XMLElement* t = trans ? trans->findFirstChild(XMLElement::Candidate) : 0;
if (t) {
JGRtpCandidate* cd = new JGRtpCandidate(m_localSid + "_transport");
cd->m_component = "1";
cd->m_generation = t->getAttribute("generation");
cd->m_address = t->getAttribute("address");
cd->m_port = t->getAttribute("port");
cd->m_protocol = t->getAttribute("protocol");;
cd->m_generation = t->getAttribute("generation");
cd->m_type = t->getAttribute("type");
c->m_rtpRemoteCandidates.m_ufrag = t->getAttribute("username");
c->m_rtpRemoteCandidates.m_password = t->getAttribute("password");
c->m_rtpRemoteCandidates.append(cd);
TelEngine::destruct(t);
}
else if (act == ActTransportInfo)
break;
// Don't set the event's element yet: this would invalidate the 'jingle' variable
event = new JGEvent(act,this,jbev->releaseXML());
event->m_contents.append(c);
break;
}
if (trans != jingle)
TelEngine::destruct(trans);
TelEngine::destruct(media);
if (!event) {
TelEngine::destruct(c);
confirm(jbev->releaseXML(),XMPPError::SServiceUnavailable);
}
return event;
}
// Create an 'iq' stanza with a 'jingle' child
XMLElement* JGSession0::createJingle(Action action, XMLElement* element1,
XMLElement* element2, XMLElement* element3)
{
XMLElement* iq = XMPPUtils::createIq(XMPPUtils::IqSet,m_localJID,m_remoteJID,0);
XMLElement* jingle = XMPPUtils::createElement(XMLElement::Session,
XMPPNamespace::JingleSession);
if (action < ActCount)
jingle->setAttribute("type",lookupAction(action,version()));
jingle->setAttribute("initiator",outgoing() ? m_localJID : m_remoteJID);
jingle->setAttribute("responder",outgoing() ? m_remoteJID : m_localJID);
jingle->setAttribute("id",m_sid);
jingle->addChild(element1);
jingle->addChild(element2);
jingle->addChild(element3);
iq->addChild(jingle);
return iq;
}
// Create a dtmf XML element
XMLElement* JGSession0::createDtmf(const char* dtmf, unsigned int msDuration)
{
XMLElement* xml = XMPPUtils::createElement(XMLElement::Dtmf,XMPPNamespace::DtmfOld);
xml->setAttribute("action","button-up");
xml->setAttribute("code",dtmf);
return xml;
}
/**
* JGSession1
*/
// Create an outgoing session
JGSession1::JGSession1(JGEngine* engine, JBStream* stream,
const String& callerJID, const String& calledJID,
const ObjList& contents, XMLElement* extra, const char* msg,
const char* subject)
const String& callerJID, const String& calledJID, const char* msg)
: JGSession(Version1,engine,stream,callerJID,calledJID,msg)
{
XMLElement* xml = createJingle(ActInitiate);
addJingleContents(xml,contents,false,true,true,true);
addJingleChild(xml,extra);
if (!null(subject))
addJingleChild(xml,new XMLElement(XMLElement::Subject,0,subject));
if (sendStanza(xml))
changeState(Pending);
else
changeState(Destroy);
}
// Create an incoming session
@ -1399,6 +1766,22 @@ JGSession1::~JGSession1()
{
}
// Build and send the initial message on an outgoing session
bool JGSession1::initiate(const ObjList& contents, XMLElement* extra, const char* subject)
{
XMLElement* xml = createJingle(ActInitiate);
addJingleContents(xml,contents,false,true,true,true);
addJingleChild(xml,extra);
if (!null(subject))
addJingleChild(xml,new XMLElement(XMLElement::Subject,0,subject));
if (sendStanza(xml)) {
changeState(Pending);
return true;
}
changeState(Destroy);
return false;
}
// Check if a given XML element is valid jingle one
XMLElement* JGSession1::checkJingle(XMLElement* xml)
{

View File

@ -88,6 +88,11 @@ TokenDict XMPPNamespace::s_value[] = {
{"urn:xmpp:jingle:transports:bytestreams:0", JingleTransportByteStreams},
{"urn:xmpp:jingle:transfer:0", JingleTransfer},
{"urn:xmpp:jingle:dtmf:0", Dtmf},
{"http://www.google.com/session", JingleSession},
{"http://www.google.com/session/phone", JingleAudio},
{"http://www.google.com/transport/p2p", JingleTransport},
{"urn:xmpp:jingle:apps:rtp:info", JingleRtpInfoOld},
{"http://jabber.org/protocol/jingle/info/dtmf", DtmfOld},
{"http://jabber.org/protocol/commands", Command},
{"http://www.google.com/xmpp/protocol/voice/v1", CapVoiceV1},
{0,0}

View File

@ -212,6 +212,11 @@ public:
JingleTransportByteStreams, // urn:xmpp:jingle:transports:bytestreams:0
JingleTransfer, // urn:xmpp:jingle:transfer:0
Dtmf, // urn:xmpp:jingle:dtmf:0
JingleSession, // http://www.google.com/session
JingleAudio, // http://www.google.com/session/phone
JingleTransport, // http://www.google.com/transport/p2p
JingleRtpInfoOld, // urn:xmpp:jingle:apps:rtp:info
DtmfOld, // http://jabber.org/protocol/jingle/info/dtmf
Command, // http://jabber.org/protocol/command
CapVoiceV1, // http://www.google.com/xmpp/protocol/voice/v1
Count,

View File

@ -40,6 +40,7 @@ class JGRtpCandidates; // A list of RTP transport candidates
class JGSessionContent; // A Jingle session content
class JGStreamHost; // A Jingle file transfer stream host
class JGSession; // A basic Jingle session
class JGSession0; // A session implementing the old jingle protocol
class JGSession1; // The version 1 of a jingle session
class JGEvent; // An event generated by a Jingle session
class JGEngine; // The Jingle engine
@ -215,7 +216,7 @@ public:
* @param cryptoMandatory True to require media encryption
*/
inline JGRtpMediaList(Media m = MediaMissing, bool cryptoMandatory = false)
: m_media(m), m_cryptoMandatory(cryptoMandatory)
: m_media(m), m_cryptoMandatory(cryptoMandatory), m_ready(false)
{}
/**
@ -289,6 +290,11 @@ public:
bool m_cryptoMandatory;
ObjList m_cryptoLocal;
ObjList m_cryptoRemote;
/**
* Flag indicating wether media was negotiated
*/
bool m_ready;
};
@ -338,7 +344,7 @@ public:
String m_network; // NIC card (diagnostic only)
String m_priority; // Candidate priority
String m_protocol; // The only allowable value is "udp"
String m_type; // A Candidate Type as defined in ICE-CORE.
String m_type; // A Candidate Type as defined in ICE-CORE
};
@ -381,6 +387,14 @@ public:
generateIceToken(m_ufrag,false);
}
/**
* Fill password and ufrag data using old transport restrictions (16 bytes length)
*/
inline void generateOldIceAuth() {
generateOldIceToken(m_password);
generateOldIceToken(m_ufrag);
}
/**
* Find a candidate by its component value
* @param component The value to search
@ -403,7 +417,7 @@ public:
void fromXML(XMLElement* element);
/**
* Generate a random password to be used with ICE-UDP transport
* Generate a random password or username to be used with ICE-UDP transport
* @param dest Destination string
* @param pwd True to generate a password, false to generate an username (ufrag)
* @param max Maximum number of characters. The maxmimum value is 256.
@ -411,6 +425,12 @@ public:
*/
static void generateIceToken(String& dest, bool pwd, unsigned int max = 0);
/**
* Generate a random password or username to be used with old ICE-UDP transport
* @param dest Destination string
*/
static void generateOldIceToken(String& dest);
/**
* Get the name associated with a list's type
* @param t The desired type
@ -676,8 +696,9 @@ public:
* Jingle session version
*/
enum Version {
Version0 = 0,
Version1 = 1,
VersionUnknown,
VersionUnknown
};
/**
@ -726,6 +747,7 @@ public:
ActContentModify, // content-modify
ActContentReject, // content-reject
ActContentRemove, // content-remove
ActContentInfo, // content-info
ActTransfer, // session-info: Transfer
ActRinging, // session-info: Ringing
ActTrying, // session-info: Trying
@ -981,6 +1003,24 @@ public:
static XMLElement* buildTransfer(const String& transferTo, const String& transferFrom,
const String& sid = String::empty());
/**
* Get the session version associated with a text
* @param value The version text
* @param def Default value to return if not found
* @return Session Version value
*/
static inline Version lookupVersion(const char* value, Version def = VersionUnknown)
{ return (Version)lookup(value,s_versions,def); }
/**
* Get the session version name
* @param value The version value
* @param def Default value to return if not found
* @return Session version name or the default value if not found
*/
static inline const char* lookupVersion(int value, const char* def = "unknown")
{ return lookup(value,s_versions,def); }
/**
* Get the termination code associated with a text
* @param value The termination text
@ -1023,6 +1063,11 @@ public:
*/
static Action lookupAction(const char* str, Version ver);
/**
* Session version names
*/
static TokenDict s_versions[];
/**
* Termination reasons
*/
@ -1033,6 +1078,11 @@ public:
*/
static TokenDict s_states[];
/**
* Action names for version Version0
*/
static TokenDict s_actions0[];
/**
* Action names for version Version1
*/
@ -1061,6 +1111,16 @@ protected:
*/
JGSession(Version ver, JGEngine* engine, JBEvent* event, const String& id);
/**
* Build and send the initial message on an outgoing session
* @param contents The session contents to be sent with session initiate element
* @param extra Optional extra child to be added to the session initiate element
* @param subject Optional session subject
* @return True on success
*/
virtual bool initiate(const ObjList& contents, XMLElement* extra,
const char* subject = 0) = 0;
/**
* Get a Jingle event from the queue.
* This method is thread safe
@ -1184,6 +1244,107 @@ private:
};
/**
* A session implementing the old jingle protocol
* @short The version 0 of a jingle session
*/
class YJINGLE_API JGSession0 : public JGSession
{
friend class JGEvent;
friend class JGEngine;
public:
/**
* Destructor
*/
virtual ~JGSession0();
/**
* Check if a given XML element is valid jingle one
* @param xml Element to check
* @return The given element if it's a valid jingle element, 0 otherwise
*/
virtual XMLElement* checkJingle(XMLElement* xml);
/**
* Accept a Pending incoming session.
* This method is thread safe
* @param contents The list of accepted contents
* @param stanzaId Optional string to be filled with sent stanza id (used to track the response)
* @return False if send failed
*/
virtual bool accept(const ObjList& contents, String* stanzaId = 0);
protected:
/**
* Constructor. Create an outgoing session
* @param engine The engine that owns this session
* @param stream The stream this session is bound to
* @param callerJID The caller's full JID
* @param calledJID The called party's full JID
* @param msg Optional message to be sent before session initiate
*/
JGSession0(JGEngine* engine, JBStream* stream,
const String& callerJID, const String& calledJID, const char* msg = 0);
/**
* Constructor. Create an incoming session.
* @param engine The engine that owns this session
* @param event A valid Jabber Jingle event with action session initiate
* @param id Session id
*/
JGSession0(JGEngine* engine, JBEvent* event, const String& id);
/**
* Build and send the initial message on an outgoing session
* @param contents The session contents to be sent with session initiate element
* @param extra Optional extra child to be added to the session initiate element
* @param subject Optional session subject
* @return True on success
*/
virtual bool initiate(const ObjList& contents, XMLElement* extra,
const char* subject = 0);
/**
* Send a stanza with session content(s)
* This method is thread safe
* @param action Must be a transport- action
* @param contents Non empty list with content(s) to send
* @param stanzaId Optional string to be filled with sent
* stanza id (used to track the response)
* @return False if send failed
*/
virtual bool sendContent(Action action, const ObjList& contents, String* stanzaId = 0);
/**
* Decode a valid jingle set event. Set the event's data on success
* @param jbev The event to decode
* @return JGEvent pointer or 0
*/
virtual JGEvent* decodeJingle(JBEvent* jbev);
/**
* Create an 'iq' of type 'set' with a 'jingle' child
* @param action The action of the Jingle stanza
* @param element1 Optional child element
* @param element2 Optional child element
* @param element3 Optional child element
* @return Valid XMLElement pointer
*/
virtual XMLElement* createJingle(Action action, XMLElement* element1 = 0,
XMLElement* element2 = 0, XMLElement* element3 = 0);
/**
* Create a dtmf XML element
* @param dtmf The dtmf string
* @param msDuration The tone duration in miliseconds. Ignored if 0
* @return Valid XMLElement pointer or 0
*/
virtual XMLElement* createDtmf(const char* dtmf, unsigned int msDuration = 0);
protected:
String m_sessContentName; // Content name advertised to upper layer
};
/**
* A session implementing the Jingle protocol including session transfer and file transfer
* @short The version 1 of a jingle session
@ -1264,15 +1425,10 @@ protected:
* @param stream The stream this session is bound to
* @param callerJID The caller's full JID
* @param calledJID The called party's full JID
* @param contents The session contents to be sent with session initiate element
* @param extra Optional extra child to be added to the session initiate element
* @param msg Optional message to be sent before session initiate
* @param subject Optional session subject
*/
JGSession1(JGEngine* engine, JBStream* stream,
const String& callerJID, const String& calledJID,
const ObjList& contents, XMLElement* extra = 0, const char* msg = 0,
const char* subject = 0);
const String& callerJID, const String& calledJID, const char* msg = 0);
/**
* Constructor. Create an incoming session.
@ -1282,6 +1438,16 @@ protected:
*/
JGSession1(JGEngine* engine, JBEvent* event, const String& id);
/**
* Build and send the initial message on an outgoing session
* @param contents The session contents to be sent with session initiate element
* @param extra Optional extra child to be added to the session initiate element
* @param subject Optional session subject
* @return True on success
*/
virtual bool initiate(const ObjList& contents, XMLElement* extra,
const char* subject = 0);
/**
* Decode a valid jingle set event. Set the event's data on success
* @param jbev The event to decode
@ -1313,7 +1479,6 @@ protected:
*/
virtual void processJabberIqEvent(JBEvent& ev);
private:
};
/**
@ -1323,6 +1488,7 @@ private:
class YJINGLE_API JGEvent
{
friend class JGSession;
friend class JGSession0;
friend class JGSession1;
public:
/**
@ -1587,6 +1753,7 @@ public:
/**
* Make an outgoing call.
* 'media' and 'transport' will be invalid on exit. Don't delete them
* @param ver The session version to use
* @param callerName The local peer's username
* @param remoteJID The remote peer's JID
* @param contents The list of session content(s)
@ -1595,7 +1762,7 @@ public:
* @param subject Optional session subject
* @return Valid JGSession pointer (referenced) on success
*/
JGSession* call(const String& callerName, const String& remoteJID,
JGSession* call(JGSession::Version ver, const String& callerName, const String& remoteJID,
const ObjList& contents, XMLElement* extra = 0, const char* msg = 0,
const char* subject = 0);

View File

@ -388,6 +388,8 @@ private:
Mutex m_mutex; // Lock transport and session
State m_state; // Connection state
JGSession* m_session; // Jingle session attached to this connection
bool m_rtpStarted; // RTP started flag used by version 0 of the jingle session
JGSession::Version m_sessVersion; // Jingle session version
JabberID m_local; // Local user's JID
JabberID m_remote; // Remote user's JID
ObjList m_audioContents; // The list of negotiated audio contents
@ -621,6 +623,7 @@ static bool s_attachPresToCmd = false; // Attach presence to command
static bool s_userRoster = false; // Send client roster with user.roster or resource.notify
static bool s_useCrypto = false;
static bool s_cryptoMandatory = false;
static JGSession::Version s_sessVersion = JGSession::VersionUnknown; // Default jingle session version for outgoing calls
static YJBEngine* s_jabber = 0;
static YJGEngine* s_jingle = 0;
static YJBMessage* s_message = 0;
@ -1310,13 +1313,16 @@ YJGConnection::YJGConnection(Message& msg, const char* caller, const char* calle
bool available, const char* file)
: Channel(&plugin,0,true),
m_mutex(true,"YJGConnection"),
m_state(Pending), m_session(0), m_local(caller),
m_remote(called), m_audioContent(0),
m_state(Pending), m_session(0), m_rtpStarted(false), m_sessVersion(s_sessVersion),
m_local(caller), m_remote(called), m_audioContent(0),
m_callerPrompt(msg.getValue("callerprompt")), m_sendRawRtpFirst(true),
m_useCrypto(s_useCrypto), m_cryptoMandatory(s_cryptoMandatory),
m_hangup(false), m_timeout(0), m_transferring(false), m_recvTransferStanza(0),
m_dataFlags(0), m_ftStatus(FTNone), m_ftHostDirection(FTHostNone)
{
NamedString* ver = msg.getParam("ojingleversion");
if (ver)
m_sessVersion = JGSession::lookupVersion(*ver);
m_subject = msg.getValue("subject");
String uri = msg.getValue("diverteruri",msg.getValue("diverter"));
// Skip protocol if present
@ -1388,7 +1394,8 @@ YJGConnection::YJGConnection(Message& msg, const char* caller, const char* calle
YJGConnection::YJGConnection(JGEvent* event)
: Channel(&plugin,0,false),
m_mutex(true,"YJGConnection"),
m_state(Active), m_session(event->session()),
m_state(Active), m_session(event->session()), m_rtpStarted(false),
m_sessVersion(event->session()->version()),
m_local(event->session()->local()), m_remote(event->session()->remote()),
m_audioContent(0), m_sendRawRtpFirst(true),
m_useCrypto(s_useCrypto), m_cryptoMandatory(s_cryptoMandatory),
@ -1479,6 +1486,13 @@ YJGConnection::YJGConnection(JGEvent* event)
m_session->sendContent(JGSession::ActContentRemove,m_ftContents);
m_ftContents.clear();
}
// Send transport accept now for version 0
if (m_sessVersion == JGSession::Version0) {
ObjList* o = m_audioContents.skipNull();
if (o)
m_session->sendContent(JGSession::ActTransportAccept,
static_cast<JGSessionContent*>(o->get()));
}
}
else {
m_state = Pending;
@ -1516,6 +1530,7 @@ bool YJGConnection::route()
m->addParam("calleruri",BUILD_XMPP_URI(m_remote));
if (m_subject)
m->addParam("subject",m_subject);
m->addParam("jingleversion",JGSession::lookupVersion(m_sessVersion));
m_mutex.lock();
// TODO: add remote ip/port
// Fill file transfer data
@ -1603,9 +1618,11 @@ bool YJGConnection::msgAnswered(Message& msg)
{
Debug(this,DebugCall,"msgAnswered [%p]",this);
if (m_ftStatus == FTNone) {
clearEndpoint();
if (m_sessVersion != JGSession::Version0)
clearEndpoint();
m_mutex.lock();
resetCurrentAudioContent(true,false,false);
if (m_sessVersion != JGSession::Version0 || !m_rtpStarted)
resetCurrentAudioContent(true,false,true);
ObjList tmp;
if (m_audioContent)
tmp.append(m_audioContent)->setDelete(false);
@ -2107,25 +2124,38 @@ bool YJGConnection::handleEvent(JGEvent* event)
return setupSocksFileTransfer(true);
// Update media
Debug(this,DebugCall,"Remote peer answered the call [%p]",this);
m_state = Active;
removeCurrentAudioContent();
m_state = Active;
// Remove current content only if not Version0
// For this version we keep only 1 content
if (m_sessVersion != JGSession::Version0)
removeCurrentAudioContent();
for (ObjList* o = event->m_contents.skipNull(); o; o = o->skipNext()) {
JGSessionContent* recv = static_cast<JGSessionContent*>(o->get());
JGSessionContent* c = findContent(*recv,m_audioContents);
if (!c)
continue;
// Update credentials for ICE-UDP
c->m_rtpRemoteCandidates.m_password = recv->m_rtpRemoteCandidates.m_password;
c->m_rtpRemoteCandidates.m_ufrag = recv->m_rtpRemoteCandidates.m_ufrag;
// only if not version 0 (this version only sends media in accept)
if (m_sessVersion != JGSession::Version0) {
c->m_rtpRemoteCandidates.m_password = recv->m_rtpRemoteCandidates.m_password;
c->m_rtpRemoteCandidates.m_ufrag = recv->m_rtpRemoteCandidates.m_ufrag;
}
// Update media
if (!matchMedia(*c,*recv)) {
Debug(this,DebugInfo,"No common media for content=%s [%p]",
c->toString().c_str(),this);
continue;
}
c->m_rtpMedia.m_ready = true;
// Update transport(s)
bool changed = updateCandidate(1,*c,*recv);
changed = updateCandidate(2,*c,*recv) || changed;
// Force changed for Version0 (we have valid common media for this version)
bool changed = false;
if (m_sessVersion != JGSession::Version0) {
changed = updateCandidate(1,*c,*recv);
changed = updateCandidate(2,*c,*recv) || changed;
}
else
changed = true;
if (changed && !m_audioContent && recv->isSession())
resetCurrentAudioContent(true,false,true,c);
}
@ -2237,19 +2267,26 @@ bool YJGConnection::presenceChanged(bool available)
XMLElement* transfer = 0;
if (m_transferFrom)
transfer = JGSession::buildTransfer(String::empty(),m_transferFrom);
if (m_sendRawRtpFirst) {
addContent(true,buildAudioContent(JGRtpCandidates::RtpRawUdp));
addContent(true,buildAudioContent(JGRtpCandidates::RtpIceUdp));
if (m_sessVersion == JGSession::Version1) {
if (m_sendRawRtpFirst) {
addContent(true,buildAudioContent(JGRtpCandidates::RtpRawUdp));
addContent(true,buildAudioContent(JGRtpCandidates::RtpIceUdp));
}
else {
addContent(true,buildAudioContent(JGRtpCandidates::RtpIceUdp));
addContent(true,buildAudioContent(JGRtpCandidates::RtpRawUdp));
}
}
else {
else if (m_sessVersion == JGSession::Version0)
addContent(true,buildAudioContent(JGRtpCandidates::RtpIceUdp));
addContent(true,buildAudioContent(JGRtpCandidates::RtpRawUdp));
}
m_session = s_jingle->call(m_local,m_remote,m_audioContents,transfer,
m_session = s_jingle->call(m_sessVersion,m_local,m_remote,m_audioContents,transfer,
m_callerPrompt,m_subject);
// Init now the transport for version 0
if (m_session && m_session->version() == JGSession::Version0)
resetCurrentAudioContent(true,false);
}
else
m_session = s_jingle->call(m_local,m_remote,m_ftContents,0,
m_session = s_jingle->call(m_sessVersion,m_local,m_remote,m_ftContents,0,
m_callerPrompt,m_subject);
if (!m_session) {
hangup("noconn");
@ -2419,7 +2456,7 @@ void YJGConnection::processActionTransportInfo(JGEvent* event)
if (!event)
return;
event->confirmElement();
bool ok = m_sessVersion != JGSession::Version0;
bool startAudioContent = false;
JGSessionContent* newContent = 0;
for (ObjList* o = event->m_contents.skipNull(); o; o = o->skipNext()) {
@ -2430,11 +2467,18 @@ void YJGConnection::processActionTransportInfo(JGEvent* event)
event->actionName(),c->toString().c_str(),this);
continue;
}
// Update transport(s)
bool changed = updateCandidate(1,*cc,*c);
// Version0: the session will give us only 1 content
if (!changed && m_sessVersion == JGSession::Version0) {
ok = false;
break;
}
ok = true;
// Update credentials for ICE-UDP
cc->m_rtpRemoteCandidates.m_password = c->m_rtpRemoteCandidates.m_password;
cc->m_rtpRemoteCandidates.m_ufrag = c->m_rtpRemoteCandidates.m_ufrag;
// Update transport(s)
bool changed = updateCandidate(1,*cc,*c);
// Check RTCP
changed = updateCandidate(2,*cc,*c) || changed;
if (!changed)
continue;
@ -2447,13 +2491,17 @@ void YJGConnection::processActionTransportInfo(JGEvent* event)
else
newContent = cc;
}
if (newContent) {
if (!dataFlags(OnHold))
resetCurrentAudioContent(isAnswered(),!isAnswered(),true,newContent);
if (ok) {
event->confirmElement();
if (newContent) {
if (!dataFlags(OnHold))
resetCurrentAudioContent(isAnswered(),!isAnswered(),true,newContent);
}
else if ((startAudioContent && !startRtp()) || !(m_audioContent || dataFlags(OnHold)))
resetCurrentAudioContent(isAnswered(),!isAnswered());
}
else if ((startAudioContent && !startRtp()) || !(m_audioContent || dataFlags(OnHold)))
resetCurrentAudioContent(isAnswered(),!isAnswered());
else
event->confirmElement(XMPPError::SNotAcceptable);
enqueueCallProgress();
}
@ -2465,6 +2513,13 @@ bool YJGConnection::updateCandidate(unsigned int component, JGSessionContent& lo
if (!rtpRecv)
return false;
JGRtpCandidate* rtp = local.m_rtpRemoteCandidates.findByComponent(component);
// Version0: check acceptable transport
if (m_sessVersion == JGSession::Version0) {
// Don't accept another transport if we already have one
// Check for valid attributes
if (rtp || rtpRecv->m_protocol != "udp" || rtpRecv->m_type != "local")
return false;
}
if (!rtp) {
DDebug(this,DebugAll,"Adding remote transport '%s' in content '%s' [%p]",
rtpRecv->toString().c_str(),local.toString().c_str(),this);
@ -2496,8 +2551,12 @@ void YJGConnection::addContent(bool local, JGSessionContent* c)
c->m_rtpRemoteCandidates.m_type = c->m_rtpLocalCandidates.m_type;
else
c->m_rtpLocalCandidates.m_type = c->m_rtpRemoteCandidates.m_type;
if (c->m_rtpLocalCandidates.m_type == JGRtpCandidates::RtpIceUdp)
c->m_rtpLocalCandidates.generateIceAuth();
if (c->m_rtpLocalCandidates.m_type == JGRtpCandidates::RtpIceUdp) {
if (m_sessVersion != JGSession::Version0)
c->m_rtpLocalCandidates.generateIceAuth();
else
c->m_rtpLocalCandidates.generateOldIceAuth();
}
// Fill synonym for received media
if (!local) {
for (ObjList* o = c->m_rtpMedia.skipNull(); o; o = o->skipNext()) {
@ -2610,7 +2669,6 @@ bool YJGConnection::resetCurrentAudioContent(bool session, bool earlyMedia,
}
else if (!newContent->isValidAudio())
return false;
if (newContent && newContent->ref()) {
m_audioContent = newContent;
Debug(this,DebugAll,"Using audio content '%s' [%p]",
@ -2633,6 +2691,9 @@ bool YJGConnection::startRtp()
return false;
}
if (m_sessVersion == JGSession::Version0 && m_rtpStarted)
return true;
JGRtpCandidate* rtpLocal = m_audioContent->m_rtpLocalCandidates.findByComponent(1);
JGRtpCandidate* rtpRemote = m_audioContent->m_rtpRemoteCandidates.findByComponent(1);
if (!(rtpLocal && rtpRemote)) {
@ -2683,6 +2744,7 @@ bool YJGConnection::startRtp()
if (m_audioContent->m_rtpLocalCandidates.m_type == JGRtpCandidates::RtpIceUdp &&
rtpRemote->m_address) {
m_rtpStarted = true;
// Start STUN
Message* msg = new Message("socket.stun");
msg->userData(m.userData());
@ -2856,6 +2918,7 @@ bool YJGConnection::processContentAdd(const JGEvent& event, ObjList& ok, ObjList
remove.append(c)->setDelete(false);
continue;
}
c->m_rtpMedia.m_ready = true;
// Check crypto
bool error = false;
@ -4228,6 +4291,7 @@ void YJGDriver::initialize()
s_userRoster = sect->getBoolValue("user.roster",false);
s_useCrypto = sect->getBoolValue("secure_rtp",false);
s_cryptoMandatory = s_useCrypto;
s_sessVersion = JGSession::lookupVersion(sect->getValue("jingleversion"),JGSession::Version1);
// Init codecs in use. Check each codec in known codecs list against the configuration
s_usedCodecs.clear();
@ -4261,6 +4325,7 @@ void YJGDriver::initialize()
if (debugAt(dbg)) {
String s;
s << " localip=" << s_localAddress ? s_localAddress.c_str() : "MISSING";
s << " jingleversion=" << JGSession::lookupVersion(s_sessVersion);
s << " singletone=" << String::boolText(m_singleTone);
s << " pending_timeout=" << s_pendingTimeout;
s << " anonymous_caller=" << s_anonymousCaller;