4027 lines
120 KiB
C++
4027 lines
120 KiB
C++
/**
|
|
* ysigchan.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* Yet Another Signalling Channel
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2006 Null Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <yatephone.h>
|
|
#include <yatesig.h>
|
|
|
|
#include <string.h>
|
|
|
|
using namespace TelEngine;
|
|
namespace { // anonymous
|
|
|
|
class SigChannel; // Signalling channel
|
|
class SigDriver; // Signalling driver
|
|
class SigTopmost; // Keep a topmost non-trunk component
|
|
class SigCircuitGroup; // Used to create a signalling circuit group descendant to set the debug name
|
|
class SigTrunk; // Keep a signalling trunk
|
|
class SigSS7Isup; // SS7 ISDN User Part call controller
|
|
class SigIsdn; // ISDN (Q.931 over HDLC interface) call control
|
|
class SigIsdnMonitor; // ISDN (Q.931 over HDLC interface) call control monitor
|
|
class SigConsumerMux; // Consumer used to push data to SigSourceMux
|
|
class SigSourceMux; // A data source multiplexer with 2 channels
|
|
class SigIsdnCallRecord; // Record an ISDN call monitor
|
|
class SigTrunkThread; // Get events and check timeout for trunks that have a call controller
|
|
class IsupDecodeHandler; // Handler for "isup.decode" message
|
|
class IsupEncodeHandler; // Handler for "isup.encode" message
|
|
class SigNotifier; // Class for handling received notifications
|
|
|
|
// The signalling channel
|
|
class SigChannel : public Channel
|
|
{
|
|
YCLASS(SigChannel,Channel)
|
|
public:
|
|
// Incoming
|
|
SigChannel(SignallingEvent* event);
|
|
// Outgoing
|
|
SigChannel(const char* caller, const char* called);
|
|
virtual ~SigChannel();
|
|
virtual void destroyed();
|
|
bool startRouter();
|
|
bool startCall(Message& msg, String& trunks);
|
|
inline SignallingCall* call() const
|
|
{ return m_call; }
|
|
inline SigTrunk* trunk() const
|
|
{ return m_trunk; }
|
|
inline bool hungup() const
|
|
{ return m_hungup; }
|
|
// Overloaded methods
|
|
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 bool msgUpdate(Message& msg);
|
|
virtual bool msgDrop(Message& msg, const char* reason);
|
|
virtual bool msgTransfer(Message& msg);
|
|
virtual bool callPrerouted(Message& msg, bool handled);
|
|
virtual bool callRouted(Message& msg);
|
|
virtual void callAccept(Message& msg);
|
|
virtual void callRejected(const char* error, const char* reason = 0,
|
|
const Message* msg = 0);
|
|
virtual void connected(const char *reason);
|
|
virtual void disconnected(bool final, const char* reason);
|
|
bool disconnect()
|
|
{ return Channel::disconnect(m_reason,parameters()); }
|
|
void handleEvent(SignallingEvent* event);
|
|
void hangup(const char* reason = 0, SignallingEvent* event = 0, const NamedList* extra = 0);
|
|
// Notifier message dispatched handler
|
|
virtual void dispatched(const Message& msg, bool handled);
|
|
protected:
|
|
void endDisconnect(const Message& params, bool handled);
|
|
private:
|
|
bool startCall(Message& msg, SigTrunk* trunk);
|
|
virtual void statusParams(String& str);
|
|
// Set call status. Print debug message
|
|
void setState(const char* state, bool updateStatus = true, bool showReason = false);
|
|
// Event handlers
|
|
void evInfo(SignallingEvent* event);
|
|
void evProgress(SignallingEvent* event);
|
|
void evRelease(SignallingEvent* event);
|
|
void evAccept(SignallingEvent* event);
|
|
void evAnswer(SignallingEvent* event);
|
|
void evRinging(SignallingEvent* event);
|
|
void evCircuit(SignallingEvent* event);
|
|
void evGeneric(SignallingEvent* event, const char* operation);
|
|
// Handle RTP forward from a message.
|
|
// Return a circuit event to be sent or 0 if not handled
|
|
SignallingCircuitEvent* handleRtp(Message& msg);
|
|
// Set RTP data from circuit to message
|
|
bool addRtp(Message& msg, bool possible = false);
|
|
// Update media type, may have switched to fax
|
|
bool updateMedia(Message& msg);
|
|
// Update circuit and format in source, optionally in consumer too
|
|
void updateCircuitFormat(SignallingEvent* event, bool consumer);
|
|
// Open or update format source/consumer
|
|
// Set force to true to change source/consumer pointer/format
|
|
bool updateConsumer(const char* format, bool force);
|
|
bool updateSource(const char* format, bool force);
|
|
// Get the circuit reserved for the call
|
|
inline SignallingCircuit* getCircuit() {
|
|
SignallingCircuit* cic = 0;
|
|
if (m_call)
|
|
cic = static_cast<SignallingCircuit*>(m_call->getObject("SignallingCircuit"));
|
|
return cic;
|
|
}
|
|
// Retrieve the ISUP call controller from trunk
|
|
SS7ISUP* isupController() const;
|
|
// Process parameter compatibility data from a message
|
|
// Return true if the call was terminated
|
|
bool processCompat(const NamedList& list);
|
|
// Send a tone through signalling call
|
|
bool sendSigTone(Message& msg, const char* tone);
|
|
// Release postponed call accepted event. Send it if requested
|
|
void releaseCallAccepted(bool send = false);
|
|
private:
|
|
String m_caller;
|
|
String m_called;
|
|
SignallingCall* m_call; // The signalling call this channel is using
|
|
SigTrunk* m_trunk; // The trunk owning the signalling call
|
|
bool m_hungup; // Hang up flag
|
|
String m_reason; // Hangup reason
|
|
bool m_inband; // True to try to send in-band tones
|
|
bool m_ringback; // Always provide ringback media
|
|
bool m_rtpForward; // Forward RTP
|
|
bool m_sdpForward; // Forward SDP (only of rtp forward is enabled)
|
|
Message* m_route; // Prepared call.preroute message
|
|
ObjList m_chanDtmf; // Postponed (received while routing) chan.dtmf messages
|
|
SignallingEvent* m_callAccdEvent; // Postponed call accepted event to be sent to remote
|
|
};
|
|
|
|
class SigDriver : public Driver
|
|
{
|
|
friend class SigTopmost;
|
|
friend class SigTrunk; // Needded for appendTrunk() / removeTrunk()
|
|
friend class SigTrunkThread; // Needded for clearTrunk()
|
|
public:
|
|
enum Operations {
|
|
Create = 0x01,
|
|
Configure = 0x02,
|
|
Unknown = 0x03,
|
|
};
|
|
|
|
SigDriver();
|
|
~SigDriver();
|
|
virtual void initialize();
|
|
virtual bool msgExecute(Message& msg, String& dest);
|
|
inline SignallingEngine* engine() const
|
|
{ return m_engine; }
|
|
void handleEvent(SignallingEvent* event);
|
|
bool received(Message& msg, int id);
|
|
// Find a trunk by name
|
|
// If callCtrl is true, match only trunks with call controller
|
|
SigTrunk* findTrunk(const char* name, bool callCtrl);
|
|
// Find a trunk by call controller
|
|
SigTrunk* findTrunk(const SignallingCallControl* ctrl);
|
|
// Disconnect channels. If trunk is not 0, disconnect only channels belonging to that trunk
|
|
void disconnectChannels(SigTrunk* trunk = 0);
|
|
// Copy incoming message parameters to another list of parameters
|
|
// The pointers of NamedPointer parameters are 'stolen' from 'sig' when copying
|
|
// If 'params' is not 0, the contained parameters will not be prefixed with
|
|
// event call controller's prefix
|
|
void copySigMsgParams(NamedList& dest, SignallingEvent* event,
|
|
const String* params = 0);
|
|
// Reinitialize
|
|
bool reInitialize(NamedList& params);
|
|
// Build an configuration file from param list
|
|
void getCfg(NamedList& params, Configuration& cfg);
|
|
// Check if the cfg sections exists in conf file
|
|
bool checkSections(Configuration* cfg);
|
|
// Append the new sections in the conf file
|
|
// Save the configuration
|
|
void saveConf(Configuration* cfg);
|
|
// Init configuration sections
|
|
bool initSection(NamedList* sect);
|
|
// Copy outgoing message parameters
|
|
void copySigMsgParams(SignallingEvent* event, const NamedList& params, const char* prePrefix = "o");
|
|
static const TokenDict s_operations[];
|
|
private:
|
|
// Handle command complete requests
|
|
virtual bool commandComplete(Message& msg, const String& partLine,
|
|
const String& partWord);
|
|
// Custom command handler
|
|
virtual bool commandExecute(String& retVal, const String& line);
|
|
// Help message handler
|
|
bool commandHelp(String& retVal, const String& line);
|
|
// Delete the given trunk if found
|
|
// Clear trunk list if name is 0
|
|
// Clear all stacks without waiting for call termination if name is 0
|
|
void clearTrunk(const char* name = 0, bool waitCallEnd = false, unsigned int howLong = 0);
|
|
// Append a trunk to the list. Duplicate names are not allowed
|
|
bool appendTrunk(SigTrunk* trunk);
|
|
// Remove a trunk from list without deleting it
|
|
void removeTrunk(SigTrunk* trunk);
|
|
// Create or initialize a trunk
|
|
bool initTrunk(NamedList& sect, int type);
|
|
// Get the status of a trunk
|
|
void status(SigTrunk* trunk, String& retVal, const String& target);
|
|
// Append a topmost non-trunk component
|
|
bool appendTopmost(SigTopmost* topmost);
|
|
// Remove a topmost component without deleting it
|
|
void removeTopmost(SigTopmost* topmost);
|
|
// Create or initialize a topmost component
|
|
bool initTopmost(NamedList& sect, int type);
|
|
// Get the status of a topmost component
|
|
void status(SigTopmost* topmost, String& retVal);
|
|
|
|
SignallingEngine* m_engine; // The signalling engine
|
|
ObjList m_trunks; // Trunk list
|
|
Mutex m_trunksMutex; // Lock trunk list operations
|
|
ObjList m_topmost; // Topmost non-trunk list
|
|
Mutex m_topmostMutex; // Lock topmost non-trunk list operations
|
|
String m_statusCmd; // Prefix for status commands
|
|
};
|
|
|
|
// Topmost element holder - call controller, link or application server
|
|
class TopMost : public RefObject
|
|
{
|
|
public:
|
|
inline const String& name() const
|
|
{ return m_name; }
|
|
virtual const String& toString() const
|
|
{ return m_name; }
|
|
virtual bool initialize(NamedList& params) = 0;
|
|
protected:
|
|
inline TopMost(const char* name)
|
|
: m_name(name)
|
|
{ }
|
|
private:
|
|
String m_name; // Element name
|
|
};
|
|
|
|
class SigTopmost : public TopMost
|
|
{
|
|
public:
|
|
SigTopmost(const char* name);
|
|
// Return the status of this component
|
|
virtual inline void status(String& retVal)
|
|
{ }
|
|
// Return the status of a signalling interface
|
|
virtual inline void ifStatus(String& status)
|
|
{ }
|
|
// Return the status of a link
|
|
virtual inline void linkStatus(String& status)
|
|
{ }
|
|
protected:
|
|
virtual void destroyed();
|
|
};
|
|
|
|
// Signalling link set (SS7 Layer 3)
|
|
class SigLinkSet : public SigTopmost
|
|
{
|
|
public:
|
|
inline SigLinkSet(const char* name)
|
|
: SigTopmost(name), m_linkset(0)
|
|
{ }
|
|
// Initialize (create or reload) the linkset
|
|
// Return false on failure
|
|
virtual bool initialize(NamedList& params);
|
|
// Return the status of this component
|
|
virtual void status(String& retVal);
|
|
virtual void ifStatus(String& status);
|
|
virtual void linkStatus(String& status);
|
|
protected:
|
|
virtual void destroyed();
|
|
private:
|
|
SS7Layer3* m_linkset;
|
|
};
|
|
|
|
// MTP Traffic Testing
|
|
class SigTesting : public SigTopmost
|
|
{
|
|
public:
|
|
inline SigTesting(const char* name)
|
|
: SigTopmost(name), m_testing(0)
|
|
{ }
|
|
// Initialize (create or reload) the linkset
|
|
// Return false on failure
|
|
virtual bool initialize(NamedList& params);
|
|
protected:
|
|
virtual void destroyed();
|
|
private:
|
|
SS7Testing* m_testing;
|
|
};
|
|
|
|
// Signalling trunk (Call Controller)
|
|
class SigTrunk : public TopMost
|
|
{
|
|
friend class SigTrunkThread; // The thread must set m_thread to 0 on terminate
|
|
public:
|
|
enum Mask {
|
|
MaskSS7 = 0x01,
|
|
MaskIsdn = 0x02,
|
|
MaskMon = 0x04,
|
|
MaskNet = 0x10,
|
|
MaskCpe = 0x20,
|
|
MaskPri = 0x40,
|
|
MaskBri = 0x80,
|
|
MaskIsup = 0x10,
|
|
MaskBicc = 0x20,
|
|
};
|
|
enum Type {
|
|
Unknown = 0,
|
|
SS7Isup = MaskSS7 | MaskIsup,
|
|
SS7Bicc = MaskSS7 | MaskBicc,
|
|
IsdnPriNet = MaskIsdn | MaskNet | MaskPri,
|
|
IsdnBriNet = MaskIsdn | MaskNet | MaskBri,
|
|
IsdnPriCpe = MaskIsdn | MaskCpe | MaskPri,
|
|
IsdnBriCpe = MaskIsdn | MaskCpe | MaskBri,
|
|
IsdnPriMon = MaskIsdn | MaskMon,
|
|
};
|
|
inline Type type() const
|
|
{ return m_type; }
|
|
inline SignallingCallControl* controller() const
|
|
{ return m_controller; }
|
|
inline bool inband() const
|
|
{ return m_inband; }
|
|
inline bool ringback() const
|
|
{ return m_ringback; }
|
|
// Set exiting flag for call controller and timeout for the thread
|
|
void setExiting(unsigned int msec);
|
|
// Initialize (create or reload) the trunk. Process the debuglayer parameter.
|
|
// Fix some type depending parameters
|
|
// Return false on failure
|
|
virtual bool initialize(NamedList& params);
|
|
// Handle events received from call controller
|
|
// Default action: calls the driver's handleEvent()
|
|
virtual void handleEvent(SignallingEvent* event);
|
|
// Handle chan.masquerade. Return true if handled
|
|
// @param id Channel id
|
|
// @param msg Received message
|
|
virtual bool masquerade(String& id, Message& msg)
|
|
{ return false; }
|
|
// Handle chan.drop. Return true if handled
|
|
// @param id Channel id
|
|
// @param msg Received message
|
|
virtual bool drop(String& id, Message& msg)
|
|
{ return false; }
|
|
// Clear channels with calls belonging to this trunk. Cancel thread if any. Call release
|
|
void cleanup();
|
|
// Type names
|
|
static const TokenDict s_type[];
|
|
virtual inline void ifStatus(String& status)
|
|
{ }
|
|
virtual inline void linkStatus(String& status)
|
|
{ }
|
|
protected:
|
|
// Cancel thread. Cleanup. Remove from plugin list
|
|
virtual void destroyed();
|
|
// Set trunk name and type. Append to plugin list
|
|
SigTrunk(const char* name, Type type);
|
|
// Start worker thread. Set error on failure
|
|
bool startThread(String& error, unsigned long sleepUsec);
|
|
// Create/Reload/Release data
|
|
virtual bool create(NamedList& params, String& error) { return false; }
|
|
virtual bool reload(NamedList& params) { return false; }
|
|
virtual void release() { }
|
|
// Build a signalling circuit group and insert it in the engine
|
|
static SignallingCircuitGroup* buildCircuits(NamedList& params, const String& device,
|
|
const String& debugName, String& error);
|
|
// Build component debug name
|
|
inline void buildName(String& dest, const char* name)
|
|
{ dest = ""; dest << this->name() << '/' << name; }
|
|
SignallingCallControl* m_controller; // Call controller, if any
|
|
bool m_init; // True if already initialized
|
|
bool m_inband; // True to send in-band tones through this trunk
|
|
bool m_ringback; // Always provide ringback media
|
|
private:
|
|
Type m_type; // Trunk type
|
|
SigTrunkThread* m_thread; // Event thread for call controller
|
|
};
|
|
|
|
// SS7 ISDN User Part call controller
|
|
class SigSS7Isup : public SigTrunk
|
|
{
|
|
public:
|
|
SigSS7Isup(const char* name, bool bicc);
|
|
virtual ~SigSS7Isup();
|
|
protected:
|
|
virtual bool create(NamedList& params, String& error);
|
|
virtual bool reload(NamedList& params);
|
|
virtual void release();
|
|
// Handle events received from call controller
|
|
// Process Verify event. Calls the driver's handleEvent() method for other events
|
|
virtual void handleEvent(SignallingEvent* event);
|
|
// Save circuits state
|
|
// Return true if changed
|
|
bool verifyController(const NamedList* params, bool save = true);
|
|
inline SS7ISUP* isup()
|
|
{ return static_cast<SS7ISUP*>(m_controller); }
|
|
bool m_bicc;
|
|
};
|
|
|
|
// Q.931 call control over HDLC interface
|
|
class SigIsdn : public SigTrunk
|
|
{
|
|
public:
|
|
SigIsdn(const char* name, Type type);
|
|
virtual ~SigIsdn();
|
|
virtual void ifStatus(String& status);
|
|
virtual void linkStatus(String& status);
|
|
protected:
|
|
virtual bool create(NamedList& params, String& error);
|
|
virtual bool reload(NamedList& params);
|
|
virtual void release();
|
|
inline ISDNQ931* q931()
|
|
{ return static_cast<ISDNQ931*>(m_controller); }
|
|
};
|
|
|
|
// Q.931 call control monitor over HDLC interface
|
|
class SigIsdnMonitor : public SigTrunk, public Mutex
|
|
{
|
|
friend class SigIsdnCallRecord;
|
|
public:
|
|
SigIsdnMonitor(const char* name);
|
|
virtual ~SigIsdnMonitor();
|
|
virtual void handleEvent(SignallingEvent* event);
|
|
virtual bool masquerade(String& id, Message& msg);
|
|
virtual bool drop(String& id, Message& msg);
|
|
unsigned int chanBuffer() const
|
|
{ return m_chanBuffer; }
|
|
unsigned char idleValue() const
|
|
{ return m_idleValue; }
|
|
// Remove a call and it's call monitor
|
|
void removeCall(SigIsdnCallRecord* call);
|
|
protected:
|
|
virtual bool create(NamedList& params, String& error);
|
|
virtual bool reload(NamedList& params);
|
|
virtual void release();
|
|
inline ISDNQ931Monitor* q931()
|
|
{ return static_cast<ISDNQ931Monitor*>(m_controller); }
|
|
private:
|
|
ObjList m_monitors; // Monitor list
|
|
unsigned int m_id; // ID generator
|
|
unsigned int m_chanBuffer; // The buffer length of one channel of a data source multiplexer
|
|
unsigned char m_idleValue; // Idle value for source multiplexer to fill when no data
|
|
};
|
|
|
|
// Factory for locally configured components
|
|
class SigFactory : public SignallingFactory
|
|
{
|
|
public:
|
|
enum SigMask {
|
|
// Mask for private bits
|
|
SigPrivate = 0x00ff,
|
|
// Created always from config file section
|
|
SigTopMost = 0x0100,
|
|
// Created on demand from config file if section exists
|
|
SigOnDemand = 0x0200,
|
|
// Created from config file, use defaults if no section
|
|
SigDefaults = 0x0400,
|
|
// Component is a signalling trunk
|
|
SigIsTrunk = 0x0800,
|
|
};
|
|
enum SigType {
|
|
Unknown = 0,
|
|
SigISDNLayer2 = 0x01 | SigDefaults,
|
|
SigISDNLayer3 = 0x02 | SigDefaults,
|
|
SigISDNIUA = 0x03 | SigDefaults,
|
|
SigSS7Layer2 = 0x11 | SigOnDemand,
|
|
SigSS7Layer3 = 0x12 | SigTopMost,
|
|
SigSS7Router = 0x13 | SigDefaults,
|
|
SigSS7Management = 0x14 | SigDefaults,
|
|
SigSS7Testing = 0x16 | SigTopMost,
|
|
SigSS7M2PA = 0x21 | SigOnDemand,
|
|
SigSS7M2UA = 0x22 | SigOnDemand,
|
|
SigSS7M3UA = 0x23 | SigTopMost,
|
|
SigISDNIUAClient = 0x31 | SigDefaults,
|
|
SigISDNIUAGateway = 0x32 | SigTopMost,
|
|
SigSS7M2UAClient = 0x33 | SigDefaults,
|
|
SigSS7M2UAGateway = 0x34 | SigTopMost,
|
|
SigSS7M3UAClient = 0x35 | SigDefaults,
|
|
SigSS7M3UAGateway = 0x36 | SigTopMost,
|
|
SigSS7Isup = SigTrunk::SS7Isup | SigIsTrunk | SigTopMost,
|
|
SigSS7Bicc = SigTrunk::SS7Bicc | SigIsTrunk | SigTopMost,
|
|
SigISDNPN = SigTrunk::IsdnPriNet | SigIsTrunk | SigTopMost,
|
|
SigISDNBN = SigTrunk::IsdnBriNet | SigIsTrunk | SigTopMost,
|
|
SigISDNPC = SigTrunk::IsdnPriCpe | SigIsTrunk | SigTopMost,
|
|
SigISDNBC = SigTrunk::IsdnBriCpe | SigIsTrunk | SigTopMost,
|
|
SigISDNMon = SigTrunk::IsdnPriMon | SigIsTrunk | SigTopMost,
|
|
};
|
|
inline SigFactory()
|
|
: SignallingFactory(true)
|
|
{ }
|
|
static const TokenDict s_compNames[];
|
|
static const TokenDict s_compClass[];
|
|
protected:
|
|
virtual SignallingComponent* create(const String& type, const NamedList& name);
|
|
};
|
|
|
|
// Consumer used to push data to SigSourceMux
|
|
class SigConsumerMux : public DataConsumer
|
|
{
|
|
friend class SigSourceMux;
|
|
public:
|
|
virtual ~SigConsumerMux()
|
|
{ }
|
|
virtual unsigned long Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags);
|
|
protected:
|
|
inline SigConsumerMux(SigSourceMux* owner, bool first, const char* format)
|
|
: DataConsumer(format), m_owner(owner), m_first(first)
|
|
{ }
|
|
private:
|
|
SigSourceMux* m_owner; // The owner of this consumer
|
|
bool m_first; // The channel allocated by the owner
|
|
};
|
|
|
|
// A data source multiplexer with 2 channels
|
|
class SigSourceMux : public DataSource
|
|
{
|
|
public:
|
|
// Create consumers
|
|
// @param idleValue Value to fill missing data when forwarded
|
|
// @param chanBuffer The length of a channel buffer (will be rounded up to a multiple of sample length)
|
|
SigSourceMux(const char* format, unsigned char idleValue, unsigned int chanBuffer);
|
|
virtual ~SigSourceMux();
|
|
inline unsigned int sampleLen() const
|
|
{ return m_sampleLen; }
|
|
bool hasSource(bool first)
|
|
{ return first ? (m_firstSrc != 0) : (m_secondSrc != 0); }
|
|
// Replace the consumer of the given source. Remove current consumer's source before
|
|
// @param first True to replace with the first channel, false for the second
|
|
// Return false if source is 0 or has invalid format (other then ours)
|
|
bool attach(bool first, DataSource* source);
|
|
// Multiplex received data from consumers and forward it
|
|
void consume(bool first, const DataBlock& data, unsigned long tStamp);
|
|
// Remove the source for the appropriate consumer
|
|
void removeSource(bool first);
|
|
protected:
|
|
// Forward the buffer if at least one channel is filled. Reset data
|
|
// If one channel is empty or incomplete, fill it with idle value
|
|
void forwardBuffer();
|
|
// Fill (interlaced samples) buffer with samples of received data
|
|
// If no data, fill the free space with idle value
|
|
void fillBuffer(bool first, unsigned char* data = 0, unsigned int samples = 0);
|
|
inline bool firstFull() const
|
|
{ return m_samplesFirst == m_maxSamples; }
|
|
inline bool secondFull() const
|
|
{ return m_samplesSecond == m_maxSamples; }
|
|
private:
|
|
Mutex m_lock; // Lock consumers changes and data processing
|
|
DataSource* m_firstSrc; // First consumer's source
|
|
DataSource* m_secondSrc; // Second consumer's source
|
|
SigConsumerMux* m_firstChan; // First channel
|
|
SigConsumerMux* m_secondChan; // Second channel
|
|
unsigned char m_idleValue; // Filling value for missing data
|
|
unsigned int m_sampleLen; // The format sample length
|
|
unsigned int m_maxSamples; // Maximum samples in a channel buffer
|
|
unsigned int m_samplesFirst; // The number of samples in first channel's buffer
|
|
unsigned int m_samplesSecond; // The number of samples in second channel's buffer
|
|
DataBlock m_buffer; // Multiplex buffer
|
|
unsigned int m_error; // Flag to show data length violation error
|
|
};
|
|
|
|
// Record an ISDN call monitor
|
|
class SigIsdnCallRecord : public CallEndpoint
|
|
{
|
|
public:
|
|
SigIsdnCallRecord(SigIsdnMonitor* monitor, const char* id, SignallingEvent* event);
|
|
virtual ~SigIsdnCallRecord();
|
|
bool update(SignallingEvent* event);
|
|
bool close(const char* reason);
|
|
bool disconnect(const char* reason);
|
|
// Process Info events. Send chan.dtmf
|
|
void evInfo(SignallingEvent* event);
|
|
protected:
|
|
virtual void disconnected(bool final, const char *reason);
|
|
// Create a message to be enqueued/dispatched to the engine
|
|
// @param peers True to caller and called parameters
|
|
// @param userdata True to add this call endpoint as user data
|
|
Message* message(const char* name, bool peers = true, bool userdata = false);
|
|
// Send call.route and call.execute (if call.route succeedded)
|
|
bool callRouteAndExec(const char* format);
|
|
private:
|
|
Mutex m_lock;
|
|
String m_caller; // The caller
|
|
String m_called; // The called
|
|
String m_address; // Address including circuit number
|
|
bool m_netInit; // The caller is from the network (true) or user (false) side of the trunk
|
|
String m_reason; // Termination reason
|
|
String m_status; // Call status
|
|
SigIsdnMonitor* m_monitor; // The owner of this recorder
|
|
ISDNQ931CallMonitor* m_call; // The call monitor
|
|
};
|
|
|
|
// Get events from call controller. Check timeouts
|
|
class SigTrunkThread : public Thread
|
|
{
|
|
friend class SigTrunk; // SigTrunk will set m_timeout when needded
|
|
public:
|
|
inline SigTrunkThread(SigTrunk* trunk, unsigned long sleepUsec)
|
|
: Thread("YSIG Trunk"),
|
|
m_trunk(trunk), m_timeout(0), m_sleepUsec(sleepUsec)
|
|
{ }
|
|
virtual ~SigTrunkThread();
|
|
virtual void run();
|
|
private:
|
|
SigTrunk* m_trunk;
|
|
u_int64_t m_timeout;
|
|
unsigned long m_sleepUsec;
|
|
};
|
|
|
|
|
|
// isup.decode handler (decode an isup message)
|
|
class IsupDecodeHandler : public MessageHandler
|
|
{
|
|
public:
|
|
// Init the ISUP component and the mesage name
|
|
IsupDecodeHandler(bool decode = true);
|
|
virtual void destruct();
|
|
virtual bool received(Message& msg);
|
|
protected:
|
|
// Get point code type (protocol version) from message
|
|
// Return SS7PointCode::Other if unknown
|
|
SS7PointCode::Type getPCType(Message& msg, const String& prefix);
|
|
SS7ISUP* m_isup;
|
|
};
|
|
|
|
// isup.encode handler (encode an isup message)
|
|
class IsupEncodeHandler : public IsupDecodeHandler
|
|
{
|
|
public:
|
|
// Init the ISUP component
|
|
inline IsupEncodeHandler()
|
|
: IsupDecodeHandler(false)
|
|
{ }
|
|
virtual bool received(Message& msg);
|
|
};
|
|
|
|
class SigNotifier : public SignallingNotifier
|
|
{
|
|
public:
|
|
inline ~SigNotifier()
|
|
{ }
|
|
virtual void notify(NamedList& params);
|
|
virtual void cleanup();
|
|
};
|
|
|
|
static SigDriver plugin;
|
|
static SigFactory factory;
|
|
static SigNotifier s_notifier;
|
|
static Configuration s_cfg;
|
|
static Configuration s_cfgData;
|
|
static const String s_noPrefixParams = "format,earlymedia";
|
|
|
|
static const char s_miniHelp[] = "sigdump component [filename]";
|
|
static const char s_fullHelp[] = "Command to dump signalling data to a file";
|
|
|
|
const TokenDict SigFactory::s_compNames[] = {
|
|
{ "isdn-q921", SigISDNLayer2 },
|
|
{ "isdn-q931", SigISDNLayer3 },
|
|
{ "isdn-iua", SigISDNIUA },
|
|
{ "isdn-iua-client", SigISDNIUAClient },
|
|
{ "isdn-iua-gateway", SigISDNIUAGateway },
|
|
{ "ss7-router", SigSS7Router },
|
|
{ "ss7-mtp2", SigSS7Layer2 },
|
|
{ "ss7-mtp3", SigSS7Layer3 },
|
|
{ "ss7-snm", SigSS7Management },
|
|
{ "ss7-test", SigSS7Testing },
|
|
{ "ss7-m2pa", SigSS7M2PA },
|
|
{ "ss7-m2ua", SigSS7M2UA },
|
|
{ "ss7-m2ua-client", SigSS7M2UAClient },
|
|
{ "ss7-m2ua-gateway", SigSS7M2UAGateway },
|
|
{ "ss7-m3ua", SigSS7M3UA },
|
|
{ "ss7-m3ua-client", SigSS7M3UAClient },
|
|
{ "ss7-m3ua-gateway", SigSS7M3UAGateway },
|
|
{ "ss7-isup", SigSS7Isup },
|
|
{ "ss7-bicc", SigSS7Bicc },
|
|
{ "isdn-pri-net", SigISDNPN },
|
|
{ "isdn-bri-net", SigISDNBN },
|
|
{ "isdn-pri-cpe", SigISDNPC },
|
|
{ "isdn-bri-cpe", SigISDNBC },
|
|
{ "isdn-pri-mon", SigISDNMon },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
const TokenDict SigDriver::s_operations[] = {
|
|
{ "create", Create },
|
|
{ "configure", Configure },
|
|
{ "unknown", Unknown },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
const TokenDict SigFactory::s_compClass[] = {
|
|
#define MAKE_CLASS(x) { #x, Sig##x }
|
|
MAKE_CLASS(ISDNLayer2),
|
|
MAKE_CLASS(ISDNIUA),
|
|
MAKE_CLASS(ISDNIUAClient),
|
|
MAKE_CLASS(SS7Router),
|
|
MAKE_CLASS(SS7Layer2),
|
|
MAKE_CLASS(SS7Layer3),
|
|
MAKE_CLASS(SS7Management),
|
|
MAKE_CLASS(SS7Testing),
|
|
MAKE_CLASS(SS7M2PA),
|
|
MAKE_CLASS(SS7M2UA),
|
|
MAKE_CLASS(SS7M2UAClient),
|
|
MAKE_CLASS(SS7M3UA),
|
|
MAKE_CLASS(SS7M3UAClient),
|
|
MAKE_CLASS(SS7Isup),
|
|
MAKE_CLASS(SS7Bicc),
|
|
MAKE_CLASS(ISDNPN),
|
|
MAKE_CLASS(ISDNBN),
|
|
MAKE_CLASS(ISDNPC),
|
|
MAKE_CLASS(ISDNBC),
|
|
MAKE_CLASS(ISDNMon),
|
|
#undef MAKE_CLASS
|
|
{ 0, 0 }
|
|
};
|
|
|
|
// Construct a locally configured component
|
|
SignallingComponent* SigFactory::create(const String& type, const NamedList& name)
|
|
{
|
|
const NamedList* config = s_cfg.getSection(name);
|
|
DDebug(&plugin,DebugAll,"SigFactory::create('%s','%s') config=%p",
|
|
type.c_str(),name.c_str(),config);
|
|
int compType = type.toInteger(s_compClass,-1);
|
|
if (compType < 0)
|
|
return 0;
|
|
if (!config) {
|
|
NamedList sec(name);
|
|
sec.copySubParams(name,name + ".");
|
|
if (sec.count())
|
|
config = &name;
|
|
else {
|
|
if ((compType & SigDefaults) != 0)
|
|
config = &name;
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
String* ty = config->getParam("type");
|
|
switch (compType) {
|
|
case SigISDNLayer2:
|
|
if (ty && *ty == "isdn-iua")
|
|
return new ISDNIUA(*config);
|
|
if (name.getBoolValue("primary",true)) {
|
|
NamedList cfg(*config);
|
|
cfg.setParam("type",lookup(compType,s_compNames));
|
|
return new ISDNQ921(cfg,name);
|
|
}
|
|
return new ISDNQ921Management(*config,name,name.getBoolValue("network",true));
|
|
case SigISDNLayer3:
|
|
return new ISDNQ931(*config,name);
|
|
case SigISDNIUA:
|
|
return new ISDNIUA(*config);
|
|
case SigISDNIUAClient:
|
|
return new ISDNIUAClient(*config);
|
|
case SigSS7Layer2:
|
|
if (ty) {
|
|
if (*ty == "ss7-m2pa")
|
|
return new SS7M2PA(*config);
|
|
if (*ty == "ss7-m2ua")
|
|
return new SS7M2UA(*config);
|
|
}
|
|
return new SS7MTP2(*config);
|
|
case SigSS7M2PA:
|
|
return new SS7M2PA(*config);
|
|
case SigSS7M2UA:
|
|
return new SS7M2UA(*config);
|
|
case SigSS7M2UAClient:
|
|
return new SS7M2UAClient(*config);
|
|
case SigSS7Layer3:
|
|
return new SS7MTP3(*config);
|
|
case SigSS7Router:
|
|
return new SS7Router(*config);
|
|
case SigSS7Management:
|
|
return new SS7Management(*config);
|
|
case SigSS7Testing:
|
|
return new SS7Testing(*config);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* SigChannel
|
|
*/
|
|
// Construct an incoming channel
|
|
SigChannel::SigChannel(SignallingEvent* event)
|
|
: Channel(&plugin,0,false),
|
|
m_call(event->call()),
|
|
m_trunk(0),
|
|
m_hungup(true),
|
|
m_inband(false),
|
|
m_ringback(false),
|
|
m_rtpForward(false),
|
|
m_sdpForward(false),
|
|
m_route(0),
|
|
m_callAccdEvent(0)
|
|
{
|
|
if (!(m_call && m_call->ref())) {
|
|
Debug(this,DebugCall,"No signalling call for this incoming call");
|
|
m_call = 0;
|
|
return;
|
|
}
|
|
SignallingMessage* msg = event->message();
|
|
m_caller = msg ? msg->params().getValue("caller") : 0;
|
|
m_called = msg ? msg->params().getValue("called") : 0;
|
|
m_call->userdata(this);
|
|
m_trunk = plugin.findTrunk(m_call->controller());
|
|
if (m_trunk) {
|
|
m_inband = m_trunk->inband();
|
|
m_ringback = m_trunk->ringback();
|
|
}
|
|
// Startup
|
|
m_hungup = false;
|
|
setState(0);
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (cic) {
|
|
if (m_trunk)
|
|
m_address << m_trunk->name() << "/" << cic->code();
|
|
m_rtpForward = cic->getBoolParam("rtp_forward");
|
|
m_sdpForward = cic->getBoolParam("sdp_forward");
|
|
}
|
|
Message* m = message("chan.startup");
|
|
m->setParam("direction",status());
|
|
m->addParam("caller",m_caller);
|
|
m->addParam("called",m_called);
|
|
if (msg)
|
|
m->copyParam(msg->params(),"callername");
|
|
// TODO: Add call control parameter ?
|
|
Engine::enqueue(m);
|
|
// call.preroute message
|
|
m_route = message("call.preroute",false,true);
|
|
// Parameters to be copied to call.preroute
|
|
static String params = "caller,called,callername,format,formats,callernumtype,callernumplan,callerpres,callerscreening,callednumtype,callednumplan,inn,overlapped";
|
|
plugin.copySigMsgParams(*m_route,event,¶ms);
|
|
if (m_route->getBoolValue("overlapped") && !m_route->getValue("called"))
|
|
m_route->setParam("called","off-hook");
|
|
if (event->message()) {
|
|
const String* pres = event->message()->params().getParam("callerpres");
|
|
if (pres && (*pres == "restricted"))
|
|
m_route->setParam("privacy",String::boolText(true));
|
|
}
|
|
}
|
|
|
|
// Construct an unstarted outgoing channel
|
|
SigChannel::SigChannel(const char* caller, const char* called)
|
|
: Channel(&plugin,0,true),
|
|
m_caller(caller),
|
|
m_called(called),
|
|
m_call(0),
|
|
m_trunk(0),
|
|
m_hungup(true),
|
|
m_reason("noconn"),
|
|
m_inband(false),
|
|
m_ringback(false),
|
|
m_rtpForward(false),
|
|
m_sdpForward(false),
|
|
m_route(0),
|
|
m_callAccdEvent(0)
|
|
{
|
|
}
|
|
|
|
SigChannel::~SigChannel()
|
|
{
|
|
TelEngine::destruct(m_route);
|
|
releaseCallAccepted();
|
|
hangup();
|
|
setState("destroyed",true,true);
|
|
}
|
|
|
|
void SigChannel::destroyed()
|
|
{
|
|
setState("destroyed",false,true);
|
|
hangup();
|
|
}
|
|
|
|
bool SigChannel::startRouter()
|
|
{
|
|
Message* m = m_route;
|
|
m_route = 0;
|
|
Lock lock(m_mutex);
|
|
SignallingCircuit* cic = getCircuit();
|
|
String addr;
|
|
if (cic && cic->getParam("rtp_addr",addr)) {
|
|
m_rtpForward = true;
|
|
m_sdpForward = cic->getBoolParam("sdp_forward");
|
|
addRtp(*m,true);
|
|
}
|
|
lock.drop();
|
|
return Channel::startRouter(m);
|
|
}
|
|
|
|
// Start outgoing call by name of a trunk or list of trunks
|
|
bool SigChannel::startCall(Message& msg, String& trunks)
|
|
{
|
|
ObjList* trunkList = trunks.split(',',false);
|
|
unsigned int n = trunkList->length();
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
const String* trunkName = static_cast<const String*>((*trunkList)[i]);
|
|
if (!trunkName)
|
|
continue;
|
|
SigTrunk* trunk = plugin.findTrunk(*trunkName,true);
|
|
if (trunk && startCall(msg,trunk)) {
|
|
// success - update trunk parameter in message
|
|
trunks = *trunkName;
|
|
break;
|
|
}
|
|
}
|
|
delete trunkList;
|
|
|
|
setState(0);
|
|
if (!m_call) {
|
|
msg.setParam("error",m_reason);
|
|
return false;
|
|
}
|
|
// Since the channel started the call remember to hang up as well
|
|
m_hungup = false;
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (cic) {
|
|
m_address << m_trunk->name() << "/" << cic->code();
|
|
// Set echo cancel
|
|
const String* echo = msg.getParam("cancelecho");
|
|
if (echo && *echo) {
|
|
int taps = echo->toInteger(-1);
|
|
if (taps > 0) {
|
|
cic->setParam("echotaps",*echo);
|
|
cic->setParam("echocancel",String::boolText(true));
|
|
}
|
|
else if (taps == 0)
|
|
cic->setParam("echocancel",String::boolText(false));
|
|
else
|
|
cic->setParam("echocancel",String::boolText(echo->toBoolean(true)));
|
|
}
|
|
const char* rfc2833 = msg.getValue("rfc2833");
|
|
if (rfc2833)
|
|
cic->setParam("rtp_rfc2833",rfc2833);
|
|
m_rtpForward = cic->getBoolParam("rtp_forward") && msg.getBoolValue("rtp_forward");
|
|
if (m_rtpForward) {
|
|
m_sdpForward = (0 != msg.getParam("sdp_raw"));
|
|
msg.setParam("rtp_forward","accepted");
|
|
}
|
|
}
|
|
setMaxcall(msg);
|
|
Message* m = message("chan.startup",msg);
|
|
m->setParam("direction",status());
|
|
m_targetid = msg.getValue("id");
|
|
m->copyParams(msg,"caller,callername,called,billid,callto,username");
|
|
// TODO: Add call control parameter ?
|
|
Engine::enqueue(m);
|
|
return true;
|
|
}
|
|
|
|
bool SigChannel::startCall(Message& msg, SigTrunk* trunk)
|
|
{
|
|
if (!(trunk && trunk->controller()))
|
|
return false;
|
|
// Data
|
|
m_inband = msg.getBoolValue("dtmfinband",trunk->inband());
|
|
m_ringback = msg.getBoolValue("ringback",trunk->ringback());
|
|
// Make the call
|
|
SignallingMessage* sigMsg = new SignallingMessage;
|
|
sigMsg->params().addParam("caller",m_caller);
|
|
sigMsg->params().addParam("called",m_called);
|
|
sigMsg->params().addParam("callername",msg.getValue("callername"));
|
|
sigMsg->params().copyParam(msg,"circuits");
|
|
sigMsg->params().copyParam(msg,"format");
|
|
sigMsg->params().copyParam(msg,"callernumtype");
|
|
sigMsg->params().copyParam(msg,"callernumplan");
|
|
if (msg.getValue("privacy") && msg.getBoolValue("privacy",true))
|
|
sigMsg->params().addParam("callerpres","restricted");
|
|
else
|
|
sigMsg->params().copyParam(msg,"callerpres");
|
|
sigMsg->params().copyParam(msg,"callerscreening");
|
|
sigMsg->params().copyParam(msg,"callednumtype");
|
|
sigMsg->params().copyParam(msg,"callednumplan");
|
|
sigMsg->params().copyParam(msg,"calledpointcode");
|
|
// Copy RTP parameters
|
|
if (msg.getBoolValue("rtp_forward")) {
|
|
NamedList* tmp = new NamedList("rtp");
|
|
tmp->copyParams(msg);
|
|
sigMsg->params().addParam(new NamedPointer("circuit_parameters",tmp));
|
|
}
|
|
// Copy routing params
|
|
unsigned int n = msg.length();
|
|
String prefix;
|
|
prefix << plugin.debugName() << ".";
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
NamedString* ns = msg.getParam(i);
|
|
if (ns && ns->name().startsWith(prefix))
|
|
sigMsg->params().addParam(ns->name().substr(prefix.length()),*ns);
|
|
}
|
|
Debug(this,DebugAll,"Trying to call on trunk '%s' [%p]",
|
|
trunk->name().safe(),this);
|
|
m_call = trunk->controller()->call(sigMsg,m_reason);
|
|
if (m_call) {
|
|
m_call->userdata(this);
|
|
m_trunk = trunk;
|
|
m_reason.clear();
|
|
return true;
|
|
}
|
|
Debug(this,DebugAll,"Failed to call on trunk '%s' reason: '%s' [%p]",
|
|
trunk->name().safe(),m_reason.c_str(),this);
|
|
return false;
|
|
}
|
|
|
|
void SigChannel::handleEvent(SignallingEvent* event)
|
|
{
|
|
if (!event)
|
|
return;
|
|
XDebug(this,DebugAll,"Got event (%p,'%s') [%p]",event,event->name(),this);
|
|
switch (event->type()) {
|
|
case SignallingEvent::Info: evInfo(event); break;
|
|
case SignallingEvent::Progress: evProgress(event); break;
|
|
case SignallingEvent::Accept: evAccept(event); break;
|
|
case SignallingEvent::Answer: evAnswer(event); break;
|
|
case SignallingEvent::Release: evRelease(event); break;
|
|
case SignallingEvent::Ringing: evRinging(event); break;
|
|
case SignallingEvent::Circuit: evCircuit(event); break;
|
|
case SignallingEvent::Generic: evGeneric(event,"transport"); break;
|
|
case SignallingEvent::Suspend: evGeneric(event,"suspend"); break;
|
|
case SignallingEvent::Resume: evGeneric(event,"resume"); break;
|
|
default:
|
|
DDebug(this,DebugStub,"No handler for event '%s' [%p]",
|
|
event->name(),this);
|
|
if (event->message())
|
|
processCompat(event->message()->params());
|
|
}
|
|
}
|
|
|
|
bool SigChannel::msgProgress(Message& msg)
|
|
{
|
|
Lock lock(m_mutex);
|
|
setState("progressing");
|
|
if (!m_call)
|
|
return true;
|
|
bool media = msg.getBoolValue("earlymedia",getPeer() && getPeer()->getSource());
|
|
const char* format = msg.getValue("format");
|
|
SignallingMessage* sm = new SignallingMessage;
|
|
if (media && updateConsumer(format,false) && format)
|
|
sm->params().addParam("format",format);
|
|
if (media)
|
|
m_ringback = false;
|
|
sm->params().addParam("earlymedia",String::boolText(media));
|
|
SignallingEvent* event = new SignallingEvent(SignallingEvent::Progress,sm,m_call);
|
|
TelEngine::destruct(sm);
|
|
SignallingCircuitEvent* cicEvent = handleRtp(msg);
|
|
lock.drop();
|
|
if (cicEvent)
|
|
cicEvent->sendEvent();
|
|
plugin.copySigMsgParams(event,msg);
|
|
event->sendEvent();
|
|
return true;
|
|
}
|
|
|
|
bool SigChannel::msgRinging(Message& msg)
|
|
{
|
|
Lock lock(m_mutex);
|
|
setState("ringing");
|
|
if (!m_call)
|
|
return true;
|
|
bool media = msg.getBoolValue("earlymedia",getPeer() && getPeer()->getSource());
|
|
const char* format = msg.getValue("format");
|
|
SignallingMessage* sm = new SignallingMessage;
|
|
if (media && updateConsumer(format,false) && format)
|
|
sm->params().addParam("format",format);
|
|
if (m_ringback && !media) {
|
|
// Attempt to provide ringback using circuit features
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (cic) {
|
|
NamedList params("ringback");
|
|
params.addParam("tone","ringback");
|
|
media = cic->sendEvent(SignallingCircuitEvent::GenericTone,¶ms);
|
|
}
|
|
}
|
|
if (media)
|
|
m_ringback = false;
|
|
sm->params().addParam("earlymedia",String::boolText(media));
|
|
SignallingEvent* event = new SignallingEvent(SignallingEvent::Ringing,sm,m_call);
|
|
TelEngine::destruct(sm);
|
|
SignallingCircuitEvent* cicEvent = handleRtp(msg);
|
|
lock.drop();
|
|
if (cicEvent)
|
|
cicEvent->sendEvent();
|
|
plugin.copySigMsgParams(event,msg);
|
|
event->sendEvent();
|
|
return true;
|
|
}
|
|
|
|
bool SigChannel::msgAnswered(Message& msg)
|
|
{
|
|
Lock lock(m_mutex);
|
|
setState("answered");
|
|
if (!m_call)
|
|
return true;
|
|
m_ringback = false;
|
|
updateSource(0,false);
|
|
// Start echo training
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (cic) {
|
|
cic->sendEvent(SignallingCircuitEvent::RingEnd);
|
|
String value;
|
|
cic->setParam("echotrain",value);
|
|
}
|
|
const char* format = msg.getValue("format");
|
|
SignallingMessage* sm = new SignallingMessage;
|
|
if (updateConsumer(format,false) && format)
|
|
sm->params().addParam("format",format);
|
|
SignallingEvent* event = new SignallingEvent(SignallingEvent::Answer,sm,m_call);
|
|
TelEngine::destruct(sm);
|
|
SignallingCircuitEvent* cicEvent = handleRtp(msg);
|
|
lock.drop();
|
|
if (cicEvent)
|
|
cicEvent->sendEvent();
|
|
plugin.copySigMsgParams(event,msg);
|
|
event->sendEvent();
|
|
return true;
|
|
}
|
|
|
|
bool SigChannel::msgTone(Message& msg, const char* tone)
|
|
{
|
|
if (!(tone && *tone))
|
|
return true;
|
|
Lock lock(m_mutex);
|
|
DDebug(this,DebugCall,"Tone. '%s' %s[%p]",tone,(m_call ? "" : ". No call "),this);
|
|
// Outgoing, overlap dialing call: try it first
|
|
if (isOutgoing() && m_call && m_call->overlapDialing()) {
|
|
lock.drop();
|
|
if (sendSigTone(msg,tone))
|
|
return true;
|
|
}
|
|
// Re-aquire lock
|
|
Lock lock2(m_mutex);
|
|
// Try to send: through the circuit, in band or through the signalling protocol
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (cic) {
|
|
NamedList params("");
|
|
params.addParam("tone",tone);
|
|
if (cic->sendEvent(SignallingCircuitEvent::Dtmf,¶ms))
|
|
return true;
|
|
}
|
|
if (m_inband && dtmfInband(tone))
|
|
return true;
|
|
if (!m_call)
|
|
return true;
|
|
lock2.drop();
|
|
lock.drop();
|
|
sendSigTone(msg,tone);
|
|
return true;
|
|
}
|
|
|
|
bool SigChannel::msgText(Message& msg, const char* text)
|
|
{
|
|
Lock lock(m_mutex);
|
|
DDebug(this,DebugCall,"Text. '%s' %s[%p]",text,(m_call ? "" : ". No call "),this);
|
|
if (!m_call)
|
|
return true;
|
|
SignallingMessage* sm = new SignallingMessage;
|
|
sm->params().addParam("text",text);
|
|
SignallingEvent* event = new SignallingEvent(SignallingEvent::Message,sm,m_call);
|
|
TelEngine::destruct(sm);
|
|
lock.drop();
|
|
plugin.copySigMsgParams(event,msg);
|
|
event->sendEvent();
|
|
return true;
|
|
}
|
|
|
|
bool SigChannel::msgUpdate(Message& msg)
|
|
{
|
|
const String& oper = msg["operation"];
|
|
SignallingEvent::Type evt = SignallingEvent::Unknown;
|
|
if (oper == "transport")
|
|
evt = SignallingEvent::Generic;
|
|
else if (oper == "suspend")
|
|
evt = SignallingEvent::Suspend;
|
|
else if (oper == "resume")
|
|
evt = SignallingEvent::Resume;
|
|
else if (m_callAccdEvent && (oper == "accepted")) {
|
|
plugin.copySigMsgParams(m_callAccdEvent,msg,"i");
|
|
releaseCallAccepted(true);
|
|
return true;
|
|
}
|
|
else if (oper == "request")
|
|
return updateMedia(msg);
|
|
if (SignallingEvent::Unknown == evt)
|
|
return Channel::msgUpdate(msg);
|
|
Lock lock(m_mutex);
|
|
if (!m_call)
|
|
return false;
|
|
SignallingMessage* sm = new SignallingMessage;
|
|
sm->params().addParam("operation",oper);
|
|
SignallingEvent* event = new SignallingEvent(evt,sm,m_call);
|
|
lock.drop();
|
|
TelEngine::destruct(sm);
|
|
plugin.copySigMsgParams(event,msg);
|
|
return event->sendEvent();
|
|
}
|
|
|
|
bool SigChannel::msgDrop(Message& msg, const char* reason)
|
|
{
|
|
hangup(reason ? reason : "dropped",0,&msg);
|
|
return Channel::msgDrop(msg,m_reason);
|
|
}
|
|
|
|
bool SigChannel::msgTransfer(Message& msg)
|
|
{
|
|
Lock lock(m_mutex);
|
|
DDebug(this,DebugCall,"msgTransfer %s[%p]",(m_call ? "" : ". No call "),this);
|
|
if (!m_call)
|
|
return true;
|
|
SignallingEvent* event = new SignallingEvent(SignallingEvent::Transfer,0,m_call);
|
|
lock.drop();
|
|
plugin.copySigMsgParams(event,msg);
|
|
return event->sendEvent();
|
|
}
|
|
|
|
bool SigChannel::callPrerouted(Message& msg, bool handled)
|
|
{
|
|
Lock lock(m_mutex);
|
|
setState("prerouted",false);
|
|
return m_call != 0;
|
|
}
|
|
|
|
bool SigChannel::callRouted(Message& msg)
|
|
{
|
|
Lock lock(m_mutex);
|
|
if (m_rtpForward && !msg.getBoolValue("rtp_forward"))
|
|
m_rtpForward = false;
|
|
setState("routed",false);
|
|
return m_call != 0;
|
|
}
|
|
|
|
void SigChannel::callAccept(Message& msg)
|
|
{
|
|
bool terminated = processCompat(msg);
|
|
Lock lock(m_mutex);
|
|
SignallingEvent* event = 0;
|
|
if (!terminated && m_call) {
|
|
// Check if we should accept now the call
|
|
bool acceptNow = msg.getBoolValue("accept_call",true);
|
|
const char* format = msg.getValue("format");
|
|
updateConsumer(format,false);
|
|
SignallingMessage* sm = new SignallingMessage;
|
|
if (format)
|
|
sm->params().addParam("format",format);
|
|
if (acceptNow)
|
|
event = new SignallingEvent(SignallingEvent::Accept,sm,m_call);
|
|
else {
|
|
DDebug(this,DebugAll,"Postponing call accepted [%p]",this);
|
|
m_callAccdEvent = new SignallingEvent(SignallingEvent::Accept,sm,m_call);
|
|
plugin.copySigMsgParams(m_callAccdEvent,msg,"i");
|
|
}
|
|
TelEngine::destruct(sm);
|
|
}
|
|
m_ringback = msg.getBoolValue("ringback",m_ringback);
|
|
if (m_rtpForward) {
|
|
const String* tmp = msg.getParam("rtp_forward");
|
|
if (!(tmp && (*tmp == "accepted")))
|
|
m_rtpForward = false;
|
|
}
|
|
setState("accepted",false);
|
|
lock.drop();
|
|
if (event) {
|
|
plugin.copySigMsgParams(event,msg,"i");
|
|
event->sendEvent();
|
|
}
|
|
Channel::callAccept(msg);
|
|
// Enqueue pending DTMFs
|
|
GenObject* gen = 0;
|
|
while (0 != (gen = m_chanDtmf.remove(false))) {
|
|
Message* m = static_cast<Message*>(gen);
|
|
complete(*m);
|
|
dtmfEnqueue(m);
|
|
}
|
|
}
|
|
|
|
void SigChannel::callRejected(const char* error, const char* reason, const Message* msg)
|
|
{
|
|
if (m_reason.null())
|
|
m_reason = error ? error : reason;
|
|
setState("rejected",false,true);
|
|
hangup(0,0,msg);
|
|
}
|
|
|
|
void SigChannel::connected(const char *reason)
|
|
{
|
|
releaseCallAccepted(true);
|
|
m_reason.clear();
|
|
Channel::connected(reason);
|
|
}
|
|
|
|
void SigChannel::disconnected(bool final, const char* reason)
|
|
{
|
|
if (m_reason.null())
|
|
m_reason = reason;
|
|
Channel::disconnected(final,m_reason);
|
|
}
|
|
|
|
void SigChannel::endDisconnect(const Message& params, bool handled)
|
|
{
|
|
const char* prefix = params.getValue("message-oprefix");
|
|
if (TelEngine::null(prefix))
|
|
return;
|
|
parameters().clearParams();
|
|
parameters().setParam("message-oprefix",prefix);
|
|
parameters().copySubParams(params,prefix,false);
|
|
}
|
|
|
|
void SigChannel::hangup(const char* reason, SignallingEvent* event, const NamedList* extra)
|
|
{
|
|
static String params = "reason";
|
|
Lock lock(m_mutex);
|
|
releaseCallAccepted();
|
|
if (m_hungup)
|
|
return;
|
|
m_hungup = true;
|
|
setSource();
|
|
setConsumer();
|
|
if (m_reason.null())
|
|
m_reason = reason ? reason : (Engine::exiting() ? "net-out-of-order" : "normal-clearing");
|
|
setState("hangup",true,true);
|
|
SignallingEvent* ev = 0;
|
|
Lock lock2(driver());
|
|
if (m_call) {
|
|
m_call->userdata(0);
|
|
lock2.drop();
|
|
SignallingMessage* msg = new SignallingMessage;
|
|
msg->params().addParam("reason",m_reason);
|
|
ev = new SignallingEvent(SignallingEvent::Release,msg,m_call);
|
|
TelEngine::destruct(msg);
|
|
TelEngine::destruct(m_call);
|
|
if (!extra)
|
|
extra = ¶meters();
|
|
plugin.copySigMsgParams(ev,*extra,"i");
|
|
}
|
|
if (event) {
|
|
parameters().clearParams();
|
|
plugin.copySigMsgParams(parameters(),event,¶ms);
|
|
}
|
|
lock2.drop();
|
|
lock.drop();
|
|
if (ev)
|
|
ev->sendEvent();
|
|
Message* m = message("chan.hangup",true);
|
|
m->setParam("status",status());
|
|
m->setParam("reason",m_reason);
|
|
Engine::enqueue(m);
|
|
}
|
|
|
|
// Notifier message dispatched handler
|
|
void SigChannel::dispatched(const Message& msg, bool handled)
|
|
{
|
|
static const String s_disc("chan.disconnected");
|
|
DDebug(this,DebugAll,"dispatched(%s,%u) [%p]",msg.c_str(),handled,this);
|
|
if (s_disc == msg)
|
|
Channel::dispatched(msg,handled);
|
|
else
|
|
processCompat(msg);
|
|
}
|
|
|
|
void SigChannel::statusParams(String& str)
|
|
{
|
|
Channel::statusParams(str);
|
|
}
|
|
|
|
void SigChannel::setState(const char* state, bool updateStatus, bool showReason)
|
|
{
|
|
if (updateStatus && state)
|
|
status(state);
|
|
if (!debugAt(DebugCall))
|
|
return;
|
|
if (!state) {
|
|
Debug(this,DebugCall,"%s call from=%s to=%s trunk=%s sigcall=%p [%p]",
|
|
isOutgoing()?"Outgoing":"Incoming",m_caller.safe(),m_called.safe(),
|
|
m_trunk ? m_trunk->name().c_str() : "no trunk",
|
|
m_call,this);
|
|
return;
|
|
}
|
|
String show;
|
|
show << "Call " << state;
|
|
if (showReason)
|
|
show << ". Reason: '" << m_reason << "'";
|
|
if (!m_call)
|
|
show << ". No signalling call ";
|
|
if (updateStatus)
|
|
Debug(this,DebugCall,"%s [%p]",show.c_str(),this);
|
|
else
|
|
DDebug(this,DebugCall,"%s [%p]",show.c_str(),this);
|
|
}
|
|
|
|
void SigChannel::evInfo(SignallingEvent* event)
|
|
{
|
|
SignallingMessage* msg = event->message();
|
|
if (!msg)
|
|
return;
|
|
// Check DTMF
|
|
String tmp = msg->params().getValue("tone");
|
|
if (!tmp.null()) {
|
|
bool inband = msg->params().getBoolValue("inband");
|
|
DDebug(this,DebugCall,"Event: '%s'. DTMF: '%s'. In band: %s [%p]",
|
|
event->name(),tmp.c_str(),
|
|
String::boolText(inband),this);
|
|
Message* m = message("chan.dtmf");
|
|
m->addParam("text",tmp);
|
|
m->addParam("detected",inband ? "inband" : "signal");
|
|
m->copyParams(event->message()->params(),"dialing");
|
|
if (status() != "incoming")
|
|
dtmfEnqueue(m);
|
|
else {
|
|
Lock lock(m_mutex);
|
|
m_chanDtmf.append(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SigChannel::evProgress(SignallingEvent* event)
|
|
{
|
|
setState("progressing");
|
|
updateCircuitFormat(event,false);
|
|
Message* msg = message("call.progress",false,true);
|
|
if (isupController())
|
|
msg->setNotify(true);
|
|
plugin.copySigMsgParams(*msg,event,&s_noPrefixParams);
|
|
addRtp(*msg);
|
|
Engine::enqueue(msg);
|
|
}
|
|
|
|
void SigChannel::evRelease(SignallingEvent* event)
|
|
{
|
|
const char* reason = 0;
|
|
SignallingMessage* sig = (event ? event->message() : 0);
|
|
if (sig)
|
|
reason = sig->params().getValue("reason");
|
|
hangup(reason,event);
|
|
}
|
|
|
|
void SigChannel::evAccept(SignallingEvent* event)
|
|
{
|
|
setState("accepted",false,false);
|
|
updateCircuitFormat(event,true);
|
|
if (!(event->message() && event->message()->params().count()))
|
|
return;
|
|
Message* msg = message("call.update",false,true);
|
|
if (isupController())
|
|
msg->setNotify(true);
|
|
plugin.copySigMsgParams(*msg,event,&s_noPrefixParams);
|
|
addRtp(*msg);
|
|
msg->setParam("operation","accepted");
|
|
Engine::enqueue(msg);
|
|
}
|
|
|
|
void SigChannel::evAnswer(SignallingEvent* event)
|
|
{
|
|
setState("answered");
|
|
updateCircuitFormat(event,true);
|
|
// Start echo training
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (cic) {
|
|
cic->sendEvent(SignallingCircuitEvent::RingEnd);
|
|
String value;
|
|
cic->setParam("echotrain",value);
|
|
}
|
|
Message* msg = message("call.answered",false,true);
|
|
if (isupController())
|
|
msg->setNotify(true);
|
|
plugin.copySigMsgParams(*msg,event,&s_noPrefixParams);
|
|
msg->clearParam("earlymedia");
|
|
addRtp(*msg);
|
|
Engine::enqueue(msg);
|
|
}
|
|
|
|
void SigChannel::evRinging(SignallingEvent* event)
|
|
{
|
|
setState("ringing");
|
|
updateCircuitFormat(event,false);
|
|
Message* msg = message("call.ringing",false,true);
|
|
if (isupController())
|
|
msg->setNotify(true);
|
|
plugin.copySigMsgParams(*msg,event,&s_noPrefixParams);
|
|
addRtp(*msg);
|
|
Engine::enqueue(msg);
|
|
}
|
|
|
|
void SigChannel::evCircuit(SignallingEvent* event)
|
|
{
|
|
SignallingCircuitEvent* ev = event->cicEvent();
|
|
if (!ev)
|
|
return;
|
|
switch (ev->type()) {
|
|
case SignallingCircuitEvent::Alarm:
|
|
case SignallingCircuitEvent::Disconnected:
|
|
hangup("nomedia");
|
|
break;
|
|
case SignallingCircuitEvent::GenericTone:
|
|
if (*ev == "fax") {
|
|
bool inband = ev->getBoolValue("inband");
|
|
Message* msg = message("call.fax",false,true);
|
|
msg->setParam("detected",(inband ? "inband" : "signal"));
|
|
plugin.copySigMsgParams(*msg,event,&s_noPrefixParams);
|
|
Engine::enqueue(msg);
|
|
break;
|
|
}
|
|
// fall through
|
|
default:
|
|
Debug(this,DebugStub,"Unhandled circuit event '%s' type=%d [%p]",
|
|
ev->c_str(),ev->type(),this);
|
|
}
|
|
}
|
|
|
|
void SigChannel::evGeneric(SignallingEvent* event, const char* operation)
|
|
{
|
|
SignallingMessage* sig = (event ? event->message() : 0);
|
|
if (!sig || sig->params().count() < 1)
|
|
return;
|
|
Message* msg = message("call.update",false,true);
|
|
plugin.copySigMsgParams(*msg,event,&s_noPrefixParams);
|
|
msg->setParam("operation",operation);
|
|
Engine::enqueue(msg);
|
|
}
|
|
|
|
// Handle RTP forward from a message.
|
|
// Return a circuit event to be sent or 0 if not handled
|
|
SignallingCircuitEvent* SigChannel::handleRtp(Message& msg)
|
|
{
|
|
if (!(m_rtpForward && msg.getBoolValue("rtp_forward")))
|
|
return 0;
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (!cic)
|
|
return 0;
|
|
setSource();
|
|
setConsumer();
|
|
SignallingCircuitEvent* ev = new SignallingCircuitEvent(cic,
|
|
SignallingCircuitEvent::Connect,"rtp");
|
|
ev->copyParams(msg);
|
|
return ev;
|
|
}
|
|
|
|
// Set RTP data from circuit to message
|
|
bool SigChannel::addRtp(Message& msg, bool possible)
|
|
{
|
|
if (!m_rtpForward)
|
|
return false;
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (!cic)
|
|
return false;
|
|
bool ok = cic->getParams(msg,"rtp");
|
|
if (m_sdpForward) {
|
|
String sdp;
|
|
if (cic->getParam("sdp_raw",sdp) && sdp) {
|
|
ok = true;
|
|
msg.setParam("sdp_raw",sdp);
|
|
}
|
|
}
|
|
if (ok)
|
|
msg.setParam("rtp_forward",possible ? "possible" : String::boolText(true));
|
|
return ok;
|
|
}
|
|
|
|
bool SigChannel::updateMedia(Message& msg)
|
|
{
|
|
bool hadRtp = !m_rtpForward;
|
|
bool rtpFwd = msg.getBoolValue("rtp_forward",m_rtpForward);
|
|
if (!rtpFwd) {
|
|
msg.setParam("error","failure");
|
|
msg.setParam("reason","RTP forwarding is not enabled");
|
|
return false;
|
|
}
|
|
RefPointer<SignallingCircuit> cic = getCircuit();
|
|
if (!(cic && cic->connected())) {
|
|
msg.setParam("error","failure");
|
|
msg.setParam("reason","Circuit missing or not connected");
|
|
return false;
|
|
}
|
|
String tmp = msg;
|
|
msg = "rtp";
|
|
bool ok = cic->setParams(msg) && cic->status(SignallingCircuit::Connected,true);
|
|
msg = tmp;
|
|
if (!ok) {
|
|
msg.setParam("error","failure");
|
|
msg.setParam("reason","Circuit does not support media change");
|
|
return false;
|
|
}
|
|
m_rtpForward = cic->getBoolParam("rtp_forward");
|
|
if (m_rtpForward && hadRtp)
|
|
clearEndpoint();
|
|
Message* m = message("call.update",false,true);
|
|
m->addParam("operation","notify");
|
|
cic->getParams(*m,"rtp");
|
|
return Engine::enqueue(m);
|
|
}
|
|
|
|
void SigChannel::updateCircuitFormat(SignallingEvent* event, bool consumer)
|
|
{
|
|
const char* format = 0;
|
|
bool cicChange = false;
|
|
if (event->message()) {
|
|
format = event->message()->params().getValue("format");
|
|
cicChange = event->message()->params().getBoolValue("circuit-change",false);
|
|
}
|
|
DDebug(this,DebugInfo,"Updating format to '%s'%s",format,cicChange ? ", circuit changed" : "");
|
|
updateSource(format,cicChange);
|
|
if (consumer)
|
|
updateConsumer(0,cicChange);
|
|
}
|
|
|
|
bool SigChannel::updateConsumer(const char* format, bool force)
|
|
{
|
|
if (m_rtpForward)
|
|
return true;
|
|
DataConsumer* consumer = getConsumer();
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (!cic)
|
|
return false;
|
|
if (consumer && !cic->updateFormat(format,-1) && !force)
|
|
return true;
|
|
// Set consumer
|
|
setConsumer();
|
|
setConsumer(static_cast<DataConsumer*>(cic->getObject("DataConsumer")));
|
|
consumer = getConsumer();
|
|
if (consumer) {
|
|
DDebug(this,DebugAll,"Data consumer set to (%p): '%s' [%p]",
|
|
consumer,consumer->getFormat().c_str(),this);
|
|
return true;
|
|
}
|
|
Debug(this,DebugNote,"Failed to set data consumer [%p]",this);
|
|
return false;
|
|
}
|
|
|
|
bool SigChannel::updateSource(const char* format, bool force)
|
|
{
|
|
if (m_rtpForward)
|
|
return true;
|
|
DataSource* source = getSource();
|
|
SignallingCircuit* cic = getCircuit();
|
|
if (!cic)
|
|
return false;
|
|
if (source && !cic->updateFormat(format,1) && !force)
|
|
return true;
|
|
// Set source
|
|
setSource();
|
|
setSource(static_cast<DataSource*>(cic->getObject("DataSource")));
|
|
source = getSource();
|
|
if (source) {
|
|
DDebug(this,DebugAll,"Data source set to (%p): '%s' [%p]",
|
|
source,source->getFormat().c_str(),this);
|
|
return true;
|
|
}
|
|
Debug(this,DebugNote,"Failed to set data source [%p]",this);
|
|
return false;
|
|
}
|
|
|
|
// Retrieve the ISUP call controller from trunk
|
|
SS7ISUP* SigChannel::isupController() const
|
|
{
|
|
if (!(m_trunk && m_trunk->type() == SigTrunk::SS7Isup))
|
|
return 0;
|
|
return static_cast<SS7ISUP*>(m_trunk->controller());
|
|
}
|
|
|
|
// Process parameter compatibility data from a message
|
|
// Return true if the call was terminated
|
|
bool SigChannel::processCompat(const NamedList& list)
|
|
{
|
|
SS7ISUP* isup = isupController();
|
|
if (!isup)
|
|
return false;
|
|
Lock lock(m_mutex);
|
|
unsigned int cic = 0;
|
|
SignallingCircuit* c = getCircuit();
|
|
if (c)
|
|
cic = c->code();
|
|
if (!cic)
|
|
return false;
|
|
lock.drop();
|
|
bool terminated = false;
|
|
isup->processParamCompat(list,cic,&terminated);
|
|
return terminated;
|
|
}
|
|
|
|
// Send a tone through signalling call
|
|
bool SigChannel::sendSigTone(Message& msg, const char* tone)
|
|
{
|
|
Lock lock(m_mutex);
|
|
if (!m_call)
|
|
return false;
|
|
SignallingMessage* sm = new SignallingMessage;
|
|
sm->params().addParam("tone",tone);
|
|
SignallingEvent* event = new SignallingEvent(SignallingEvent::Info,sm,m_call);
|
|
TelEngine::destruct(sm);
|
|
lock.drop();
|
|
plugin.copySigMsgParams(event,msg);
|
|
return event->sendEvent();
|
|
}
|
|
|
|
// Release postponed call accepted event. Sent it if requested
|
|
void SigChannel::releaseCallAccepted(bool send)
|
|
{
|
|
Lock lock(m_mutex);
|
|
if (!m_callAccdEvent)
|
|
return;
|
|
if (send)
|
|
m_callAccdEvent->sendEvent();
|
|
else
|
|
delete m_callAccdEvent;
|
|
m_callAccdEvent = 0;
|
|
}
|
|
|
|
/**
|
|
* SigDriver
|
|
*/
|
|
SigDriver::SigDriver()
|
|
: Driver("sig","fixchans"),
|
|
m_engine(0),
|
|
m_trunksMutex(true,"SigDriver::trunks"),
|
|
m_topmostMutex(true,"SigDriver::topmost")
|
|
{
|
|
Output("Loaded module Signalling Channel");
|
|
m_statusCmd << "status " << name();
|
|
}
|
|
|
|
SigDriver::~SigDriver()
|
|
{
|
|
Output("Unloading module Signalling Channel");
|
|
clearTrunk();
|
|
// Now that the call controllers are gone we can destroy the networks
|
|
m_topmostMutex.lock();
|
|
m_topmost.clear();
|
|
m_topmostMutex.unlock();
|
|
if (m_engine)
|
|
delete m_engine;
|
|
}
|
|
|
|
bool SigDriver::msgExecute(Message& msg, String& dest)
|
|
{
|
|
Channel* peer = static_cast<Channel*>(msg.userData());
|
|
if (!peer) {
|
|
Debug(this,DebugNote,"Signalling call failed. No data channel");
|
|
msg.setParam("error","failure");
|
|
return false;
|
|
}
|
|
// Locate and check the trunk parameter
|
|
String* trunk = msg.getParam("trunk");
|
|
// The "link" parameter is OBSOLETE!
|
|
if (!trunk)
|
|
trunk = msg.getParam("link");
|
|
if (!trunk || trunk->null()) {
|
|
Debug(this,DebugNote,
|
|
"Signalling call failed. No trunk specified");
|
|
msg.setParam("error","noconn");
|
|
return false;
|
|
}
|
|
// Create channel
|
|
lock();
|
|
SigChannel* sigCh = new SigChannel(msg.getValue("caller"),dest);
|
|
sigCh->initChan();
|
|
unlock();
|
|
bool ok = sigCh->startCall(msg,*trunk);
|
|
if (ok) {
|
|
if (sigCh->connect(peer,msg.getValue("reason"))) {
|
|
sigCh->callConnect(msg);
|
|
msg.setParam("peerid",sigCh->id());
|
|
msg.setParam("targetid",sigCh->id());
|
|
}
|
|
}
|
|
else {
|
|
if (!msg.getValue("error"))
|
|
msg.setParam("error","failure");
|
|
Debug(this,DebugNote,"Signalling call failed with reason '%s'",msg.getValue("error"));
|
|
}
|
|
TelEngine::destruct(sigCh);
|
|
return ok;
|
|
}
|
|
|
|
bool SigDriver::received(Message& msg, int id)
|
|
{
|
|
String target;
|
|
|
|
switch (id) {
|
|
case Masquerade:
|
|
target = msg.getValue("id");
|
|
if (!target.startsWith(prefix())) {
|
|
SigTrunk* trunk = findTrunk(target.substr(0,target.find('/')),false);
|
|
if (trunk && trunk->masquerade(target,msg))
|
|
return false;
|
|
}
|
|
return Driver::received(msg,id);
|
|
case Status:
|
|
target = msg.getValue("module");
|
|
// Target is the driver or channel
|
|
if (!target || target == name() || target.startsWith(prefix()))
|
|
return Driver::received(msg,id);
|
|
break;
|
|
case Drop:
|
|
target = msg.getValue("id");
|
|
if (!target.startsWith(prefix())) {
|
|
SigTrunk* trunk = findTrunk(target.substr(0,target.find('/')),false);
|
|
if (trunk && trunk->drop(target,msg))
|
|
return true;
|
|
}
|
|
return Driver::received(msg,id);
|
|
case Control:
|
|
{
|
|
const String* dest = msg.getParam("component");
|
|
if (dest && (*dest == "sig"))
|
|
return reInitialize(msg);
|
|
}
|
|
if (m_engine && m_engine->control(msg))
|
|
return true;
|
|
return Driver::received(msg,id);
|
|
case Halt:
|
|
clearTrunk();
|
|
if (m_engine)
|
|
m_engine->stop();
|
|
return Driver::received(msg,id);
|
|
case Help:
|
|
return commandHelp(msg.retValue(),msg.getValue("line"));
|
|
default:
|
|
return Driver::received(msg,id);
|
|
}
|
|
|
|
if (!target.startSkip(name()))
|
|
return false;
|
|
if (target.startSkip("links")) {
|
|
msg.retValue() << "module=" << name();
|
|
msg.retValue() << ",format=Type|Status|Uptime;";
|
|
|
|
String ret;
|
|
m_trunksMutex.lock();
|
|
for (ObjList* o = m_trunks.skipNull(); o; o = o->skipNext()) {
|
|
SigTrunk* trunk = static_cast<SigTrunk*>(o->get());
|
|
trunk->linkStatus(ret);
|
|
}
|
|
m_trunksMutex.unlock();
|
|
m_topmostMutex.lock();
|
|
for (ObjList* o = m_topmost.skipNull(); o; o = o->skipNext()) {
|
|
SigTopmost* top = static_cast<SigTopmost*>(o->get());
|
|
top->linkStatus(ret);
|
|
}
|
|
m_topmostMutex.unlock();
|
|
|
|
ObjList* l = ret.split(',',false);
|
|
msg.retValue() << "count=" << l->count();
|
|
TelEngine::destruct(l);
|
|
if (msg.getBoolValue("details",true))
|
|
msg.retValue().append(ret,";");
|
|
|
|
msg.retValue() << "\r\n";
|
|
}
|
|
if (target.startSkip("ifaces")) {
|
|
msg.retValue() << "module=" << name();
|
|
msg.retValue() << ",format=Status;";
|
|
|
|
String ret;
|
|
m_trunksMutex.lock();
|
|
for (ObjList* o = m_trunks.skipNull(); o; o = o->skipNext()) {
|
|
SigTrunk* trunk = static_cast<SigTrunk*>(o->get());
|
|
trunk->ifStatus(ret);
|
|
}
|
|
m_trunksMutex.unlock();
|
|
m_topmostMutex.lock();
|
|
for (ObjList* o = m_topmost.skipNull(); o; o = o->skipNext()) {
|
|
SigTopmost* top = static_cast<SigTopmost*>(o->get());
|
|
top->ifStatus(ret);
|
|
}
|
|
m_topmostMutex.unlock();
|
|
|
|
ObjList* l = ret.split(',',false);
|
|
msg.retValue() << "count=" << l->count();
|
|
TelEngine::destruct(l);
|
|
if (msg.getBoolValue("details",true))
|
|
msg.retValue().append(ret,";");
|
|
|
|
msg.retValue() << "\r\n";
|
|
}
|
|
|
|
// Status target=trunk[/cic|/range]
|
|
int pos = target.find("/");
|
|
String trunkName = target.substr(0,pos);
|
|
target = target.substr(trunkName.length() + 1);
|
|
m_trunksMutex.lock();
|
|
RefPointer<SigTrunk> trunk = findTrunk(trunkName,false);
|
|
m_trunksMutex.unlock();
|
|
if (trunk) {
|
|
status(trunk,msg.retValue(),target);
|
|
return true;
|
|
}
|
|
m_topmostMutex.lock();
|
|
RefPointer<SigTopmost> topmost = static_cast<SigTopmost*>(m_topmost[trunkName]);
|
|
m_topmostMutex.unlock();
|
|
if (topmost) {
|
|
status(topmost,msg.retValue());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SigDriver::status(SigTrunk* trunk, String& retVal, const String& target)
|
|
{
|
|
String detail;
|
|
unsigned int circuits = 0;
|
|
unsigned int count = 0;
|
|
while (true) {
|
|
SignallingCallControl* ctrl = trunk ? trunk->controller() : 0;
|
|
if (!ctrl)
|
|
break;
|
|
Lock lckCtrl(ctrl);
|
|
|
|
if (ctrl->circuits())
|
|
circuits = ctrl->circuits()->count();
|
|
else
|
|
break;
|
|
|
|
SignallingCircuitRange range(target,0);
|
|
if (target == "*" || target == "all")
|
|
range.add(ctrl->circuits()->base(),ctrl->circuits()->last());
|
|
for (unsigned int i = 0; i < range.count(); i++) {
|
|
SignallingCircuit* cic = ctrl->circuits()->find(range[i]);
|
|
if (!cic)
|
|
continue;
|
|
count++;
|
|
detail.append(String(cic->code()) + "=",",");
|
|
if (cic->span())
|
|
detail << cic->span()->id();
|
|
detail << "|" << SignallingCircuit::lookupStatus(cic->status());
|
|
detail << "|" << String::boolText(0 != cic->locked(SignallingCircuit::LockLocal));
|
|
detail << "|" << String::boolText(0 != cic->locked(SignallingCircuit::LockRemote));
|
|
detail << "|" << String::boolText(0 != cic->locked(SignallingCircuit::LockChanged|SignallingCircuit::Resetting));
|
|
}
|
|
break;
|
|
}
|
|
|
|
retVal.clear();
|
|
retVal << "module=" << name();
|
|
retVal << ",trunk=" << trunk->name();
|
|
retVal << ",type=" << lookup(trunk->type(),SigTrunk::s_type);
|
|
retVal << ",circuits=" << circuits;
|
|
retVal << ",status=" << (trunk->controller() ? trunk->controller()->statusName() : "Unknown");
|
|
retVal << ",calls=" << (trunk->controller() ? trunk->controller()->calls().count() : 0);
|
|
if (!target.null()) {
|
|
retVal << ";count=" << count;
|
|
retVal << ",format=Span|Status|LockedLocal|LockedRemote|Changing";
|
|
retVal << ";" << detail;
|
|
}
|
|
retVal << "\r\n";
|
|
}
|
|
|
|
void SigDriver::status(SigTopmost* topmost, String& retVal)
|
|
{
|
|
String detail;
|
|
retVal.clear();
|
|
retVal << "module=" << name();
|
|
retVal << ",component=" << topmost->name();
|
|
String details = "";
|
|
topmost->status(details);
|
|
if (!details.null())
|
|
retVal << "," << details;
|
|
retVal << "\r\n";
|
|
}
|
|
|
|
void SigDriver::handleEvent(SignallingEvent* event)
|
|
{
|
|
if (!event)
|
|
return;
|
|
// Check if we have a call and a message
|
|
if (!event->call()) {
|
|
switch (event->type()) {
|
|
case SignallingEvent::Disable:
|
|
// Fall through if no call controller
|
|
if (event->controller())
|
|
break;
|
|
default:
|
|
DDebug(this,DebugMild,
|
|
"Received event (%p,'%s') without call. Controller: (%p)",
|
|
event,event->name(),event->controller());
|
|
return;
|
|
}
|
|
// Remove trunk
|
|
Lock lock(m_trunksMutex);
|
|
SigTrunk* trunk = findTrunk(event->controller());
|
|
if (!trunk)
|
|
return;
|
|
clearTrunk(trunk->name(),false,0);
|
|
return;
|
|
}
|
|
if (!event->message() && event->type() != SignallingEvent::Circuit) {
|
|
Debug(this,DebugGoOn,"Received event (%p,'%s') without message",event,event->name());
|
|
return;
|
|
}
|
|
// Ok. Send the message to the channel if we have one
|
|
lock();
|
|
RefPointer<SigChannel> ch = static_cast<SigChannel*>(event->call()->userdata());
|
|
unlock();
|
|
if (ch) {
|
|
ch->handleEvent(event);
|
|
if (event->type() == SignallingEvent::Release ||
|
|
(event->type() == SignallingEvent::Circuit && event->cicEvent() &&
|
|
event->cicEvent()->type() == SignallingCircuitEvent::Disconnected))
|
|
ch->disconnect();
|
|
return;
|
|
}
|
|
// No channel
|
|
if (event->type() == SignallingEvent::NewCall) {
|
|
lock();
|
|
ch = new SigChannel(event);
|
|
ch->initChan();
|
|
unlock();
|
|
// Route the call
|
|
if (!ch->startRouter()) {
|
|
ch->hangup("temporary-failure");
|
|
TelEngine::destruct(ch);
|
|
}
|
|
}
|
|
else
|
|
XDebug(this,DebugNote,"Received event (%p,'%s') from call without user data",
|
|
event,event->name());
|
|
}
|
|
|
|
// Find a trunk by name
|
|
// If callCtrl is true, match only trunks with call controller
|
|
SigTrunk* SigDriver::findTrunk(const char* name, bool callCtrl)
|
|
{
|
|
if (!name)
|
|
return 0;
|
|
Lock lock(m_trunksMutex);
|
|
for (ObjList* o = m_trunks.skipNull(); o; o = o->skipNext()) {
|
|
SigTrunk* trunk = static_cast<SigTrunk*>(o->get());
|
|
if (trunk->name() == name) {
|
|
if (callCtrl && !trunk->controller())
|
|
return 0;
|
|
return trunk;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Find a trunk by call controller
|
|
SigTrunk* SigDriver::findTrunk(const SignallingCallControl* ctrl)
|
|
{
|
|
if (!ctrl)
|
|
return 0;
|
|
Lock lock(m_trunksMutex);
|
|
for (ObjList* o = m_trunks.skipNull(); o; o = o->skipNext()) {
|
|
SigTrunk* trunk = static_cast<SigTrunk*>(o->get());
|
|
if (trunk->controller() == ctrl)
|
|
return trunk;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Disconnect channels. If trunk is not NULL, disconnect only its channels
|
|
void SigDriver::disconnectChannels(SigTrunk* trunk)
|
|
{
|
|
RefPointer<SigChannel> o;
|
|
lock();
|
|
ListIterator iter(channels());
|
|
unlock();
|
|
for (;;) {
|
|
lock();
|
|
o = static_cast<SigChannel*>(iter.get());
|
|
unlock();
|
|
if (!o)
|
|
return;
|
|
if (!trunk || (trunk == o->trunk()))
|
|
o->disconnect();
|
|
o = 0;
|
|
}
|
|
}
|
|
|
|
// Copy incoming message parameters to another list of parameters
|
|
// The pointers of NamedPointer parameters are 'stolen' from 'sig' when copying
|
|
// If 'params' is not 0, the contained parameters will not be prefixed with
|
|
// event call controller's prefix
|
|
void SigDriver::copySigMsgParams(NamedList& dest, SignallingEvent* event,
|
|
const String* params)
|
|
{
|
|
SignallingMessage* sig = event ? event->message() : 0;
|
|
if (!sig)
|
|
return;
|
|
|
|
ObjList exclude;
|
|
// Copy 'params'
|
|
if (params) {
|
|
ObjList* p = params->split(',',false);
|
|
for (ObjList* o = p->skipNull(); o; o = o->skipNext()) {
|
|
NamedString* ns = sig->params().getParam(static_cast<String*>(o->get())->c_str());
|
|
if (!ns)
|
|
continue;
|
|
dest.addParam(ns->name(),*ns);
|
|
exclude.append(ns)->setDelete(false);
|
|
}
|
|
TelEngine::destruct(p);
|
|
}
|
|
// Copy all other parameters
|
|
String prefix = (event->controller() ? event->controller()->msgPrefix() : String::empty());
|
|
if (!prefix.null())
|
|
dest.addParam("message-prefix",prefix);
|
|
unsigned int n = sig->params().length();
|
|
bool noParams = true;
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
NamedString* param = sig->params().getParam(i);
|
|
if (!param || exclude.find(param->name()))
|
|
continue;
|
|
noParams = false;
|
|
NamedPointer* np = static_cast<NamedPointer*>(param->getObject("NamedPointer"));
|
|
if (!np)
|
|
dest.addParam(prefix+param->name(),*param);
|
|
else
|
|
dest.addParam(new NamedPointer(prefix+param->name(),np->takeData(),*param));
|
|
}
|
|
if (!prefix.null() && noParams)
|
|
dest.clearParam("message-prefix");
|
|
}
|
|
|
|
// Copy outgoing message parameters
|
|
void SigDriver::copySigMsgParams(SignallingEvent* event,
|
|
const NamedList& params, const char* prePrefix)
|
|
{
|
|
if (!(event && event->message() && event->controller()))
|
|
return;
|
|
String prefix = event->controller()->msgPrefix();
|
|
if (prefix)
|
|
prefix = prePrefix + prefix;
|
|
prefix = params.getValue("message-oprefix",prefix);
|
|
if (prefix.null())
|
|
return;
|
|
event->message()->params().copySubParams(params,prefix);
|
|
}
|
|
|
|
// Handle command complete requests
|
|
bool SigDriver::commandComplete(Message& msg, const String& partLine,
|
|
const String& partWord)
|
|
{
|
|
if (partLine.null() || partLine == "help") {
|
|
if (itemComplete(msg.retValue(),"sigdump",partWord))
|
|
return false;
|
|
}
|
|
else if (partLine == "sigdump") {
|
|
if (m_engine) {
|
|
NamedList params("");
|
|
params.addParam("operation","sigdump");
|
|
params.addParam("partword",partWord);
|
|
params.addParam("completion",msg.retValue());
|
|
if (m_engine->control(params))
|
|
msg.retValue() = params.getValue("completion");
|
|
}
|
|
}
|
|
else if (partLine == "control") {
|
|
if (m_engine) {
|
|
NamedList params("");
|
|
params.addParam("partword",partWord);
|
|
params.addParam("completion",msg.retValue());
|
|
if (m_engine->control(params))
|
|
msg.retValue() = params.getValue("completion");
|
|
}
|
|
return false;
|
|
}
|
|
else {
|
|
String tmp = partLine;
|
|
if (tmp.startSkip("control")) {
|
|
if (m_engine) {
|
|
NamedList params("");
|
|
params.addParam("component",tmp);
|
|
params.addParam("partword",partWord);
|
|
params.addParam("completion",msg.retValue());
|
|
if (m_engine->control(params))
|
|
msg.retValue() = params.getValue("completion");
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
bool status = partLine.startsWith("status");
|
|
bool drop = !status && partLine.startsWith("drop");
|
|
if (!(status || drop))
|
|
return Driver::commandComplete(msg,partLine,partWord);
|
|
String overviewCmd = "status overview " + name();
|
|
if (partLine == overviewCmd) {
|
|
itemComplete(msg.retValue(),"links",partWord);
|
|
itemComplete(msg.retValue(),"ifaces",partWord);
|
|
return true;
|
|
}
|
|
|
|
Lock lock(this);
|
|
// line='status sig': add trunks and topmost components
|
|
if (partLine == m_statusCmd) {
|
|
itemComplete(msg.retValue(),"links",partWord);
|
|
itemComplete(msg.retValue(),"ifaces",partWord);
|
|
ObjList* o;
|
|
m_trunksMutex.lock();
|
|
for (o = m_trunks.skipNull(); o; o = o->skipNext())
|
|
itemComplete(msg.retValue(),static_cast<SigTrunk*>(o->get())->name(),partWord);
|
|
m_trunksMutex.unlock();
|
|
m_topmostMutex.lock();
|
|
for (o = m_topmost.skipNull(); o; o = o->skipNext())
|
|
itemComplete(msg.retValue(),static_cast<SigTopmost*>(o->get())->name(),partWord);
|
|
m_topmostMutex.unlock();
|
|
return true;
|
|
}
|
|
|
|
if (partLine != "status" && partLine != "drop")
|
|
return false;
|
|
|
|
// Empty partial word or name start with it: add name and prefix
|
|
if (!partWord || name().startsWith(partWord)) {
|
|
msg.retValue().append(name(),"\t");
|
|
if (channels().skipNull())
|
|
msg.retValue().append(prefix(),"\t");
|
|
return false;
|
|
}
|
|
// Non empty partial word greater then module name: check if we have a prefix
|
|
if (!partWord.startsWith(prefix()))
|
|
return false;
|
|
// Partial word is not empty and starts with module's prefix
|
|
// Complete channels
|
|
bool all = (partWord == prefix());
|
|
for (ObjList* o = channels().skipNull(); o; o = o->skipNext()) {
|
|
CallEndpoint* c = static_cast<CallEndpoint*>(o->get());
|
|
if (all || c->id().startsWith(partWord))
|
|
msg.retValue().append(c->id(),"\t");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Custom command handler
|
|
bool SigDriver::commandExecute(String& retVal, const String& line)
|
|
{
|
|
String tmp = line;
|
|
if (tmp.startSkip("sigdump")) {
|
|
tmp.trimBlanks();
|
|
if (tmp.null() || tmp == "help" || tmp == "?")
|
|
retVal << "Usage: " << s_miniHelp << "\r\n" << s_fullHelp;
|
|
else {
|
|
if (m_engine) {
|
|
NamedList params("");
|
|
params.addParam("operation","sigdump");
|
|
int sep = tmp.find(' ');
|
|
if (sep > 0) {
|
|
params.addParam("component",tmp.substr(0,sep));
|
|
tmp >> " ";
|
|
params.addParam("file",tmp.trimBlanks());
|
|
}
|
|
else {
|
|
params.addParam("component",tmp);
|
|
params.addParam("file","");
|
|
}
|
|
return m_engine->control(params);
|
|
}
|
|
return false;
|
|
}
|
|
retVal << "\r\n";
|
|
return true;
|
|
}
|
|
return Driver::commandExecute(retVal,line);
|
|
}
|
|
|
|
// Help message handler
|
|
bool SigDriver::commandHelp(String& retVal, const String& line)
|
|
{
|
|
if (line.null() || line == "sigdump") {
|
|
retVal << " " << s_miniHelp << "\r\n";
|
|
if (line) {
|
|
retVal << s_fullHelp << "\r\n";
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Append a topmost component to the list. Duplicate names are not allowed
|
|
bool SigDriver::appendTopmost(SigTopmost* topmost)
|
|
{
|
|
if (!topmost || topmost->name().null())
|
|
return false;
|
|
Lock lock(m_topmostMutex);
|
|
if (m_topmost.find(topmost->name())) {
|
|
Debug(this,DebugWarn,"Can't append topmost (%p): '%s'. Duplicate name",
|
|
topmost,topmost->name().c_str());
|
|
return false;
|
|
}
|
|
m_topmost.append(topmost);
|
|
DDebug(this,DebugAll,"Topmost (%p): '%s' added",topmost,topmost->name().c_str());
|
|
return true;
|
|
}
|
|
|
|
// Remove a topmost component from list without deleting it
|
|
void SigDriver::removeTopmost(SigTopmost* topmost)
|
|
{
|
|
if (!topmost)
|
|
return;
|
|
Lock lock(m_topmostMutex);
|
|
m_topmost.remove(topmost,false);
|
|
DDebug(this,DebugAll,"Topmost (%p): '%s' removed",topmost,topmost->name().c_str());
|
|
}
|
|
|
|
// Append a trunk to the list. Duplicate names are not allowed
|
|
bool SigDriver::appendTrunk(SigTrunk* trunk)
|
|
{
|
|
if (!trunk || trunk->name().null())
|
|
return false;
|
|
if (findTrunk(trunk->name(),false)) {
|
|
Debug(this,DebugWarn,"Can't append trunk (%p): '%s'. Duplicate name",
|
|
trunk,trunk->name().c_str());
|
|
return false;
|
|
}
|
|
Lock lock(m_trunksMutex);
|
|
m_trunks.append(trunk);
|
|
DDebug(this,DebugAll,"Trunk (%p): '%s' added",trunk,trunk->name().c_str());
|
|
return true;
|
|
}
|
|
|
|
// Remove a trunk from list without deleting it
|
|
void SigDriver::removeTrunk(SigTrunk* trunk)
|
|
{
|
|
if (!trunk)
|
|
return;
|
|
Lock lock(m_trunksMutex);
|
|
m_trunks.remove(trunk,false);
|
|
DDebug(this,DebugAll,"Trunk (%p): '%s' removed",trunk,trunk->name().c_str());
|
|
}
|
|
|
|
// Delete the given trunk if found
|
|
// Clear trunk list if name is 0
|
|
// Clear all stacks without waiting for call termination if name is 0
|
|
void SigDriver::clearTrunk(const char* name, bool waitCallEnd, unsigned int howLong)
|
|
{
|
|
Lock lock(m_trunksMutex);
|
|
if (!name) {
|
|
DDebug(this,DebugAll,"Clearing all trunks");
|
|
disconnectChannels();
|
|
ObjList* obj = m_trunks.skipNull();
|
|
for (; obj; obj = obj->skipNext()) {
|
|
SigTrunk* trunk = static_cast<SigTrunk*>(obj->get());
|
|
trunk->cleanup();
|
|
}
|
|
m_trunks.clear();
|
|
return;
|
|
}
|
|
SigTrunk* trunk = findTrunk(name,false);
|
|
if (!trunk)
|
|
return;
|
|
DDebug(this,DebugAll,"Clearing trunk '%s'%s",trunk->name().c_str(),
|
|
waitCallEnd ? ". Waiting for active calls to end" : "");
|
|
// Delay clearing if trunk has a call controller
|
|
if (waitCallEnd && trunk->controller()) {
|
|
trunk->setExiting(howLong);
|
|
return;
|
|
}
|
|
trunk->cleanup();
|
|
m_trunks.remove(trunk,true);
|
|
}
|
|
|
|
bool SigDriver::initTrunk(NamedList& sect, int type)
|
|
{
|
|
// Disable ?
|
|
if (!sect.getBoolValue("enable",true)) {
|
|
if (!findTrunk(sect,false) && !sect.null())
|
|
return false;
|
|
clearTrunk(sect,false);
|
|
return true;
|
|
}
|
|
const char* ttype = lookup(type,SigTrunk::s_type);
|
|
// Create or initialize
|
|
DDebug(this,DebugAll,"Initializing trunk '%s' of type '%s'",
|
|
sect.c_str(),ttype);
|
|
SigTrunk* trunk = findTrunk(sect.c_str(),false);
|
|
bool create = (trunk == 0);
|
|
if (create) {
|
|
switch (type) {
|
|
case SigTrunk::SS7Isup:
|
|
trunk = new SigSS7Isup(sect,false);
|
|
break;
|
|
case SigTrunk::SS7Bicc:
|
|
trunk = new SigSS7Isup(sect,true);
|
|
break;
|
|
case SigTrunk::IsdnPriNet:
|
|
case SigTrunk::IsdnBriNet:
|
|
case SigTrunk::IsdnPriCpe:
|
|
case SigTrunk::IsdnBriCpe:
|
|
trunk = new SigIsdn(sect,(SigTrunk::Type)type);
|
|
break;
|
|
case SigTrunk::IsdnPriMon:
|
|
trunk = new SigIsdnMonitor(sect);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
if (!trunk->initialize(sect)) {
|
|
Debug(this,DebugWarn,"Failed to initialize trunk '%s' of type '%s'",
|
|
sect.c_str(),ttype);
|
|
if (create)
|
|
clearTrunk(sect);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SigDriver::initTopmost(NamedList& sect, int type)
|
|
{
|
|
// Disable ?
|
|
if (!sect.getBoolValue("enable",true)) {
|
|
m_topmost.remove(sect);
|
|
return true;
|
|
}
|
|
const char* ttype = lookup(type,SigFactory::s_compNames);
|
|
// Create or initialize
|
|
DDebug(this,DebugAll,"Initializing topmost '%s' of type '%s'",
|
|
sect.c_str(),ttype);
|
|
|
|
SigTopmost* topmost = static_cast<SigTopmost*>(m_topmost[sect]);
|
|
bool create = (topmost == 0);
|
|
if (create) {
|
|
switch (type) {
|
|
case SigFactory::SigSS7Layer3:
|
|
case SigFactory::SigSS7M3UA:
|
|
topmost = new SigLinkSet(sect);
|
|
break;
|
|
case SigFactory::SigSS7Testing:
|
|
topmost = new SigTesting(sect);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
if (!topmost->initialize(sect)) {
|
|
Debug(this,DebugWarn,"Failed to initialize '%s' of type '%s'",
|
|
sect.c_str(),ttype);
|
|
if (create)
|
|
TelEngine::destruct(topmost);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SigDriver::reInitialize(NamedList& params)
|
|
{
|
|
Lock2 lock(m_trunksMutex,m_topmostMutex);
|
|
Configuration* cfg = new Configuration();
|
|
getCfg(params,*cfg);
|
|
String oper = params.getValue("operation");
|
|
int op = lookup(oper,s_operations,Unknown);
|
|
switch (op) {
|
|
case Create:
|
|
if (checkSections(cfg))
|
|
break;
|
|
return false;
|
|
case Configure:
|
|
saveConf(cfg);
|
|
break;
|
|
default:
|
|
Debug(this,DebugNote,"Received Unknown control operation: %s", oper.c_str());
|
|
return false;
|
|
}
|
|
bool ret = false;
|
|
unsigned int n = cfg->sections();
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
NamedList* sect = cfg->getSection(i);
|
|
ret = initSection(sect);
|
|
}
|
|
TelEngine::destruct(cfg);
|
|
SignallingComponent* router = 0;
|
|
while ((router = engine()->find("","SS7Router",router)))
|
|
YOBJECT(SS7Router,router)->printRoutes();
|
|
return ret;
|
|
}
|
|
|
|
bool SigDriver::checkSections(Configuration* cfg)
|
|
{
|
|
unsigned int n = cfg->sections();
|
|
for (unsigned int i = 0;i < n;i ++) {
|
|
NamedList* sect = cfg->getSection(i);
|
|
if (!sect || sect->null())
|
|
continue;
|
|
NamedList* sect1 = s_cfg.getSection(*sect);
|
|
if (sect1 && !sect1->getBoolValue("enable")) {
|
|
Debug(this,DebugAll,"Section '%s' already exists in %s conf file",sect->c_str(),name().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
// If we are here no section was found in configuration
|
|
// Just append the sections in the specifyed conf file
|
|
saveConf(cfg);
|
|
return true;
|
|
}
|
|
|
|
void SigDriver::saveConf(Configuration* cfg)
|
|
{
|
|
unsigned int n = cfg->sections();
|
|
for (unsigned int i = 0;i < n;i ++) {
|
|
NamedList* sect = cfg->getSection(i);
|
|
if (!sect || sect->null())
|
|
continue;
|
|
NamedList* sect1 = s_cfg.getSection(*sect);
|
|
if (!sect1) {
|
|
s_cfg.createSection(*sect);
|
|
sect1 = s_cfg.getSection(*sect);
|
|
}
|
|
else {
|
|
sect1->clearParams();
|
|
}
|
|
sect1->copyParams(*sect);
|
|
if(!s_cfg.save())
|
|
Debug(this,DebugWarn,"Failed to save configuration data in file: %s",s_cfg.c_str());
|
|
}
|
|
}
|
|
|
|
void SigDriver::getCfg(NamedList& params, Configuration& cfg)
|
|
{
|
|
String trunk = params.getValue("section");
|
|
unsigned int n = params.count();
|
|
for (unsigned int i = 0;i <= n;i ++) {
|
|
NamedString* ns = params.getParam(i);
|
|
if (!ns || ns->name() == "operation" || ns->name() == "targetid"
|
|
|| ns->name() == "component" || ns->name() == "section")
|
|
continue;
|
|
cfg.setValue(trunk,ns->name(),*ns);
|
|
}
|
|
}
|
|
|
|
void SigDriver::initialize()
|
|
{
|
|
Output("Initializing module Signalling Channel");
|
|
Lock2 lock(m_trunksMutex,m_topmostMutex);
|
|
s_cfg = Engine::configFile("ysigchan");
|
|
s_cfg.load();
|
|
// Startup
|
|
if (!m_engine) {
|
|
s_cfgData = Engine::configFile("ysigdata");
|
|
s_cfgData.load(false);
|
|
setup();
|
|
installRelay(Masquerade);
|
|
installRelay(Halt);
|
|
installRelay(Help);
|
|
installRelay(Progress);
|
|
installRelay(Update);
|
|
installRelay(Route);
|
|
Engine::install(new IsupDecodeHandler);
|
|
Engine::install(new IsupEncodeHandler);
|
|
m_engine = SignallingEngine::self(true);
|
|
m_engine->debugChain(this);
|
|
m_engine->start();
|
|
m_engine->setNotifier(&s_notifier);
|
|
}
|
|
// Apply debug levels to driver and engine
|
|
int level = s_cfg.getIntValue("general","debuglevel",-1);
|
|
if (level >= 0)
|
|
debugLevel(level);
|
|
level = s_cfg.getIntValue("general","debuglevel_engine",-1);
|
|
if (level >= 0)
|
|
m_engine->debugLevel(level);
|
|
// Build/initialize trunks and topmost components
|
|
unsigned int n = s_cfg.sections();
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
NamedList* sect = s_cfg.getSection(i);
|
|
initSection(sect);
|
|
}
|
|
SignallingComponent* router = 0;
|
|
while ((router = engine()->find("","SS7Router",router)))
|
|
YOBJECT(SS7Router,router)->printRoutes();
|
|
}
|
|
|
|
bool SigDriver::initSection(NamedList* sect)
|
|
{
|
|
if (!sect || sect->null() || *sect == "general")
|
|
return false;
|
|
bool ret = false;
|
|
const char* stype = sect->getValue("type");
|
|
int type = lookup(stype,SigFactory::s_compNames,SigFactory::Unknown);
|
|
// Check for valid type
|
|
if (type == SigFactory::Unknown) {
|
|
Debug(this,DebugNote,"Section '%s'. Unknown/missing type '%s'",sect->c_str(),stype);
|
|
return false;
|
|
}
|
|
if (type & SigFactory::SigIsTrunk)
|
|
ret = initTrunk(*sect,type & SigFactory::SigPrivate);
|
|
else if (type & SigFactory::SigTopMost)
|
|
ret = initTopmost(*sect,type);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* SigTopmost
|
|
*/
|
|
SigTopmost::SigTopmost(const char* name)
|
|
: TopMost(name)
|
|
{
|
|
XDebug(&plugin,DebugAll,"SigTopmost::SigTopmost('%s') [%p]",name,this);
|
|
plugin.appendTopmost(this);
|
|
}
|
|
|
|
void SigTopmost::destroyed()
|
|
{
|
|
XDebug(&plugin,DebugAll,"SigTopmost::destroyed() [%p]",this);
|
|
plugin.removeTopmost(this);
|
|
TopMost::destroyed();
|
|
}
|
|
|
|
|
|
/**
|
|
* SigLinkSet
|
|
*/
|
|
void SigLinkSet::destroyed()
|
|
{
|
|
TelEngine::destruct(m_linkset);
|
|
SigTopmost::destroyed();
|
|
}
|
|
|
|
bool SigLinkSet::initialize(NamedList& params)
|
|
{
|
|
if (!m_linkset) {
|
|
m_linkset = YSIGCREATE(SS7Layer3,¶ms);
|
|
plugin.engine()->insert(m_linkset);
|
|
}
|
|
return m_linkset && m_linkset->initialize(¶ms);
|
|
}
|
|
|
|
void SigLinkSet::status(String& retVal)
|
|
{
|
|
retVal << "type=" << (m_linkset ? m_linkset->componentType() : "");
|
|
retVal << ",status=" << (m_linkset && m_linkset->operational() ? "" : "non-") << "operational";
|
|
}
|
|
|
|
void SigLinkSet::ifStatus(String& status)
|
|
{
|
|
SS7MTP3* mtp3 = static_cast<SS7MTP3*>(m_linkset);
|
|
if (mtp3) {
|
|
const ObjList* list = mtp3->links();
|
|
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
|
|
GenPointer<SS7Layer2>* p = static_cast<GenPointer<SS7Layer2>*>(o->get());
|
|
if (!*p)
|
|
continue;
|
|
SS7Layer2* link = *p;
|
|
SS7MTP2* mtp2 = static_cast<SS7MTP2*>(link->getObject("SS7MTP2"));
|
|
if (mtp2) {
|
|
SignallingInterface* sigIface = mtp2->iface();
|
|
if (sigIface) {
|
|
status.append(sigIface->toString(),",");
|
|
status << "=" << (sigIface->control(SignallingInterface::Query) ? "" : "non-");
|
|
status << "operational";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SigLinkSet::linkStatus(String& status)
|
|
{
|
|
SS7MTP3* mtp3 = YOBJECT(SS7MTP3,m_linkset);
|
|
if (mtp3) {
|
|
const ObjList* list = mtp3->links();
|
|
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
|
|
GenPointer<SS7Layer2>* p = static_cast<GenPointer<SS7Layer2>*>(o->get());
|
|
if (!*p)
|
|
continue;
|
|
SS7Layer2* link = *p;
|
|
if (link) {
|
|
status.append(link->toString(),",") << "=";
|
|
status << link->componentType() ;
|
|
status << "|" << link->statusName();
|
|
status << "|" << link->upTime();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* SigTesting
|
|
*/
|
|
void SigTesting::destroyed()
|
|
{
|
|
TelEngine::destruct(m_testing);
|
|
SigTopmost::destroyed();
|
|
}
|
|
|
|
bool SigTesting::initialize(NamedList& params)
|
|
{
|
|
if (!m_testing) {
|
|
m_testing = YSIGCREATE(SS7Testing,¶ms);
|
|
plugin.engine()->insert(m_testing);
|
|
}
|
|
return m_testing && m_testing->initialize(¶ms);
|
|
}
|
|
|
|
|
|
/**
|
|
* SigTrunk
|
|
*/
|
|
const TokenDict SigTrunk::s_type[] = {
|
|
{ "ss7-isup", SS7Isup },
|
|
{ "ss7-bicc", SS7Bicc },
|
|
{ "isdn-pri-net", IsdnPriNet },
|
|
{ "isdn-bri-net", IsdnBriNet },
|
|
{ "isdn-pri-cpe", IsdnPriCpe },
|
|
{ "isdn-bri-cpe", IsdnBriCpe },
|
|
{ "isdn-pri-mon", IsdnPriMon },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
SigTrunk::SigTrunk(const char* name, Type type)
|
|
: TopMost(name),
|
|
m_controller(0),
|
|
m_init(false),
|
|
m_inband(false),
|
|
m_ringback(false),
|
|
m_type(type),
|
|
m_thread(0)
|
|
{
|
|
XDebug(&plugin,DebugAll,"SigTrunk::SigTrunk('%s') [%p]",name,this);
|
|
plugin.appendTrunk(this);
|
|
}
|
|
|
|
void SigTrunk::destroyed()
|
|
{
|
|
XDebug(&plugin,DebugAll,"SigTrunk::destroyed() [%p]",this);
|
|
cleanup();
|
|
plugin.removeTrunk(this);
|
|
}
|
|
|
|
// Set exiting flag for call controller and timeout for the thread
|
|
void SigTrunk::setExiting(unsigned int msec)
|
|
{
|
|
if (m_controller)
|
|
m_controller->setExiting();
|
|
if (m_thread)
|
|
m_thread->m_timeout = Time::msecNow() + msec;
|
|
}
|
|
|
|
// Initialize (create or reload) the trunk. Set debug levels for contained objects
|
|
// Fix some type depending parameters:
|
|
// Force 'readonly' to true for ISDN monitors
|
|
// Check the value of 'rxunderruninterval' for SS7 and non monitor ISDN links
|
|
bool SigTrunk::initialize(NamedList& params)
|
|
{
|
|
// Reload common parameters
|
|
m_inband = params.getBoolValue("dtmfinband",s_cfg.getBoolValue("general","dtmfinband",false));
|
|
m_ringback = params.getBoolValue("ringback",s_cfg.getBoolValue("general","ringback",false));
|
|
|
|
// Check error:
|
|
// No need to initialize if no signalling engine or not in plugin's list
|
|
// For SS7 trunks check the router
|
|
String error;
|
|
bool init = true;
|
|
while (true) {
|
|
if (!(plugin.engine() && plugin.findTrunk(name(),false))) {
|
|
error = "No engine or not in module's list";
|
|
break;
|
|
}
|
|
// Create/reload
|
|
init = m_init;
|
|
m_init = true;
|
|
if (init ? reload(params) : create(params,error))
|
|
return true;
|
|
break;
|
|
}
|
|
Debug(&plugin,DebugNote,"Trunk('%s'). %s failure: %s [%p]",
|
|
name().c_str(),init?"Reload":"Create",error.safe(),this);
|
|
return false;
|
|
}
|
|
|
|
void SigTrunk::handleEvent(SignallingEvent* event)
|
|
{
|
|
plugin.handleEvent(event);
|
|
}
|
|
|
|
// Clear channels with calls belonging to this trunk. Cancel thread if any. Call release
|
|
void SigTrunk::cleanup()
|
|
{
|
|
plugin.disconnectChannels(this);
|
|
if (m_thread) {
|
|
m_thread->cancel();
|
|
while(m_thread)
|
|
Thread::yield();
|
|
Debug(&plugin,DebugAll,"Trunk('%s'). Worker thread terminated [%p]",name().c_str(),this);
|
|
}
|
|
release();
|
|
}
|
|
|
|
bool SigTrunk::startThread(String& error, unsigned long sleepUsec)
|
|
{
|
|
if (!m_thread) {
|
|
if (m_controller)
|
|
m_thread = new SigTrunkThread(this,sleepUsec);
|
|
else {
|
|
Debug(&plugin,DebugNote,
|
|
"Trunk('%s'). No worker thread for trunk without call controller [%p]",
|
|
name().c_str(),this);
|
|
return true;
|
|
}
|
|
}
|
|
if (!(m_thread->running() || m_thread->startup())) {
|
|
error = "Failed to start worker thread";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Build a signalling circuit group for this trunk
|
|
SignallingCircuitGroup* SigTrunk::buildCircuits(NamedList& params, const String& device,
|
|
const String& debugName, String& error)
|
|
{
|
|
ObjList* voice = device.split(',',false);
|
|
if (!voice) {
|
|
error = "Missing or invalid voice parameter";
|
|
return 0;
|
|
}
|
|
unsigned int start = params.getIntValue("offset",0);
|
|
SignallingCircuitGroup* group =
|
|
new SignallingCircuitGroup(0,SignallingCircuitGroup::Increment,debugName);
|
|
for (ObjList* o = voice->skipNull(); o; o = o->skipNext()) {
|
|
String* s = static_cast<String*>(o->get());
|
|
if (s->null())
|
|
continue;
|
|
NamedList spanParams(*s);
|
|
spanParams.copySubParams(params,*s + ".");
|
|
SignallingCircuitSpan* span = group->buildSpan(*s,start,&spanParams);
|
|
if (!span) {
|
|
error << "Failed to build voice span '" << *s << "'";
|
|
break;
|
|
}
|
|
group->insertRange(span,*s);
|
|
start += span->increment();
|
|
}
|
|
TelEngine::destruct(voice);
|
|
if (error.null()) {
|
|
unsigned int n = params.length();
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
const NamedString* s = params.getParam(i);
|
|
if (!s || (s->name() != "range"))
|
|
continue;
|
|
ObjList* o = s->split(':',false);
|
|
switch (o->count()) {
|
|
case 2:
|
|
// name:range
|
|
group->insertRange(o->at(1)->toString(),o->at(0)->toString());
|
|
break;
|
|
case 3:
|
|
// name:strategy:range
|
|
group->insertRange(o->at(2)->toString(),o->at(0)->toString(),
|
|
SignallingCircuitGroup::str2strategy(o->at(1)->toString(),-1));
|
|
break;
|
|
default:
|
|
error << "Invalid range: '" << *s << "'";
|
|
n = 0;
|
|
}
|
|
TelEngine::destruct(o);
|
|
}
|
|
}
|
|
if (error.null()) {
|
|
plugin.engine()->insert(group);
|
|
return group;
|
|
}
|
|
SignallingEngine::destruct(group);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* SigSS7Isup
|
|
*/
|
|
SigSS7Isup::SigSS7Isup(const char* name, bool bicc)
|
|
: SigTrunk(name,SS7Isup),
|
|
m_bicc(bicc)
|
|
{
|
|
}
|
|
|
|
SigSS7Isup::~SigSS7Isup()
|
|
{
|
|
release();
|
|
}
|
|
|
|
bool SigSS7Isup::create(NamedList& params, String& error)
|
|
{
|
|
release();
|
|
|
|
String compName; // Component name
|
|
|
|
// Voice transfer: circuit group, spans, circuits
|
|
// Use the same span as the signalling channel if missing
|
|
buildName(compName,"L1/Data");
|
|
SignallingCircuitGroup* group = buildCircuits(params,
|
|
params.getValue("voice",name()),compName,error);
|
|
if (!group)
|
|
return false;
|
|
|
|
// Load circuits lock state from file
|
|
NamedList* sect = s_cfgData.getSection(name());
|
|
if (sect) {
|
|
DDebug(&plugin,DebugAll,
|
|
"SigSS7Isup('%s'). Loading circuits lock state from config [%p]",
|
|
name().c_str(),this);
|
|
unsigned int n = sect->count();
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
NamedString* ns = sect->getParam(i);
|
|
if (!ns)
|
|
continue;
|
|
unsigned int code = ns->name().toInteger(0);
|
|
if (!code)
|
|
continue;
|
|
SignallingCircuit* cic = group->find(code);
|
|
if (!cic) {
|
|
DDebug(&plugin,DebugMild,"SigSS7Isup('%s'). Can't find circuit %u [%p]",
|
|
name().c_str(),code,this);
|
|
continue;
|
|
}
|
|
int flags = 0;
|
|
SignallingUtils::encodeFlags(group,flags,*ns,SignallingCircuit::s_lockNames);
|
|
// Allow only remote HW/maintenance and local maintenance
|
|
flags &= SignallingCircuit::LockRemote | SignallingCircuit::LockLocalMaint |
|
|
SignallingCircuit::LockLocalMaintChg;
|
|
Debug(&plugin,DebugAll,"SigSS7Isup('%s'). Set lock %u=%s flags=0x%x [%p]",
|
|
name().c_str(),code,ns->c_str(),flags,this);
|
|
cic->setLock(flags);
|
|
}
|
|
}
|
|
|
|
plugin.engine()->insert(group);
|
|
// ISUP
|
|
buildName(compName,(m_bicc ? "BICC" : "ISUP"));
|
|
params.setParam("debugname",compName);
|
|
m_controller = m_bicc ? new SS7BICC(params) : new SS7ISUP(params);
|
|
controller()->attach(group);
|
|
if (isup()) {
|
|
plugin.engine()->insert(isup());
|
|
isup()->initialize(¶ms);
|
|
if (!isup()->setPointCode(params)) {
|
|
error = "No point codes";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Start thread
|
|
if (!startThread(error,plugin.engine()->tickDefault()))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SigSS7Isup::reload(NamedList& params)
|
|
{
|
|
if (isup()) {
|
|
isup()->initialize(¶ms);
|
|
isup()->setPointCode(params);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void SigSS7Isup::release()
|
|
{
|
|
// m_controller is a SS7ISUP call controller
|
|
if (m_controller) {
|
|
verifyController(0);
|
|
m_controller->cleanup();
|
|
if (plugin.engine()) {
|
|
plugin.engine()->remove(isup());
|
|
SignallingCircuitGroup* group = m_controller->attach((SignallingCircuitGroup*)0);
|
|
if (group) {
|
|
plugin.engine()->remove(group);
|
|
TelEngine::destruct(group);
|
|
}
|
|
}
|
|
isup()->destruct();
|
|
m_controller = 0;
|
|
}
|
|
XDebug(&plugin,DebugAll,"SigSS7Isup('%s'). Released [%p]",name().c_str(),this);
|
|
}
|
|
|
|
// Handle events received from call controller
|
|
// Process Verify event. Calls the driver's handleEvent() method for other events
|
|
void SigSS7Isup::handleEvent(SignallingEvent* event)
|
|
{
|
|
if (!event)
|
|
return;
|
|
if (event->type() != SignallingEvent::Verify) {
|
|
SigTrunk::handleEvent(event);
|
|
return;
|
|
}
|
|
if (event->message())
|
|
verifyController(&event->message()->params());
|
|
else
|
|
verifyController(0);
|
|
}
|
|
|
|
// Save circuits state
|
|
bool SigSS7Isup::verifyController(const NamedList* params, bool save)
|
|
{
|
|
if (!isup())
|
|
return false;
|
|
NamedList tmp("");
|
|
// Do that anyway to reset the verify flag
|
|
bool verify = isup()->verify();
|
|
if (!params) {
|
|
if (!verify)
|
|
return false;
|
|
isup()->buildVerifyEvent(tmp);
|
|
params = &tmp;
|
|
}
|
|
|
|
Lock lock(m_controller);
|
|
SignallingCircuitGroup* group = m_controller->circuits();
|
|
if (!group)
|
|
return false;
|
|
DDebug(&plugin,DebugInfo,"SigSS7Isup('%s'). Verifying circuits state [%p]",
|
|
name().c_str(),this);
|
|
Lock lockGroup(group);
|
|
NamedList* sect = s_cfgData.getSection(name());
|
|
if (!sect) {
|
|
s_cfgData.createSection(name());
|
|
sect = s_cfgData.getSection(name());
|
|
}
|
|
bool changed = false;
|
|
// Save local changed maintenance status
|
|
// Save all remote lock flags (except for changed)
|
|
for (ObjList* o = group->circuits().skipNull(); o; o = o->skipNext()) {
|
|
SignallingCircuit* cic = static_cast<SignallingCircuit*>(o->get());
|
|
String code(cic->code());
|
|
bool saveCic = false;
|
|
|
|
// Param exists and remote state didn't changed: check local
|
|
// maintenance flag against params's value (save if last state
|
|
// is not equal to the current one)
|
|
NamedString* cicParam = sect->getParam(code);
|
|
if (cicParam && !cic->locked(SignallingCircuit::LockRemoteChg)) {
|
|
int cicFlags = 0;
|
|
SignallingUtils::encodeFlags(0,cicFlags,*cicParam,SignallingCircuit::s_lockNames);
|
|
cicFlags &= cic->locked(SignallingCircuit::LockLocalMaintChg|SignallingCircuit::LockLocalMaint);
|
|
saveCic = (0 != cicFlags);
|
|
}
|
|
else
|
|
saveCic = (0 != cic->locked(SignallingCircuit::LockRemoteChg));
|
|
|
|
if (!saveCic)
|
|
continue;
|
|
|
|
int flags = 0;
|
|
if (cic->locked(SignallingCircuit::LockLocalMaintChg))
|
|
flags |= SignallingCircuit::LockLocalMaintChg;
|
|
if (cic->locked(SignallingCircuit::LockLocalMaint))
|
|
flags |= SignallingCircuit::LockLocalMaint;
|
|
if (cic->locked(SignallingCircuit::LockRemoteHWFailChg) &&
|
|
cic->locked(SignallingCircuit::LockRemoteHWFail))
|
|
flags |= SignallingCircuit::LockRemoteHWFail;
|
|
if (cic->locked(SignallingCircuit::LockRemoteMaintChg) &&
|
|
cic->locked(SignallingCircuit::LockRemoteMaint))
|
|
flags |= SignallingCircuit::LockRemoteMaint;
|
|
|
|
// Save only if we have something
|
|
if (flags || (cicParam && !cicParam->null())) {
|
|
String tmp;
|
|
for (const TokenDict* dict = SignallingCircuit::s_lockNames; dict->token; dict++)
|
|
if (0 != (flags & dict->value))
|
|
tmp.append(dict->token,",");
|
|
DDebug(&plugin,DebugInfo,
|
|
"SigSS7Isup('%s'). Saving cic %s flags 0x%x '%s' (all=0x%x) [%p]",
|
|
name().c_str(),code.c_str(),flags,tmp.c_str(),cic->locked(-1),this);
|
|
sect->setParam(code,tmp);
|
|
changed = true;
|
|
}
|
|
cic->resetLock(SignallingCircuit::LockRemoteChg);
|
|
}
|
|
lockGroup.drop();
|
|
lock.drop();
|
|
|
|
if (changed && save)
|
|
s_cfgData.save();
|
|
return changed;
|
|
}
|
|
|
|
|
|
/**
|
|
* SigIsdn
|
|
*/
|
|
SigIsdn::SigIsdn(const char* name, Type type)
|
|
: SigTrunk(name,type)
|
|
{
|
|
}
|
|
|
|
SigIsdn::~SigIsdn()
|
|
{
|
|
release();
|
|
}
|
|
|
|
bool SigIsdn::create(NamedList& params, String& error)
|
|
{
|
|
release();
|
|
String compName; // Component name
|
|
|
|
// Voice transfer: circuit group, spans, circuits
|
|
// Use the same span as the signalling channel if missing
|
|
buildName(compName,"B");
|
|
SignallingCircuitGroup* group = buildCircuits(params,
|
|
params.getValue("voice",params.getValue("sig",name())),compName,error);
|
|
if (!group)
|
|
return false;
|
|
|
|
// Q931
|
|
params.setParam("network",String::boolText(0 != (MaskNet & type())));
|
|
params.setParam("primary",String::boolText(0 != (MaskPri & type())));
|
|
buildName(compName,"Q931");
|
|
params.setParam("debugname",compName);
|
|
m_controller = new ISDNQ931(params,compName);
|
|
plugin.engine()->insert(q931());
|
|
|
|
// Create links between components and enable them
|
|
controller()->attach(group);
|
|
q931()->initialize(¶ms);
|
|
|
|
// Start thread
|
|
if (!startThread(error,plugin.engine()->tickDefault()))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SigIsdn::reload(NamedList& params)
|
|
{
|
|
if (q931())
|
|
q931()->initialize(¶ms);
|
|
return true;
|
|
}
|
|
|
|
void SigIsdn::release()
|
|
{
|
|
// m_controller is an ISDNQ931 call controller
|
|
if (m_controller) {
|
|
m_controller->cleanup();
|
|
if (plugin.engine()) {
|
|
plugin.engine()->remove(q931());
|
|
SignallingCircuitGroup* group = m_controller->attach((SignallingCircuitGroup*)0);
|
|
if (group) {
|
|
plugin.engine()->remove(group);
|
|
TelEngine::destruct(group);
|
|
}
|
|
}
|
|
q931()->destruct();
|
|
m_controller = 0;
|
|
}
|
|
XDebug(&plugin,DebugAll,"SigIsdn('%s'). Released [%p]",name().c_str(),this);
|
|
}
|
|
|
|
void SigIsdn::ifStatus(String& status)
|
|
{
|
|
if (m_controller) {
|
|
const ISDNQ921* l2 = static_cast<const ISDNQ921*>(q931()->layer2());
|
|
if (l2) {
|
|
SignallingInterface* sigIface = l2->iface();
|
|
if (sigIface) {
|
|
status.append(sigIface->toString(),",");
|
|
status << "=" << (sigIface->control(SignallingInterface::Query) ? "" : "non-") << "operational";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SigIsdn::linkStatus(String& status)
|
|
{
|
|
if (m_controller) {
|
|
const ISDNLayer2* l2 = static_cast<const ISDNLayer2*>(q931()->layer2());
|
|
if (l2) {
|
|
status.append(l2->toString(),",") << "=";
|
|
status << l2->componentType();
|
|
status << "|" << l2->stateName(l2->state());
|
|
status << "|" << l2->upTime();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* SigIsdnMonitor
|
|
*/
|
|
SigIsdnMonitor::SigIsdnMonitor(const char* name)
|
|
: SigTrunk(name,IsdnPriMon),
|
|
Mutex(true),
|
|
m_id(0),
|
|
m_chanBuffer(160),
|
|
m_idleValue(255)
|
|
{
|
|
}
|
|
|
|
SigIsdnMonitor::~SigIsdnMonitor()
|
|
{
|
|
release();
|
|
}
|
|
|
|
void SigIsdnMonitor::handleEvent(SignallingEvent* event)
|
|
{
|
|
if (!event)
|
|
return;
|
|
if (!event->call()) {
|
|
XDebug(&plugin,DebugNote,
|
|
"SigIsdnMonitor('%s'). Received event (%p,'%s') without call [%p]",
|
|
name().c_str(),event,event->name(),this);
|
|
return;
|
|
}
|
|
|
|
Lock lock(this);
|
|
SigIsdnCallRecord* rec = 0;
|
|
ISDNQ931CallMonitor* mon = static_cast<ISDNQ931CallMonitor*>(event->call());
|
|
|
|
// Find monitor
|
|
for (ObjList* o = m_monitors.skipNull(); o; o = o->skipNext()) {
|
|
rec = static_cast<SigIsdnCallRecord*>(o->get());
|
|
if (rec == mon->userdata())
|
|
break;
|
|
rec = 0;
|
|
}
|
|
|
|
if (rec) {
|
|
switch (event->type()) {
|
|
case SignallingEvent::Info:
|
|
rec->evInfo(event);
|
|
break;
|
|
case SignallingEvent::Accept:
|
|
case SignallingEvent::Ringing:
|
|
case SignallingEvent::Answer:
|
|
if (rec->update(event))
|
|
break;
|
|
// Fall through to release if update failed
|
|
case SignallingEvent::Release:
|
|
rec->disconnect(event->message() ? event->message()->params().getValue("reason") : "normal-clearing");
|
|
break;
|
|
default:
|
|
DDebug(&plugin,DebugStub,
|
|
"SigIsdnMonitor('%s'). No handler for event '%s' [%p]",
|
|
name().c_str(),event->name(),this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event->type() == SignallingEvent::NewCall) {
|
|
String id;
|
|
id << name() << "/" << ++m_id;
|
|
rec = new SigIsdnCallRecord(this,id,event);
|
|
if (rec->update(event)) {
|
|
mon->userdata(rec);
|
|
m_monitors.append(rec);
|
|
}
|
|
else
|
|
rec->disconnect(0);
|
|
lock.drop();
|
|
TelEngine::destruct(rec);
|
|
}
|
|
else
|
|
XDebug(&plugin,DebugNote,
|
|
"SigIsdnMonitor('%s'). Received event (%p,'%s') with invalid user data (%p) [%p]",
|
|
name().c_str(),event,event->name(),mon->userdata(),this);
|
|
}
|
|
|
|
bool SigIsdnMonitor::masquerade(String& id, Message& msg)
|
|
{
|
|
for (ObjList* o = m_monitors.skipNull(); o; o = o->skipNext()) {
|
|
SigIsdnCallRecord* rec = static_cast<SigIsdnCallRecord*>(o->get());
|
|
if (id != rec->id())
|
|
continue;
|
|
msg = msg.getValue("message");
|
|
msg.clearParam("message");
|
|
msg.userData(rec);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SigIsdnMonitor::drop(String& id, Message& msg)
|
|
{
|
|
const char* reason = msg.getValue("reason","dropped");
|
|
if (id == name()) {
|
|
lock();
|
|
ListIterator iter(m_monitors);
|
|
GenObject* o = 0;
|
|
for (; (o = iter.get()); ) {
|
|
RefPointer<CallEndpoint> c = static_cast<CallEndpoint*>(o);
|
|
unlock();
|
|
c->disconnect(reason);
|
|
c = 0;
|
|
lock();
|
|
}
|
|
unlock();
|
|
return true;
|
|
}
|
|
Lock lock(this);
|
|
for (ObjList* o = m_monitors.skipNull(); o; o = o->skipNext()) {
|
|
RefPointer<SigIsdnCallRecord> rec = static_cast<SigIsdnCallRecord*>(o->get());
|
|
if (id == rec->id()) {
|
|
lock.drop();
|
|
rec->disconnect(reason);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SigIsdnMonitor::removeCall(SigIsdnCallRecord* call)
|
|
{
|
|
Lock lock(this);
|
|
m_monitors.remove(call,false);
|
|
}
|
|
|
|
bool SigIsdnMonitor::create(NamedList& params, String& error)
|
|
{
|
|
release();
|
|
String compName; // Component name
|
|
m_chanBuffer = params.getIntValue("muxchanbuffer",160);
|
|
if (!m_chanBuffer)
|
|
m_chanBuffer = 160;
|
|
unsigned int ui = params.getIntValue("idlevalue",255);
|
|
m_idleValue = (ui <= 255 ? ui : 255);
|
|
|
|
// Set auto detection for Layer 2 (Q.921) type side of the trunk
|
|
params.setParam("detect",String::boolText(true));
|
|
|
|
// Voice transfer: circuit groups, spans, circuits
|
|
// Use the same span as the signalling channel if missing
|
|
buildName(compName,"Net/B");
|
|
const char* device = params.getValue("voice-net",params.getValue("sig-net"));
|
|
SignallingCircuitGroup* groupNet = buildCircuits(params,device,compName,error);
|
|
if (!groupNet)
|
|
return false;
|
|
buildName(compName,"Cpe/B");
|
|
device = params.getValue("voice-cpe",params.getValue("sig-cpe"));
|
|
SignallingCircuitGroup* groupCpe = buildCircuits(params,device,compName,error);
|
|
if (!groupCpe) {
|
|
TelEngine::destruct(groupNet);
|
|
return false;
|
|
}
|
|
String sNet, sCpe;
|
|
groupNet->getCicList(sNet);
|
|
groupCpe->getCicList(sCpe);
|
|
if (sNet != sCpe)
|
|
Debug(&plugin,DebugWarn,
|
|
"SigIsdnMonitor('%s'). Circuit groups are not equal [%p]",
|
|
name().c_str(),this);
|
|
|
|
// Q931
|
|
compName = "";
|
|
compName << name() << '/' << "Q931";
|
|
params.setParam("debugname",compName);
|
|
params.setParam("print-messages",params.getValue("print-layer3PDU"));
|
|
m_controller = new ISDNQ931Monitor(params,compName);
|
|
plugin.engine()->insert(q931());
|
|
|
|
// Create links between components and enable them
|
|
q931()->attach(groupNet,true);
|
|
q931()->attach(groupCpe,false);
|
|
|
|
// Start thread
|
|
if (!startThread(error,plugin.engine()->tickDefault()))
|
|
return false;
|
|
|
|
if (debugAt(DebugInfo)) {
|
|
String tmp;
|
|
tmp << "\r\nChannel buffer: " << m_chanBuffer;
|
|
tmp << "\r\nIdle value: " << (int)m_idleValue;
|
|
Debug(&plugin,DebugInfo,"SigIsdnMonitor('%s'). Initialized: [%p]%s",
|
|
name().c_str(),this,tmp.c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SigIsdnMonitor::reload(NamedList& params)
|
|
{
|
|
if (q931())
|
|
q931()->initialize(¶ms);
|
|
return true;
|
|
}
|
|
|
|
void SigIsdnMonitor::release()
|
|
{
|
|
lock();
|
|
ListIterator iter(m_monitors);
|
|
GenObject* o = 0;
|
|
for (; (o = iter.get()); ) {
|
|
RefPointer<CallEndpoint> c = static_cast<CallEndpoint*>(o);
|
|
unlock();
|
|
c->disconnect();
|
|
c = 0;
|
|
lock();
|
|
}
|
|
unlock();
|
|
|
|
// m_controller is a ISDNQ931Monitor call controller
|
|
if (m_controller) {
|
|
m_controller->cleanup();
|
|
if (plugin.engine()) {
|
|
plugin.engine()->remove(q931());
|
|
SignallingCircuitGroup* group = q931()->attach((SignallingCircuitGroup*)0,false);
|
|
if (group) {
|
|
plugin.engine()->remove(group);
|
|
TelEngine::destruct(group);
|
|
}
|
|
group = q931()->attach((SignallingCircuitGroup*)0,true);
|
|
if (group) {
|
|
plugin.engine()->remove(group);
|
|
TelEngine::destruct(group);
|
|
}
|
|
}
|
|
q931()->destruct();
|
|
m_controller = 0;
|
|
}
|
|
XDebug(&plugin,DebugAll,"SigIsdnMonitor('%s'). Released [%p]",name().c_str(),this);
|
|
}
|
|
|
|
/**
|
|
* SigConsumerMux
|
|
*/
|
|
unsigned long SigConsumerMux::Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags)
|
|
{
|
|
if (m_owner) {
|
|
m_owner->consume(m_first,data,tStamp);
|
|
return invalidStamp();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* SigSourceMux
|
|
*/
|
|
SigSourceMux::SigSourceMux(const char* format, unsigned char idleValue, unsigned int chanBuffer)
|
|
: DataSource(format),
|
|
m_lock(true,"SigSourceMux"),
|
|
m_firstSrc(0),
|
|
m_secondSrc(0),
|
|
m_firstChan(0),
|
|
m_secondChan(0),
|
|
m_idleValue(idleValue),
|
|
m_sampleLen(0),
|
|
m_maxSamples(0),
|
|
m_samplesFirst(0),
|
|
m_samplesSecond(0),
|
|
m_error(0)
|
|
{
|
|
if (getFormat() == "2*slin")
|
|
m_sampleLen = 2;
|
|
else if (getFormat() == "2*mulaw")
|
|
m_sampleLen = 1;
|
|
else if (getFormat() == "2*alaw")
|
|
m_sampleLen = 1;
|
|
else {
|
|
Debug(&plugin,DebugNote,
|
|
"SigSourceMux::SigSourceMux(). Unsupported format %s [%p]",
|
|
format,this);
|
|
return;
|
|
}
|
|
// Adjust channel buffer to be multiple of sample length and not lesser then it
|
|
if (chanBuffer < m_sampleLen)
|
|
chanBuffer = m_sampleLen;
|
|
m_maxSamples = chanBuffer / m_sampleLen;
|
|
chanBuffer = m_maxSamples * m_sampleLen;
|
|
m_buffer.assign(0,2 * chanBuffer);
|
|
// +2 to skip ofer the "2*"
|
|
m_firstChan = new SigConsumerMux(this,true,format+2);
|
|
m_secondChan = new SigConsumerMux(this,false,format+2);
|
|
XDebug(&plugin,DebugAll,
|
|
"SigSourceMux::SigSourceMux(). Format: %s, sample=%u, buffer=%u [%p]",
|
|
getFormat().c_str(),m_sampleLen,m_buffer.length(),this);
|
|
}
|
|
|
|
SigSourceMux::~SigSourceMux()
|
|
{
|
|
Lock lock(m_lock);
|
|
removeSource(true);
|
|
removeSource(false);
|
|
TelEngine::destruct(m_firstChan);
|
|
TelEngine::destruct(m_secondChan);
|
|
XDebug(&plugin,DebugAll,"SigSourceMux::~SigSourceMux() [%p]",this);
|
|
}
|
|
|
|
#define MUX_CHAN (first ? '1' : '2')
|
|
|
|
// Replace the consumer of the given source. Remove current consumer's source before
|
|
// @param first True to replace with the first channel, false for the second
|
|
// Return false if source is 0 or has invalid format (other then ours)
|
|
bool SigSourceMux::attach(bool first, DataSource* source)
|
|
{
|
|
Lock lock(m_lock);
|
|
removeSource(first);
|
|
if (!(source && source->ref()))
|
|
return false;
|
|
if (first) {
|
|
m_firstSrc = source;
|
|
source->attach(m_firstChan);
|
|
}
|
|
else {
|
|
m_secondSrc = source;
|
|
source->attach(m_secondChan);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Multiplex received data from consumers and forward it
|
|
// Forward multiplexed buffer if chan already filled
|
|
// If received data is not greater then free space:
|
|
// Fill chan buffer with data
|
|
// If both channels are filled, forward the multiplexed buffer
|
|
// Otherwise:
|
|
// Fill free chan buffer
|
|
// Forward buffer and consume the rest
|
|
void SigSourceMux::consume(bool first, const DataBlock& data, unsigned long tStamp)
|
|
{
|
|
Lock lock(m_lock);
|
|
unsigned int samples = data.length() / m_sampleLen;
|
|
if (!m_error && (data.length() % m_sampleLen)) {
|
|
Debug(&plugin,DebugWarn,
|
|
"SigSourceMux. Wrong sample (received %u bytes) on channel %c [%p]",
|
|
data.length(),MUX_CHAN,this);
|
|
m_error++;
|
|
}
|
|
if (!samples)
|
|
return;
|
|
|
|
// Forward buffer if already filled for this channel
|
|
if ((first && firstFull()) || (!first && secondFull())) {
|
|
DDebug(&plugin,DebugMild,"SigSourceMux. Buffer overrun on channel %c [%p]",
|
|
MUX_CHAN,this);
|
|
forwardBuffer();
|
|
}
|
|
|
|
unsigned int freeSamples = m_maxSamples - (first ? m_samplesFirst: m_samplesSecond);
|
|
unsigned char* buf = (unsigned char*)data.data();
|
|
|
|
if (samples <= freeSamples) {
|
|
fillBuffer(first,buf,samples);
|
|
if (firstFull() && secondFull())
|
|
forwardBuffer();
|
|
return;
|
|
}
|
|
|
|
// Received more samples that free space in buffer
|
|
fillBuffer(first,buf,freeSamples);
|
|
forwardBuffer();
|
|
unsigned int consumed = freeSamples * m_sampleLen;
|
|
DataBlock rest(buf + consumed,data.length() - consumed);
|
|
consume(first,rest,tStamp);
|
|
}
|
|
|
|
// Forward the buffer if at least one channel is filled. Reset data
|
|
// If one channel is empty or incomplete, fill it with idle value
|
|
void SigSourceMux::forwardBuffer()
|
|
{
|
|
if (!(firstFull() || secondFull()))
|
|
return;
|
|
if (!(firstFull() && secondFull()))
|
|
fillBuffer(!firstFull());
|
|
m_samplesFirst = m_samplesSecond = 0;
|
|
Forward(m_buffer);
|
|
}
|
|
|
|
// Fill interlaced samples buffer with samples of received data
|
|
// If no data, fill the free space with idle value
|
|
void SigSourceMux::fillBuffer(bool first, unsigned char* data, unsigned int samples)
|
|
{
|
|
unsigned int* count = (first ? &m_samplesFirst : &m_samplesSecond);
|
|
unsigned char* buf = (unsigned char*)m_buffer.data() + *count * m_sampleLen * 2;
|
|
if (!first)
|
|
buf += m_sampleLen;
|
|
// Fill received data
|
|
if (data) {
|
|
if (samples > m_maxSamples - *count)
|
|
samples = m_maxSamples - *count;
|
|
*count += samples;
|
|
switch (m_sampleLen) {
|
|
case 1:
|
|
for (; samples; samples--, buf += 2)
|
|
*buf = *data++;
|
|
break;
|
|
case 2:
|
|
for (; samples; samples--, buf += 4) {
|
|
buf[0] = *data++;
|
|
buf[1] = *data++;
|
|
}
|
|
break;
|
|
case 0:
|
|
samples = 0;
|
|
default: {
|
|
unsigned int delta = 2 * m_sampleLen;
|
|
for (; samples; samples--, buf += delta, data += m_sampleLen)
|
|
::memcpy(buf,data,m_sampleLen);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
// Fill with idle value
|
|
samples = m_maxSamples - *count;
|
|
*count = m_maxSamples;
|
|
switch (m_sampleLen) {
|
|
case 1:
|
|
for (; samples; samples--, buf += 2)
|
|
*buf = m_idleValue;
|
|
break;
|
|
case 2:
|
|
for (; samples; samples--, buf += 4)
|
|
buf[0] = buf[1] = m_idleValue;
|
|
break;
|
|
case 0:
|
|
samples = 0;
|
|
default: {
|
|
unsigned int delta = 2 * m_sampleLen;
|
|
for (; samples; samples--, buf += delta, data += m_sampleLen)
|
|
::memset(buf,m_idleValue,m_sampleLen);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the source for the appropriate consumer
|
|
void SigSourceMux::removeSource(bool first)
|
|
{
|
|
DataSource*& src = first ? m_firstSrc : m_secondSrc;
|
|
if (src) {
|
|
src->clear();
|
|
TelEngine::destruct(src);
|
|
}
|
|
}
|
|
|
|
#undef MUX_CHAN
|
|
|
|
/**
|
|
* SigIsdnCallRecord
|
|
*/
|
|
SigIsdnCallRecord::SigIsdnCallRecord(SigIsdnMonitor* monitor, const char* id,
|
|
SignallingEvent* event)
|
|
: CallEndpoint(id),
|
|
m_lock(true,"SigIsdnCallRecord"),
|
|
m_netInit(false),
|
|
m_monitor(monitor),
|
|
m_call(0)
|
|
{
|
|
m_status = "startup";
|
|
// These parameters should be checked by the monitor
|
|
if (!(monitor && event && event->message() && event->call() && event->call()->ref())) {
|
|
m_reason = "Invalid initiating event";
|
|
return;
|
|
}
|
|
m_call = static_cast<ISDNQ931CallMonitor*>(event->call());
|
|
m_netInit = m_call->netInit();
|
|
SignallingMessage* msg = event->message();
|
|
m_caller = msg->params().getValue("caller");
|
|
m_called = msg->params().getValue("called");
|
|
SignallingCircuit* cic = static_cast<SignallingCircuit*>(m_call->getObject("SignallingCircuitCaller"));
|
|
if (!cic)
|
|
cic = static_cast<SignallingCircuit*>(m_call->getObject("SignallingCircuitCalled"));
|
|
if (cic)
|
|
m_address << monitor->name() << "/" << cic->code();
|
|
Debug(this->id(),DebugCall,"Initialized. Caller: '%s'. Called: '%s' [%p]",
|
|
m_caller.c_str(),m_called.c_str(),this);
|
|
}
|
|
|
|
SigIsdnCallRecord::~SigIsdnCallRecord()
|
|
{
|
|
close(0);
|
|
if (m_monitor)
|
|
m_monitor->removeCall(this);
|
|
Message* m = message("chan.hangup",false);
|
|
m->addParam("reason",m_reason);
|
|
Engine::enqueue(m);
|
|
Debug(id(),DebugCall,"Destroyed. Reason: '%s' [%p]",m_reason.safe(),this);
|
|
}
|
|
|
|
// Update call endpoint status. Send chan.startup, call.route, call.execute
|
|
// Create the multiplexer if missing
|
|
// Update sources for the multiplexer. Change them if circuit changed
|
|
// Start recording if the multiplexer has at least one source
|
|
bool SigIsdnCallRecord::update(SignallingEvent* event)
|
|
{
|
|
Lock lock(m_lock);
|
|
if (!(m_call && m_monitor && event && event->message()))
|
|
return false;
|
|
switch (event->type()) {
|
|
case SignallingEvent::NewCall:
|
|
Engine::enqueue(message("chan.startup"));
|
|
break;
|
|
case SignallingEvent::Ringing:
|
|
if (m_status == "ringing")
|
|
break;
|
|
m_status = "ringing";
|
|
Engine::enqueue(message("call.ringing"));
|
|
break;
|
|
case SignallingEvent::Answer:
|
|
m_status = "answered";
|
|
Engine::enqueue(message("call.answered"));
|
|
break;
|
|
case SignallingEvent::Accept:
|
|
break;
|
|
default: ;
|
|
}
|
|
SignallingMessage* msg = event->message();
|
|
bool chg = (msg->params().getValue("circuit-change") != 0);
|
|
String format = msg->params().getValue("format");
|
|
if (format)
|
|
format = "2*" + format;
|
|
SigSourceMux* source = static_cast<SigSourceMux*>(getSource());
|
|
m_reason = "";
|
|
while (!source) {
|
|
if (!format)
|
|
return true;
|
|
source = new SigSourceMux(format,m_monitor->idleValue(),m_monitor->chanBuffer());
|
|
if (!source->sampleLen()) {
|
|
TelEngine::destruct(source);
|
|
m_reason = "Unsupported audio format";
|
|
break;
|
|
}
|
|
setSource(source);
|
|
source->deref();
|
|
if (!getSource()) {
|
|
m_reason = "Failed to set data source";
|
|
break;
|
|
}
|
|
// Start recording
|
|
if (!callRouteAndExec(format)) {
|
|
if (m_reason)
|
|
m_reason = "local-" + m_reason;
|
|
break;
|
|
}
|
|
DDebug(id(),DebugCall,"Start recording. Format: %s [%p]",format.c_str(),this);
|
|
}
|
|
if (m_reason.null() && format && source->getFormat() != format)
|
|
m_reason = "Data format changed";
|
|
if (!m_reason.null())
|
|
return close(0);
|
|
if (chg) {
|
|
source->removeSource(true);
|
|
source->removeSource(false);
|
|
}
|
|
// Set sources if missing
|
|
bool first = true;
|
|
while (true) {
|
|
if (!source->hasSource(first)) {
|
|
SignallingCircuit* cic = static_cast<SignallingCircuit*>(m_call->getObject(
|
|
first ? "SignallingCircuitCaller" : "SignallingCircuitCalled"));
|
|
DataSource* src = cic ? static_cast<DataSource*>(cic->getObject("DataSource")) : 0;
|
|
if (src) {
|
|
source->attach(first,src);
|
|
DDebug(id(),DebugAll,"Data source on channel %c set to (%p) [%p]",
|
|
first ? '1' : '2',src,this);
|
|
}
|
|
}
|
|
if (!first)
|
|
break;
|
|
first = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Close
|
|
bool SigIsdnCallRecord::close(const char* reason)
|
|
{
|
|
Lock lock(m_lock);
|
|
m_status = "hangup";
|
|
if (!m_call)
|
|
return false;
|
|
if (m_reason.null())
|
|
m_reason = reason;
|
|
if (m_reason.null())
|
|
m_reason = Engine::exiting() ? "net-out-of-order" : "unknown";
|
|
Lock lock2(m_monitor);
|
|
m_call->userdata(0);
|
|
lock2.drop();
|
|
if (m_monitor)
|
|
m_monitor->q931()->terminateMonitor(m_call,m_reason);
|
|
TelEngine::destruct(m_call);
|
|
setSource();
|
|
Debug(id(),DebugCall,"Closed. Reason: '%s' [%p]",m_reason.c_str(),this);
|
|
return false;
|
|
}
|
|
|
|
bool SigIsdnCallRecord::disconnect(const char* reason)
|
|
{
|
|
close(reason);
|
|
XDebug(id(),DebugCall,"Disconnecting. Reason: '%s' [%p]",m_reason.safe(),this);
|
|
return CallEndpoint::disconnect(m_reason);
|
|
}
|
|
|
|
void SigIsdnCallRecord::disconnected(bool final, const char* reason)
|
|
{
|
|
DDebug(id(),DebugCall,"Disconnected. Final: %s. Reason: '%s' [%p]",
|
|
String::boolText(final),reason,this);
|
|
if (m_reason.null())
|
|
m_reason = reason;
|
|
CallEndpoint::disconnected(final,m_reason);
|
|
}
|
|
|
|
Message* SigIsdnCallRecord::message(const char* name, bool peers, bool userdata)
|
|
{
|
|
Message* m = new Message(name);
|
|
m->addParam("id",id());
|
|
m->addParam("status",m_status);
|
|
if (m_address)
|
|
m->addParam("address",m_address);
|
|
if (peers) {
|
|
m->addParam("caller",m_caller);
|
|
m->addParam("called",m_called);
|
|
}
|
|
if (userdata)
|
|
m->userData(this);
|
|
return m;
|
|
}
|
|
|
|
bool SigIsdnCallRecord::callRouteAndExec(const char* format)
|
|
{
|
|
Message* m = message("call.preroute");
|
|
bool ok = false;
|
|
while (true) {
|
|
if (Engine::dispatch(m) && (m->retValue() == "-" || m->retValue() == "error")) {
|
|
m_reason = m->getValue("reason",m->getValue("error","failure"));
|
|
break;
|
|
}
|
|
*m = "call.route";
|
|
m->addParam("type","record");
|
|
m->addParam("format",format);
|
|
m->addParam("callsource",m_netInit ? "net" : "cpe");
|
|
if (!Engine::dispatch(m) || m->retValue().null()) {
|
|
m_reason = "noroute";
|
|
break;
|
|
}
|
|
*m = "call.execute";
|
|
m->userData(this);
|
|
m->setParam("callto",m->retValue());
|
|
m->retValue().clear();
|
|
if (!Engine::dispatch(m)) {
|
|
m_reason = "noconn";
|
|
break;
|
|
}
|
|
ok = true;
|
|
break;
|
|
}
|
|
TelEngine::destruct(m);
|
|
return ok;
|
|
}
|
|
|
|
void SigIsdnCallRecord::evInfo(SignallingEvent* event)
|
|
{
|
|
if (!(event && event->message()))
|
|
return;
|
|
String tmp = event->message()->params().getValue("tone");
|
|
if (!tmp.null()) {
|
|
Message* m = message("chan.dtmf",false);
|
|
m->addParam("text",tmp);
|
|
bool fromCaller = event->message()->params().getBoolValue("fromcaller",false);
|
|
m->addParam("sender",fromCaller ? m_caller : m_called);
|
|
Engine::enqueue(m);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* SigTrunkThread
|
|
*/
|
|
SigTrunkThread::~SigTrunkThread()
|
|
{
|
|
if (m_trunk)
|
|
m_trunk->m_thread = 0;
|
|
DDebug(&plugin,DebugAll,"Worker destroyed for trunk '%s' [%p]",
|
|
m_trunk?m_trunk->name().c_str():"",this);
|
|
}
|
|
|
|
void SigTrunkThread::run()
|
|
{
|
|
if (!(m_trunk && m_trunk->controller()))
|
|
return;
|
|
DDebug(&plugin,DebugAll,"%s is running for trunk '%s' [%p]",
|
|
name(),m_trunk->name().c_str(),this);
|
|
SignallingEvent* event = 0;
|
|
while (true) {
|
|
if (!event)
|
|
Thread::usleep(m_sleepUsec,true);
|
|
else
|
|
Thread::check(true);
|
|
Time time;
|
|
event = m_trunk->controller()->getEvent(time);
|
|
if (event) {
|
|
XDebug(&plugin,DebugAll,"Trunk('%s'). Got event (%p,'%s',%p,%u)",
|
|
m_trunk->name().c_str(),event,event->name(),event->call(),
|
|
event->call()?event->call()->refcount():0);
|
|
m_trunk->handleEvent(event);
|
|
delete event;
|
|
}
|
|
// Check timeout if waiting to terminate
|
|
if (m_timeout && time.msec() > m_timeout) {
|
|
DDebug(&plugin,DebugInfo,
|
|
"SigTrunkThread::run(). Trunk '%s' timed out [%p]",
|
|
m_trunk->name().c_str(),this);
|
|
String name = m_trunk->name();
|
|
m_trunk->m_thread = 0;
|
|
m_trunk = 0;
|
|
plugin.clearTrunk(name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* IsupDecodeHandler
|
|
*/
|
|
// Init the ISUP component
|
|
IsupDecodeHandler::IsupDecodeHandler(bool decode)
|
|
: MessageHandler(decode ? "isup.decode" : "isup.encode",100)
|
|
{
|
|
NamedList params("");
|
|
String dname = plugin.prefix() + *this;
|
|
params.addParam("debugname",dname);
|
|
// Avoid some useless debug messages
|
|
params.addParam("pointcodetype",SS7PointCode::lookup(SS7PointCode::ITU));
|
|
params.addParam("remotepointcode","1-1-1");
|
|
m_isup = new SS7ISUP(params);
|
|
m_isup->debugChain(&plugin);
|
|
}
|
|
|
|
void IsupDecodeHandler::destruct()
|
|
{
|
|
SignallingEngine::destruct(m_isup);
|
|
}
|
|
|
|
bool IsupDecodeHandler::received(Message& msg)
|
|
{
|
|
NamedString* ns = msg.getParam("rawdata");
|
|
DataBlock* data = 0;
|
|
if (ns) {
|
|
NamedPointer* p = static_cast<NamedPointer*>(ns->getObject("NamedPointer"));
|
|
if (p && p->userObject("DataBlock"))
|
|
data = static_cast<DataBlock*>(p->userData());
|
|
}
|
|
if (!data || data->length() < 2) {
|
|
DDebug(&plugin,DebugNote,"%s. Invalid data len %u",c_str(),data->length());
|
|
return false;
|
|
}
|
|
|
|
String prefix = msg.getValue("message-prefix");
|
|
const unsigned char* paramPtr = (const unsigned char*)data->data();
|
|
SS7MsgISUP::Type msgType = (SS7MsgISUP::Type)(*paramPtr++);
|
|
DDebug(&plugin,DebugAll,"%s msg=%s type=%s basetype=%s [%p]",
|
|
msg.c_str(),SS7MsgISUP::lookup(msgType),
|
|
msg.getValue(prefix+"protocol-type"),
|
|
msg.getValue(prefix+"protocol-basetype"),this);
|
|
|
|
SS7PointCode::Type pcType = getPCType(msg,prefix);
|
|
if (pcType == SS7PointCode::Other)
|
|
return false;
|
|
|
|
if (m_isup->decodeMessage(msg,msgType,pcType,paramPtr,data->length()-1))
|
|
return true;
|
|
msg.setParam("error","Parser failure");
|
|
return false;
|
|
}
|
|
|
|
// Get point code type (protocol version) from message
|
|
SS7PointCode::Type IsupDecodeHandler::getPCType(Message& msg, const String& prefix)
|
|
{
|
|
String proto = msg.getValue(prefix+"protocol-type");
|
|
if (proto == "itu-t")
|
|
return SS7PointCode::ITU;
|
|
else if (proto == "ansi")
|
|
return SS7PointCode::ANSI;
|
|
// Check if protocol-basetype starts with known values
|
|
// Use the protocol-type if base is missing
|
|
const char* base = msg.getValue(prefix+"protocol-basetype");
|
|
if (base)
|
|
proto = base;
|
|
if (proto.startsWith("itu-t"))
|
|
return SS7PointCode::ITU;
|
|
else if (proto.startsWith("ansi"))
|
|
return SS7PointCode::ANSI;
|
|
msg.setParam("error","Unknown protocol-type");
|
|
return SS7PointCode::Other;
|
|
}
|
|
|
|
|
|
/**
|
|
* IsupEncodeHandler
|
|
*/
|
|
bool IsupEncodeHandler::received(Message& msg)
|
|
{
|
|
String prefix = msg.getValue("message-prefix");
|
|
|
|
DDebug(&plugin,DebugAll,"%s msg=%s type=%s basetype=%s [%p]",
|
|
msg.c_str(),msg.getValue(prefix+"message-type"),
|
|
msg.getValue(prefix+"protocol-type"),
|
|
msg.getValue(prefix+"protocol-basetype"),this);
|
|
|
|
SS7MsgISUP::Type msgType = (SS7MsgISUP::Type)SS7MsgISUP::lookup(msg.getValue(prefix+"message-type"));
|
|
if (msgType == SS7MsgISUP::Unknown) {
|
|
msg.setParam("error","Unknown message-type");
|
|
return false;
|
|
}
|
|
SS7PointCode::Type pcType = getPCType(msg,prefix);
|
|
if (pcType == SS7PointCode::Other)
|
|
return false;
|
|
|
|
DataBlock* data = new DataBlock;
|
|
if (m_isup->encodeMessage(*data,msgType,pcType,msg)) {
|
|
msg.addParam(new NamedPointer("rawdata",data,"isup"));
|
|
return true;
|
|
}
|
|
TelEngine::destruct(data);
|
|
msg.setParam("error","Encoder failure");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* SigNotifier
|
|
*/
|
|
void SigNotifier::notify(NamedList& notifs)
|
|
{
|
|
DDebug(&plugin,DebugInfo,"SigNotifier [%p] received a notify ",this);
|
|
Message* msg = new Message("module.update");
|
|
msg->addParam("module",plugin.name());
|
|
msg->copyParams(notifs);
|
|
Engine::enqueue(msg);
|
|
}
|
|
|
|
void SigNotifier::cleanup()
|
|
{
|
|
DDebug(&plugin,DebugInfo,"SigNotifier [%p] cleanup()",this);
|
|
SignallingEngine* engine = plugin.engine();
|
|
if (engine)
|
|
engine->removeNotifier(this);
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|