/** * wpcard.cpp * This file is part of the YATE Project http://YATE.null.ro * * Wanpipe PRI cards signalling and data driver * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2014 Null Team * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #ifdef _WINDOWS #error This module is not for Windows #else extern "C" { #define INVALID_HANDLE_VALUE (-1) #define __LINUX__ #ifdef HAVE_WANPIPE_API #include #include #include #include #ifdef NEW_WANPIPE_API #include #else #include #endif #else #include #include #include #include #include #include #endif #ifdef HAVE_WANPIPE_HWEC #include #ifdef HAVE_WANPIPE_HWEC_API #include #ifdef NEW_WANPIPE_API #define WAN_EC_CMD_DTMF_ENABLE WAN_EC_API_CMD_TONE_ENABLE #define WAN_EC_CMD_DTMF_DISABLE WAN_EC_API_CMD_TONE_DISABLE #else #define WAN_EC_CMD_DTMF_ENABLE WAN_EC_API_CMD_DTMF_ENABLE #define WAN_EC_CMD_DTMF_DISABLE WAN_EC_API_CMD_DTMF_DISABLE #endif #ifdef u_buffer_config #define HAVE_WANPIPE_HWEC_3310 #endif #endif #ifndef WANEC_DEV_DIR #warning Incompatible echo canceller API, upgrade or configure --without-wphwec #undef HAVE_WANPIPE_HWEC #endif // WANEC_DEV_DIR #endif // HAVE_WANPIPE_HWEC }; #include #include #include #include #include #include #ifdef NEW_WANPIPE_API #define WP_HEADER WAN_MAX_HDR_SZ #else #define WP_HEADER 16 #define WP_RD_ERROR 0 #define WP_RPT_REPEAT 0 // Repeat flag in header #define WP_RPT_LEN 1 // Repeated data length #define WP_RPT_DATA 2 // Repeated data offset in header #endif #define WP_RPT_MAXDATA 8 // Max repeated data length #define WP_ERR_FIFO (1 << WP_FIFO_ERROR_BIT) #define WP_ERR_CRC (1 << WP_CRC_ERROR_BIT) #define WP_ERR_ABORT (1 << WP_ABORT_ERROR_BIT) #ifdef NEW_WANPIPE_API #define WP_ERR_FRAME (1 << WP_FRAME_ERROR_BIT) #define WP_ERR_DMA (1 << WP_DMA_ERROR_BIT) #endif // by default ignore ABORT and OVERFLOW conditions unrelated to current packet #define WP_ERR_MASK (0xff & ~(WP_ERR_FIFO|WP_ERR_ABORT)) #define MAX_PACKET 1200 #define MAX_READ_ERRORS 250 // WpSpan::run(): Display read error message #define WPSOCKET_SELECT_TIMEOUT 125 // usec/sample used in WpSocket::select() to timeout #define WPSOCKET_SELECT_SAMPLES 32 // Maximum signaling samples to wait for using namespace TelEngine; namespace { // anonymous class Fifo; // Circular queue for data consumer class WpSocket; // I/O for D and B channels class WpInterface; // Wanpipe D-channel (SignallingInterface) class WpSigThread; // D-channel read data class WpSource; // Data source class WpConsumer; // Data consumer class WpCircuit; // Single Wanpipe B-channel (SignallingCircuit) class WpSpan; // Wanpipe span B-channel group class WpSpanThread; // B-channel group read/write data class WpModule; // The driver // Implements a circular queue for data consumer class Fifo : public Mutex { public: inline Fifo(unsigned int buflen) : Mutex(true,"WPCard::Fifo"), m_buffer(0,buflen), m_head(0), m_tail(1) {} inline void clear() { m_head = 0; m_tail = 1; } // Put a byte in fifo, overwrite last byte if full // Return false on buffer overrun bool put(unsigned char value); // Put data buffer in fifo, one byte at a time // Return the number of overwritten bytes unsigned int put(const unsigned char* buf, unsigned int length); // Get a byte from fifo, return last read if empty unsigned char get(); protected: unsigned char& operator[](unsigned int index) { return ((unsigned char*)m_buffer.data())[index]; } private: DataBlock m_buffer; unsigned int m_head; unsigned int m_tail; }; // I/O socket for WpInterface and WpSpan class WpSocket { public: // Link state enumeration enum LinkStatus { Connected, // Link up Disconnected, // Link down Connecting, // Link is connecting }; WpSocket(DebugEnabler* dbg, const char* card = 0, const char* device = 0); inline ~WpSocket() { close(); } inline LinkStatus status() { return m_status; } inline bool valid() const { return m_socket.valid(); } inline const String& card() const { return m_card; } inline const String& device() const { return m_device; } inline void card(const char* name) { m_card = name; } inline void device(const char* name) { m_device = name; } // Get/Set echo canceller availability inline bool echoCanAvail() const { return m_echoCanAvail; } void echoCanAvail(bool val); // Set echo canceller and tone detection if available bool echoCancel(bool enable, unsigned long chanmap); // Set tone detection if available bool dtmfDetect(bool enable); inline bool canRead() const { return m_canRead; } inline bool canWrite() const { return m_canWrite; } inline bool event() const { return m_event; } // Open socket. Return false on failure bool open(bool blocking); // Close socket void close(); // Read data. Return -1 on failure int recv(void* buffer, int len, int flags = 0); // Send data. Return -1 on failure int send(const void* buffer, int len, int flags = 0); // Check socket. Set flags to the appropriate values on success // Return false on failure bool select(unsigned int multiplier, bool checkWrite = false); // Update the state of the link and return true if changed bool updateLinkStatus(); protected: inline void showError(const char* action, const char* info = 0, int level = DebugWarn) { Debug(m_dbg,level,"WpSocket(%s/%s). %s failed%s. %d: %s [%p]", m_card.c_str(),m_device.c_str(),action,c_safe(info), m_socket.error(),::strerror(m_socket.error()),this); } private: DebugEnabler* m_dbg; // Debug enabler owning this socket LinkStatus m_status; // The state of the link Socket m_socket; // The socket String m_card; // Card name used to open socket String m_device; // Device name used to open socket bool m_echoCanAvail; // Echo canceller is available or not bool m_canRead; // Set by select(). Can read from socket bool m_canWrite; // Set by select(). Can write to socket bool m_event; // Set by select(). An event occurred bool m_readError; // Flag used to print read errors bool m_writeError; // Flag used to print write errors bool m_selectError; // Flag used to print select errors }; // Wanpipe D-channel class WpInterface : public SignallingInterface { friend class WpSigThread; public: // Create an instance of WpInterface or WpSpan static SignallingComponent* create(const String& type, NamedList& name); WpInterface(const NamedList& params); virtual ~WpInterface(); // Initialize interface. Return false on failure bool init(const NamedList& config, NamedList& params); // Remove links. Dispose memory virtual void destruct() { cleanup(true); } // Send signalling packet virtual bool transmitPacket(const DataBlock& packet, bool repeat, PacketType type); // Interface control virtual bool control(Operation oper, NamedList* params); // Update link status. Notify the receiver if state changed bool updateStatus(); protected: virtual void timerTick(const Time& when); // Read data from socket bool receiveAttempt(); private: inline void cleanup(bool release) { control(Disable,0); attach(0); if (release) RefObject::destruct(); } WpSocket m_socket; WpSigThread* m_thread; // Thread used to read data from socket bool m_readOnly; // Readonly interface int m_notify; // Upper layer notification on received data (0: success. 1: not notified. 2: notified) int m_overRead; // Header extension unsigned char m_errorMask; // Error mask to filter received errors unsigned char m_lastError; // Last error seen bool m_sendReadOnly; // Print send attempt on readonly interface error SignallingTimer m_timerRxUnder; // RX underrun notification // Repeat packet bool m_repeatCapable; // HW repeat available Mutex m_repeatMutex; // Lock repeat buffer DataBlock m_repeatPacket; // Packet to repeat bool m_down; }; // Read signalling data for WpInterface class WpSigThread : public Thread { friend class WpInterface; public: inline WpSigThread(WpInterface* iface, Priority prio = Normal) : Thread("Wp Interface",prio), m_interface(iface) {} virtual ~WpSigThread(); virtual void run(); private: WpInterface* m_interface; }; // Wanpipe data source class WpSource : public DataSource { friend class WpCircuit; public: WpSource(WpCircuit* owner, const char* format, unsigned int bufsize); virtual ~WpSource(); inline void changeFormat(const char* format) { m_format = format; } // Add a byte to the source buffer void put(unsigned char c); protected: WpCircuit* m_owner; // B-channel owning this source DataBlock m_buffer; // Data buffer unsigned int m_bufpos; // First free byte's index unsigned int m_total; }; // Wanpipe data consumer class WpConsumer : public DataConsumer, public Fifo { friend class WpCircuit; public: WpConsumer(WpCircuit* owner, const char* format, unsigned int bufsize); virtual ~WpConsumer(); inline void changeFormat(const char* format) { m_format = format; } virtual unsigned long Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags); protected: WpCircuit* m_owner; // B-channel owning this consumer u_int32_t m_errorCount; // The number of times the fifo was full u_int32_t m_errorBytes; // The number of overwritten bytes in one session unsigned int m_total; }; // Single Wanpipe B-channel class WpCircuit : public SignallingCircuit, public Mutex { public: WpCircuit(unsigned int code, SignallingCircuitGroup* group, WpSpan* data, unsigned int buflen, unsigned int channel); // Get circuit channel number inside its span unsigned int channel() const { return m_channel; } virtual ~WpCircuit(); virtual bool status(Status newStat, bool sync = false); virtual bool updateFormat(const char* format, int direction); virtual bool setParam(const String& param, const String& value); virtual void* getObject(const String& name) const; inline bool validSource() { return 0 != m_sourceValid; } inline bool validConsumer() { return 0 != m_consumerValid; } inline WpSource* source() { return m_source; } inline WpConsumer* consumer() { return m_consumer; } // Enqueue received events bool enqueueEvent(SignallingCircuitEvent* e); private: unsigned int m_channel; // Channel number inside span WpSource* m_sourceValid; // Circuit's source if reserved, otherwise: 0 WpConsumer* m_consumerValid; // Circuit's consumer if reserved, otherwise: 0 WpSource* m_source; WpConsumer* m_consumer; String m_specialMode; }; // Wanpipe B-channel group class WpSpan : public SignallingCircuitSpan { friend class WpSpanThread; public: WpSpan(const NamedList& params, const char* debugname); virtual ~WpSpan(); // Initialize data channel span. Return false on failure bool init(const NamedList& config, const NamedList& defaults, NamedList& params); // Swap data if necessary inline unsigned char swap(unsigned char c) { return m_swap ? s_bitswap[c] : c; } // Data processor // Read events and data from socket. Send data when succesfully read // Received data is splitted for each circuit // Sent data from each circuit is merged into one data block void run(); // Find a circuit by channel WpCircuit* find(unsigned int channel); protected: // Create circuits (all or nothing) // delta: number to add to each circuit code // cicList: Circuits to create bool createCircuits(unsigned int delta, const String& cicList); // Clear circuit vector safely void clearCircuits(); // Check for received event (including in-band events) bool readEvent(); // Read data from socket. Check for errors or in-band events // Return -1 on error int readData(); // Decode received event bool decodeEvent(); // Update link status. Enqueue an event in each circuit's queue on status change bool updateStatus(); // Swapped bits table static unsigned char s_bitswap[256]; private: WpSocket m_socket; WpSpanThread* m_thread; bool m_canSend; // Can send data (not a readonly span) bool m_swap; // Swap bits flag unsigned long m_chanMap; // Channel map used to set tone detector bool m_echoCancel; // Enable/disable echo canceller bool m_dtmfDetect; // Enable/disable tone detector unsigned int m_chans; // Total number of circuits for this span unsigned int m_count; // Circuit count unsigned int m_first; // First circuit code unsigned int m_samples; // Sample count unsigned int m_noData; // Value to send on idle channels unsigned int m_buflen; // Buffer length for sources/consumers // Used for data processing WpCircuit** m_circuits; // The circuits belonging to this span unsigned int m_readErrors; // Count data read errors unsigned char* m_buffer; // I/O data buffer unsigned int m_bufferLen; // I/O data buffer length }; // B-channel group read/write data class WpSpanThread : public Thread { friend class WpSpan; public: inline WpSpanThread(WpSpan* data, Priority prio = Normal) : Thread("Wp Span",prio), m_data(data) {} virtual ~WpSpanThread(); virtual void run(); private: WpSpan* m_data; }; // The module class WpModule : public Module { public: WpModule(); ~WpModule(); virtual void initialize(); private: bool m_init; }; #define MAKE_NAME(x) { #x, WpSocket::x } static TokenDict s_linkStatus[] = { MAKE_NAME(Connected), MAKE_NAME(Disconnected), MAKE_NAME(Connecting), {0,0} }; #undef MAKE_NAME YSIGFACTORY2(WpInterface); static Mutex s_ifaceNotify(true,"WPCard::notify"); // WpInterface: lock recv data notification counter static bool s_repeatCapable = true; // Global repeat packet capability static WpModule driver; static void sendModuleUpdate(bool& notifStat, int status, const String& device) { Message* msg = new Message("module.update"); msg->addParam("module",driver.name()); msg->addParam("interface",device); if(notifStat && status == SignallingInterface::LinkUp) { msg->addParam("notify","interfaceUp"); notifStat = false; Engine::enqueue(msg); return; } if (!notifStat && status == SignallingInterface::LinkDown) { msg->addParam("notify","interfaceDown"); notifStat = true; Engine::enqueue(msg); return; } TelEngine::destruct(msg); } /** * Fifo */ bool Fifo::put(unsigned char value) { (*this)[m_tail] = value; bool full = (m_head == m_tail); m_tail++; if (m_tail >= m_buffer.length()) m_tail = 0; if (full) m_head = m_tail; return full; } unsigned int Fifo::put(const unsigned char* buf, unsigned int length) { lock(); unsigned int errors = 0; while (length--) if (put(*buf++)) errors++; unlock(); return errors; } unsigned char Fifo::get() { unsigned char tmp = (*this)[m_head]; unsigned int nh = m_head + 1; if (nh >= m_buffer.length()) nh = 0; if (nh != m_tail) m_head = nh; return tmp; } /** * WpSocket */ WpSocket::WpSocket(DebugEnabler* dbg, const char* card, const char* device) : m_dbg(dbg), m_status(Disconnected), m_card(card), m_device(device), #ifdef HAVE_WANPIPE_HWEC m_echoCanAvail(true), #else m_echoCanAvail(false), #endif m_canRead(false), m_canWrite(false), m_event(false), m_readError(false), m_writeError(false), m_selectError(false) { } // Set echo canceller availability void WpSocket::echoCanAvail(bool val) { #ifdef HAVE_WANPIPE_HWEC m_echoCanAvail = val; #endif } // Set echo canceller and tone detection if available bool WpSocket::echoCancel(bool enable, unsigned long chanmap) { if (!m_echoCanAvail) { Debug(m_dbg,DebugNote, "WpSocket(%s/%s). Echo canceller is unavailable. Can't %s it [%p]", m_card.c_str(),m_device.c_str(),enable?"enable":"disable",this); return false; } bool ok = false; #ifdef HAVE_WANPIPE_HWEC int fd = -1; String dev; dev << WANEC_DEV_DIR << WANEC_DEV_NAME; for (int i = 0; i < 5; i++) { fd = ::open(dev,O_RDONLY); if (fd >= 0) break; Thread::msleep(200); } const char* operation = 0; if (fd >= 0) { wan_ec_api_t ecapi; ::memset(&ecapi,0,sizeof(ecapi)); #ifdef HAVE_WANPIPE_HWEC_3310 ecapi.fe_chan_map = chanmap; #else ecapi.channel_map = chanmap; #endif if (enable) { ecapi.cmd = WAN_EC_CMD_DTMF_ENABLE; ecapi.verbose = WAN_EC_VERBOSE_EXTRA1; // event on start of tone, before echo canceller #ifdef NEW_WANPIPE_API ecapi.u_tone_config.type = WAN_EC_TONE_PRESENT; ecapi.u_tone_config.port_map = WAN_EC_CHANNEL_PORT_SOUT; #else ecapi.u_dtmf_config.type = WAN_EC_TONE_PRESENT; #ifdef HAVE_WANPIPE_HWEC_3310 ecapi.u_dtmf_config.port_map = WAN_EC_CHANNEL_PORT_SOUT; #else ecapi.u_dtmf_config.port = WAN_EC_CHANNEL_PORT_SOUT; #endif #endif } else ecapi.cmd = WAN_EC_CMD_DTMF_DISABLE; ecapi.err = WAN_EC_API_RC_OK; if (::ioctl(fd,ecapi.cmd,ecapi)) operation = "IOCTL"; } else operation = "Open"; ok = (0 == operation); if (!ok && m_dbg && m_dbg->debugAt(DebugNote)) Debug(m_dbg,DebugNote, "WpSocket(%s/%s). %s failed dev=%s. Can't %s echo canceller. %d: %s [%p]", m_card.c_str(),m_device.c_str(),operation,dev.c_str(),(enable?"enable":"disable"), errno,::strerror(errno),this); ::close(fd); #endif #ifdef DEBUG if (ok && m_dbg && m_dbg->debugAt(DebugInfo)) { String map('0',32); for (unsigned int i = 0; i < 32; i++) if (chanmap & (1 << i)) ((char*)(map.c_str()))[i] = '1'; DDebug(m_dbg,DebugInfo, "WpSocket(%s/%s). %sabled echo canceller chanmap=%s [%p]", m_card.c_str(),m_device.c_str(),enable?"En":"Dis",map.c_str(),this); } #endif return ok; } // Set tone detection if available bool WpSocket::dtmfDetect(bool enable) { bool ok = false; // TODO: new API has different event handling style, may need total rewrite #if defined(HAVE_WANPIPE_HWEC) && !defined(NEW_WANPIPE_API) api_tx_hdr_t a; ::memset(&a,0,sizeof(api_tx_hdr_t)); a.wp_api_tx_hdr_event_type = WP_API_EVENT_DTMF; a.wp_api_tx_hdr_event_mode = enable ? WP_API_EVENT_ENABLE : WP_API_EVENT_DISABLE; ok = (::ioctl(m_socket.handle(),SIOC_WANPIPE_API,&a) >= 0); #else // pretend enabling fails, disabling succeeds if (!enable) ok = true; else errno = ENOSYS; #endif if (ok) DDebug(m_dbg,DebugInfo, "WpSocket(%s/%s). %sabled tone detector [%p]", m_card.c_str(),m_device.c_str(),enable?"En":"Dis",this); else showError("dtmfDetect"); return ok; } // Open socket bool WpSocket::open(bool blocking) { DDebug(m_dbg,DebugAll, "WpSocket::open(). Card: '%s'. Device: '%s'. Blocking: %s [%p]", m_card.c_str(),m_device.c_str(),String::boolText(blocking),this); if (!m_socket.create(AF_WANPIPE,SOCK_RAW)) { showError("Create"); return false; } // Bind to the card/interface struct wan_sockaddr_ll sa; memset(&sa,0,sizeof(struct wan_sockaddr_ll)); ::strncpy((char*)sa.sll_card,m_card.safe(),sizeof(sa.sll_card)); ::strncpy((char*)sa.sll_device,m_device.safe(),sizeof(sa.sll_device)); sa.sll_protocol = htons(PVC_PROT); sa.sll_family = AF_WANPIPE; if (!m_socket.bind((struct sockaddr *)&sa, sizeof(sa))) { showError("Bind"); close(); return false; } if (!m_socket.setBlocking(blocking)) { showError("Set blocking"); close(); return false; } return true; } // Close socket void WpSocket::close() { if (!m_socket.valid()) return; DDebug(m_dbg,DebugAll,"WpSocket::close(). Card: '%s'. Device: '%s' [%p]", m_card.c_str(),m_device.c_str(),this); m_socket.setLinger(-1); m_socket.terminate(); } // Read data from socket int WpSocket::recv(void* buffer, int len, int flags) { int r = m_socket.recv(buffer,len,flags); if (r != Socket::socketError()) { m_readError = false; return r; } if (!(m_socket.canRetry() || m_readError)) { showError("Read"); m_readError = true; } return -1; } // Write data to socket int WpSocket::send(const void* buffer, int len, int flags) { int w = m_socket.send(buffer,len,flags); if (w != Socket::socketError() && w == len) { m_writeError = false; return w; } if (m_writeError) return -1; if (w == Socket::socketError()) w = 0; String info; info << " (Sent " << w << " instead of " << len << ')'; showError("Send",info); m_writeError = true; return -1; } // Check events and socket availability bool WpSocket::select(unsigned int multiplier, bool checkWrite) { m_canRead = m_canWrite = m_event = false; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = multiplier * WPSOCKET_SELECT_TIMEOUT; if (m_socket.select(&m_canRead,(checkWrite ? &m_canWrite : 0),&m_event,&tv)) { m_selectError = false; return true; } if (m_selectError) return false; showError("Select"); m_selectError = true; return false; } // Update the state of the link and return true if changed bool WpSocket::updateLinkStatus() { LinkStatus old = m_status; if (valid()) switch (::ioctl(m_socket.handle(),SIOC_WANPIPE_SOCK_STATE,0)) { case 0: m_status = Connected; break; case 1: m_status = Disconnected; break; default: m_status = Connecting; } else m_status = Disconnected; return m_status != old; } /** * WpInterface */ // Create WpInterface or WpSpan SignallingComponent* WpInterface::create(const String& type, NamedList& name) { const String* module = name.getParam("module"); if (module && *module != "wpcard") { DDebug(&driver,DebugWarn,"We aren't the target for creating %s",type.c_str()); return 0; } bool interface = false; if (type == "SignallingInterface") interface = true; else if (type == "SignallingCircuitSpan") ; else return 0; TempObjectCounter cnt(driver.objectsCounter()); Configuration cfg(Engine::configFile("wpcard")); const char* sectName = name.getValue((interface ? "sig" : "voice"),name.getValue("basename",name)); NamedList* config = cfg.getSection(sectName); if (!name.getBoolValue(YSTRING("local-config"),false)) config = &name; else if (!config) { DDebug(&driver,DebugConf,"No section '%s' in configuration",c_safe(sectName)); return 0; } else name.copyParams(*config); #ifdef DEBUG if (driver.debugAt(DebugAll)) { String tmp; config->dump(tmp,"\r\n ",'\'',true); Debug(&driver,DebugAll,"WpInterface::create %s%s", (interface ? "interface" : "span"),tmp.c_str()); } #endif if (interface) { WpInterface* iface = new WpInterface(name); if (iface->init(*config,(NamedList&)name)) return iface; TelEngine::destruct(iface); return 0; } NamedList* general = cfg.getSection("general"); NamedList dummy("general"); WpSpan* data = new WpSpan(name,sectName); if (data->init(*config,general?*general:dummy,(NamedList&)name)) return data; TelEngine::destruct(data); return 0; } WpInterface::WpInterface(const NamedList& params) : SignallingComponent(params,¶ms,"tdm"), m_socket(this), m_thread(0), m_readOnly(false), m_notify(0), m_overRead(0), m_lastError(0), m_sendReadOnly(false), m_timerRxUnder(0), m_repeatCapable(s_repeatCapable), m_repeatMutex(true,"WpInterface::repeat") { DDebug(this,DebugAll,"WpInterface::WpInterface() [%p]",this); } WpInterface::~WpInterface() { cleanup(false); DDebug(this,DebugAll,"WpInterface::~WpInterface() [%p]",this); } bool WpInterface::init(const NamedList& config, NamedList& params) { // Set socket card / device m_socket.card(!params.null() ? params : config); const char* sig = params.getValue("siggroup",config.getValue("siggroup")); if (!(sig && *sig)) { Debug(this,DebugWarn, "Missing or invalid siggroup='%s' in configuration [%p]", c_safe(sig),this); return false; } m_socket.device(sig); m_readOnly = params.getBoolValue("readonly",config.getBoolValue("readonly",false)); int i = params.getIntValue("errormask",config.getIntValue("errormask",WP_ERR_MASK)); m_errorMask = ((i >= 0 && i < 256) ? i : WP_ERR_MASK); int rx = params.getIntValue("rxunderrun"); if (rx > 0) m_timerRxUnder.interval(rx); m_repeatCapable = params.getBoolValue("hwrepeatcapable", config.getBoolValue("hwrepeatcapable",m_repeatCapable)); if (debugAt(DebugInfo)) { String s; s << "driver=" << driver.debugName(); s << " section=" << config.c_str(); s << " type=" << config.getValue("type","T1"); s << " card=" << m_socket.card(); s << " device=" << m_socket.device(); s << " errormask=" << (unsigned int)m_errorMask; s << " readonly=" << String::boolText(m_readOnly); s << " rxunderruninterval=" << (unsigned int)m_timerRxUnder.interval() << "ms"; s << " hwrepeatcapable=" << String::boolText(m_repeatCapable); Debug(this,DebugInfo,"D-channel: %s [%p]",s.c_str(),this); } m_down = false; return true; } // Send signalling packet bool WpInterface::transmitPacket(const DataBlock& packet, bool repeat, PacketType type) { if (m_readOnly) { if (!m_sendReadOnly) Debug(this,DebugWarn,"Attempt to send data on read only interface"); m_sendReadOnly = true; return false; } if (!m_socket.valid()) return false; #ifdef XDEBUG if (debugAt(DebugAll)) { String str; str.hexify(packet.data(),packet.length(),' '); Debug(this,DebugAll,"Sending %u bytes: %s",packet.length(),str.c_str()); } #endif m_repeatMutex.lock(); m_repeatPacket.clear(); m_repeatMutex.unlock(); DataBlock data(0,WP_HEADER); data += packet; // using a while is a hack so we can break out of it while (repeat) { #ifdef wp_api_tx_hdr_hdlc_rpt_data if (m_repeatCapable) { if (packet.length() <= WP_RPT_MAXDATA) { unsigned char* hdr = (unsigned char*)data.data(); #ifdef NEW_WANPIPE_API ((wp_api_hdr_t*)hdr)->wp_api_tx_hdr_hdlc_rpt_repeat = 1; ((wp_api_hdr_t*)hdr)->wp_api_tx_hdr_hdlc_rpt_len = packet.length(); ::memcpy(((wp_api_hdr_t*)hdr)->wp_api_tx_hdr_hdlc_rpt_data, packet.data(),packet.length()); #else hdr[WP_RPT_REPEAT] = 1; hdr[WP_RPT_LEN] = packet.length(); ::memcpy(hdr+WP_RPT_DATA,packet.data(),packet.length()); #endif } else Debug(this,DebugWarn,"Can't repeat packet (type=%u) with length=%u", type,packet.length()); break; } #endif m_repeatMutex.lock(); m_repeatPacket = data; m_repeatMutex.unlock(); break; } return -1 != m_socket.send(data.data(),data.length(),0); } // Receive signalling packet // Send repeated packet if needed bool WpInterface::receiveAttempt() { if (!m_socket.valid()) return false; if (!m_socket.select(WPSOCKET_SELECT_SAMPLES,(0 != m_repeatPacket.length()))) return false; m_repeatMutex.lock(); if (m_socket.canWrite() && m_repeatPacket.length()) m_socket.send(m_repeatPacket.data(),m_repeatPacket.length(),0); m_repeatMutex.unlock(); updateStatus(); if (!m_socket.canRead()) return false; unsigned char buf[WP_HEADER + MAX_PACKET]; int r = m_socket.recv(buf,sizeof(buf),MSG_NOSIGNAL); if (r == -1) return false; if (r > (WP_HEADER + m_overRead)) { XDebug(this,DebugAll,"Received %d bytes packet. Header length is %u [%p]", r,WP_HEADER + m_overRead,this); r -= (WP_HEADER + m_overRead); #ifdef NEW_WANPIPE_API unsigned char err = ((wp_api_hdr_t*)buf)->wp_api_rx_hdr_error_map; #else unsigned char err = buf[WP_RD_ERROR]; #endif if (err != m_lastError) { m_lastError = err; if (err) { String errText; if (err & WP_ERR_CRC) errText.append("CRC"); if (err & WP_ERR_FIFO) errText.append("RxOver"," "); if (err & WP_ERR_ABORT) errText.append("Align"," "); if (errText) errText = " (" + errText + ")"; Debug(this,DebugWarn,"Packet got error: %u%s [%p]", err,errText.safe(),this); } } err &= m_errorMask; if (err) { if (err & WP_ERR_FIFO) notify(RxOverflow); if (err & WP_ERR_CRC) notify(CksumError); if (err & WP_ERR_ABORT) notify(AlignError); return true; } s_ifaceNotify.lock(); m_notify = 0; s_ifaceNotify.unlock(); #ifdef XDEBUG if (debugAt(DebugAll)) { String str; str.hexify(buf+WP_HEADER,r,' '); Debug(this,DebugAll,"Received %d bytes: %s",r,str.c_str()); } #endif DataBlock data(buf+WP_HEADER,r,false); receivedPacket(data); data.clear(false); } return true; } // Interface control // Enable: Open thread and create thread if not already created // Disable: Cancel thread. Close socket bool WpInterface::control(Operation oper, NamedList* params) { DDebug(this,DebugAll,"Control with oper=%u [%p]",oper,this); switch (oper) { case Enable: case Disable: break; case EnableTx: case DisableTx: if (m_readOnly == (oper == DisableTx)) return TelEngine::controlReturn(params,true); m_readOnly = (oper == DisableTx); m_sendReadOnly = false; Debug(this,DebugInfo,"Tx is %sabled [%p]",m_readOnly?"dis":"en",this); return TelEngine::controlReturn(params,true); case Query: return TelEngine::controlReturn(params,m_socket.valid() && m_thread && m_thread->running()); default: return SignallingInterface::control(oper,params); } if (oper == Enable) { bool ok = false; if (m_socket.valid() || m_socket.open(true)) { if (!m_thread) m_thread = new WpSigThread(this); if (m_thread->running()) ok = true; else ok = m_thread->startup(); } if (ok) { DDebug(this,DebugAll,"Enabled [%p]",this); m_timerRxUnder.start(); } else { Debug(this,DebugWarn,"Enable failed [%p]",this); control(Disable,0); } return TelEngine::controlReturn(params,ok); } // oper is Disable m_timerRxUnder.stop(); if (m_thread) { m_thread->cancel(); while (m_thread) Thread::yield(); } m_socket.close(); DDebug(this,DebugAll,"Disabled [%p]",this); return TelEngine::controlReturn(params,true); } // Update link status. Notify the receiver if state changed bool WpInterface::updateStatus() { if (!m_socket.updateLinkStatus()) return false; Debug(this,DebugNote,"Link status changed to %s [%p]", lookup(m_socket.status(),s_linkStatus),this); if (m_socket.status() == WpSocket::Connected) { notify(LinkUp); sendModuleUpdate(m_down,LinkUp,m_socket.card()); } else { notify(LinkDown); sendModuleUpdate(m_down,LinkDown,m_socket.card()); } return true; } void WpInterface::timerTick(const Time& when) { if (!m_timerRxUnder.timeout(when.msec())) return; s_ifaceNotify.lock(); if (m_notify) { if (m_notify == 1) { DDebug(this,DebugMild,"RX idle for " FMT64 "ms. Notifying receiver [%p]", m_timerRxUnder.interval(),this); notify(RxUnderrun); m_notify = 2; } } else m_notify = 1; s_ifaceNotify.unlock(); m_timerRxUnder.start(when.msec()); } /** * WpSigThread */ WpSigThread::~WpSigThread() { if (m_interface) { Debug(m_interface,DebugAll,"Worker thread stopped [%p]",this); m_interface->m_thread = 0; } else Debug(DebugAll,"WpSigThread::~WpSigThread() [%p]",this); } void WpSigThread::run() { if (!m_interface) { Debug(DebugWarn,"WpSigThread::run(). No client object [%p]",this); return; } Debug(m_interface,DebugAll,"Worker thread started [%p]",this); m_interface->updateStatus(); for (;;) { Thread::yield(true); while (m_interface && m_interface->receiveAttempt()) Thread::check(true); } } /** * WpSource */ WpSource::WpSource(WpCircuit* owner, const char* format, unsigned int bufsize) : DataSource(format), m_owner(owner), m_buffer(0,bufsize), m_bufpos(0), m_total(0) { XDebug(DebugAll,"WpSource::WpSource(%p,%u,'%s') [%p]", owner,bufsize,format,this); } WpSource::~WpSource() { XDebug(DebugAll,"WpSource::~WpSource() [%p]",this); } // Put a byte in buffer. Forward data when full void WpSource::put(unsigned char c) { ((char*)m_buffer.data())[m_bufpos] = c; if (++m_bufpos == m_buffer.length()) { m_bufpos = 0; Forward(m_buffer); m_total += m_buffer.length(); } } /** * WpConsumer */ WpConsumer::WpConsumer(WpCircuit* owner, const char* format, unsigned int bufsize) : DataConsumer(format), Fifo(2 * bufsize), m_owner(owner), m_errorCount(0), m_errorBytes(0), m_total(0) { XDebug(DebugAll,"WpConsumer::WpConsumer(%p,%u,'%s') [%p]", owner,bufsize,format,this); } WpConsumer::~WpConsumer() { XDebug(DebugAll,"WpConsumer::~WpConsumer. [%p]",this); } // Put data in fifo buffer unsigned long WpConsumer::Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags) { unsigned int err = put((const unsigned char*)data.data(),data.length()); if (err) { m_errorCount++; m_errorBytes += err; } m_total += data.length(); return invalidStamp(); } /** * WpCircuit */ WpCircuit::WpCircuit(unsigned int code, SignallingCircuitGroup* group, WpSpan* data, unsigned int buflen, unsigned int channel) : SignallingCircuit(TDM,code,Idle,group,data), Mutex(true,"WpCircuit"), m_channel(channel), m_sourceValid(0), m_consumerValid(0), m_source(0), m_consumer(0) { if (buflen) { m_source = new WpSource(this,"alaw",buflen); m_consumer = new WpConsumer(this,"alaw",buflen); XDebug(group,DebugAll,"WpCircuit(%u). Source (%p). Consumer (%p) [%p]", code,m_source,m_consumer,this); } else Debug(group,DebugNote, "WpCircuit(%u). No source and consumer. Buffer length is 0 [%p]", code,this); } WpCircuit::~WpCircuit() { XDebug(group(),DebugAll,"WpCircuit::~WpCircuit(%u) [%p]",code(),this); Lock lock(this); status(Missing); TelEngine::destruct(m_source); TelEngine::destruct(m_consumer); } // Change circuit status. Clear events on succesfully changes status // Connected: Set valid source and consumer // Otherwise: Invalidate and reset source and consumer bool WpCircuit::status(Status newStat, bool sync) { Lock lock(this); if (SignallingCircuit::status() == newStat) return true; TempObjectCounter cnt(driver.objectsCounter()); // Allow status change for the following values switch (newStat) { case Missing: case Disabled: case Idle: case Reserved: m_specialMode.clear(); // fall through case Special: case Connected: break; default: ; Debug(group(),DebugNote, "WpCircuit(%u). Can't change status to unhandled value %u [%p]", code(),newStat,this); return false; } if (SignallingCircuit::status() == Missing) { Debug(group(),DebugNote, "WpCircuit(%u). Can't change status to '%s'. Circuit is missing [%p]", code(),lookupStatus(newStat),this); return false; } Status oldStat = SignallingCircuit::status(); // Change status if (!SignallingCircuit::status(newStat,sync)) return false; // Enable/disable data transfer clearEvents(); bool enableData = false; if (SignallingCircuit::status() >= Special) enableData = true; // Don't put this message for final states if (!Engine::exiting()) DDebug(group(),DebugAll,"WpCircuit(%u). Changed status to '%s' [%p]", code(),lookupStatus(newStat),this); if (enableData) { m_sourceValid = m_source; m_consumerValid = m_consumer; if (newStat == Special) { Message m("circuit.special"); m.userData(this); if (group()) m.addParam("group",group()->toString()); if (span()) m.addParam("span",span()->toString()); if (m_specialMode) m.addParam("mode",m_specialMode); return Engine::dispatch(m); } return true; } // Disable data if not already disabled if (m_consumerValid) { m_consumerValid = 0; if (oldStat == Connected) { XDebug(group(),DebugAll,"WpCircuit(%u). Consumer transferred %u byte(s) [%p]", code(),m_consumer->m_total,this); if (m_consumer->m_errorCount) DDebug(group(),DebugMild,"WpCircuit(%u). Consumer errors: %u. Lost: %u/%u [%p]", code(),m_consumer->m_errorCount,m_consumer->m_errorBytes, m_consumer->m_total,this); } m_consumer->clear(); m_consumer->m_errorCount = m_consumer->m_errorBytes = 0; m_consumer->m_total = 0; } if (m_sourceValid) { m_sourceValid = 0; if (oldStat == Connected) XDebug(group(),DebugAll,"WpCircuit(%u). Source transferred %u byte(s) [%p]", code(),m_source->m_total,this); m_source->clear(); m_source->m_total = 0; } return true; } // Update source/consumer data format bool WpCircuit::updateFormat(const char* format, int direction) { if (!(format && *format)) return false; TempObjectCounter cnt(driver.objectsCounter()); bool consumerChanged = true; bool sourceChanged = true; Lock lock(this); if (direction == -1 || direction == 0) { if (m_consumer && m_consumer->getFormat() != format) { m_consumer->changeFormat(format); DDebug(group(),DebugAll,"WpCircuit(%u). Consumer format set to '%s' [%p]", code(),format,this); } else consumerChanged = false; } if (direction == 1 || direction == 0) { if (m_source && m_source->getFormat() != format) { m_source->changeFormat(format); DDebug(group(),DebugAll,"WpCircuit(%u). Source format set to '%s' [%p]", code(),format,this); } else sourceChanged = false; } return consumerChanged && sourceChanged; } bool WpCircuit::setParam(const String& param, const String& value) { TempObjectCounter cnt(driver.objectsCounter()); if (param == "special_mode") m_specialMode = value; else return false; return true; } // Get source or consumer void* WpCircuit::getObject(const String& name) const { if (!group()) return 0; if (name == "DataSource") return m_sourceValid; if (name == "DataConsumer") return m_consumerValid; return SignallingCircuit::getObject(name); } // Enqueue received events inline bool WpCircuit::enqueueEvent(SignallingCircuitEvent* e) { if (e) { addEvent(e); XDebug(group(),e->type()!=SignallingCircuitEvent::Unknown?DebugAll:DebugStub, "WpCircuit(%u). Enqueued event '%s' [%p]",code(),e->c_str(),this); } return true; } /** * WpSpan */ unsigned char WpSpan::s_bitswap[256] = { 0x00,0x80,0x40,0xc0,0x20,0xa0,0x60,0xe0,0x10,0x90,0x50,0xd0,0x30,0xb0,0x70,0xf0,0x08,0x88,0x48,0xc8, 0x28,0xa8,0x68,0xe8,0x18,0x98,0x58,0xd8,0x38,0xb8,0x78,0xf8,0x04,0x84,0x44,0xc4,0x24,0xa4,0x64,0xe4, 0x14,0x94,0x54,0xd4,0x34,0xb4,0x74,0xf4,0x0c,0x8c,0x4c,0xcc,0x2c,0xac,0x6c,0xec,0x1c,0x9c,0x5c,0xdc, 0x3c,0xbc,0x7c,0xfc,0x02,0x82,0x42,0xc2,0x22,0xa2,0x62,0xe2,0x12,0x92,0x52,0xd2,0x32,0xb2,0x72,0xf2, 0x0a,0x8a,0x4a,0xca,0x2a,0xaa,0x6a,0xea,0x1a,0x9a,0x5a,0xda,0x3a,0xba,0x7a,0xfa,0x06,0x86,0x46,0xc6, 0x26,0xa6,0x66,0xe6,0x16,0x96,0x56,0xd6,0x36,0xb6,0x76,0xf6,0x0e,0x8e,0x4e,0xce,0x2e,0xae,0x6e,0xee, 0x1e,0x9e,0x5e,0xde,0x3e,0xbe,0x7e,0xfe,0x01,0x81,0x41,0xc1,0x21,0xa1,0x61,0xe1,0x11,0x91,0x51,0xd1, 0x31,0xb1,0x71,0xf1,0x09,0x89,0x49,0xc9,0x29,0xa9,0x69,0xe9,0x19,0x99,0x59,0xd9,0x39,0xb9,0x79,0xf9, 0x05,0x85,0x45,0xc5,0x25,0xa5,0x65,0xe5,0x15,0x95,0x55,0xd5,0x35,0xb5,0x75,0xf5,0x0d,0x8d,0x4d,0xcd, 0x2d,0xad,0x6d,0xed,0x1d,0x9d,0x5d,0xdd,0x3d,0xbd,0x7d,0xfd,0x03,0x83,0x43,0xc3,0x23,0xa3,0x63,0xe3, 0x13,0x93,0x53,0xd3,0x33,0xb3,0x73,0xf3,0x0b,0x8b,0x4b,0xcb,0x2b,0xab,0x6b,0xeb,0x1b,0x9b,0x5b,0xdb, 0x3b,0xbb,0x7b,0xfb,0x07,0x87,0x47,0xc7,0x27,0xa7,0x67,0xe7,0x17,0x97,0x57,0xd7,0x37,0xb7,0x77,0xf7, 0x0f,0x8f,0x4f,0xcf,0x2f,0xaf,0x6f,0xef,0x1f,0x9f,0x5f,0xdf,0x3f,0xbf,0x7f,0xff }; // Initialize B-channel group // Create circuits. Start worker thread WpSpan::WpSpan(const NamedList& params, const char* debugname) : SignallingCircuitSpan(params.getValue("debugname",debugname), static_cast(params.getObject("SignallingCircuitGroup"))), m_socket(m_group), m_thread(0), m_canSend(true), m_swap(false), m_chanMap(0), m_echoCancel(false), m_dtmfDetect(false), m_chans(0), m_count(0), m_first(0), m_samples(0), m_noData(0), m_buflen(0), m_circuits(0), m_readErrors(0), m_buffer(0), m_bufferLen(0) { DDebug(m_group,DebugAll,"WpSpan::WpSpan(). Name '%s' [%p]",id().safe(),this); } // Terminate worker thread // Close socket. Clear circuit list WpSpan::~WpSpan() { if (m_thread) { m_thread->cancel(); while (m_thread) Thread::yield(); } m_socket.close(); clearCircuits(); if (m_buffer) delete[] m_buffer; DDebug(m_group,DebugAll,"WpSpan::~WpSpan() [%p]",this); } // Initialize bool WpSpan::init(const NamedList& config, const NamedList& defaults, NamedList& params) { if (!m_group) { Debug(DebugNote,"WpSpan('%s'). Circuit group is missing [%p]", id().safe(),this); return false; } TempObjectCounter cnt(driver.objectsCounter()); // Set socket card / device m_socket.card(!params.null() ? params : config); const char* voice = params.getValue("voicegroup",config.getValue("voicegroup")); if (!voice) { Debug(m_group,DebugNote,"WpSpan('%s'). Missing or invalid voice group [%p]", id().safe(),this); return false; } m_socket.device(voice); m_canSend = !params.getBoolValue("readonly",config.getBoolValue("readonly",false)); // Type depending data: channel count, samples, circuit list String type = params.getValue("type",config.getValue("type")); String cics = params.getValue("voicechans",config.getValue("voicechans")); unsigned int start = params.getIntValue("offset",config.getIntValue("offset",0)); start += params.getIntValue("start"); start = config.getIntValue("start",start); m_samples = params.getIntValue("samples",config.getIntValue("samples")); int idleValue = 0xd5; // A-Law idle code if (type.null()) type = "E1"; if (type == "E1") { m_chans = 31; m_increment = 32; if (cics.null()) cics = "1-15,17-31"; if (!m_samples) m_samples = 50; } else if (type == "T1") { idleValue = 0xff; // mu-Law idle code m_chans = 24; m_increment = 24; if (cics.null()) cics = "1-23"; if (!m_samples) m_samples = 64; } else if (type == "BRI") { m_chans = 3; m_increment = 3; if (cics.null()) cics = "1-2"; if (!m_samples) m_samples = 80; } else { Debug(m_group,DebugNote,"WpSpan('%s'). Invalid voice group type '%s' [%p]", id().safe(),type.safe(),this); return false; } m_increment = config.getIntValue("increment",m_increment); // Other data m_swap = defaults.getBoolValue("bitswap",true); m_noData = defaults.getIntValue("idlevalue",idleValue); m_buflen = defaults.getIntValue("buflen",160); m_swap = params.getBoolValue("bitswap",config.getBoolValue("bitswap",m_swap)); m_noData = params.getIntValue("idlevalue",config.getIntValue("idlevalue",m_noData)); m_buflen = params.getIntValue("buflen",config.getIntValue("buflen",m_buflen)); bool tmpDefault = defaults.getBoolValue("echocancel",config.getBoolValue("echocancel",false)); m_echoCancel = params.getBoolValue("echocancel",tmpDefault); tmpDefault = defaults.getBoolValue("dtmfdetect",config.getBoolValue("dtmfdetect",false)); m_dtmfDetect = params.getBoolValue("dtmfdetect",tmpDefault); // Buffer length can't be 0 if (!m_buflen) m_buflen = 160; // Channels if (!createCircuits(start,cics)) { Debug(m_group,DebugNote, "WpSpan('%s'). Failed to create voice chans (voicechans=%s) [%p]", id().safe(),cics.safe(),this); return false; } // Start processing data m_thread = new WpSpanThread(this); if (!m_thread->startup()) { Debug(m_group,DebugNote,"WpSpan('%s'). Failed to start worker thread [%p]", id().safe(),this); return false; } if (debugAt(DebugInfo)) { String s; s << "driver=" << driver.debugName(); s << " section=" << config.c_str(); s << " type=" << type; s << " card=" << m_socket.card(); s << " device=" << m_socket.device(); s << " samples=" << m_samples; s << " bitswap=" << String::boolText(m_swap); if (m_noData < 256) s << " idlevalue=" << m_noData; else s << " idlevalue=(circuit)"; s << " buflen=" << (unsigned int)m_buflen; s << " echocancel=" << String::boolText(m_echoCancel); s << " dtmfdetect=" << String::boolText(m_dtmfDetect); s << " readonly=" << String::boolText(!m_canSend); s << " channels=" << cics << " (" << m_count << ")"; String cicList; if (m_circuits) for (unsigned int i = 0; i < m_count; i++) cicList.append(String(m_circuits[i]->code()),","); s << " circuits=" << cicList; Debug(m_group,DebugInfo,"WpSpan('%s') %s [%p]",id().safe(),s.safe(),this); } return true; } // Create circuits (all or nothing) // delta: number to add to each circuit code // cicList: Circuits to create bool WpSpan::createCircuits(unsigned int delta, const String& cicList) { unsigned int* cicCodes = SignallingUtils::parseUIntArray(cicList,1,m_chans,m_count,true); if (!cicCodes) return false; clearCircuits(); m_circuits = new WpCircuit*[m_count]; unsigned int i; for (i = 0; i < m_count; i++) m_circuits[i] = 0; bool ok = true; m_chanMap = 0; for (i = 0; i < m_count; i++) { m_circuits[i] = new WpCircuit(delta + cicCodes[i],m_group,this,m_buflen,cicCodes[i]); if (m_group->insert(m_circuits[i])) { m_circuits[i]->ref(); if (m_circuits[i]->channel()) m_chanMap |= ((unsigned long)1 << (m_circuits[i]->channel() - 1)); continue; } // Failure Debug(m_group,DebugNote, "WpSpan('%s'). Failed to create/insert circuit %u. Rollback [%p]", id().safe(),cicCodes[i],this); m_group->removeSpan(this,true,false); clearCircuits(); ok = false; break; } delete[] cicCodes; return ok; } void WpSpan::clearCircuits() { WpCircuit** circuits = m_circuits; m_circuits = 0; if (!circuits) return; for (unsigned int i = 0; i < m_count; i++) TelEngine::destruct(circuits[i]); delete[] circuits; } // Read events and data from socket. Send data when succesfully read // Received data is splitted for each circuit // Sent data from each circuit is merged into one data block void WpSpan::run() { if (!m_socket.open(true)) return; // Set echo canceller / tone detector if (m_socket.echoCancel(m_echoCancel,m_chanMap)) m_socket.dtmfDetect(m_dtmfDetect); if (!m_buffer) { m_bufferLen = WP_HEADER + m_samples * m_count; m_buffer = new unsigned char[m_bufferLen]; } DDebug(m_group,DebugInfo, "WpSpan('%s'). Worker is running: circuits=%u, buffer=%u, samples=%u [%p]", id().safe(),m_count,m_bufferLen,m_samples,this); updateStatus(); while (true) { Thread::check(true); if (!m_socket.select(m_samples)) continue; updateStatus(); if (m_socket.event()) readEvent(); if (!m_socket.canRead()) continue; int r = readData(); if (r == -1) continue; r -= WP_HEADER; #ifdef NEW_WANPIPE_API if (r == WAN_MAX_EVENT_SZ) { // Got an event wp_api_event_t* ev = (wp_api_event_t*)(m_buffer + WP_HEADER); SignallingCircuitEvent* e = 0; WpCircuit* circuit = 0; switch (ev->wp_api_event_type) { case WP_API_EVENT_DTMF: if (ev->wp_api_event_dtmf_type == WAN_EC_TONE_PRESENT) { String tone((char)ev->wp_api_event_dtmf_digit); tone.toUpper(); int chan = ev->wp_api_event_channel; circuit = find(chan); if (circuit) { e = new SignallingCircuitEvent(circuit,SignallingCircuitEvent::Dtmf,"DTMF"); e->addParam("tone",tone); } else Debug(m_group,DebugMild, "WpSpan('%s'). Detected DTMF '%s' for invalid channel %d [%p]", id().safe(),tone.c_str(),chan,this); } break; #ifdef DEBUG default: { String tmp; tmp.hexify(m_buffer + WP_HEADER,r,' '); Debug(m_group,DebugAll,"Event %u: %s",ev->wp_api_event_type,tmp.c_str()); } break; #endif } if (e) (static_cast(e->circuit()))->enqueueEvent(e); continue; } #endif // Calculate received samples. Check if we received valid data unsigned int samples = 0; if ((r > 0) && ((r % m_count) == 0)) samples = (unsigned int)r / m_count; if (!samples) { Debug(m_group,DebugNote, "WpSpan('%s'). Received data %d is not a multiple of circuit number %u [%p]", id().safe(),r,m_count,this); continue; } if (samples != m_samples) Debug(m_group,DebugInfo, "WpSpan('%s'). Received %u samples. Expected %u [%p]", id().safe(),samples,m_samples,this); if (m_canSend) { for (unsigned int i = 0; i < m_count; i++) { WpCircuit* circuit = m_circuits[i]; // Forward read data if we have a source WpSource* s = (circuit && circuit->validSource()) ? circuit->source() : 0; if (s) { unsigned char* dat = m_buffer + WP_HEADER + i; for (int n = samples; n > 0; n--, dat += m_count) s->put(swap(*dat)); } // Fill send buffer for current circuit unsigned char* dat = m_buffer + WP_HEADER + i; WpConsumer* c = (circuit && circuit->validConsumer()) ? circuit->consumer() : 0; if (c) { Lock lock(c); for (int n = samples; n > 0; n--, dat += m_count) *dat = swap(c->get()); } else { unsigned char noData = swap((m_noData < 256 || !circuit) ? m_noData : circuit->code() & 0xff); for (int n = samples; n > 0; n--, dat += m_count) *dat = noData; } } ::memset(m_buffer,0,WP_HEADER); m_socket.send(m_buffer,WP_HEADER + samples * m_count,MSG_DONTWAIT); } else { for (unsigned int i = 0; i < m_count; i++) { WpCircuit* circuit = m_circuits[i]; if (!(circuit && circuit->validSource())) continue; WpSource* s = circuit->source(); unsigned char* dat = m_buffer + WP_HEADER + i; for (int n = samples; n > 0; n--, dat += m_count) s->put(swap(*dat)); } } } } // Find a circuit by channel WpCircuit* WpSpan::find(unsigned int channel) { if (!m_circuits) return 0; for (unsigned int i = 0; i < m_count; i++) if (m_circuits[i] && m_circuits[i]->channel() == channel) return m_circuits[i]; return 0; } // Check for received event (including in-band events) bool WpSpan::readEvent() { XDebug(m_group,DebugInfo,"WpSpan('%s'). Got event. Checking OOB [%p]", id().safe(),this); int r = m_socket.recv(m_buffer,m_bufferLen,MSG_OOB); if (r >= WP_HEADER) decodeEvent(); return true; } // Read data from socket. Check for errors or in-band events // Return -1 on error int WpSpan::readData() { #ifdef NEW_WANPIPE_API ((wp_api_hdr_t*)m_buffer)->wp_api_rx_hdr_error_map = 0; #else m_buffer[WP_RD_ERROR] = 0; #endif int r = m_socket.recv(m_buffer,m_bufferLen); // Check errors if (r == -1) return -1; if (r < WP_HEADER) { Debug(m_group,DebugCrit,"WpSpan('%s'). Short read %u byte(s) [%p]", id().safe(),r,this); return -1; } #ifdef NEW_WANPIPE_API unsigned char err = ((wp_api_hdr_t*)m_buffer)->wp_api_rx_hdr_error_map; #else unsigned char err = m_buffer[WP_RD_ERROR]; #endif if (err) { if (++m_readErrors >= MAX_READ_ERRORS) { Debug(m_group,DebugCrit,"WpSpan('%s'). Read error 0x%02X [%p]", id().safe(),err,this); m_readErrors = 0; } } else m_readErrors = 0; // Check events decodeEvent(); return r; } bool WpSpan::decodeEvent() { // TODO: new API has different event handling style, may need total rewrite #if defined(WAN_EC_TONE_PRESENT) && !defined(NEW_WANPIPE_API) api_rx_hdr_t* ev = (api_rx_hdr_t*)m_buffer; SignallingCircuitEvent* e = 0; WpCircuit* circuit = 0; switch (ev->event_type) { case WP_API_EVENT_NONE: return false; case WP_API_EVENT_DTMF: if (ev->hdr_u.wp_api_event.u_event.dtmf.type == WAN_EC_TONE_PRESENT) { String tone((char)ev->hdr_u.wp_api_event.u_event.dtmf.digit); tone.toUpper(); int chan = ev->hdr_u.wp_api_event.channel; circuit = find(chan); if (circuit) { e = new SignallingCircuitEvent(circuit,SignallingCircuitEvent::Dtmf,"DTMF"); e->addParam("tone",tone); } else Debug(m_group,DebugMild, "WpSpan('%s'). Detected DTMF '%s' for invalid channel %d [%p]", id().safe(),tone.c_str(),chan,this); } break; default: Debug(m_group,DebugStub,"WpSpan('%s'). Unhandled event %u [%p]", id().safe(),ev->event_type,this); } if (e) (static_cast(e->circuit()))->enqueueEvent(e); return true; #else return false; #endif } // Update link status. Notify the receiver if state changed bool WpSpan::updateStatus() { if (!m_socket.updateLinkStatus()) return false; const char* evName = lookup(m_socket.status(),s_linkStatus); Debug(m_group,DebugNote,"WpSpan('%s'). Link status changed to %s [%p]", id().safe(),evName,this); SignallingCircuitEvent::Type evType = SignallingCircuitEvent::NoAlarm; if (m_socket.status() != WpSocket::Connected) evType = SignallingCircuitEvent::Alarm; for (unsigned int i = 0; i < m_count; i++) if (m_circuits[i]) { SignallingCircuitEvent* e = new SignallingCircuitEvent(m_circuits[i],evType,evName); if (evType == SignallingCircuitEvent::Alarm) e->addParam("alarms","red"); m_circuits[i]->enqueueEvent(e); } return true; } /** * WpSpanThread */ WpSpanThread::~WpSpanThread() { if (m_data) { Debug(m_data->group(),DebugAll,"WpSpan('%s'). Worker thread stopped [%p]", m_data->id().safe(),this); m_data->m_thread = 0; } else Debug(DebugAll,"WpSpanThread::~WpSpanThread() [%p]",this); } void WpSpanThread::run() { if (m_data) { Debug(m_data->group(),DebugAll,"WpSpan('%s'). Worker thread started [%p]", m_data->id().safe(),this); m_data->run(); } else Debug(DebugWarn,"WpSpanThread::run(). No client object [%p]",this); } /** * WpModule */ WpModule::WpModule() : Module("wanpipe","misc",true), m_init(false) { Output("Loaded module Wanpipe"); } WpModule::~WpModule() { Output("Unloading module Wanpipe"); } void WpModule::initialize() { Output("Initializing module Wanpipe"); Configuration cfg(Engine::configFile("wpcard")); s_repeatCapable = cfg.getBoolValue("general","hwrepeatcapable",s_repeatCapable); if (!m_init) { m_init = true; setup(); String events; #ifndef HAVE_WANPIPE_HWEC events.append("set/reset echo canceller",", "); #endif #ifndef WAN_EC_TONE_PRESENT events.append("detect tones",", "); #endif if (!events.null()) Debug(this,DebugWarn,"The module is unable to: %s [%p]", events.c_str(),this); } } }; // anonymous namespace #endif /* _WINDOWS */ /* vi: set ts=8 sw=4 sts=4 noet: */