3135 lines
91 KiB
C++
3135 lines
91 KiB
C++
/**
|
|
* yiaxchan.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* IAX channel
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2014 Null Team
|
|
* Author: Marian Podgoreanu
|
|
*
|
|
* This software is distributed under multiple licenses;
|
|
* see the COPYING file in the main directory for licensing
|
|
* information for this specific distribution.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <yatephone.h>
|
|
#include <yateversn.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <yateiax.h>
|
|
|
|
using namespace TelEngine;
|
|
namespace { // anonymous
|
|
|
|
static const TokenDict dict_payloads[] = {
|
|
{"gsm", IAXFormat::GSM},
|
|
{"ilbc30", IAXFormat::ILBC},
|
|
{"speex", IAXFormat::SPEEX},
|
|
{"lpc10", IAXFormat::LPC10},
|
|
{"mulaw", IAXFormat::ULAW},
|
|
{"alaw", IAXFormat::ALAW},
|
|
{"g723", IAXFormat::G723_1},
|
|
{"g729", IAXFormat::G729},
|
|
{"adpcm", IAXFormat::ADPCM},
|
|
{"g726", IAXFormat::G726},
|
|
{"slin", IAXFormat::SLIN},
|
|
{"g722/16000", IAXFormat::G722},
|
|
{"amr", IAXFormat::AMR},
|
|
{"gsmhr", IAXFormat::GSM_HR},
|
|
{0, 0}
|
|
};
|
|
|
|
static const TokenDict dict_payloads_video[] = {
|
|
{"h261", IAXFormat::H261},
|
|
{"h263", IAXFormat::H263},
|
|
{"h263p", IAXFormat::H263p},
|
|
{"h264", IAXFormat::H264},
|
|
{0, 0}
|
|
};
|
|
|
|
static const TokenDict dict_tos[] = {
|
|
{ "lowdelay", Socket::LowDelay },
|
|
{ "throughput", Socket::MaxThroughput },
|
|
{ "reliability", Socket::MaxReliability },
|
|
{ "mincost", Socket::MinCost },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
class YIAXLineContainer;
|
|
class YIAXEngine;
|
|
class IAXURI;
|
|
|
|
/*
|
|
* Keep a single registration line
|
|
*/
|
|
class YIAXLine : public RefObject, public Mutex
|
|
{
|
|
friend class YIAXLineContainer;
|
|
public:
|
|
YIAXLine(const String& name);
|
|
virtual ~YIAXLine();
|
|
virtual const String& toString() const
|
|
{ return m_name; }
|
|
inline bool registered() const
|
|
{ return m_registered; }
|
|
inline const String& username() const
|
|
{ return m_username; }
|
|
inline const String& password() const
|
|
{ return m_password; }
|
|
inline const String& callingNo() const
|
|
{ return m_callingNo; }
|
|
inline const String& callingName() const
|
|
{ return m_callingName; }
|
|
inline unsigned int expire() const
|
|
{ return m_expire; }
|
|
inline const String& remoteAddr() const
|
|
{ return m_remoteAddr; }
|
|
inline int remotePort() const
|
|
{ return m_remotePort; }
|
|
inline const SocketAddr& remote() const
|
|
{ return m_remote; }
|
|
inline bool callToken() const
|
|
{ return m_callToken; }
|
|
inline bool trunking() const
|
|
{ return m_trunking; }
|
|
inline const NamedList& trunkInfo() const
|
|
{ return m_trunkInfo; }
|
|
inline YIAXEngine* engine() const
|
|
{ return m_engine; }
|
|
inline IAXTransaction* transaction() const
|
|
{ return m_transaction; }
|
|
|
|
protected:
|
|
virtual void destroyed() {
|
|
if (m_transaction) {
|
|
m_transaction->setUserData(0);
|
|
m_transaction = 0;
|
|
}
|
|
m_engine = 0;
|
|
RefObject::destroyed();
|
|
}
|
|
|
|
private:
|
|
void setRegistered(bool registered, const char* reason = 0, const char* error = 0);
|
|
inline void setNextRegFromExpire(unsigned int val1, unsigned int val2)
|
|
{ m_nextReg = Time::now() + 1000000 * (m_expire * (val1 ? val1 : 1) / (val2 ? val2 : 1)); }
|
|
|
|
String m_name;
|
|
int m_oper; // Requested operation
|
|
String m_username; // Username
|
|
String m_password; // Password
|
|
String m_callingNo; // Calling number
|
|
String m_callingName; // Calling name
|
|
bool m_callToken; // Advertise CALLTOKEN support
|
|
unsigned int m_expire; // Expire time
|
|
String m_remoteAddr;
|
|
int m_remotePort;
|
|
SocketAddr m_remote;
|
|
u_int64_t m_nextReg; // Time to next registration
|
|
u_int64_t m_nextKeepAlive; // Time to next keep alive signal
|
|
unsigned int m_keepAliveInterval; // Keep alive interval
|
|
bool m_registered; // Registered flag. If true the line is registered
|
|
RefPointer<IAXTransaction> m_transaction;
|
|
RefPointer<YIAXEngine> m_engine; // The engine to use
|
|
NamedList m_trunkInfo; // Trunk info parameters
|
|
bool m_trunking; // Enable trunking
|
|
NamedList m_engineToUse; // Parameters used to identify the engine
|
|
};
|
|
|
|
/*
|
|
* Line container: Add/Delete/Update/Register/Unregister lines
|
|
*/
|
|
class YIAXLineContainer : public Mutex
|
|
{
|
|
public:
|
|
enum Operation {
|
|
Unknown = 0,
|
|
Create,
|
|
Login, // Login
|
|
Logout, // Logout and remain
|
|
Delete, // Logout and delete after
|
|
};
|
|
inline YIAXLineContainer()
|
|
: Mutex(true,"YIAXLineContainer"),
|
|
m_lineTransMutex(false,"YIAXLineTransMutex")
|
|
{}
|
|
inline bool busy(const YIAXLine* line) {
|
|
Lock lck(m_lineTransMutex);
|
|
return line && 0 != line->transaction();
|
|
}
|
|
// Logout and remove all lines
|
|
// Process only lines using a specified engine if given
|
|
void clear(bool final = false, YIAXEngine* eng = 0);
|
|
// Update a line from a message
|
|
bool updateLine(Message &msg);
|
|
// Re-check lines engine
|
|
void updateEngine();
|
|
// Event handler for a registration.
|
|
void handleEvent(IAXEvent* event);
|
|
// Terminate notification of a Register/Unregister operation
|
|
void regTerminate(IAXEvent* event);
|
|
// Process an authentication request for a Register/Unregister operation
|
|
void regAuthReq(IAXEvent* event);
|
|
// Timer notification
|
|
void evTimer(Time& time);
|
|
// Fill parameters with information taken from line
|
|
void fillList(YIAXLine& line, NamedList& dest, SocketAddr& addr);
|
|
// Find a line by name
|
|
inline bool findLine(RefPointer<YIAXLine>& line, const String& name) {
|
|
Lock lck(this);
|
|
line = findLine(name);
|
|
return line != 0;
|
|
}
|
|
// Check if a line exists
|
|
inline bool hasLine(const String& line)
|
|
{ Lock lock(this); return findLine(line) != 0; }
|
|
// Retrieve the lines list
|
|
inline ObjList& lines()
|
|
{ return m_lines;}
|
|
|
|
Mutex m_lineTransMutex;
|
|
|
|
protected:
|
|
// Set a line's transaction
|
|
void setTransaction(YIAXLine* line, IAXTransaction* tr = 0,
|
|
bool abortReg = true);
|
|
// Change a line String parameter. Unregister it if changed
|
|
inline void changeLine(bool& changed, YIAXLine* line, String& dest, const String& src) {
|
|
if (!line || dest == src)
|
|
return;
|
|
unregisterLineNonBusy(line);
|
|
dest = src;
|
|
changed = true;
|
|
}
|
|
inline void changeLine(bool& changed, YIAXLine* line, unsigned int& dest, unsigned int src) {
|
|
if (!line || dest == src)
|
|
return;
|
|
unregisterLineNonBusy(line);
|
|
dest = src;
|
|
changed = true;
|
|
}
|
|
inline void changeLine(bool& changed, YIAXLine* line, int& dest, int src) {
|
|
if (!line || dest == src)
|
|
return;
|
|
unregisterLineNonBusy(line);
|
|
dest = src;
|
|
changed = true;
|
|
}
|
|
// Unregister a non busy registered line. Return true if unregistering
|
|
inline bool unregisterLineNonBusy(YIAXLine* line) {
|
|
if (!line || !line->m_registered || busy(line))
|
|
return false;
|
|
return registerLine(line,false);
|
|
}
|
|
// Find a line by name
|
|
inline YIAXLine* findLine(const String& name) {
|
|
ObjList* o = m_lines.find(name);
|
|
return o ? static_cast<YIAXLine*>(o->get()) : 0;
|
|
}
|
|
// Find a line by address, return same if found
|
|
inline YIAXLine* findLine(YIAXLine* line)
|
|
{ return (line && m_lines.find(line)) ? line : 0; }
|
|
// Find line from event. Clear event transaction if line is not found
|
|
// Return referenced pointer
|
|
YIAXLine* findLine(IAXEvent* ev);
|
|
// (Un)register a line
|
|
bool registerLine(YIAXLine* line, bool reg);
|
|
private:
|
|
ObjList m_lines;
|
|
};
|
|
|
|
|
|
//
|
|
// Engine thread
|
|
//
|
|
class YIAX_API YIAXThread : public Thread, public GenObject
|
|
{
|
|
public:
|
|
enum Operation {
|
|
Listener,
|
|
GetEvent,
|
|
Trunking
|
|
};
|
|
inline YIAXThread(YIAXEngine* engine, int oper, Priority prio = Normal)
|
|
: Thread(lookup(oper,s_operThName),prio), m_oper(oper), m_engine(engine)
|
|
{}
|
|
~YIAXThread()
|
|
{ notify(true); }
|
|
inline const char* operThName() const
|
|
{ return lookup(m_oper,s_operThName); }
|
|
virtual void cleanup()
|
|
{ notify(true); }
|
|
virtual void run();
|
|
static const TokenDict s_operThName[];
|
|
|
|
protected:
|
|
void notify(bool final = false);
|
|
|
|
int m_oper;
|
|
YIAXEngine* m_engine;
|
|
};
|
|
|
|
/*
|
|
* The IAX engine for this driver
|
|
*/
|
|
class YIAXEngine : public IAXEngine, public RefObject
|
|
{
|
|
friend class YIAXDriver;
|
|
friend class YIAXThread;
|
|
public:
|
|
enum Status {
|
|
Idle,
|
|
Listening,
|
|
Disabled,
|
|
Removed,
|
|
Exiting,
|
|
BindFailed
|
|
};
|
|
YIAXEngine(const String& name, const char* iface, int port, const NamedList* params);
|
|
virtual ~YIAXEngine()
|
|
{}
|
|
inline bool bound() const
|
|
{ return m_status != BindFailed && m_status != Idle; }
|
|
inline bool valid() const
|
|
{ return bound() && !exiting(); }
|
|
inline const char* statusName()
|
|
{ return statusName(m_status); }
|
|
inline void readSocket() {
|
|
SocketAddr addr;
|
|
IAXEngine::readSocket(addr);
|
|
}
|
|
inline bool timeout(const Time& now)
|
|
{ return m_timeout && m_timeout < now; }
|
|
void changeStatus(int stat);
|
|
bool bind(const NamedList& params);
|
|
// Schedule for termination
|
|
void setTerminate(int stat);
|
|
// Setup a given transaction from parameters
|
|
void initTransaction(IAXTransaction* tr, const NamedList& params, YIAXLine* line = 0);
|
|
// Process media from remote peer
|
|
virtual void processMedia(IAXTransaction* transaction, DataBlock& data,
|
|
u_int32_t tStamp, int type, bool mark);
|
|
// Initiate (un)register transaction
|
|
inline IAXTransaction* reg(bool regreq, const SocketAddr& addr, IAXIEList& ieList) {
|
|
if (regreq)
|
|
return startLocalTransaction(IAXTransaction::RegReq,addr,ieList,true,false);
|
|
return startLocalTransaction(IAXTransaction::RegRel,addr,ieList,true,false);
|
|
}
|
|
// Initiate an aoutgoing call.
|
|
IAXTransaction* call(SocketAddr& addr, NamedList& params);
|
|
// Initiate a test of existence of a remote IAX peer.
|
|
IAXTransaction* poke(SocketAddr& addr);
|
|
// Cancel threads
|
|
void cancelThreads(unsigned int waitTerminateMs = 100);
|
|
// Add engine data to message
|
|
inline void fillMessage(Message& msg, bool addRoute = true) {
|
|
msg.setParam("connection_id",toString());
|
|
msg.setParam("connection_reliable",String::boolText(false));
|
|
if (addRoute) {
|
|
msg.setParam("route_params","oconnection_id");
|
|
msg.setParam("oconnection_id",toString());
|
|
}
|
|
}
|
|
virtual const String& toString() const
|
|
{ return name(); }
|
|
static inline const char* statusName(int stat)
|
|
{ return lookup(stat,s_statusName); }
|
|
static const TokenDict s_statusName[];
|
|
|
|
protected:
|
|
virtual void destroyed();
|
|
// Event handler for transaction with a connection
|
|
virtual void processEvent(IAXEvent* event);
|
|
// Process a new format received with a full frame
|
|
virtual bool mediaFormatChanged(IAXTransaction* trans, int type, u_int32_t format);
|
|
// Event handler for incoming registration transactions
|
|
void processRemoteReg(IAXEvent* event,bool first);
|
|
// Send Register/Unregister messages to Engine
|
|
// Return the message on success
|
|
Message* userreg(IAXTransaction* tr, bool regrel = true);
|
|
// Create threads
|
|
void createThreads(const NamedList& params);
|
|
void createThreads(unsigned int count, int oper, Thread::Priority prio);
|
|
|
|
private:
|
|
void threadTerminated(YIAXThread* th);
|
|
|
|
bool m_initialized;
|
|
bool m_default;
|
|
ObjList m_threads;
|
|
u_int64_t m_timeout; // Timeout: destroy the engine
|
|
int m_status; // Engine status
|
|
String m_bindAddr; // Requested bind addr
|
|
int m_bindPort; // Requested bind port
|
|
};
|
|
|
|
/*
|
|
* YIAXDriver
|
|
*/
|
|
class YIAXDriver : public Driver
|
|
{
|
|
public:
|
|
enum Relay {
|
|
EngineStop = Private
|
|
};
|
|
YIAXDriver();
|
|
virtual ~YIAXDriver();
|
|
virtual void initialize();
|
|
virtual bool hasLine(const String& line) const;
|
|
virtual bool msgExecute(Message& msg, String& dest);
|
|
virtual bool msgRoute(Message& msg);
|
|
virtual bool received(Message& msg, int id);
|
|
// Find a channel. Return a refferenced pointer
|
|
inline Channel* findChan(Channel* chan) {
|
|
if (!chan)
|
|
return 0;
|
|
Lock lck(this);
|
|
return channels().find(chan) && chan->ref() ? chan : 0;
|
|
}
|
|
// Find an engine from parameters. Return a refferenced pointer
|
|
bool findEngine(RefPointer<YIAXEngine>& eng, const NamedList& params,
|
|
const String& prefix = String::empty(), YIAXLine* line = 0,
|
|
NamedList* copyParams = 0);
|
|
// Find an engine by name. Return true if found
|
|
inline bool findEngine(RefPointer<YIAXEngine>& engine, const String& name) {
|
|
if (!name)
|
|
return false;
|
|
Lock lck(m_enginesMutex);
|
|
engine = findEngine(name);
|
|
return engine != 0;
|
|
}
|
|
// Update codecs a list of parameters
|
|
// Return false if the result is 0 (no intersection)
|
|
bool updateCodecsFromRoute(u_int32_t& codecs, const NamedList& params, int type);
|
|
// Dispatch user.auth
|
|
// tr The IAX transaction
|
|
// response True if it is a response.
|
|
// requestAuth True on exit: the caller should request authentication
|
|
// invalidAuth True on exit: authentication response is incorrect
|
|
// billid The billing ID parameter if available
|
|
// Return false if not authenticated
|
|
bool userAuth(IAXTransaction* tr, bool response, bool& requestAuth,
|
|
bool& invalidAuth, const char* billid = 0);
|
|
// Start/stop engine list changes
|
|
void engineListChanging(bool start);
|
|
// Setup an engine
|
|
void setupEngine(const String& name, bool& valid, bool enable, bool def,
|
|
const NamedList& params, const NamedList* defaults = 0);
|
|
// Terminate exiting engines
|
|
void checkEngineTerminate(const Time& now);
|
|
|
|
protected:
|
|
virtual bool commandComplete(Message& msg, const String& partLine, const String& partWord);
|
|
virtual void msgStatus(Message& msg);
|
|
virtual void statusParams(String& str);
|
|
void msgStatusAccounts(Message& msg);
|
|
void msgStatusListeners(Message& msg);
|
|
virtual void genUpdate(Message& msg);
|
|
// Update default engine
|
|
void updateDefaultEngine();
|
|
// Terminate an engine. Terminate all if no engine is given
|
|
void terminateEngine(YIAXEngine* eng, bool final, int reason);
|
|
// Initialize formats from config
|
|
void initFormats(const NamedList& params);
|
|
// Find an engine by name
|
|
inline YIAXEngine* findEngine(const String& name) {
|
|
if (!name)
|
|
return 0;
|
|
for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext()) {
|
|
YIAXEngine* e = static_cast<YIAXEngine*>(o->get());
|
|
if (e->name() == name)
|
|
return e;
|
|
}
|
|
return 0;
|
|
}
|
|
// Find an engine by local ip/port
|
|
inline YIAXEngine* findEngine(const String& addr, int port) {
|
|
for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext()) {
|
|
YIAXEngine* e = static_cast<YIAXEngine*>(o->get());
|
|
if (e->addr().host() == addr && e->addr().port() == port)
|
|
return e;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ObjList m_engines;
|
|
Mutex m_enginesMutex;
|
|
YIAXEngine* m_defaultEngine; // The default engine
|
|
unsigned int m_haveEngTerminate; // The number of terminating engines
|
|
u_int32_t m_format; // The default media format
|
|
u_int32_t m_formatVideo; // Default video format
|
|
u_int32_t m_capability; // The media capability
|
|
unsigned int m_failedAuths;
|
|
bool m_init;
|
|
};
|
|
|
|
/*
|
|
* YIAXRegDataHandler
|
|
*/
|
|
class YIAXRegDataHandler : public MessageHandler
|
|
{
|
|
public:
|
|
YIAXRegDataHandler(const char* trackName)
|
|
: MessageHandler("user.login",150,trackName)
|
|
{ }
|
|
virtual bool received(Message &msg);
|
|
};
|
|
|
|
class YIAXConnection;
|
|
|
|
/*
|
|
* Base class for data consumer/source
|
|
*/
|
|
class YIAXData
|
|
{
|
|
public:
|
|
inline YIAXData(YIAXConnection* conn, u_int32_t format, int type)
|
|
: m_connection(conn), m_total(0), m_format(format), m_type(type)
|
|
{}
|
|
inline u_int32_t format()
|
|
{ return m_format; }
|
|
protected:
|
|
YIAXConnection* m_connection;
|
|
unsigned m_total;
|
|
u_int32_t m_format; // in IAX coding
|
|
int m_type;
|
|
};
|
|
|
|
/*
|
|
* Connection's data consumer
|
|
*/
|
|
class YIAXConsumer : public DataConsumer, public YIAXData
|
|
{
|
|
YCLASS(YIAXConsumer,DataConsumer)
|
|
public:
|
|
YIAXConsumer(YIAXConnection* conn, u_int32_t format, const char* formatText, int type);
|
|
~YIAXConsumer();
|
|
virtual unsigned long Consume(const DataBlock &data, unsigned long tStamp, unsigned long flags);
|
|
};
|
|
|
|
/*
|
|
* Connection's data source
|
|
*/
|
|
class YIAXSource : public DataSource, public YIAXData
|
|
{
|
|
YCLASS(YIAXSource,DataSource)
|
|
public:
|
|
YIAXSource(YIAXConnection* conn, u_int32_t format, const char* formatText, int type);
|
|
~YIAXSource();
|
|
unsigned long Forward(const DataBlock &data, unsigned long tStamp, unsigned long flags);
|
|
};
|
|
|
|
/*
|
|
* The connection
|
|
*/
|
|
class YIAXConnection : public Channel
|
|
{
|
|
friend class YIAXDriver;
|
|
public:
|
|
YIAXConnection(IAXTransaction* transaction, Message* msg = 0, NamedList* params = 0);
|
|
virtual ~YIAXConnection();
|
|
virtual void callAccept(Message& msg);
|
|
virtual void callRejected(const char* error, const char* reason = 0, const Message* msg = 0);
|
|
virtual bool callPrerouted(Message& msg, bool handled);
|
|
virtual bool callRouted(Message& msg);
|
|
virtual bool msgProgress(Message& msg);
|
|
virtual bool msgRinging(Message& msg);
|
|
virtual bool msgAnswered(Message& msg);
|
|
virtual bool msgTone(Message& msg, const char* tone);
|
|
virtual bool msgText(Message& msg, const char* text);
|
|
virtual void disconnected(bool final, const char* reason);
|
|
bool disconnect(const char* reason = 0);
|
|
inline IAXTransaction* transaction() const
|
|
{ return m_transaction; }
|
|
inline bool mutedIn() const
|
|
{ return m_mutedIn; }
|
|
inline bool mutedOut() const
|
|
{ return m_mutedOut; }
|
|
void handleEvent(IAXEvent* event);
|
|
bool route(IAXEvent* event, bool authenticated = false);
|
|
// Set reason
|
|
inline void setReason(const char* reason) {
|
|
Lock lck(m_mutexChan);
|
|
m_reason = reason;
|
|
}
|
|
// Retrieve the data consumer from IAXFormat media type
|
|
YIAXConsumer* getConsumer(int type);
|
|
// Retrieve the data source from IAXFormat media type
|
|
YIAXSource* getSource(int type);
|
|
// Retrieve the data source from IAXFormat
|
|
DataSource* getSourceMedia(int type);
|
|
|
|
protected:
|
|
// location: 0: from peer, else: from protocol
|
|
void hangup(int location, const char* error = 0, const char* reason = 0, bool reject = false);
|
|
void resetTransaction(NamedList* params = 0, bool reject = false);
|
|
void startMedia(bool in, int type = IAXFormat::Audio);
|
|
void clearMedia(bool in, int type = IAXFormat::Audio);
|
|
void evAuthRep(IAXEvent* event);
|
|
void evAuthReq(IAXEvent* event);
|
|
// Safe deref the connection if the reference counter was increased during registration
|
|
void safeDeref();
|
|
bool safeRefIncrease();
|
|
private:
|
|
RefPointer<IAXTransaction> m_transaction; // IAX transaction
|
|
String m_password; // Password for client authentication
|
|
bool m_mutedIn; // No remote media accepted
|
|
bool m_mutedOut; // No local media accepted
|
|
bool m_audio; // Audio endpoint was used
|
|
bool m_video; // Video endpoint was used
|
|
String m_reason; // Call end reason text
|
|
bool m_hangup; // Need to send chan.hangup message
|
|
Mutex m_mutexChan;
|
|
Mutex m_mutexTrans; // Safe m_transaction operations
|
|
Mutex m_mutexRefIncreased; // Safe ref/deref connection
|
|
bool m_refIncreased; // If true, the reference counter was increased
|
|
unsigned int m_routeCount; // Incoming: route counter
|
|
RefPointer<DataSource> m_sources[IAXFormat::TypeCount]; // Keep all types of sources
|
|
};
|
|
|
|
//
|
|
// An IAX URI parser
|
|
// [iax[2]:][username@]host[:port][/called_number[@called_context]]
|
|
//
|
|
class IAXURI : public String
|
|
{
|
|
public:
|
|
inline IAXURI(String& s) : String(s), m_port(0), m_parsed(false) {}
|
|
inline IAXURI(const char* s) : String(s), m_port(0), m_parsed(false) {}
|
|
IAXURI(const char* user, const char* host, const char* calledNo, const char* calledContext, int port = 4569);
|
|
inline ~IAXURI() {}
|
|
void parse();
|
|
bool fillList(NamedList& dest);
|
|
bool setAddr(SocketAddr& dest);
|
|
inline const String& username() const
|
|
{ return m_username; }
|
|
inline const String& host() const
|
|
{ return m_host; }
|
|
inline int port() const
|
|
{ return m_port; }
|
|
inline const String& calledNo() const
|
|
{ return m_calledNo; }
|
|
inline const String& calledContext() const
|
|
{ return m_calledContext; }
|
|
protected:
|
|
inline IAXURI() {}
|
|
private:
|
|
String m_username;
|
|
String m_host;
|
|
int m_port;
|
|
String m_calledNo;
|
|
String m_calledContext;
|
|
bool m_parsed;
|
|
};
|
|
|
|
|
|
/*
|
|
* Local data
|
|
*/
|
|
static YIAXLineContainer s_lines; // Lines
|
|
static YIAXDriver iplugin; // Init the driver
|
|
static bool s_callTokenOut = true; // Send an empty call token on outgoing calls
|
|
static unsigned int s_extendEngTerminate = 1000; // Extend engine terminate interval (milliseconds)
|
|
static unsigned int s_expires_min = 60;
|
|
static unsigned int s_expires_def = 60;
|
|
static unsigned int s_expires_max = 3600;
|
|
static bool s_authReq = true;
|
|
static unsigned int s_engineStop = 0;
|
|
static const String s_iaxEngineName = "iaxengine";
|
|
// Engine parameters to delete when using defaults
|
|
// They can't be inherited
|
|
static const String s_delEngParams[] = {"enable","default","addr","port","force_bind",""};
|
|
|
|
const TokenDict YIAXEngine::s_statusName[] = {
|
|
{"Listening", Listening},
|
|
{"Disabled", Disabled},
|
|
{"Removed", Removed},
|
|
{"Exiting", Exiting},
|
|
{"BindFailed", BindFailed},
|
|
{"Idle", Idle},
|
|
{0,0}
|
|
};
|
|
|
|
const TokenDict YIAXThread::s_operThName[] = {
|
|
{"YIAXListener", Listener},
|
|
{"YIAXGetEvent", GetEvent},
|
|
{"YIAXTrunking", Trunking},
|
|
{0,0}
|
|
};
|
|
|
|
static inline bool isGeneralEngine(const String& name)
|
|
{
|
|
return name == s_iaxEngineName;
|
|
}
|
|
|
|
// Retrieve data endpoint name from IAXFormat type
|
|
static inline const String& dataEpName(int type)
|
|
{
|
|
return IAXFormat::typeNameStr(type);
|
|
}
|
|
|
|
// Retrieve format name from IAXFormat type
|
|
static const char* lookupFormat(u_int32_t format, int type)
|
|
{
|
|
if (type == IAXFormat::Audio)
|
|
return lookup(format,dict_payloads);
|
|
if (type == IAXFormat::Video)
|
|
return lookup(format,dict_payloads_video);
|
|
return 0;
|
|
}
|
|
|
|
// Retrieve a parameter, find its value in a dictionary
|
|
// Return true if a non empty parameter was found
|
|
static inline bool getKeyword(const NamedList& list, const String& param,
|
|
const TokenDict* tokens, int& val)
|
|
{
|
|
const char* s = list.getValue(param);
|
|
if (s)
|
|
val = lookup(s,tokens);
|
|
return (s != 0);
|
|
}
|
|
|
|
// Retrieve a configuration section
|
|
static inline const NamedList& safeSect(Configuration& cfg, const String& name)
|
|
{
|
|
NamedList* sect = cfg.getSection(name);
|
|
if (sect)
|
|
return *sect;
|
|
return NamedList::empty();
|
|
}
|
|
|
|
// Clear multiple list parameters
|
|
static void clearListParams(NamedList& list, const String* params)
|
|
{
|
|
if (!params)
|
|
return;
|
|
for (; *params; params++)
|
|
list.clearParam(*params);
|
|
}
|
|
|
|
// Retrieve bind addr/port parameters
|
|
static inline void getBindAddr(const NamedList& params, String& addr, int& port,
|
|
bool* force = 0)
|
|
{
|
|
addr = params.getValue(YSTRING("addr"));
|
|
port = IAXEngine::getPort(params);
|
|
if (force)
|
|
*force = params.getBoolValue("force_bind",true);
|
|
}
|
|
|
|
|
|
//
|
|
// YIAXLine
|
|
//
|
|
// Create an idle line
|
|
YIAXLine::YIAXLine(const String& name)
|
|
: Mutex(true,"IAX:Line"),
|
|
m_name(name),
|
|
m_oper(YIAXLineContainer::Create),
|
|
m_callToken(false),
|
|
m_expire(60), m_remotePort(4569),
|
|
m_nextReg(0), m_nextKeepAlive(0),
|
|
m_keepAliveInterval(0),
|
|
m_registered(false),
|
|
m_transaction(0),
|
|
m_trunkInfo(""),
|
|
m_trunking(false),
|
|
m_engineToUse("")
|
|
{
|
|
Debug(&iplugin,DebugAll,"Line(%s) created [%p]",m_name.c_str(),this);
|
|
}
|
|
|
|
YIAXLine::~YIAXLine()
|
|
{
|
|
Debug(&iplugin,DebugAll,"Line(%s) destroyed [%p]",m_name.c_str(),this);
|
|
}
|
|
|
|
// Set the registered status, emits user.notify messages if necessary
|
|
void YIAXLine::setRegistered(bool registered, const char* reason, const char* error)
|
|
{
|
|
if (m_registered && registered)
|
|
return;
|
|
m_registered = registered;
|
|
if (!m_registered) {
|
|
m_nextKeepAlive = 0;
|
|
m_remote.clear();
|
|
}
|
|
Message* m = new Message("user.notify");
|
|
m->addParam("account",toString());
|
|
m->addParam("protocol","iax");
|
|
m->addParam("username",m_username);
|
|
m->addParam("registered",String::boolText(registered));
|
|
m->addParam("reason",reason,false);
|
|
m->addParam("error",error,false);
|
|
Engine::enqueue(m);
|
|
}
|
|
|
|
|
|
//
|
|
// YIAXLineContainer
|
|
//
|
|
// Set a line's transaction
|
|
void YIAXLineContainer::setTransaction(YIAXLine* line, IAXTransaction* tr, bool abortReg)
|
|
{
|
|
if (!line)
|
|
return;
|
|
Lock lck(m_lineTransMutex);
|
|
if (line->transaction() == tr)
|
|
return;
|
|
if (line->transaction()) {
|
|
Debug(&iplugin,DebugAll,"Line(%s) clearing transaction (%p) [%p]",
|
|
line->toString().c_str(),line->transaction(),line);
|
|
RefPointer<IAXTransaction> trans = line->m_transaction;
|
|
line->m_transaction = 0;
|
|
if (trans)
|
|
trans->setUserData(0);
|
|
lck.drop();
|
|
if (abortReg && trans)
|
|
trans->abortReg();
|
|
trans = 0;
|
|
}
|
|
if (!tr)
|
|
return;
|
|
line->m_transaction = tr;
|
|
tr->setUserData(line);
|
|
Debug(&iplugin,DebugAll,
|
|
"Line(%s) set transaction (%p) listener='%s' callno=%u [%p]",
|
|
line->toString().c_str(),tr,tr->getEngine()->name().c_str(),
|
|
tr->localCallNo(),line);
|
|
}
|
|
|
|
// Find and update a line with parameters from message, create if needed
|
|
bool YIAXLineContainer::updateLine(Message& msg)
|
|
{
|
|
const String& name = msg[YSTRING("account")];
|
|
if (!name)
|
|
return false;
|
|
int oper = Unknown;
|
|
const String& op = msg[YSTRING("operation")];
|
|
if (op == YSTRING("login"))
|
|
oper = Login;
|
|
else if (op == YSTRING("create"))
|
|
oper = Create;
|
|
else if (op == YSTRING("logout"))
|
|
oper = Logout;
|
|
else if (op == YSTRING("delete"))
|
|
oper = Delete;
|
|
else
|
|
return false;
|
|
Lock lck(this);
|
|
Debug(&iplugin,DebugAll,"Updating line '%s' oper=%s",name.c_str(),op.c_str());
|
|
RefPointer<YIAXLine> line = findLine(name);
|
|
bool oldLine = true;
|
|
if (!line) {
|
|
if (oper != Login && oper != Create)
|
|
return false;
|
|
line = new YIAXLine(name);
|
|
m_lines.append((YIAXLine*)line);
|
|
oldLine = false;
|
|
}
|
|
lck.drop();
|
|
line->lock();
|
|
if (oper == Login || oper == Create) {
|
|
bool changed = (line->m_oper != oper);
|
|
String addr = msg.getValue("server",msg.getValue("domain"));
|
|
int pos = addr.find(":");
|
|
int port = -1;
|
|
if (pos >= 0) {
|
|
port = addr.substr(pos + 1).toInteger();
|
|
addr = addr.substr(0,pos);
|
|
}
|
|
else
|
|
port = IAXEngine::getPort(msg);
|
|
if (port > 0)
|
|
changeLine(changed,line,line->m_remotePort,port);
|
|
line->m_keepAliveInterval = msg.getIntValue("keepalive",25,0);
|
|
changeLine(changed,line,line->m_remoteAddr,addr);
|
|
changeLine(changed,line,line->m_username,msg.getValue("username"));
|
|
changeLine(changed,line,line->m_password,msg.getValue("password"));
|
|
changeLine(changed,line,line->m_callingNo,msg.getValue("caller"));
|
|
changeLine(changed,line,line->m_callingName,msg.getValue("callername"));
|
|
unsigned int interval = msg.getIntValue("interval",60,60,0xffff);
|
|
changeLine(changed,line,line->m_expire,interval);
|
|
line->m_callToken = msg.getBoolValue("calltoken",s_callTokenOut);
|
|
line->m_trunking = msg.getBoolValue("trunking");
|
|
line->m_trunkInfo.clearParams();
|
|
line->m_trunkInfo.copySubParams(msg,"trunk_");
|
|
line->m_engineToUse.clearParams();
|
|
RefPointer<YIAXEngine> engine;
|
|
iplugin.findEngine(engine,msg,String::empty(),0,&line->m_engineToUse);
|
|
if (engine != line->engine()) {
|
|
if (line->engine()) {
|
|
// Registering: reset transaction and unregister
|
|
// Unregistering: let the transaction finish
|
|
if (line->registered()) {
|
|
setTransaction(line);
|
|
registerLine(line,false);
|
|
}
|
|
}
|
|
line->m_engine = engine;
|
|
}
|
|
if (changed || oper == Login)
|
|
line->m_oper = Login;
|
|
else if (line->m_oper != Login && line->m_oper != Create)
|
|
line->m_oper = oper;
|
|
if (engine) {
|
|
if (!busy(line) && (changed || line->m_oper == Login))
|
|
registerLine(line,true);
|
|
}
|
|
else if (line->m_oper == Login)
|
|
Debug(&iplugin,DebugNote,"No listener to register line '%s'",name.c_str());
|
|
}
|
|
else {
|
|
if (line->m_registered)
|
|
unregisterLineNonBusy(line);
|
|
line->m_oper = oper;
|
|
}
|
|
line->unlock();
|
|
if (oper == Delete) {
|
|
if (!busy(line))
|
|
m_lines.remove(line);
|
|
}
|
|
else if (oldLine) {
|
|
// The line might be removed in regTerminate()
|
|
Lock lck(this);
|
|
if (!findLine((YIAXLine*)line) && line->ref())
|
|
m_lines.append(m_lines.append((YIAXLine*)line));
|
|
}
|
|
line = 0;
|
|
return true;
|
|
}
|
|
|
|
// Re-check lines engine
|
|
void YIAXLineContainer::updateEngine()
|
|
{
|
|
lock();
|
|
ListIterator iter(m_lines);
|
|
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
|
|
RefPointer<YIAXLine> line = static_cast<YIAXLine*>(gen);
|
|
if (!line)
|
|
continue;
|
|
unlock();
|
|
line->lock();
|
|
if (!line->engine())
|
|
iplugin.findEngine(line->m_engine,line->m_engineToUse);
|
|
if (line->engine() && line->m_oper == Login && !line->m_registered &&
|
|
!line->m_nextReg && !busy(line))
|
|
registerLine(line,true);
|
|
line->unlock();
|
|
line = 0;
|
|
lock();
|
|
}
|
|
unlock();
|
|
}
|
|
|
|
// Handle registration related transaction terminations
|
|
void YIAXLineContainer::regTerminate(IAXEvent* event)
|
|
{
|
|
YIAXLine* line = findLine(event);
|
|
DDebug(&iplugin,DebugAll,"YIAXLineContainer::regTerminate() ev=%d line=%p (%s)",
|
|
event ? event->type() : -1,line,line ? line->toString().c_str() : "");
|
|
if (!line)
|
|
return;
|
|
IAXTransaction* tr = event->getTransaction();
|
|
line->lock();
|
|
String reason;
|
|
bool accepted = false;
|
|
bool rejected = false;
|
|
switch (event->type()) {
|
|
case IAXEvent::Accept:
|
|
accepted = true;
|
|
break;
|
|
case IAXEvent::Reject:
|
|
rejected = true;
|
|
event->getList().getString(IAXInfoElement::CAUSE,reason);
|
|
break;
|
|
case IAXEvent::Timeout:
|
|
reason = "timeout";
|
|
break;
|
|
case IAXEvent::Terminated:
|
|
reason = "failure";
|
|
break;
|
|
default:
|
|
line->unlock();
|
|
TelEngine::destruct(line);
|
|
return;
|
|
}
|
|
line->m_remote.clear();
|
|
// Line is going to perform another operation
|
|
if (line->engine()) {
|
|
// Engine changed ?
|
|
if (line->engine() == tr->getEngine()) {
|
|
setTransaction(line,0,false);
|
|
bool reg = (tr->type() == IAXTransaction::RegReq);
|
|
bool notify = true;
|
|
// Operation changed ?
|
|
if (line->m_oper == Login) {
|
|
if (reg) {
|
|
if (accepted) {
|
|
// Honor server registration refresh. Assume default (60) if missing
|
|
unsigned int interval = 60;
|
|
if (event->getList().getNumeric(IAXInfoElement::REFRESH,interval) && !interval)
|
|
interval = 60;
|
|
if (line->m_expire != interval) {
|
|
DDebug(&iplugin,DebugNote,
|
|
"Line(%s) changed expire interval from %d to %u [%p]",
|
|
line->toString().c_str(),line->m_expire,interval,line);
|
|
line->m_expire = interval;
|
|
}
|
|
if (tr->callingNo())
|
|
line->m_callingNo = tr->callingNo();
|
|
if (tr->callingName())
|
|
line->m_callingName = tr->callingName();
|
|
// Re-register at 75% of the expire time
|
|
line->setNextRegFromExpire(3,4);
|
|
Lock lck(tr);
|
|
line->m_remote = tr->remoteAddr();
|
|
}
|
|
else if (rejected)
|
|
// retry at 25% of the expire time
|
|
line->setNextRegFromExpire(1,4);
|
|
else
|
|
// retry at 50% of the expire time
|
|
line->setNextRegFromExpire(1,2);
|
|
}
|
|
else {
|
|
notify = false;
|
|
registerLine(line,true);
|
|
}
|
|
}
|
|
else if (line->m_oper == Logout || line->m_oper == Delete) {
|
|
if (reg && accepted) {
|
|
line->m_registered = true;
|
|
notify = false;
|
|
registerLine(line,false);
|
|
}
|
|
else
|
|
line->m_nextReg = 0;
|
|
}
|
|
if (notify) {
|
|
const char* what = (line->m_oper == Login) ? "logon" : "logoff";
|
|
const char* tf = (line->m_oper == Login) ? "to" : "from";
|
|
if (accepted)
|
|
Debug(&iplugin,DebugCall,"IAX line '%s' %s success %s %s:%d",
|
|
line->toString().c_str(),what,tf,line->m_remoteAddr.c_str(),
|
|
line->m_remotePort);
|
|
else
|
|
Debug(&iplugin,DebugWarn,"IAX line '%s' %s failure %s %s:%d reason=%s",
|
|
line->toString().c_str(),what,tf,line->m_remoteAddr.c_str(),
|
|
line->m_remotePort,reason.safe());
|
|
line->setRegistered(reg && accepted,reason,rejected ? "noauth" : 0);
|
|
}
|
|
}
|
|
else {
|
|
bool process = true;
|
|
// Unregister from the old one if registered
|
|
if (accepted && tr->type() == IAXTransaction::RegReq) {
|
|
RefPointer<YIAXEngine> tmp = line->engine();
|
|
line->m_engine = static_cast<YIAXEngine*>(tr->getEngine());
|
|
setTransaction(line,0,false);
|
|
process = !(line->engine() && registerLine(line,false));
|
|
line->m_engine = tmp;
|
|
tmp = 0;
|
|
}
|
|
// Register to the new one
|
|
if (process) {
|
|
setTransaction(line,0,false);
|
|
if (line->m_oper == Login)
|
|
registerLine(line,true);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// Line has no engine
|
|
// Unregister from the old one if registered
|
|
if (accepted && tr->type() == IAXTransaction::RegReq)
|
|
line->m_engine = static_cast<YIAXEngine*>(tr->getEngine());
|
|
setTransaction(line,0,false);
|
|
if (!(line->engine() && registerLine(line,false)))
|
|
line->setRegistered(false,reason,rejected ? "noauth" : 0);
|
|
line->m_engine = 0;
|
|
line->m_nextReg = 0;
|
|
}
|
|
bool remove = (line->m_oper == Delete && !busy(line));
|
|
line->unlock();
|
|
if (remove) {
|
|
Lock lock(this);
|
|
m_lines.remove(line);
|
|
}
|
|
TelEngine::destruct(line);
|
|
}
|
|
|
|
// Handle registration related authentication
|
|
void YIAXLineContainer::regAuthReq(IAXEvent* event)
|
|
{
|
|
YIAXLine* line = findLine(event);
|
|
DDebug(&iplugin,DebugAll,"YIAXLineContainer::regAuthReq() ev=%d line=%p",
|
|
event ? event->type() : -1,line);
|
|
IAXTransaction* tr = event->getTransaction();
|
|
if (!line) {
|
|
if (tr)
|
|
tr->setDestroy();
|
|
return;
|
|
}
|
|
line->lock();
|
|
String response;
|
|
tr->getEngine()->getMD5FromChallenge(response,tr->challenge(),line->password());
|
|
line->unlock();
|
|
tr->sendAuthReply(response);
|
|
TelEngine::destruct(line);
|
|
}
|
|
|
|
// Handle registration related events
|
|
void YIAXLineContainer::handleEvent(IAXEvent* event)
|
|
{
|
|
switch (event->type()) {
|
|
case IAXEvent::Accept:
|
|
case IAXEvent::Reject:
|
|
case IAXEvent::Timeout:
|
|
case IAXEvent::Terminated:
|
|
regTerminate(event);
|
|
break;
|
|
case IAXEvent::AuthReq:
|
|
regAuthReq(event);
|
|
break;
|
|
default:
|
|
if (event->getTransaction())
|
|
event->getTransaction()->getEngine()->defaultEventHandler(event);
|
|
}
|
|
}
|
|
|
|
// Tick the timer for all lines, send keepalives and reregister
|
|
void YIAXLineContainer::evTimer(Time& time)
|
|
{
|
|
lock();
|
|
ListIterator iter(m_lines);
|
|
GenObject* gen = 0;
|
|
while (0 != (gen = iter.get())) {
|
|
RefPointer<YIAXLine> line = static_cast<YIAXLine*>(gen);
|
|
if (!line)
|
|
continue;
|
|
unlock();
|
|
line->lock();
|
|
if (line->m_nextReg && line->m_nextReg <= time)
|
|
registerLine(line,line->m_oper == Login);
|
|
else if (line->m_registered && line->m_keepAliveInterval &&
|
|
time > line->m_nextKeepAlive) {
|
|
if (line->m_nextKeepAlive && line->remote().host() && line->engine() &&
|
|
line->engine()->valid()) {
|
|
DDebug(&iplugin,DebugAll,"Line(%s) sending keep alive to %s:%d [%p]",
|
|
line->toString().c_str(),line->remote().host().c_str(),
|
|
line->remote().port(),(YIAXLine*)line);
|
|
line->engine()->keepAlive(line->remote());
|
|
}
|
|
line->m_nextKeepAlive = time + line->m_keepAliveInterval * 1000000;
|
|
}
|
|
else if (line->m_oper == Delete && !busy(line))
|
|
m_lines.remove((YIAXLine*)line);
|
|
line->unlock();
|
|
line = 0;
|
|
lock();
|
|
}
|
|
unlock();
|
|
}
|
|
|
|
// Fill parameters with information taken from line
|
|
void YIAXLineContainer::fillList(YIAXLine& line, NamedList& dest, SocketAddr& addr)
|
|
{
|
|
line.lock();
|
|
dest.setParam("username",line.username());
|
|
dest.setParam("password",line.password());
|
|
if (line.callingNo())
|
|
dest.setParam("caller",line.callingNo());
|
|
if (line.callingName())
|
|
dest.setParam("callername",line.callingName());
|
|
String a = line.remoteAddr();
|
|
int p = line.remotePort();
|
|
line.unlock();
|
|
addr.host(a);
|
|
addr.port(p);
|
|
}
|
|
|
|
// Find line from event. Clear event transaction if line is not found
|
|
// Return referenced pointer
|
|
YIAXLine* YIAXLineContainer::findLine(IAXEvent* ev)
|
|
{
|
|
if (!ev)
|
|
return 0;
|
|
IAXTransaction* tr = ev->getTransaction();
|
|
if (!tr)
|
|
return 0;
|
|
Lock2 lck(this,&m_lineTransMutex);
|
|
YIAXLine* line = findLine(static_cast<YIAXLine*>(tr->getUserData()));
|
|
bool ok = line && line->m_transaction == tr && line->ref();
|
|
if (!ok)
|
|
tr->setUserData(0);
|
|
lck.drop();
|
|
if (!ok) {
|
|
tr->abortReg();
|
|
line = 0;
|
|
}
|
|
return line;
|
|
}
|
|
|
|
// Initiate line (un)register
|
|
bool YIAXLineContainer::registerLine(YIAXLine* line, bool reg)
|
|
{
|
|
if (!line)
|
|
return false;
|
|
line->m_nextReg = 0;
|
|
line->m_nextKeepAlive = 0;
|
|
setTransaction(line);
|
|
if (!reg)
|
|
line->m_registered = false;
|
|
if (!(line->engine() && line->engine()->bound()))
|
|
return false;
|
|
// Deny register on exiting engines
|
|
if (reg && line->engine()->exiting()) {
|
|
line->m_engine = 0;
|
|
return false;
|
|
}
|
|
SocketAddr addr(AF_INET);
|
|
addr.host(line->remoteAddr());
|
|
addr.port(line->remotePort());
|
|
Debug(&iplugin,DebugAll,"%s line '%s' listener='%s' server=%s:%d [%p]",
|
|
reg ? "Registering" : "Unregistering",line->toString().c_str(),
|
|
line->engine()->name().c_str(),addr.host().c_str(),addr.port(),line);
|
|
// Create IE list
|
|
IAXIEList ieList;
|
|
ieList.appendString(IAXInfoElement::USERNAME,line->username());
|
|
ieList.appendString(IAXInfoElement::PASSWORD,line->password());
|
|
ieList.appendNumeric(IAXInfoElement::REFRESH,line->expire(),2);
|
|
if (line->callToken())
|
|
ieList.appendBinary(IAXInfoElement::CALLTOKEN,0,0);
|
|
IAXTransaction* tr = line->engine()->reg(reg,addr,ieList);
|
|
if (!tr)
|
|
return false;
|
|
setTransaction(line,tr);
|
|
tr->start();
|
|
TelEngine::destruct(tr);
|
|
return true;
|
|
}
|
|
|
|
// Unregister all lines
|
|
void YIAXLineContainer::clear(bool final, YIAXEngine* eng)
|
|
{
|
|
if (final && !eng) {
|
|
Lock lock(this);
|
|
m_lines.clear();
|
|
return;
|
|
}
|
|
lock();
|
|
ListIterator iter(m_lines);
|
|
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
|
|
RefPointer<YIAXLine> line = static_cast<YIAXLine*>(gen);
|
|
if (!line)
|
|
continue;
|
|
unlock();
|
|
line->lock();
|
|
if (!eng || eng == line->engine()) {
|
|
if (final) {
|
|
setTransaction(line);
|
|
line->m_engine = 0;
|
|
if (line->registered())
|
|
line->setRegistered(false,"notransport");
|
|
}
|
|
else if (line->registered()) {
|
|
setTransaction(line);
|
|
registerLine(line,false);
|
|
}
|
|
}
|
|
line->unlock();
|
|
line = 0;
|
|
lock();
|
|
}
|
|
unlock();
|
|
}
|
|
|
|
|
|
//
|
|
// Engine thread
|
|
//
|
|
void YIAXThread::run()
|
|
{
|
|
if (!m_engine)
|
|
return;
|
|
DDebug(m_engine,DebugAll,"%s started [%p]",currentName(),this);
|
|
switch (m_oper) {
|
|
case Listener:
|
|
m_engine->readSocket();
|
|
break;
|
|
case GetEvent:
|
|
m_engine->runGetEvents();
|
|
break;
|
|
case Trunking:
|
|
m_engine->runProcessTrunkFrames();
|
|
break;
|
|
default:
|
|
DDebug(m_engine,DebugStub,"YIAXThread::run() not defined oper %d",m_oper);
|
|
}
|
|
notify(false);
|
|
}
|
|
|
|
void YIAXThread::notify(bool final)
|
|
{
|
|
if (!m_engine)
|
|
return;
|
|
if (!final)
|
|
DDebug(m_engine,DebugAll,"Thread '%s' terminated [%p]",operThName(),this);
|
|
else
|
|
Debug(m_engine,DebugWarn,"Thread '%s' abnormally terminated [%p]",
|
|
operThName(),this);
|
|
m_engine->threadTerminated(this);
|
|
m_engine = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// YIAXEngine
|
|
//
|
|
YIAXEngine::YIAXEngine(const String& name, const char* iface, int port,
|
|
const NamedList* params)
|
|
: IAXEngine(iface,port,0,0,params,name),
|
|
m_initialized(true),
|
|
m_default(false),
|
|
m_timeout(0),
|
|
m_status(Idle),
|
|
m_bindAddr(iface),
|
|
m_bindPort(port)
|
|
{
|
|
if (socket().valid())
|
|
changeStatus(Listening);
|
|
else
|
|
changeStatus(BindFailed);
|
|
}
|
|
|
|
void YIAXEngine::changeStatus(int stat)
|
|
{
|
|
if (stat == m_status)
|
|
return;
|
|
Debug(this,DebugInfo,"Status changed %s -> %s [%p]",
|
|
statusName(),statusName(stat),this);
|
|
m_status = stat;
|
|
}
|
|
|
|
bool YIAXEngine::bind(const NamedList& params)
|
|
{
|
|
bool force = false;
|
|
getBindAddr(params,m_bindAddr,m_bindPort,&force);
|
|
if (IAXEngine::bind(m_bindAddr,m_bindPort,force)) {
|
|
changeStatus(Listening);
|
|
return true;
|
|
}
|
|
changeStatus(BindFailed);
|
|
return false;
|
|
}
|
|
|
|
// Schedule for termination
|
|
void YIAXEngine::setTerminate(int stat)
|
|
{
|
|
setExiting();
|
|
Lock lck(this);
|
|
changeStatus(stat);
|
|
RefPointer<IAXTrunkInfo> ti;
|
|
trunkInfo(ti);
|
|
unsigned int tout = ti ? overallTout(ti->m_retransInterval,ti->m_retransCount) :
|
|
overallTout();
|
|
ti = 0;
|
|
tout += s_extendEngTerminate;
|
|
m_timeout = Time::now() + tout * 1000;
|
|
Debug(this,DebugAll,"Set terminate timeout=%ums status=%s [%p]",
|
|
tout,statusName(),this);
|
|
}
|
|
|
|
// Setup a given transaction from parameters
|
|
void YIAXEngine::initTransaction(IAXTransaction* tr, const NamedList& params, YIAXLine* line)
|
|
{
|
|
if (!tr)
|
|
return;
|
|
String prefix = tr->outgoing() ? "trunkout" : "trunkin";
|
|
String pref = prefix + "_";
|
|
IAXTrunkInfo* trunk = 0;
|
|
Lock lckLine(line);
|
|
bool trunkOut = params.getBoolValue(prefix,line && line->trunking());
|
|
if (line && line->trunkInfo().getParam(0)) {
|
|
RefPointer<IAXTrunkInfo> def;
|
|
tr->getEngine()->trunkInfo(def);
|
|
trunk = new IAXTrunkInfo;
|
|
trunk->initTrunking(line->trunkInfo(),String::empty(),def,trunkOut,true);
|
|
trunk->updateTrunking(params,pref,trunkOut,true);
|
|
}
|
|
lckLine.drop();
|
|
tr->getEngine()->initOutDataAdjust(params,tr);
|
|
if (trunkOut) {
|
|
if (trunk)
|
|
enableTrunking(tr,*trunk);
|
|
else
|
|
enableTrunking(tr,¶ms,pref);
|
|
}
|
|
if (trunk)
|
|
initTrunkIn(tr,*trunk);
|
|
else
|
|
initTrunkIn(tr,¶ms,pref);
|
|
TelEngine::destruct(trunk);
|
|
}
|
|
|
|
// Handle received voice data, forward it to connection's source
|
|
void YIAXEngine::processMedia(IAXTransaction* transaction, DataBlock& data,
|
|
u_int32_t tStamp, int type, bool mark)
|
|
{
|
|
if (transaction) {
|
|
YIAXConnection* conn = static_cast<YIAXConnection*>(transaction->getUserData());
|
|
if (conn) {
|
|
DataSource* src = conn->getSourceMedia(type);
|
|
if (src) {
|
|
unsigned long flags = 0;
|
|
if (mark)
|
|
flags = DataNode::DataMark;
|
|
src->Forward(data,tStamp,flags);
|
|
}
|
|
else
|
|
DDebug(this,DebugAll,"processMedia. No media source [%p]",this);
|
|
}
|
|
else
|
|
DDebug(this,DebugAll,
|
|
"processMedia. Transaction doesn't have a connection [%p]",this);
|
|
}
|
|
else
|
|
DDebug(this,DebugAll,"processMedia. No transaction [%p]",this);
|
|
}
|
|
|
|
// Create a new call transaction from target address and message params
|
|
IAXTransaction* YIAXEngine::call(SocketAddr& addr, NamedList& params)
|
|
{
|
|
Debug(this,DebugAll,
|
|
"Outgoing Call: username=%s host=%s:%d called=%s called context=%s [%p]",
|
|
params.getValue("username"),addr.host().c_str(),addr.port(),
|
|
params.getValue("called"),params.getValue("calledname"),this);
|
|
// Set format and capabilities
|
|
u_int32_t audioCaps = capability();
|
|
u_int32_t videoCaps = 0;
|
|
iplugin.updateCodecsFromRoute(audioCaps,params,IAXFormat::Audio);
|
|
if (params.getBoolValue("media_video")) {
|
|
videoCaps = capability();
|
|
iplugin.updateCodecsFromRoute(videoCaps,params,IAXFormat::Video);
|
|
}
|
|
u_int32_t fmtAudio = IAXFormat::pickFormat(audioCaps,format(true));
|
|
u_int32_t fmtVideo = IAXFormat::pickFormat(videoCaps,format(false));
|
|
u_int32_t codecs = audioCaps | videoCaps;
|
|
u_int32_t fmt = fmtAudio | fmtVideo;
|
|
if (!fmt) {
|
|
DDebug(this,DebugNote,"Outgoing call failed: No preffered format [%p]",this);
|
|
params.setParam("error","nomedia");
|
|
return 0;
|
|
}
|
|
// Create IE list
|
|
IAXIEList ieList;
|
|
ieList.appendString(IAXInfoElement::USERNAME,params.getValue("username"));
|
|
ieList.appendString(IAXInfoElement::PASSWORD,params.getValue("password"));
|
|
ieList.appendString(IAXInfoElement::CALLING_NUMBER,params.getValue("caller"));
|
|
int tmp = 0;
|
|
if (getKeyword(params,YSTRING("callernumtype"),IAXInfoElement::s_typeOfNumber,tmp))
|
|
ieList.appendNumeric(IAXInfoElement::CALLINGTON,tmp,1);
|
|
u_int8_t cPres = callingPres();
|
|
if (getKeyword(params,YSTRING("callerpres"),IAXInfoElement::s_presentation,tmp))
|
|
cPres = (cPres & 0x0f) | tmp;
|
|
if (getKeyword(params,YSTRING("callerscreening"),IAXInfoElement::s_screening,tmp))
|
|
cPres = (cPres & 0xf0) | tmp;
|
|
ieList.appendNumeric(IAXInfoElement::CALLINGPRES,cPres,1);
|
|
ieList.appendString(IAXInfoElement::CALLING_NAME,params.getValue("callername"));
|
|
ieList.appendString(IAXInfoElement::CALLED_NUMBER,params.getValue("called"));
|
|
ieList.appendString(IAXInfoElement::CALLED_CONTEXT,params.getValue("iaxcontext"));
|
|
ieList.appendNumeric(IAXInfoElement::FORMAT,fmt,4);
|
|
ieList.appendNumeric(IAXInfoElement::CAPABILITY,codecs,4);
|
|
if (params.getBoolValue("calltoken_out",s_callTokenOut))
|
|
ieList.appendBinary(IAXInfoElement::CALLTOKEN,0,0);
|
|
return startLocalTransaction(IAXTransaction::New,addr,ieList,true,false);
|
|
}
|
|
|
|
// Create a POKE transaction
|
|
IAXTransaction* YIAXEngine::poke(SocketAddr& addr)
|
|
{
|
|
Debug(this,DebugAll,"Outgoing POKE: Host: %s Port: %d [%p]",
|
|
addr.host().c_str(),addr.port(),this);
|
|
IAXIEList ieList;
|
|
return startLocalTransaction(IAXTransaction::Poke,addr,ieList);
|
|
}
|
|
|
|
// Cancel threads
|
|
void YIAXEngine::cancelThreads(unsigned int waitTerminateMs)
|
|
{
|
|
Lock lck(this);
|
|
ObjList* o = m_threads.skipNull();
|
|
if (!o)
|
|
return;
|
|
for (; o; o = o->skipNext())
|
|
(static_cast<YIAXThread*>(o->get()))->cancel(false);
|
|
if (!waitTerminateMs)
|
|
return;
|
|
lck.drop();
|
|
unsigned int intervals = waitTerminateMs / Thread::idleMsec();
|
|
if (!intervals)
|
|
intervals++;
|
|
for (; intervals; intervals--) {
|
|
Thread::idle(false);
|
|
if (Thread::check(false))
|
|
break;
|
|
Lock lck(this);
|
|
if (!m_threads.skipNull())
|
|
break;
|
|
}
|
|
}
|
|
|
|
void YIAXEngine::destroyed()
|
|
{
|
|
cancelThreads();
|
|
Debug(this,DebugAll,"Destroyed [%p]",this);
|
|
RefObject::destroyed();
|
|
}
|
|
|
|
// Process all IAX events
|
|
void YIAXEngine::processEvent(IAXEvent* event)
|
|
{
|
|
IAXTransaction* tr = event ? event->getTransaction() : 0;
|
|
if (!tr) {
|
|
if (event)
|
|
delete event;
|
|
return;
|
|
}
|
|
if (tr->type() == IAXTransaction::New) {
|
|
if (tr->getUserData()) {
|
|
Channel* chan = static_cast<Channel*>(tr->getUserData());
|
|
YIAXConnection* conn = static_cast<YIAXConnection*>(iplugin.findChan(chan));
|
|
if (conn) {
|
|
// We already have a channel for this call
|
|
conn->handleEvent(event);
|
|
if (event->final()) {
|
|
// Final event: disconnect
|
|
DDebug(this,DebugAll,"processEvent. Disconnecting (%p): '%s' [%p]",
|
|
conn,conn->id().c_str(),this);
|
|
conn->disconnect();
|
|
}
|
|
TelEngine::destruct(conn);
|
|
}
|
|
else {
|
|
Debug(this,DebugNote,
|
|
"No connection (%p) for transaction (%p) callno=%u [%p]",
|
|
conn,tr,tr->localCallNo(),this);
|
|
tr->setDestroy();
|
|
}
|
|
}
|
|
else if (event->type() == IAXEvent::New) {
|
|
// Incoming request for a new call
|
|
if (iplugin.canAccept(true) && !exiting()) {
|
|
YIAXConnection* conn = new YIAXConnection(tr);
|
|
conn->initChan();
|
|
tr->setUserData(conn);
|
|
if (!conn->route(event))
|
|
tr->setUserData(0);
|
|
}
|
|
else {
|
|
Debug(&iplugin,DebugWarn,"Refusing new IAX call, full or exiting [%p]",
|
|
this);
|
|
// Cause code 42: switch congestion
|
|
tr->sendReject(0,42);
|
|
}
|
|
}
|
|
}
|
|
else if (tr->type() == IAXTransaction::RegReq ||
|
|
tr->type() == IAXTransaction::RegRel) {
|
|
if (tr->outgoing())
|
|
s_lines.handleEvent(event);
|
|
else if (event->type() == IAXEvent::New || event->type() == IAXEvent::AuthRep)
|
|
processRemoteReg(event,(event->type() == IAXEvent::New));
|
|
}
|
|
delete event;
|
|
}
|
|
|
|
// Process a new format received with a full frame
|
|
bool YIAXEngine::mediaFormatChanged(IAXTransaction* trans, int type, u_int32_t format)
|
|
{
|
|
if (!trans)
|
|
return false;
|
|
Debug(this,DebugNote,"Refusing media change for transaction (%p) [%p]",trans,this);
|
|
// TODO: implement
|
|
return false;
|
|
}
|
|
|
|
// Process events for remote users registering to us
|
|
void YIAXEngine::processRemoteReg(IAXEvent* event, bool first)
|
|
{
|
|
IAXTransaction* tr = event->getTransaction();
|
|
DDebug(this,DebugAll,"processRemoteReg: %s username: '%s' [%p]",
|
|
tr->type() == IAXTransaction::RegReq?"Register":"Unregister",
|
|
tr->username().c_str(),this);
|
|
// Check for automatomatically authentication request if it's the first request
|
|
if (first && s_authReq) {
|
|
DDebug(this,DebugAll,"processRemoteReg. Requesting authentication [%p]",this);
|
|
tr->sendAuth();
|
|
return;
|
|
}
|
|
// Authenticated: register/unregister
|
|
bool requestAuth = false, invalidAuth = false;
|
|
if (iplugin.userAuth(tr,!first,requestAuth,invalidAuth)) {
|
|
// Authenticated. Try to (un)register
|
|
const char* user = tr->username();
|
|
bool regrel = (event->subclass() == IAXControl::RegRel);
|
|
Message* msg = userreg(tr,regrel);
|
|
if (msg) {
|
|
unsigned int exp = 0;
|
|
if (!regrel) {
|
|
int expires = msg->getIntValue("expires");
|
|
if (expires < 1)
|
|
expires = s_expires_def;
|
|
exp = expires;
|
|
Debug(&iplugin,DebugNote,"Registered user '%s' expires in %us [%p]",
|
|
user,exp,this);
|
|
}
|
|
else
|
|
Debug(&iplugin,DebugNote,"Unregistered user '%s' [%p]",user,this);
|
|
tr->sendAccept(&exp);
|
|
TelEngine::destruct(msg);
|
|
}
|
|
else {
|
|
Debug(&iplugin,DebugNote,"%s failed for authenticated user '%s' [%p]",
|
|
regrel ? "Unregister" : "Register",user,this);
|
|
tr->sendReject("not registered");
|
|
}
|
|
return;
|
|
}
|
|
// First request: check if we should request auth
|
|
if (first && requestAuth) {
|
|
DDebug(this,DebugAll,"processRemoteReg. Requesting authentication [%p]",this);
|
|
tr->sendAuth();
|
|
return;
|
|
}
|
|
const char* reason = 0;
|
|
if (invalidAuth)
|
|
reason = IAXTransaction::s_iax_modInvalidAuth;
|
|
if (!reason)
|
|
reason = "not authenticated";
|
|
Debug(&iplugin,DebugNote,"User '%s' addr=%s:%d not authenticated reason=%s' [%p]",
|
|
tr->username().c_str(),tr->remoteAddr().host().c_str(),
|
|
tr->remoteAddr().port(),reason,this);
|
|
tr->sendReject(reason);
|
|
}
|
|
|
|
// Build and dispatch the user.(un)register message
|
|
Message* YIAXEngine::userreg(IAXTransaction* tr, bool regrel)
|
|
{
|
|
DDebug(this,DebugAll,"YIAXEngine - userreg. %s username: '%s' [%p]",
|
|
regrel ? "Unregistering":"Registering",tr->username().c_str(),this);
|
|
Message* msg = new Message(regrel ? "user.unregister" : "user.register");
|
|
msg->addParam("username",tr->username());
|
|
msg->addParam("driver","iax");
|
|
if (!regrel) {
|
|
IAXURI uri(tr->username(),tr->remoteAddr().host(),tr->calledNo(),
|
|
tr->calledContext(),tr->remoteAddr().port());
|
|
String data = "iax/" + uri;
|
|
msg->addParam("data",data);
|
|
unsigned int exp = tr->expire();
|
|
if (!exp)
|
|
exp = s_expires_def;
|
|
else if (exp < s_expires_min)
|
|
exp = s_expires_min;
|
|
else if (exp > s_expires_max)
|
|
exp = s_expires_max;
|
|
msg->addParam("expires",String(exp));
|
|
}
|
|
msg->addParam("ip_host",tr->remoteAddr().host());
|
|
msg->addParam("ip_port",String(tr->remoteAddr().port()));
|
|
fillMessage(*msg);
|
|
if (!Engine::dispatch(*msg))
|
|
TelEngine::destruct(msg);
|
|
return msg;
|
|
}
|
|
|
|
void YIAXEngine::createThreads(const NamedList& params)
|
|
{
|
|
unsigned int read = params.getIntValue("read_threads",Engine::clientMode() ? 1 : 3,1);
|
|
unsigned int event = params.getIntValue("event_threads",Engine::clientMode() ? 1 : 3,1);
|
|
unsigned int trunking = params.getIntValue("trunk_threads",1,0);
|
|
Thread::Priority prio = Thread::priority(params.getValue("thread"));
|
|
createThreads(read,YIAXThread::Listener,prio);
|
|
createThreads(event,YIAXThread::GetEvent,Thread::Normal);
|
|
createThreads(trunking,YIAXThread::Trunking,prio);
|
|
}
|
|
|
|
void YIAXEngine::createThreads(unsigned int count, int oper, Thread::Priority prio)
|
|
{
|
|
if (!count)
|
|
return;
|
|
Lock lck(this);
|
|
unsigned int n = 0;
|
|
for (unsigned int i = 0; i < count; i++) {
|
|
YIAXThread* th = new YIAXThread(this,oper,prio);
|
|
if (!th->startup())
|
|
continue;
|
|
m_threads.append(th)->setDelete(false);
|
|
n++;
|
|
}
|
|
if (n && oper == YIAXThread::Trunking)
|
|
m_trunking = -1;
|
|
if (n == count)
|
|
Debug(this,DebugAll,"Created %u '%s' threads [%p]",
|
|
n,lookup(oper,YIAXThread::s_operThName),this);
|
|
else
|
|
Debug(this,DebugNote,"Created %u/%u '%s' threads [%p]",
|
|
n,count,lookup(oper,YIAXThread::s_operThName),this);
|
|
}
|
|
|
|
void YIAXEngine::threadTerminated(YIAXThread* th)
|
|
{
|
|
if (!th)
|
|
return;
|
|
Lock lck(this);
|
|
if (m_threads.remove(th,false))
|
|
Debug(this,DebugAll,"Thread (%p) '%s' terminated [%p]",th,th->operThName(),this);
|
|
}
|
|
|
|
|
|
//
|
|
// YIAXRegDataHandler
|
|
//
|
|
// Handler for outgoing registration messages
|
|
bool YIAXRegDataHandler::received(Message& msg)
|
|
{
|
|
String tmp(msg.getValue("protocol"));
|
|
if (tmp != "iax")
|
|
return false;
|
|
tmp = msg.getValue("account");
|
|
if (tmp.null())
|
|
return false;
|
|
s_lines.updateLine(msg);
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// YIAXDriver
|
|
//
|
|
YIAXDriver::YIAXDriver()
|
|
: Driver("iax","varchans"),
|
|
m_enginesMutex(false,"IAXEngineList"),
|
|
m_defaultEngine(0),
|
|
m_haveEngTerminate(0),
|
|
m_format(0),
|
|
m_formatVideo(0),
|
|
m_capability(0),
|
|
m_failedAuths(0),
|
|
m_init(true)
|
|
{
|
|
Output("Loaded module YIAX");
|
|
}
|
|
|
|
YIAXDriver::~YIAXDriver()
|
|
{
|
|
Output("Unloading module YIAX");
|
|
lock();
|
|
channels().clear();
|
|
unlock();
|
|
s_lines.clear(true);
|
|
m_engines.clear();
|
|
}
|
|
|
|
void YIAXDriver::initialize()
|
|
{
|
|
Output("Initializing module YIAX");
|
|
// Load configuration
|
|
Configuration cfg(Engine::configFile("yiaxchan"));
|
|
const NamedList& gen = safeSect(cfg,"general");
|
|
const NamedList& registrar = safeSect(cfg,"registrar");
|
|
s_callTokenOut = gen.getBoolValue("calltoken_out",true);
|
|
maxChans(gen.getIntValue("maxchans",maxChans()));
|
|
s_expires_min = registrar.getIntValue("expires_min",60,1);
|
|
s_expires_max = registrar.getIntValue("expires_max",3600,s_expires_min);
|
|
s_expires_def = registrar.getIntValue("expires_def",60,s_expires_min,s_expires_max);
|
|
s_authReq = registrar.getBoolValue("auth_required",true);
|
|
initFormats(safeSect(cfg,"formats"));
|
|
DDebug(this,DebugAll,
|
|
"Initialized calltoken_out=%s expires_min=%u expires_max=%u expires_def=%u",
|
|
String::boolText(s_callTokenOut),s_expires_min,s_expires_max,s_expires_def);
|
|
if (m_init) {
|
|
setup();
|
|
installRelay(Halt);
|
|
installRelay(Route);
|
|
installRelay(Progress);
|
|
installRelay(Status);
|
|
installRelay(EngineStop,"engine.stop");
|
|
Engine::install(new YIAXRegDataHandler(name()));
|
|
m_init = false;
|
|
}
|
|
engineListChanging(true);
|
|
bool upd = false;
|
|
setupEngine(s_iaxEngineName,upd,true,gen.getBoolValue("default",true),gen);
|
|
unsigned int n = cfg.sections();
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
NamedList* p = cfg.getSection(i);
|
|
if (!p)
|
|
continue;
|
|
String name = *p;
|
|
if (!name.startSkip("listener ",false))
|
|
continue;
|
|
name.trimBlanks();
|
|
if (!name)
|
|
continue;
|
|
if (isGeneralEngine(name)) {
|
|
Debug(this,DebugConf,"Ignoring section '%s': '%s' is reserved",p->c_str(),name.c_str());
|
|
continue;
|
|
}
|
|
bool enable = p->getBoolValue("enable",true);
|
|
bool def = p->getBoolValue("default");
|
|
setupEngine(name,upd,enable,def,*p,&gen);
|
|
}
|
|
engineListChanging(false);
|
|
if (upd)
|
|
s_lines.updateEngine();
|
|
}
|
|
|
|
// Check if we have a line
|
|
bool YIAXDriver::hasLine(const String& line) const
|
|
{
|
|
return line && s_lines.hasLine(line);
|
|
}
|
|
|
|
// Route calls that use a line owned by this driver
|
|
bool YIAXDriver::msgRoute(Message& msg)
|
|
{
|
|
if (!isE164(msg.getValue("called")))
|
|
return false;
|
|
return Driver::msgRoute(msg);
|
|
}
|
|
|
|
bool YIAXDriver::msgExecute(Message& msg, String& dest)
|
|
{
|
|
CallEndpoint* ch = YOBJECT(CallEndpoint,msg.userData());
|
|
if (!ch) {
|
|
Debug(this,DebugAll,"IAX call failed: no endpoint");
|
|
return false;
|
|
}
|
|
NamedList params(msg);
|
|
SocketAddr addr(AF_INET);
|
|
RefPointer<YIAXLine> line;
|
|
if (isE164(dest)) {
|
|
const String& lName = params["line"];
|
|
if (!lName) {
|
|
Debug(this,DebugNote,"IAX call to '%s' failed: no line",dest.c_str());
|
|
msg.setParam("error","failure");
|
|
return false;
|
|
}
|
|
if (!s_lines.findLine(line,lName)) {
|
|
Debug(this,DebugNote,"IAX call failed: no line '%s'",lName.c_str());
|
|
msg.setParam("error","offline");
|
|
return false;
|
|
}
|
|
if (!line->registered()) {
|
|
Debug(this,DebugNote,"IAX call failed: line '%s' is not registered",
|
|
lName.c_str());
|
|
msg.setParam("error","offline");
|
|
return false;
|
|
}
|
|
s_lines.fillList(*line,params,addr);
|
|
params.setParam("called",dest);
|
|
}
|
|
else {
|
|
// dest should be an URI
|
|
IAXURI uri(dest);
|
|
uri.parse();
|
|
uri.fillList(params);
|
|
uri.setAddr(addr);
|
|
}
|
|
if (!addr.host().length()) {
|
|
Debug(this,DebugNote,"IAX call failed: no remote address");
|
|
msg.setParam("error","failure");
|
|
return false;
|
|
}
|
|
RefPointer<YIAXEngine> engine;
|
|
if (!(findEngine(engine,msg,"o",line) && engine->valid())) {
|
|
Debug(this,DebugNote,"IAX call failed: no listener");
|
|
msg.setParam("error","notransport");
|
|
engine = 0;
|
|
return false;
|
|
}
|
|
IAXTransaction* tr = engine->call(addr,params);
|
|
if (!tr) {
|
|
msg.copyParams(params,"error");
|
|
engine = 0;
|
|
return false;
|
|
}
|
|
YIAXConnection* conn = new YIAXConnection(tr,&msg,¶ms);
|
|
conn->initChan();
|
|
tr->setUserData(conn);
|
|
engine->initTransaction(tr,msg,line);
|
|
tr->start();
|
|
if (conn->connect(ch,msg.getValue("reason"))) {
|
|
conn->callConnect(msg);
|
|
msg.setParam("peerid",conn->id());
|
|
msg.setParam("targetid",conn->id());
|
|
}
|
|
else {
|
|
tr->setUserData(0);
|
|
tr->setDestroy();
|
|
}
|
|
TelEngine::destruct(tr);
|
|
line = 0;
|
|
engine = 0;
|
|
conn->deref();
|
|
return true;
|
|
}
|
|
|
|
bool YIAXDriver::received(Message& msg, int id)
|
|
{
|
|
if (id == Timer) {
|
|
s_lines.evTimer(msg.msgTime());
|
|
if (m_haveEngTerminate)
|
|
checkEngineTerminate(msg.msgTime());
|
|
return Driver::received(msg,id);
|
|
}
|
|
if (id == Status) {
|
|
String target = msg.getValue("module");
|
|
if (target && target.startsWith(name(),true) && !target.startsWith(prefix())) {
|
|
msgStatus(msg);
|
|
return false;
|
|
}
|
|
return Driver::received(msg,id);
|
|
}
|
|
if (id == EngineStop) {
|
|
s_engineStop++;
|
|
if (s_engineStop == 1)
|
|
terminateEngine(0,false,YIAXEngine::Exiting);
|
|
if (m_haveEngTerminate)
|
|
checkEngineTerminate(msg.msgTime());
|
|
if (m_haveEngTerminate)
|
|
Debug(this,DebugAll,"Returning true from '%s' handler",msg.c_str());
|
|
return m_haveEngTerminate != 0;
|
|
}
|
|
if (id == Halt) {
|
|
dropAll(msg);
|
|
lock();
|
|
channels().clear();
|
|
unlock();
|
|
s_lines.clear(true);
|
|
terminateEngine(0,true,YIAXEngine::Exiting);
|
|
return Driver::received(msg,id);
|
|
}
|
|
return Driver::received(msg,id);
|
|
}
|
|
|
|
// Find an engine from parameters. Return a refferenced pointer
|
|
bool YIAXDriver::findEngine(RefPointer<YIAXEngine>& eng, const NamedList& params,
|
|
const String& prefix, YIAXLine* line, NamedList* copyParams)
|
|
{
|
|
if (copyParams) {
|
|
if (prefix)
|
|
copyParams->copySubParams(params,prefix);
|
|
else
|
|
copyParams->copyParams(params,
|
|
"connection_id,ip_transport_localip,ip_transport_localport");
|
|
}
|
|
Lock lck(m_enginesMutex);
|
|
const String& engName = params[prefix + "connection_id"];
|
|
if (engName) {
|
|
eng = findEngine(engName);
|
|
if (eng)
|
|
return true;
|
|
}
|
|
String addr = params[prefix + "ip_transport_localip"];
|
|
addr.trimBlanks();
|
|
if (addr) {
|
|
int port = IAXEngine::getPort(params,prefix + "ip_transport_localport");
|
|
eng = findEngine(addr,port);
|
|
if (eng)
|
|
return true;
|
|
}
|
|
// Done if engine name and/or local ip were given
|
|
if (engName || addr)
|
|
return false;
|
|
// Check for line engine
|
|
if (line) {
|
|
Lock lck(line);
|
|
eng = line->engine();
|
|
if (eng)
|
|
return true;
|
|
}
|
|
eng = m_defaultEngine;
|
|
return eng != 0;
|
|
}
|
|
|
|
// Extract individual codecs from 'formats'
|
|
// Check if IAXFormat contains it
|
|
// Before exiting: update 'codecs'
|
|
bool YIAXDriver::updateCodecsFromRoute(u_int32_t& codecs, const NamedList& params, int type)
|
|
{
|
|
String* formats = 0;
|
|
if (type == IAXFormat::Audio)
|
|
formats = params.getParam("formats");
|
|
if (TelEngine::null(formats)) {
|
|
String tmp("formats_");
|
|
tmp << dataEpName(type);
|
|
formats = params.getParam(tmp);
|
|
}
|
|
XDebug(this,DebugAll,"updateCodecsFromRoute(%u,%d) formats=%s",
|
|
codecs,type,TelEngine::c_safe(formats));
|
|
if (formats) {
|
|
// Set intersection
|
|
if (type == IAXFormat::Audio)
|
|
codecs &= IAXFormat::encode(*formats,dict_payloads);
|
|
else if (type == IAXFormat::Video)
|
|
codecs &= IAXFormat::encode(*formats,dict_payloads_video);
|
|
else
|
|
codecs = 0;
|
|
}
|
|
else {
|
|
// Reset formats for non audio
|
|
// They must be explicitely set
|
|
if (type != IAXFormat::Audio)
|
|
codecs = 0;
|
|
}
|
|
return codecs != 0;
|
|
}
|
|
|
|
bool YIAXDriver::userAuth(IAXTransaction* tr, bool response, bool& requestAuth,
|
|
bool& invalidAuth, const char* billid)
|
|
{
|
|
bool newCall = (tr->type() == IAXTransaction::New);
|
|
DDebug(this,DebugAll,"YIAXDriver::userAuth(%p,%u) newcall=%u",tr,response,newCall);
|
|
requestAuth = invalidAuth = false;
|
|
// Create and dispatch user.auth
|
|
Message msg("user.auth");
|
|
msg.addParam("protocol","iax");
|
|
msg.addParam("username",tr->username());
|
|
msg.addParam("called",tr->calledNo());
|
|
msg.addParam("caller",tr->callingNo());
|
|
msg.addParam("callername",tr->callingName());
|
|
msg.addParam("ip_host",tr->remoteAddr().host());
|
|
msg.addParam("ip_port",String(tr->remoteAddr().port()));
|
|
if (response) {
|
|
msg.addParam("nonce",tr->challenge());
|
|
msg.addParam("response",tr->authdata());
|
|
}
|
|
msg.addParam("billid",billid,false);
|
|
msg.addParam("newcall",String::boolText(newCall));
|
|
if (!Engine::dispatch(msg))
|
|
return false;
|
|
String pwd = msg.retValue();
|
|
// We have a password
|
|
if (pwd) {
|
|
// Not a response: request authentication
|
|
if (!response) {
|
|
requestAuth = true;
|
|
return false;
|
|
}
|
|
// Check response
|
|
if (!IAXEngine::isMD5ChallengeCorrect(tr->authdata(),tr->challenge(),pwd)) {
|
|
invalidAuth = true;
|
|
m_failedAuths++;
|
|
changed();
|
|
Message* fail = new Message(msg);
|
|
*fail = "user.authfail";
|
|
fail->retValue().clear();
|
|
Engine::enqueue(fail);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Start stop engine list changes
|
|
void YIAXDriver::engineListChanging(bool start)
|
|
{
|
|
m_enginesMutex.lock();
|
|
if (start)
|
|
for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext())
|
|
(static_cast<YIAXEngine*>(o->get()))->m_initialized = false;
|
|
else {
|
|
ListIterator iter(m_engines);
|
|
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
|
|
YIAXEngine* engine = static_cast<YIAXEngine*>(gen);
|
|
if (engine->m_initialized)
|
|
continue;
|
|
RefPointer<YIAXEngine> eng = engine;
|
|
if (!eng)
|
|
continue;
|
|
m_enginesMutex.unlock();
|
|
terminateEngine(eng,false,YIAXEngine::Removed);
|
|
eng = 0;
|
|
m_enginesMutex.lock();
|
|
}
|
|
updateDefaultEngine();
|
|
}
|
|
m_enginesMutex.unlock();
|
|
}
|
|
|
|
// Setup an engine
|
|
void YIAXDriver::setupEngine(const String& name, bool& valid, bool enable, bool def,
|
|
const NamedList& params, const NamedList* defaults)
|
|
{
|
|
if (!name)
|
|
return;
|
|
Lock lck(m_enginesMutex);
|
|
NamedList p("");
|
|
const NamedList* init = ¶ms;
|
|
if (enable && defaults) {
|
|
p.copyParams(*defaults);
|
|
if (!isGeneralEngine(name))
|
|
clearListParams(p,s_delEngParams);
|
|
p.copyParams(params);
|
|
init = &p;
|
|
}
|
|
#ifdef XDEBUG
|
|
String s;
|
|
init->dump(s,"\r\n");
|
|
Debug(this,DebugAll,"setupEngine(%s,%u,%u)%s",name.c_str(),enable,def,s.safe());
|
|
#endif
|
|
YIAXEngine* eng = findEngine(name);
|
|
bool bound = false;
|
|
if (eng) {
|
|
eng->m_initialized = true;
|
|
eng->m_default = def;
|
|
if (!enable) {
|
|
if (!eng->exiting() && eng->ref()) {
|
|
lck.drop();
|
|
terminateEngine(eng,false,YIAXEngine::Disabled);
|
|
eng->deref();
|
|
}
|
|
return;
|
|
}
|
|
if (eng->exiting()) {
|
|
Debug(this,DebugConf,
|
|
"Ignoring init for listener '%s': scheduled for termination",name.c_str());
|
|
return;
|
|
}
|
|
if (!eng->bound()) {
|
|
bound = eng->bind(params);
|
|
if (!bound)
|
|
return;
|
|
}
|
|
eng->initialize(*init);
|
|
}
|
|
else if (enable) {
|
|
String addr;
|
|
int port = 0;
|
|
getBindAddr(params,addr,port);
|
|
// Create the engine
|
|
eng = new YIAXEngine(name,addr,port,init);
|
|
eng->debugChain(this);
|
|
eng->m_default = def;
|
|
bound = eng->bound();
|
|
m_engines.append(eng);
|
|
Debug(this,DebugInfo,"Added listener (%p) '%s' status='%s'",
|
|
eng,eng->name().c_str(),eng->statusName());
|
|
}
|
|
else
|
|
return;
|
|
if (bound) {
|
|
int tos = init->getIntValue("tos",dict_tos,0);
|
|
if (tos && !eng->socket().setTOS(tos))
|
|
Debug(eng,DebugWarn,"Could not set IP TOS to 0x%02x",tos);
|
|
eng->createThreads(*init);
|
|
valid = true;
|
|
}
|
|
// Init engine parameters from module
|
|
eng->setFormats(m_capability,m_format,m_formatVideo);
|
|
}
|
|
|
|
// Terminate exiting engines
|
|
void YIAXDriver::checkEngineTerminate(const Time& now)
|
|
{
|
|
m_enginesMutex.lock();
|
|
if (!m_haveEngTerminate) {
|
|
m_enginesMutex.unlock();
|
|
return;
|
|
}
|
|
ListIterator iter(m_engines);
|
|
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
|
|
YIAXEngine* engine = static_cast<YIAXEngine*>(gen);
|
|
if (!engine->exiting())
|
|
continue;
|
|
RefPointer<YIAXEngine> eng = engine;
|
|
if (!eng)
|
|
continue;
|
|
m_enginesMutex.unlock();
|
|
if (eng->timeout(now) || !eng->haveTransactions())
|
|
terminateEngine(eng,true,eng->m_status);
|
|
eng = 0;
|
|
m_enginesMutex.lock();
|
|
}
|
|
m_enginesMutex.unlock();
|
|
}
|
|
|
|
bool YIAXDriver::commandComplete(Message& msg, const String& partLine, const String& partWord)
|
|
{
|
|
if (partLine == ("status " + name()) ||
|
|
partLine == ("status overview " + name())) {
|
|
itemComplete(msg.retValue(),"accounts",partWord);
|
|
itemComplete(msg.retValue(),"listeners",partWord);
|
|
}
|
|
return Driver::commandComplete(msg,partLine,partWord);
|
|
}
|
|
|
|
void YIAXDriver::msgStatus(Message& msg)
|
|
{
|
|
String str = msg.getValue("module");
|
|
while (str.startSkip(name())) {
|
|
str.trimBlanks();
|
|
if (str.null())
|
|
break;
|
|
if (str.startSkip("accounts")) {
|
|
msgStatusAccounts(msg);
|
|
return;
|
|
}
|
|
if (str.startSkip("listeners")) {
|
|
msgStatusListeners(msg);
|
|
return;
|
|
}
|
|
}
|
|
Driver::msgStatus(msg);
|
|
}
|
|
|
|
void YIAXDriver::statusParams(String& str)
|
|
{
|
|
Driver::statusParams(str);
|
|
m_enginesMutex.lock();
|
|
unsigned int cnt = 0;
|
|
ListIterator iter(m_engines);
|
|
while (YIAXEngine* eng = static_cast<YIAXEngine*>(iter.get()))
|
|
cnt += eng->transactionCount();
|
|
m_enginesMutex.unlock();
|
|
str.append("transactions=",",") << cnt;
|
|
}
|
|
|
|
void YIAXDriver::msgStatusAccounts(Message& msg)
|
|
{
|
|
msg.retValue().clear();
|
|
msg.retValue() << "module=" << name();
|
|
msg.retValue() << ",protocol=IAX";
|
|
msg.retValue() << ",format=Username|Status;";
|
|
unsigned int n = 0;
|
|
String det;
|
|
bool details = msg.getBoolValue("details",true);
|
|
s_lines.lock();
|
|
for (ObjList* o = s_lines.lines().skipNull(); o; o = o->skipNext()) {
|
|
n++;
|
|
if (!details)
|
|
continue;
|
|
YIAXLine* line = static_cast<YIAXLine*>(o->get());
|
|
Lock lckLine(line);
|
|
det.append(line->toString(),",") << "=";
|
|
det.append(line->username()) << "|";
|
|
det << (line->registered() ? "online" : "offline");
|
|
}
|
|
s_lines.unlock();
|
|
msg.retValue() << "accounts=" << n;
|
|
msg.retValue().append(det,";");
|
|
msg.retValue() << "\r\n";
|
|
}
|
|
|
|
void YIAXDriver::msgStatusListeners(Message& msg)
|
|
{
|
|
msg.retValue().clear();
|
|
msg.retValue() << "module=" << name();
|
|
msg.retValue() << ",protocol=IAX";
|
|
msg.retValue() << ",format=Address|Status|Transactions;";
|
|
unsigned int n = 0;
|
|
String det;
|
|
bool details = msg.getBoolValue("details",true);
|
|
String def;
|
|
m_enginesMutex.lock();
|
|
ListIterator iter(m_engines);
|
|
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
|
|
RefPointer<YIAXEngine> e = static_cast<YIAXEngine*>(gen);
|
|
if (!e)
|
|
continue;
|
|
n++;
|
|
if (e == m_defaultEngine)
|
|
def = e->name();
|
|
if (!details)
|
|
continue;
|
|
det.append(e->toString(),",") << "=";
|
|
if (e->bound())
|
|
det << e->addr().host() << ":" << e->addr().port();
|
|
else
|
|
det << e->m_bindAddr << ":" << e->m_bindPort;
|
|
det << "|" << e->statusName();
|
|
m_enginesMutex.unlock();
|
|
det << "|" << e->transactionCount();
|
|
m_enginesMutex.lock();
|
|
}
|
|
m_enginesMutex.unlock();
|
|
msg.retValue() << "listeners=" << n << ",default=" << def;
|
|
msg.retValue().append(det,";");
|
|
msg.retValue() << "\r\n";
|
|
}
|
|
|
|
// Add specific module update parameters
|
|
void YIAXDriver::genUpdate(Message& msg)
|
|
{
|
|
unsigned int tmp = m_failedAuths;
|
|
m_failedAuths = 0;
|
|
msg.setParam("failed_auths",String(tmp));
|
|
}
|
|
|
|
// Update default engine
|
|
void YIAXDriver::updateDefaultEngine()
|
|
{
|
|
YIAXEngine* def = 0;
|
|
for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext()) {
|
|
YIAXEngine* eng = static_cast<YIAXEngine*>(o->get());
|
|
if (!eng->valid())
|
|
continue;
|
|
if (eng->m_default && !def) {
|
|
def = eng;
|
|
break;
|
|
}
|
|
}
|
|
m_defaultEngine = def;
|
|
if (s_engineStop)
|
|
return;
|
|
if (m_defaultEngine)
|
|
Debug(this,DebugInfo,"Default listener is '%s'",def->name().c_str());
|
|
else
|
|
Debug(this,DebugNote,"No default listener set");
|
|
}
|
|
|
|
// Terminate an engine
|
|
void YIAXDriver::terminateEngine(YIAXEngine* eng, bool final, int reason)
|
|
{
|
|
XDebug(this,DebugAll,"terminateEngine(%p,%u,%d)",eng,final,reason);
|
|
if (!eng) {
|
|
m_enginesMutex.lock();
|
|
ListIterator iter(m_engines);
|
|
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
|
|
RefPointer<YIAXEngine> e = static_cast<YIAXEngine*>(gen);
|
|
if (!e)
|
|
continue;
|
|
m_enginesMutex.unlock();
|
|
terminateEngine(e,final,reason);
|
|
e = 0;
|
|
m_enginesMutex.lock();
|
|
}
|
|
m_enginesMutex.unlock();
|
|
return;
|
|
}
|
|
// Already set to terminate ?
|
|
if (!final && eng->exiting())
|
|
return;
|
|
if (!eng->exiting()) {
|
|
eng->setTerminate(reason);
|
|
// Unregister all users registered using this engine
|
|
Message* m = new Message("user.unregister");
|
|
m->addParam("module",name());
|
|
m->addParam("connection_id",eng->toString());
|
|
Engine::enqueue(m);
|
|
}
|
|
// Drop all calls using the engine
|
|
lock();
|
|
ListIterator iter(channels());
|
|
for (GenObject* gen = 0; 0 != (gen = iter.get());) {
|
|
RefPointer<YIAXConnection> c = static_cast<YIAXConnection*>(gen);
|
|
if (!c)
|
|
continue;
|
|
unlock();
|
|
c->m_mutexTrans.lock();
|
|
if (c->m_transaction && c->m_transaction->getEngine() == eng) {
|
|
c->disconnect("notransport");
|
|
if (final)
|
|
c->resetTransaction();
|
|
}
|
|
c->m_mutexTrans.unlock();
|
|
c = 0;
|
|
lock();
|
|
}
|
|
unlock();
|
|
// Unregister all lines using the engine
|
|
s_lines.clear(final,eng);
|
|
Lock lck(m_enginesMutex);
|
|
// Remove the engine from list
|
|
if (final && m_engines.remove(eng,false)) {
|
|
Debug(this,DebugInfo,"Removed listener (%p) '%s' status='%s'",
|
|
eng,eng->name().c_str(),eng->statusName());
|
|
if (eng == m_defaultEngine)
|
|
updateDefaultEngine();
|
|
TelEngine::destruct(eng);
|
|
}
|
|
// Update engine terminate count
|
|
unsigned int n = 0;
|
|
for (ObjList* o = m_engines.skipNull(); o; o = o->skipNext())
|
|
if ((static_cast<YIAXEngine*>(o->get()))->exiting())
|
|
n++;
|
|
m_haveEngTerminate = n;
|
|
}
|
|
|
|
// Initialize formats from config
|
|
void YIAXDriver::initFormats(const NamedList& params)
|
|
{
|
|
Lock lck(m_enginesMutex);
|
|
u_int32_t codecsAudio = 0;
|
|
u_int32_t codecsVideo = 0;
|
|
u_int32_t defAudio = 0;
|
|
u_int32_t defVideo = 0;
|
|
bool def = params.getBoolValue("default",true);
|
|
const TokenDict* dicts[2] = {dict_payloads,dict_payloads_video};
|
|
for (int i = 0; i < 2; i++) {
|
|
u_int32_t fallback = 0;
|
|
bool audio = (i == 0);
|
|
const char* media = audio ? "audio" : "video";
|
|
const String& preferred = params[audio ? "preferred" : "preferred_video"];
|
|
u_int32_t& codecs = audio ? codecsAudio : codecsVideo;
|
|
u_int32_t& defaultCodec = audio ? defAudio : defVideo;
|
|
for (const TokenDict* d = dicts[i]; d->token; d++) {
|
|
bool defVal = def && DataTranslator::canConvert(d->token);
|
|
if (!params.getBoolValue(d->token,defVal))
|
|
continue;
|
|
XDebug(this,DebugAll,"Adding supported %s codec %u: '%s'.",
|
|
media,d->value,d->token);
|
|
codecs |= d->value;
|
|
fallback = d->value;
|
|
// Set default (desired) codec
|
|
if (!defaultCodec && preferred == d->token)
|
|
defaultCodec = d->value;
|
|
}
|
|
// If desired codec is disabled fall back to last in list
|
|
if (!defaultCodec)
|
|
defaultCodec = fallback;
|
|
if (codecs) {
|
|
String tmp;
|
|
IAXFormat::formatList(tmp,codecs,dicts[i]);
|
|
Debug(this,DebugAll,"Enabled %s format(s) '%s' default=%s",
|
|
media,tmp.c_str(),lookup(defaultCodec,dicts[i],""));
|
|
}
|
|
else
|
|
Debug(this,audio ? DebugWarn : DebugAll,"No %s format(s) available",media);
|
|
}
|
|
m_format = defAudio;
|
|
m_formatVideo = defVideo;
|
|
m_capability = codecsAudio | codecsVideo;
|
|
}
|
|
|
|
|
|
//
|
|
// IAXConsumer
|
|
//
|
|
YIAXConsumer::YIAXConsumer(YIAXConnection* conn, u_int32_t format,
|
|
const char* formatText, int type)
|
|
: DataConsumer(formatText),
|
|
YIAXData(conn,format,type)
|
|
{
|
|
Debug(m_connection,DebugAll,"YIAXConsumer '%s' format=%s [%p]",
|
|
IAXFormat::typeName(m_type),formatText,this);
|
|
}
|
|
|
|
YIAXConsumer::~YIAXConsumer()
|
|
{
|
|
Debug(m_connection,DebugAll,"YIAXConsumer '%s' destroyed total=%u [%p]",
|
|
IAXFormat::typeName(m_type),m_total,this);
|
|
}
|
|
|
|
unsigned long YIAXConsumer::Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags)
|
|
{
|
|
unsigned long sent = 0;
|
|
if (m_connection && !m_connection->mutedOut()) {
|
|
m_total += data.length();
|
|
if (m_connection->transaction()) {
|
|
bool mark = (flags & DataMark) != 0;
|
|
sent = m_connection->transaction()->sendMedia(data,tStamp,format(),m_type,mark);
|
|
}
|
|
}
|
|
return sent;
|
|
}
|
|
|
|
//
|
|
// YIAXSource
|
|
//
|
|
YIAXSource::YIAXSource(YIAXConnection* conn, u_int32_t format, const char* formatText, int type)
|
|
: DataSource(formatText),
|
|
YIAXData(conn,format,type)
|
|
{
|
|
Debug(m_connection,DebugAll,"YIAXSource '%s' format=%s [%p]",
|
|
IAXFormat::typeName(m_type),formatText,this);
|
|
}
|
|
|
|
YIAXSource::~YIAXSource()
|
|
{
|
|
Debug(m_connection,DebugAll,"YIAXSource '%s' destroyed total=%u [%p]",
|
|
IAXFormat::typeName(m_type),m_total,this);
|
|
}
|
|
|
|
unsigned long YIAXSource::Forward(const DataBlock& data, unsigned long tStamp, unsigned long flags)
|
|
{
|
|
if (m_connection && m_connection->mutedIn())
|
|
return invalidStamp();
|
|
m_total += data.length();
|
|
return DataSource::Forward(data,tStamp,flags);
|
|
}
|
|
|
|
//
|
|
// YIAXConnection
|
|
//
|
|
YIAXConnection::YIAXConnection(IAXTransaction* tr, Message* msg, NamedList* params)
|
|
: Channel(&iplugin,0,tr->outgoing()),
|
|
m_transaction(tr), m_mutedIn(false), m_mutedOut(false),
|
|
m_audio(false), m_video(false),
|
|
m_hangup(true),
|
|
m_mutexChan(true,"YIAXConnection"),
|
|
m_mutexTrans(true,"YIAXConnection::trans"),
|
|
m_mutexRefIncreased(true,"YIAXConnection::refIncreased"),
|
|
m_refIncreased(false),
|
|
m_routeCount(0)
|
|
{
|
|
Debug(this,DebugAll,"%s call. Transaction (%p) callno=%u [%p]",
|
|
isOutgoing() ? "Outgoing" : "Incoming",tr,tr->localCallNo(),this);
|
|
setMaxcall(msg);
|
|
if (msg) {
|
|
setMaxPDD(*msg);
|
|
setChanParams(*msg);
|
|
}
|
|
if (tr)
|
|
m_address << tr->remoteAddr().host() << ":" << tr->remoteAddr().port();
|
|
if (msg)
|
|
m_targetid = msg->getValue("id");
|
|
Message* m = message("chan.startup",msg);
|
|
m->addParam("username",tr->username(),false);
|
|
if (params) {
|
|
// outgoing call
|
|
m_password = params->getValue("password");
|
|
m->copyParams(*params,"caller,callername,called,billid,callto,username");
|
|
}
|
|
else {
|
|
// incoming call
|
|
m->addParam("called",tr->calledNo(),false);
|
|
m->addParam("caller",tr->callingNo(),false);
|
|
m->addParam("callername",tr->callingName(),false);
|
|
}
|
|
Engine::enqueue(m);
|
|
}
|
|
|
|
YIAXConnection::~YIAXConnection()
|
|
{
|
|
status("destroyed");
|
|
setConsumer();
|
|
setSource();
|
|
hangup(0);
|
|
m_transaction = 0;
|
|
Debug(this,DebugAll,"Destroyed with reason '%s' [%p]",m_reason.safe(),this);
|
|
}
|
|
|
|
// Incoming call accepted, possibly set trunking on this connection
|
|
void YIAXConnection::callAccept(Message& msg)
|
|
{
|
|
DDebug(this,DebugCall,"callAccept [%p]",this);
|
|
m_mutexTrans.lock();
|
|
if (m_transaction) {
|
|
YIAXEngine* eng = static_cast<YIAXEngine*>(m_transaction->getEngine());
|
|
u_int32_t codecs = eng->capability();
|
|
if (msg.getValue("formats")) {
|
|
u_int32_t ca = IAXFormat::mask(codecs,IAXFormat::Audio);
|
|
iplugin.updateCodecsFromRoute(ca,msg,IAXFormat::Audio);
|
|
eng->acceptFormatAndCapability(m_transaction,&ca);
|
|
}
|
|
u_int32_t cv = IAXFormat::mask(codecs,IAXFormat::Video);
|
|
iplugin.updateCodecsFromRoute(cv,msg,IAXFormat::Video);
|
|
eng->acceptFormatAndCapability(m_transaction,&cv,IAXFormat::Video);
|
|
u_int32_t ci = IAXFormat::mask(codecs,IAXFormat::Image);
|
|
iplugin.updateCodecsFromRoute(ci,msg,IAXFormat::Image);
|
|
eng->acceptFormatAndCapability(m_transaction,&ci,IAXFormat::Image);
|
|
eng->initTransaction(m_transaction,msg);
|
|
m_transaction->sendAccept();
|
|
}
|
|
m_mutexTrans.unlock();
|
|
Channel::callAccept(msg);
|
|
}
|
|
|
|
// Call rejected, check if we have to authenticate caller
|
|
void YIAXConnection::callRejected(const char* error, const char* reason, const Message* msg)
|
|
{
|
|
if (!error)
|
|
error = reason;
|
|
Lock lock(m_mutexTrans);
|
|
if (m_routeCount == 1 && m_transaction && error && String(error) == "noauth") {
|
|
if (safeRefIncrease()) {
|
|
Debug(this,DebugAll,"Requesting authentication [%p]",this);
|
|
m_transaction->sendAuth();
|
|
return;
|
|
}
|
|
error = "temporary-failure";
|
|
}
|
|
lock.drop();
|
|
Channel::callRejected(error,reason,msg);
|
|
hangup(0,error,reason,true);
|
|
}
|
|
|
|
bool YIAXConnection::callRouted(Message& msg)
|
|
{
|
|
// check if the caller did abort the call while routing
|
|
if (!m_transaction) {
|
|
Debug(this,DebugMild,"callRouted. No transaction: ABORT [%p]",this);
|
|
return false;
|
|
}
|
|
DDebug(this,DebugAll,"callRouted [%p]",this);
|
|
return true;
|
|
}
|
|
|
|
bool YIAXConnection::msgProgress(Message& msg)
|
|
{
|
|
Lock lock(&m_mutexTrans);
|
|
if (m_transaction) {
|
|
m_transaction->sendProgress();
|
|
// only start audio output for early media
|
|
startMedia(false);
|
|
return Channel::msgProgress(msg);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool YIAXConnection::msgRinging(Message& msg)
|
|
{
|
|
Lock lock(&m_mutexTrans);
|
|
if (m_transaction) {
|
|
m_transaction->sendRinging();
|
|
// only start audio output for early media
|
|
startMedia(false);
|
|
return Channel::msgRinging(msg);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool YIAXConnection::msgAnswered(Message& msg)
|
|
{
|
|
Lock lock(&m_mutexTrans);
|
|
if (m_transaction) {
|
|
m_transaction->sendAnswer();
|
|
// fully start media
|
|
startMedia(true);
|
|
startMedia(false);
|
|
startMedia(true,IAXFormat::Video);
|
|
startMedia(false,IAXFormat::Video);
|
|
return Channel::msgAnswered(msg);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool YIAXConnection::msgTone(Message& msg, const char* tone)
|
|
{
|
|
Lock lock(&m_mutexTrans);
|
|
if (m_transaction) {
|
|
while (tone && *tone)
|
|
m_transaction->sendDtmf(*tone++);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool YIAXConnection::msgText(Message& msg, const char* text)
|
|
{
|
|
Lock lock(&m_mutexTrans);
|
|
if (m_transaction) {
|
|
m_transaction->sendText(text);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void YIAXConnection::disconnected(bool final, const char* reason)
|
|
{
|
|
DDebug(this,DebugAll,"Disconnected. Final: %s . Reason: '%s' [%p]",
|
|
String::boolText(final),reason,this);
|
|
hangup(0,reason);
|
|
Channel::disconnected(final,reason);
|
|
safeDeref();
|
|
}
|
|
|
|
bool YIAXConnection::disconnect(const char* reason)
|
|
{
|
|
Lock lck(m_mutexChan);
|
|
if (!m_reason)
|
|
m_reason = reason;
|
|
String res = m_reason;
|
|
lck.drop();
|
|
return Channel::disconnect(res,parameters());
|
|
}
|
|
|
|
bool YIAXConnection::callPrerouted(Message& msg, bool handled)
|
|
{
|
|
// check if the caller did abort the call while prerouting
|
|
if (!m_transaction) {
|
|
Debug(this,DebugMild,"callPrerouted. No transaction: ABORT [%p]",this);
|
|
return false;
|
|
}
|
|
DDebug(this,DebugAll,"callPrerouted [%p]",this);
|
|
return true;
|
|
}
|
|
|
|
void YIAXConnection::handleEvent(IAXEvent* event)
|
|
{
|
|
switch(event->type()) {
|
|
case IAXEvent::Text: {
|
|
String text;
|
|
event->getList().getString(IAXInfoElement::textframe,text);
|
|
DDebug(this,DebugInfo,"TEXT: '%s' [%p]",text.safe(),this);
|
|
Message* m = message("chan.text");
|
|
m->addParam("text",text);
|
|
Engine::enqueue(m);
|
|
}
|
|
break;
|
|
case IAXEvent::Dtmf: {
|
|
String dtmf((char)event->subclass());
|
|
dtmf.toUpper();
|
|
DDebug(this,DebugCall,"DTMF: %s [%p]",dtmf.safe(),this);
|
|
Message* m = message("chan.dtmf");
|
|
m->addParam("text",dtmf);
|
|
m->addParam("detected","iax-event");
|
|
dtmfEnqueue(m);
|
|
}
|
|
break;
|
|
case IAXEvent::Noise:
|
|
DDebug(this,DebugInfo,"NOISE: %u [%p]",event->subclass(),this);
|
|
break;
|
|
case IAXEvent::Progressing:
|
|
DDebug(this,DebugInfo,"CALL PROGRESSING [%p]",this);
|
|
Engine::enqueue(message("call.progress",false,true));
|
|
break;
|
|
case IAXEvent::Accept:
|
|
DDebug(this,DebugCall,"ACCEPT [%p]",this);
|
|
startMedia(true);
|
|
break;
|
|
case IAXEvent::Answer:
|
|
if (isAnswered())
|
|
break;
|
|
DDebug(this,DebugCall,"ANSWER [%p]",this);
|
|
status("answered");
|
|
startMedia(true);
|
|
startMedia(false);
|
|
startMedia(true,IAXFormat::Video);
|
|
startMedia(false,IAXFormat::Video);
|
|
Engine::enqueue(message("call.answered",false,true));
|
|
break;
|
|
case IAXEvent::Quelch:
|
|
DDebug(this,DebugCall,"QUELCH [%p]",this);
|
|
m_mutedOut = true;
|
|
break;
|
|
case IAXEvent::Unquelch:
|
|
DDebug(this,DebugCall,"UNQUELCH [%p]",this);
|
|
m_mutedOut = false;
|
|
break;
|
|
case IAXEvent::Ringing:
|
|
DDebug(this,DebugInfo,"RINGING [%p]",this);
|
|
startMedia(true);
|
|
Engine::enqueue(message("call.ringing",false,true));
|
|
break;
|
|
case IAXEvent::Hangup:
|
|
case IAXEvent::Reject:
|
|
{
|
|
String error;
|
|
String reason;
|
|
event->getList().getString(IAXInfoElement::CAUSE,reason);
|
|
u_int32_t code = 0;
|
|
event->getList().getNumeric(IAXInfoElement::CAUSECODE,code);
|
|
if (code) {
|
|
const char* s = IAXInfoElement::causeName(code);
|
|
if (s)
|
|
error = s;
|
|
else
|
|
error = (unsigned int)code;
|
|
}
|
|
DDebug(this,DebugCall,"REJECT/HANGUP: error='%s' reason='%s' [%p]",
|
|
error.c_str(),reason.c_str(),this);
|
|
hangup(event->local() ? 1 : -1,error,reason);
|
|
}
|
|
break;
|
|
case IAXEvent::Timeout:
|
|
DDebug(this,DebugNote,"TIMEOUT. Transaction: %u,%u, Frame: %u,%u [%p]",
|
|
event->getTransaction()->localCallNo(),event->getTransaction()->remoteCallNo(),
|
|
event->frameType(),event->subclass(),this);
|
|
if (event->final())
|
|
setReason("offline");
|
|
break;
|
|
case IAXEvent::Busy:
|
|
DDebug(this,DebugCall,"BUSY [%p]",this);
|
|
if (event->final())
|
|
setReason("busy");
|
|
break;
|
|
case IAXEvent::AuthRep:
|
|
evAuthRep(event);
|
|
break;
|
|
case IAXEvent::AuthReq:
|
|
evAuthReq(event);
|
|
break;
|
|
default:
|
|
if (m_transaction) {
|
|
Lock lck(m_mutexTrans);
|
|
if (m_transaction)
|
|
m_transaction->getEngine()->defaultEventHandler(event);
|
|
}
|
|
if (!m_transaction)
|
|
event->setFinal();
|
|
}
|
|
if (event->final()) {
|
|
safeDeref();
|
|
m_transaction = 0;
|
|
}
|
|
}
|
|
|
|
void YIAXConnection::hangup(int location, const char* error, const char* reason, bool reject)
|
|
{
|
|
Lock lck(m_mutexChan);
|
|
if (!m_hangup)
|
|
return;
|
|
m_hangup = false;
|
|
if (!m_reason)
|
|
m_reason = error ? error : reason;
|
|
if (!m_reason && location != -1)
|
|
m_reason = Engine::exiting() ? "shutdown" : "";
|
|
const char* loc = location ? (location > 0 ? "internal" : "remote") : "peer";
|
|
clearMedia(true);
|
|
clearMedia(false);
|
|
clearMedia(true,IAXFormat::Video);
|
|
clearMedia(false,IAXFormat::Video);
|
|
Message* m = message("chan.hangup");
|
|
m->setParam("status","hangup");
|
|
m->setParam("reason",m_reason);
|
|
Debug(this,DebugCall,"Hangup location=%s reason=%s [%p]",loc,m_reason.safe(),this);
|
|
lck.drop();
|
|
resetTransaction(m,reject);
|
|
Engine::enqueue(m);
|
|
}
|
|
|
|
void YIAXConnection::resetTransaction(NamedList* params, bool reject)
|
|
{
|
|
Lock lckChan(m_mutexChan);
|
|
String reason = m_reason;
|
|
lckChan.drop();
|
|
Lock lck(m_mutexTrans);
|
|
if (!m_transaction)
|
|
return;
|
|
if (params) {
|
|
IAXMediaData* d = m_audio ? m_transaction->getData(IAXFormat::Audio) : 0;
|
|
if (d) {
|
|
String tmp;
|
|
d->print(tmp);
|
|
params->addParam("iax_stats",tmp);
|
|
}
|
|
d = m_video ? m_transaction->getData(IAXFormat::Video) : 0;
|
|
if (d) {
|
|
String tmp;
|
|
d->print(tmp);
|
|
params->addParam("iax_stats_video",tmp);
|
|
}
|
|
}
|
|
u_int8_t code = 0;
|
|
if (reason) {
|
|
int val = IAXInfoElement::causeCode(reason);
|
|
if (val)
|
|
code = val;
|
|
else
|
|
code = reason.toInteger(0,0,0,127);
|
|
}
|
|
if (reject)
|
|
m_transaction->sendReject(reason,code);
|
|
else
|
|
m_transaction->sendHangup(reason,code);
|
|
m_transaction->setUserData(0);
|
|
m_transaction = 0;
|
|
}
|
|
|
|
bool YIAXConnection::route(IAXEvent* ev, bool authenticated)
|
|
{
|
|
Lock lck(&m_mutexTrans);
|
|
if (!m_transaction)
|
|
return false;
|
|
if (m_routeCount >= 2)
|
|
return false;
|
|
m_routeCount++;
|
|
Message* m = message("call.preroute",false,true);
|
|
if (authenticated) {
|
|
DDebug(this,DebugAll,"Route pass 2: Password accepted [%p]",this);
|
|
m_refIncreased = false;
|
|
m->addParam("username",m_transaction->username());
|
|
}
|
|
else {
|
|
DDebug(this,DebugAll,"Route pass 1: No username [%p]",this);
|
|
if (!m_transaction->getEngine()->acceptFormatAndCapability(m_transaction)) {
|
|
hangup(0,"nomedia",0,true);
|
|
return false;
|
|
}
|
|
// Advertise the not yet authenticated username
|
|
if (m_transaction->username())
|
|
m->addParam("authname",m_transaction->username());
|
|
}
|
|
m->addParam("called",m_transaction->calledNo());
|
|
m->addParam("caller",m_transaction->callingNo());
|
|
if (ev) {
|
|
u_int32_t val = 0;
|
|
if (ev->getList().getNumeric(IAXInfoElement::CALLINGTON,val))
|
|
IAXEngine::addKeyword(*m,"callernumtype",IAXInfoElement::s_typeOfNumber,val);
|
|
if (ev->getList().getNumeric(IAXInfoElement::CALLINGPRES,val)) {
|
|
IAXEngine::addKeyword(*m,"callerpres",IAXInfoElement::s_presentation,val & 0xf0);
|
|
IAXEngine::addKeyword(*m,"callerscreening",IAXInfoElement::s_screening,val & 0x0f);
|
|
}
|
|
}
|
|
m->addParam("callername",m_transaction->callingName());
|
|
m->addParam("ip_host",m_transaction->remoteAddr().host());
|
|
m->addParam("ip_port",String(m_transaction->remoteAddr().port()));
|
|
String fmtsAudio;
|
|
IAXFormat::formatList(fmtsAudio,m_transaction->capability(),dict_payloads);
|
|
m->addParam("formats",fmtsAudio);
|
|
String fmtsVideo;
|
|
IAXFormat::formatList(fmtsVideo,m_transaction->capability(),dict_payloads_video);
|
|
if (fmtsVideo) {
|
|
if (fmtsAudio) {
|
|
m->addParam("media_audio",String::boolText(true));
|
|
m->addParam("formats_audio",fmtsAudio);
|
|
}
|
|
m->addParam("media_video",String::boolText(true));
|
|
m->addParam("formats_video",fmtsVideo);
|
|
}
|
|
(static_cast<YIAXEngine*>(m_transaction->getEngine()))->fillMessage(*m,false);
|
|
lck.drop();
|
|
return startRouter(m);
|
|
}
|
|
|
|
// Retrieve the data consumer from IAXFormat media type
|
|
YIAXConsumer* YIAXConnection::getConsumer(int type)
|
|
{
|
|
const String& name = dataEpName(type);
|
|
return YOBJECT(YIAXConsumer,Channel::getConsumer(name));
|
|
}
|
|
|
|
// Retrieve the data source from IAXFormat media type
|
|
YIAXSource* YIAXConnection::getSource(int type)
|
|
{
|
|
const String& name = dataEpName(type);
|
|
return YOBJECT(YIAXSource,Channel::getSource(name));
|
|
}
|
|
|
|
DataSource* YIAXConnection::getSourceMedia(int type)
|
|
{
|
|
const String& name = dataEpName(type);
|
|
DataSource* src = Channel::getSource(name);
|
|
if (m_sources[type] != src) {
|
|
m_sources[type] = 0;
|
|
DataEndpoint::commonMutex().lock();
|
|
m_sources[type] = Channel::getSource(name);
|
|
DataEndpoint::commonMutex().unlock();
|
|
}
|
|
return m_sources[type];
|
|
}
|
|
|
|
// Create audio source with the proper format
|
|
void YIAXConnection::startMedia(bool in, int type)
|
|
{
|
|
u_int32_t format = 0;
|
|
m_mutexTrans.lock();
|
|
if (m_transaction) {
|
|
format = in ? m_transaction->formatIn(type) : m_transaction->formatOut(type);
|
|
if (format) {
|
|
if (type == IAXFormat::Audio)
|
|
m_audio = true;
|
|
else if (type == IAXFormat::Video)
|
|
m_video = true;
|
|
}
|
|
}
|
|
m_mutexTrans.unlock();
|
|
bool exists = false;
|
|
if (in) {
|
|
YIAXSource* src = getSource(type);
|
|
if (src && src->format() == format)
|
|
return;
|
|
exists = (src != 0);
|
|
}
|
|
else {
|
|
YIAXConsumer* cons = getConsumer(type);
|
|
if (cons && cons->format() == format)
|
|
return;
|
|
exists = (cons != 0);
|
|
}
|
|
clearMedia(in,type);
|
|
const String& epName = dataEpName(type);
|
|
const char* formatText = format ? lookupFormat(format,type) : 0;
|
|
const char* dir = in ? "incoming" : "outgoing";
|
|
if (exists || format)
|
|
Debug(this,DebugAll,"Starting %s media '%s' format '%s' (%u) [%p]",
|
|
dir,epName.c_str(),formatText,format,this);
|
|
bool ok = true;
|
|
if (in) {
|
|
YIAXSource* src = 0;
|
|
if (formatText)
|
|
src = new YIAXSource(this,format,formatText,type);
|
|
setSource(src,epName);
|
|
TelEngine::destruct(src);
|
|
ok = !format || getSource(type);
|
|
}
|
|
else {
|
|
YIAXConsumer* cons = 0;
|
|
if (formatText)
|
|
cons = new YIAXConsumer(this,format,formatText,type);
|
|
setConsumer(cons,epName);
|
|
TelEngine::destruct(cons);
|
|
ok = !format || getConsumer(type);
|
|
}
|
|
if (!ok)
|
|
Debug(this,DebugNote,"Failed to start %s media '%s' format '%s' (%u) [%p]",
|
|
dir,epName.c_str(),formatText,format,this);
|
|
}
|
|
|
|
void YIAXConnection::clearMedia(bool in, int type)
|
|
{
|
|
const String& epName = dataEpName(type);
|
|
DDebug(this,DebugAll,"Clearing %s %s media [%p]",
|
|
in ? "incoming" : "outgoing",epName.c_str(),this);
|
|
if (in)
|
|
setSource(0,epName);
|
|
else
|
|
setConsumer(0,epName);
|
|
}
|
|
|
|
void YIAXConnection::evAuthRep(IAXEvent* event)
|
|
{
|
|
DDebug(this,DebugAll,"AUTHREP [%p]",this);
|
|
bool requestAuth, invalidAuth;
|
|
const char* reason = "noauth";
|
|
if (iplugin.userAuth(event->getTransaction(),true,requestAuth,invalidAuth,billid())) {
|
|
if (route(event,true))
|
|
return;
|
|
Debug(this,DebugNote,"Failed to route. Rejecting [%p]",this);
|
|
reason = "temporary-failure";
|
|
}
|
|
else
|
|
Debug(this,DebugNote,"Not authenticated. Rejecting [%p]",this);
|
|
event->setFinal();
|
|
hangup(1,reason,0,true);
|
|
}
|
|
|
|
void YIAXConnection::evAuthReq(IAXEvent* event)
|
|
{
|
|
DDebug(this,DebugAll,"AUTHREQ [%p]",this);
|
|
String response;
|
|
Lock lck(m_mutexTrans);
|
|
if (m_transaction) {
|
|
m_transaction->getEngine()->getMD5FromChallenge(response,
|
|
m_transaction->challenge(),m_password);
|
|
m_transaction->sendAuthReply(response);
|
|
}
|
|
}
|
|
|
|
// Get rid of the extra reference
|
|
void YIAXConnection::safeDeref()
|
|
{
|
|
m_mutexRefIncreased.lock();
|
|
bool bref = m_refIncreased;
|
|
m_refIncreased = false;
|
|
m_mutexRefIncreased.unlock();
|
|
if (bref)
|
|
deref();
|
|
}
|
|
|
|
// Keep an extra reference to prevent destroying the connection
|
|
bool YIAXConnection::safeRefIncrease()
|
|
{
|
|
bool ok = false;
|
|
m_mutexRefIncreased.lock();
|
|
if (!m_refIncreased && ref())
|
|
m_refIncreased = ok = true;
|
|
m_mutexRefIncreased.unlock();
|
|
return ok;
|
|
}
|
|
|
|
|
|
//
|
|
// IAXURI
|
|
//
|
|
IAXURI::IAXURI(const char* user, const char* host, const char* calledNo, const char* calledContext, int port)
|
|
: m_username(user),
|
|
m_host(host),
|
|
m_port(port),
|
|
m_calledNo(calledNo),
|
|
m_calledContext(calledContext),
|
|
m_parsed(true)
|
|
|
|
{
|
|
*this << "iax2:";
|
|
if (m_username)
|
|
*this << m_username << "@";
|
|
*this << m_host;
|
|
if (m_port)
|
|
*this << ":" << m_port;
|
|
if (m_calledNo) {
|
|
*this << "/" << m_calledNo;
|
|
if (m_calledContext)
|
|
*this << "@" << m_calledContext;
|
|
}
|
|
}
|
|
|
|
void IAXURI::parse()
|
|
{
|
|
/*
|
|
proto: user@ host :port /calledno @context
|
|
proto: user@ host :port /calledno ?context
|
|
*/
|
|
if (m_parsed)
|
|
return;
|
|
String tmp(*this), _port;
|
|
static const Regexp r("^\\([Ii][Aa][Xx]2\\?:\\)\\?\\([^[:space:][:cntrl:]@]\\+@\\)\\?\\([[:alnum:]._-]\\+\\)\\(:[0-9]\\+\\)\\?\\(/[[:alnum:]]*\\)\\?\\([@?][^@?:/]*\\)\\?$");
|
|
if (tmp.matches(r))
|
|
{
|
|
m_username = tmp.matchString(2);
|
|
m_username = m_username.substr(0,m_username.length() -1);
|
|
m_host = tmp.matchString(3).toLower();
|
|
_port = tmp.matchString(4);
|
|
m_port = _port.substr(1,_port.length()).toInteger();
|
|
m_calledNo = tmp.matchString(5);
|
|
m_calledNo = m_calledNo.substr(1,m_calledNo.length());
|
|
m_calledContext = tmp.matchString(6);
|
|
m_calledContext = m_calledContext.substr(1,m_calledContext.length());
|
|
}
|
|
else
|
|
{
|
|
m_username = "";
|
|
m_host = "";
|
|
m_port = 0;
|
|
m_calledNo = "";
|
|
m_calledContext = "";
|
|
}
|
|
m_parsed = true;
|
|
}
|
|
|
|
// Pick URI parameters from a message or setting
|
|
bool IAXURI::fillList(NamedList& dest)
|
|
{
|
|
if (!m_parsed)
|
|
return false;
|
|
if (m_username.length())
|
|
dest.setParam("username",m_username);
|
|
if (m_calledNo.length())
|
|
dest.setParam("called",m_calledNo);
|
|
if (m_calledContext.length())
|
|
dest.setParam("iaxcontext",m_calledContext);
|
|
return true;
|
|
}
|
|
|
|
bool IAXURI::setAddr(SocketAddr& dest)
|
|
{
|
|
parse();
|
|
if (!m_host.length())
|
|
return false;
|
|
dest.host(m_host);
|
|
dest.port(m_port ? m_port : 0);
|
|
return true;
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|