/** * yatejingle.h * Yet Another Jingle Stack * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2006 Null Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __YATEJINGLE_H #define __YATEJINGLE_H #include #include /** * Holds all Telephony Engine related classes. */ namespace TelEngine { class JGAudio; class JGTransport; class JGSession; class JGEvent; class JGEngine; class JGSentStanza; // Time to wait before destroing a session after hangup #define JGSESSION_ENDTIMEOUT 2000 // Time to wait for a response #define JGSESSION_STANZATIMEOUT 10000 /** * This class holds a Jingle audio payload description. * @short A Jingle audio payload. */ class YJINGLE_API JGAudio : public RefObject { public: /** * Constructor. * Fill this object from the given attributes. * @param id The 'id' attribute. * @param name The 'name' attribute. * @param clockrate The 'clockrate' attribute. * @param bitrate The 'bitrate' attribute. */ inline JGAudio(const char* id, const char* name, const char* clockrate, const char* bitrate = 0) { set(id,name,clockrate,bitrate); } /** * Copy constructor. */ inline JGAudio(const JGAudio& src) { set(src.m_id,src.m_name,src.m_clockrate,src.m_bitrate); } /** * Constructor. * Fill this object from an XML element. * @param element The element to fill from. */ inline JGAudio(XMLElement* element) { fromXML(element); } /** * Destructor. */ virtual ~JGAudio() {} /** * Create a 'description' element. * @return Valid XMLElement pointer. */ static XMLElement* createDescription(); /** * Create a 'payload-type' element from this object. * @return Valid XMLElement pointer. */ XMLElement* toXML(); /** * Fill this object from a given element. * @param element The element. */ void fromXML(XMLElement* element); /** * Create and add a 'payload-type' child to the given element. * @param description The element. */ inline void addTo(XMLElement* description) { if (description) description->addChild(toXML()); } /** * Set the data. * @param id The 'id' attribute. * @param name The 'name' attribute. * @param clockrate The 'clockrate' attribute. * @param bitrate The 'bitrate' attribute. */ void set(const char* id, const char* name, const char* clockrate, const char* bitrate = 0); // Attributes String m_id; String m_name; String m_clockrate; String m_bitrate; }; /** * This class holds a Jingle transport method. * @short A Jingle transport. */ class YJINGLE_API JGTransport : public RefObject { public: /** * Constructor. */ inline JGTransport() {} /** * Copy constructor. */ JGTransport(const JGTransport& src); /** * Constructor. * Fill this object from an XML element. * @param element The element to fill from. */ inline JGTransport(XMLElement* element) { fromXML(element); } /** * Destructor. */ virtual ~JGTransport() {} /** * Create a 'transport' element. * @return Valid XMLElement pointer. */ static XMLElement* createTransport(); /** * Create a 'candidate' element from this object. * @return Valid XMLElement pointer. */ XMLElement* toXML(); /** * Fill this object from a given element. * @param element The element. */ void fromXML(XMLElement* element); /** * Create and add a 'candidate' child to the given element. * @param transport The element. */ inline void addTo(XMLElement* transport) { if (transport) transport->addChild(toXML()); } // Attributes String m_name; String m_address; String m_port; String m_preference; String m_username; String m_protocol; String m_generation; String m_password; String m_type; String m_network; }; /** * This class does the management of a Jingle session. * @short A Jingle session. */ class YJINGLE_API JGSession : public RefObject, public Mutex { friend class JGEvent; friend class JGEngine; public: /** * Session state enumeration. */ enum State { Idle, // Outgoing stream is waiting for Pending, // Session is pending, session-initiate sent/received Active, // Session is active, session-accept sent/received Ending, // Session terminated: Wait for write result Destroy, // The session will be destroyed }; /** * Jingle action enumeration. */ enum Action { ActAccept, // accept ActInitiate, // initiate ActModify, // modify ActRedirect, // redirect ActReject, // reject ActTerminate, // terminate ActTransport, // Used to set/get transport info ActTransportInfo, // transport-info ActTransportCandidates, // candidates ActTransportAccept, // transport-accept ActContentInfo, // content-info ActDtmf, // Used to set/get dtmf content-info element ActDtmfMethod, // Used to set/get dtmf method content-info element ActCount, }; /** * Jingle client type. */ enum TransportType { TransportInfo, // transport-info TransportCandidates, // candidates }; /** * Destructor. * Send SessionTerminate if Pending or Active. * Notify the owner of termination. Deref the owner. */ virtual ~JGSession(); /** * Get the session direction. * @return True if it is an incoming session. */ inline bool incoming() const { return m_incoming; } /** * Get the session id. * @return The session id. */ const String& sid() const { return m_sid; } /** * Get the local peer's JID. * @return The local peer's JID. */ const JabberID& local() const { return m_localJID; } /** * Get the remote peer's JID. * @return The remote peer's JID. */ const JabberID& remote() const { return m_remoteJID; } /** * Get the initiator of this session. * @return The initiator of this session. */ const String& initiator() const { return m_incoming ? m_remoteJID : m_localJID; } /** * Get the session state. * @return The session state as enumeration. */ inline State state() const { return m_state; } inline const JBComponentStream* stream() const { return m_stream; } /** * Get the arbitrary user data of this session. * @return The arbitrary user data of this session. */ inline void* jingleConn() { return m_private; } /** * Set the arbitrary user data of this session. * @param jingleconn The new arbitrary user data's value. */ inline void jingleConn(void* jingleconn) { m_private = jingleconn; } /** * Send a message to the remote peer. * This method is thread safe. * @param message The message to send. * @return False on socket error. */ bool sendMessage(const char* message); /** * Send an 'iq' of type 'result' to the remote peer. * This method is thread safe. * @param id The element's id attribute. * @return False if send failed. */ bool sendResult(const char* id); /** * Send a dtmf character to remote peer. * This method is thread safe. * @param dtmf The dtmf character. * @param buttonUp True to send button-up action. False to send button-down. * @return False if send failed. */ bool sendDtmf(char dtmf, bool buttonUp = true); /** * Send a dtmf method to remote peer. * This method is thread safe. * @param method The method to send. * @return False if send failed. */ bool sendDtmfMethod(const char* method); /** * Deny a dtmf method request from remote peer. * This method is thread safe. * @param element The received 'iq' element with the method. * @return False if send failed. */ bool denyDtmfMethod(XMLElement* element); /** * Create and sent an 'error' element. * This method is thread safe. * @param element The element to respond to. * @param error The error. * @param type Error type. * @param text Optional text to add to the error element. * @return False if send failed or element is 0. */ bool sendError(XMLElement* element, XMPPError::Type error, XMPPError::ErrorType type = XMPPError::TypeModify, const char* text = 0); /** * Send a 'transport-info' element to the remote peer. * This method is thread safe. * @param transport The transport data. * @return False if send failed. */ inline bool requestTransport(JGTransport* transport) { return sendTransport(transport,ActTransport); } /** * Send a 'transport-accept' element to the remote peer. * This method is thread safe. * @param transport Optional transport data to send. * @return False if send failed. */ inline bool acceptTransport(JGTransport* transport = 0) { return sendTransport(transport,ActTransportAccept); } /** * Accept a session. * This method is thread safe. * @param description The media description element to send. * @return False if send failed. */ bool accept(XMLElement* description); /** * Send a session terminate element. * Requirements: . * This method is thread safe. * @param reject True to reject. * @param message Optional message to send before hangup. * @return False if the requirements are not met. */ bool hangup(bool reject = false, const char* message = 0); /** * Check if this session is a destination for an event. * Process it if it is. * This method is thread safe. * @param event The event event to process. * @return False if the event doesn't belong to this session. */ bool receive(JBEvent* event); /** * Get a Jingle event from the queue. * This method is thread safe. * @param time Current time in miliseconds. * @return JGEvent pointer or 0. */ JGEvent* getEvent(u_int64_t time); /** * Get the jingle action as enumeration from the given text. * @param txt Text to check. * @return The jingle action as enumeration from the given text. */ static inline Action action(const char* txt) { return (Action)lookup(txt,s_actions,ActCount); } /** * Get the text associated with an action. * @param action The action to find. * @return Pointer to the text or 0. */ static inline const char* actionText(Action action) { return lookup(action,s_actions); } /** * Check if the given parameter is a valid dtmf character. * @param dtmf Character to check. * @return True if the given parameter is a valid dtmf character. */ static inline bool isDtmf(char dtmf) { return -1 != s_dtmf.find(dtmf); } /** * Keeps the dtmf chars. */ static String s_dtmf; 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. */ JGSession(JGEngine* engine, JBComponentStream* stream, const String& callerJID, const String& calledJID); /** * Constructor. * Create an incoming session. * @param engine The engine that owns this session. * @param event A valid Jabber Jingle event with action session initiate. */ JGSession(JGEngine* engine, JBEvent* event); /** * Send a bad-request error. Delete event. * @param event An already generated event. * @return 0. */ JGEvent* badRequest(JGEvent* event); /** * Process a received event. * @param jbev The Jabber Component event to process. * @param time Current time. * @return The Jingle event or 0. */ JGEvent* processEvent(JBEvent* jbev, u_int64_t time); /** * Process received elements in state Pending. * @param jbev The Jabber Component event to process. * @param event The jingle event to construct. * @return The event parameter on success. 0 on failure. */ JGEvent* processStatePending(JBEvent* jbev, JGEvent* event); /** * Process received elements in state Active. * @param jbev The Jabber Component event to process. * @param event The jingle event to construct. * @return The event parameter on success. 0 on failure. */ JGEvent* processStateActive(JBEvent* jbev, JGEvent* event); /** * Process received elements in state Idle. * @param jbev The Jabber Component event to process. * @param event The jingle event to construct. * @return The event parameter on success. 0 on failure. */ JGEvent* processStateIdle(JBEvent* jbev, JGEvent* event); /** * Check if a given event is a valid Jingle one. Send an error if not. * Set the event's data on success. * @param event The event to check. * @return True on success. */ bool decodeJingle(JGEvent* event); /** * Process a content-info jingle element. * Set the event's data on success. * @param event The event to check. * @return True on success. */ bool processContentInfo(JGEvent* event); /** * Update media payloads from a Jingle event. * @param event The event to process. * @return True on success. */ bool updateMedia(JGEvent* event); /** * Update transport candidates from a Jingle event. * @param event The event to process. * @return True on success. */ bool updateTransport(JGEvent* event); /** * Set the message text of the the given event. * @param event The event to process. * @return The event parameter. */ JGEvent* decodeMessage(JGEvent* event); /** * Check if a given event is a valid Error one. Send an error if not. * Set the event's data on success. * @param event The event to check. * @return True on success. */ bool decodeError(JGEvent* event); /** * Send a 'service-unavailable' error to the remote peer. * @param element The element that generated the error. * @return True on success. */ inline bool sendEServiceUnavailable(XMLElement* element) { return sendError(element,XMPPError::SServiceUnavailable); } /** * Send a 'bad-request' error to the remote peer. * @param element The element that generated the error. * @return True on success. */ inline bool sendEBadRequest(XMLElement* element) { return sendError(element,XMPPError::SBadRequest); } /** * Send a transport related element to the remote peer. * @param transport Transport data to send. * @param act The element's type (info, accept, etc). * @return True on success. */ bool sendTransport(JGTransport* transport, Action act); /** * Initiate an outgoing call. * @param media Media description element. * @param transport Transport description element. * @return True on success. */ bool initiate(XMLElement* media, XMLElement* transport); /** * Send an XML element to remote peer. * @param e The element to send. * @param addId True to add 'id' attribute. * @return True on success. */ bool sendXML(XMLElement* e, bool addId = true); /** * Create an event. * @param jbev Optional Jabber event that generated the event. * @return Valid JGEvent pointer. */ JGEvent* createEvent(JBEvent* jbev); /** * Set last event. Change state. Deref this session if event type is Destroy. * @param event Event to raise. * @return Valid JGEvent pointer. */ JGEvent* raiseEvent(JGEvent* event); /** * Create an 'iq' of type 'set' or 'get' with a 'jingle' child. * @param action The action of the Jingle stanza. * @param element1 Optional child element. * @param element2 Optional child element. * @return Valid XMLElement pointer. */ XMLElement* createJingleSet(Action action, XMLElement* element1 = 0, XMLElement* element2 = 0); /** * Confirm the given element if has to. * @param element The element to confirm. */ void confirmIq(XMLElement* element); /** * Selectively confirm an 'iq' (skip confirmation for transport-info). * @param event The event containing the element to confirm. */ void confirmIqSelect(JGEvent* event); /** * Terminate notification from an event. Reset the last generated event. * @param event The notifier. */ void eventTerminated(JGEvent* event); /** * Check if a received event contains a confirmation of a sent stanza. * @param jbev The received Jabber Component event. * @return The confirmed element or 0. */ JGSentStanza* isResponse(const JBEvent* jbev); /** * Check if any element timed out. * @return True if timeout. */ bool timeout(u_int64_t time); /** * Keeps the associations between jingle actions and their text. */ static TokenDict s_actions[]; private: JGSession() {} // Don't use it void appendSent(XMLElement* element); // Accept the appropriate events. Called in receive() // @param retValue The value receive() will return if the message was not accepted // @return True if the message is accepted. False if not bool receiveMessage(const JBEvent* event, bool& retValue); bool receiveResult(const JBEvent* event, bool& retValue); bool receiveJingle(const JBEvent* event, bool& retValue); bool receiveDestroy(const JBEvent* event, bool& retValue); // State info State m_state; // Session state TransportType m_transportType; // Remote client type // Links JGEngine* m_engine; // The engine that owns this session JBComponentStream* m_stream; // The stream this session is bound to // Session info bool m_incoming; // Session direction String m_sid; // Session id JabberID m_localJID; // Local peer's JID JabberID m_remoteJID; // Remote peer's JID // Session data ObjList m_events; // Incoming events from Jabber engine JGEvent* m_lastEvent; // Last generated event JGEvent* m_terminateEvent; // Terminate event void* m_private; // Arbitrary user data // Sent stanzas id generation String m_localSid; // Local session id (used to generate element's id) u_int32_t m_stanzaId; // Sent stanza id counter // Timeout u_int64_t m_timeout; // Timeout period for Ending state or for sent stanzas ObjList m_sentStanza; // Sent stanzas' id }; /** * This class holds an event generated by a Jingle session. * @short A Jingle event */ class YJINGLE_API JGEvent { friend class JGSession; public: enum Type { Jingle, // Actions: // ActAccept // ActInitiate // ActModify // ActRedirect // ActReject Never: See Terminated event // ActTerminate Never: See Terminated event // ActTransport Transport candidade(s) // ActTransportInfo Never // ActTransportCandidates Never // ActTransportAccept // ActContentInfo Never // ActDtmf m_reason is button-up/button-down. m_text is the dtmf // ActDtmfMethod m_text is the dtmf method: rtp/xmpp Message, // m_text is the message body Error, Unexpected, // Unexpected or invalid element // Final Terminated, // m_element is the element that caused the termination // m_reason contains the reason Destroy, // The engine sould delete the event (causing session destruction) }; /** * Destructor. * Deref the session. Delete the XML element. */ virtual ~JGEvent(); /** * Get the type of this event. * @return The type of this event as enumeration. */ inline Type type() const { return m_type; } /** * Get the session that generated this event. * @return The session that generated this event. */ inline JGSession* session() const { return m_session; } /** * Get the XML element that generated this event. * @return The XML element that generated this event. */ inline XMLElement* element() const { return m_element; } /** * Get the jingle action as enumeration. * @return The jingle action as enumeration. */ inline JGSession::Action action() const { return m_action; } /** * Get the audio payloads list. * @return The audio payloads list. */ inline ObjList& audio() { return m_audio; } /** * Get the transports list. * @return The transports list. */ inline ObjList& transport() { return m_transport; } /** * Get the id. * @return The id. */ inline const String& id() { return m_id; } /** * Get the reason. * @return The reason. */ inline const String& reason() { return m_reason; } /** * Get the text. * @return The text. */ inline const String& text() { return m_text; } /** * Get the XML element that generated this event and set it to 0. * @return The XML element that generated this event. */ inline XMLElement* releaseXML() { XMLElement* tmp = m_element; m_element = 0; return tmp; } /** * Check if this event is a final one (Terminated or Destroy). * @return True if it is. */ bool final(); protected: /** * Constructor. * @param type Event type. * @param session The session that generated this event. * @param element Optional XML element that generated this event. */ JGEvent(Type type, JGSession* session, XMLElement* element = 0); private: JGEvent() {} // Don't use it Type m_type; // The type of this event JGSession* m_session; // Jingle session that generated this event XMLElement* m_element; // XML element that generated this event // Event specific JGSession::Action m_action; // The action if type is Jingle ObjList m_audio; // The received audio payloads ObjList m_transport; // The received transport data String m_id; // The element's id attribute String m_reason; // The reason if type is Error or Terminated String m_text; // The text if type is Error or any other text }; /** * This class holds the Jingle engine. * @short The Jingle engine. */ class YJINGLE_API JGEngine : public JBClient, public DebugEnabler, public Mutex { friend class JGSession; public: /** * Constructor. * Constructs a Jingle engine. * @param jb The JBEngine. * @param params Engine's parameters. */ JGEngine(JBEngine* jb, const NamedList& params); /** * Destructor. * Terminates all active sessions. Delete the XMPP engine. */ virtual ~JGEngine(); /** * Initialize this engine. * Parameters: None * @param params Engine's parameters. */ void initialize(const NamedList& params); /** * Get events from the Jabber engine. * This method is thread safe. * @return True if data was received. */ bool receive(); /** * Keep calling receive(). */ void runReceive(); /** * Keep calling getEvent() for each session list until no more event is generated. * Call processEvent if needded. * @return True if at least one event was generated. */ bool process(); /** * Keep calling process(). */ void runProcess(); /** * Call getEvent() for each session list until an event is generated or the end is reached. * This method is thread safe. * @param time Current time in miliseconds. * @return The first generated event. */ JGEvent* getEvent(u_int64_t time); /** * Make an outgoing call. * 'media' and 'transport' will be invalid on exit. Don't delete them. * @param callerName The local peer's username. * @param remoteJID The remote peer's JID. * @param media A valid 'description' XML element. * @param transport A valid 'transport' XML element. * @param message Optional message to send before call. * @return Valid JGSession pointer (referenced) on success. */ JGSession* call(const String& callerName, const String& remoteJID, XMLElement* media, XMLElement* transport, const char* message = 0); /** * Default event processor. * Action: Delete event. * @param event The event to process. */ void defProcessEvent(JGEvent* event); /** * Create a session id for an outgoing one. * @param id Destination string. */ void createSessionId(String& id); protected: /** * Process events from the sessions. * Default action: Delete event. * Descendants must override this method. * @param event The event to process. */ virtual void processEvent(JGEvent* event); /** * Remove a session from the list. * @param session Session to remove. */ void removeSession(JGSession* session); private: ObjList m_sessions; // List of sessions Mutex m_sessionIdMutex; // Session id counter lock u_int32_t m_sessionId; // Session id counter }; /** * This class holds sent stanzas info used for timeout checking. * @short Timeout info. */ class YJINGLE_API JGSentStanza : public RefObject { friend class JGSession; public: /** * Constructor. * @param id The sent stanza's id. * @param time The sent time. */ JGSentStanza(const char* id, u_int64_t time = Time::msecNow()) : m_id(id), m_time(time + JGSESSION_STANZATIMEOUT) {} /** * Destructor. */ virtual ~JGSentStanza() {} /** * Check if a received element is an iq result or error with the given id or * a sent element failed to be written to socket. * @param jbev The received Jabber Component event. * @return False if the given element is not a response one or is 0. */ bool isResponse(const JBEvent* jbev) { if (jbev && (jbev->type() == JBEvent::IqResult || jbev->type() == JBEvent::IqError || jbev->type() == JBEvent::WriteFail) && m_id == jbev->id()) return true; return false; } /** * Check if this element timed out. * @return True if timeout. */ inline bool timeout(u_int64_t time) const { return time > m_time; } private: String m_id; // Sent stanza's id u_int64_t m_time; // Timeout }; } #endif /* __YATEJINGLE_H */ /* vi: set ts=8 sw=4 sts=4 noet: */