1609 lines
48 KiB
C++
1609 lines
48 KiB
C++
/**
|
|
* frame.cpp
|
|
* Yet Another IAX2 Stack
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2014 Null Team
|
|
* Author: Marian Podgoreanu
|
|
*
|
|
* This software is distributed under multiple licenses;
|
|
* see the COPYING file in the main directory for licensing
|
|
* information for this specific distribution.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
#include <yateiax.h>
|
|
|
|
#include <string.h> // For memcpy()
|
|
#include <stdio.h> // For sprintf()
|
|
|
|
using namespace TelEngine;
|
|
|
|
static inline void setStringFromInteger(String& dest, u_int32_t value, u_int8_t length)
|
|
{
|
|
char tmp[11];
|
|
switch (length) {
|
|
case 1:
|
|
sprintf(tmp,"0x%02x",(u_int8_t)value);
|
|
break;
|
|
case 2:
|
|
sprintf(tmp,"0x%04x",(u_int16_t)value);
|
|
break;
|
|
default:
|
|
sprintf(tmp,"0x%08x",value);
|
|
break;
|
|
}
|
|
dest = tmp;
|
|
}
|
|
|
|
|
|
class IAXTrunkFrameTrans : public GenObject
|
|
{
|
|
public:
|
|
inline IAXTrunkFrameTrans(u_int16_t callNo)
|
|
: m_callNo(callNo)
|
|
{}
|
|
static IAXTrunkFrameTrans* get(ObjList& list, u_int16_t rCallNo);
|
|
u_int16_t m_callNo;
|
|
ObjList m_blocks;
|
|
};
|
|
|
|
IAXTrunkFrameTrans* IAXTrunkFrameTrans::get(ObjList& list, u_int16_t callNo)
|
|
{
|
|
for (ObjList* o = list.skipNull(); o; o = o->skipNext()) {
|
|
IAXTrunkFrameTrans* t = static_cast<IAXTrunkFrameTrans*>(o->get());
|
|
if (t->m_callNo == callNo)
|
|
return t;
|
|
}
|
|
IAXTrunkFrameTrans* t = new IAXTrunkFrameTrans(callNo);
|
|
list.append(t);
|
|
return t;
|
|
}
|
|
|
|
|
|
/*
|
|
* IAXInfoElement
|
|
*/
|
|
const TokenDict IAXInfoElement::s_causeName[] = {
|
|
{"unallocated", 1}, // Unallocated (unassigned) number
|
|
{"noroute-to-network", 2}, // No route to specified transit network
|
|
{"noroute", 3}, // No route to destination
|
|
{"channel-unacceptable", 6}, // Channel unacceptable
|
|
{"call-delivered", 7}, // Call awarded and being delivered in an established channel
|
|
{"normal-clearing", 16}, // Normal Clearing
|
|
{"busy", 17}, // User busy
|
|
{"noresponse", 18}, // No user responding
|
|
{"noanswer", 19}, // No answer from user (user alerted)
|
|
{"rejected", 21}, // Call Rejected
|
|
{"moved", 22}, // Number changed
|
|
{"out-of-order", 27}, // Destination out of order
|
|
{"invalid-number", 28}, // Invalid number format
|
|
{"facility-rejected", 29}, // Facility rejected
|
|
{"status-enquiry-rsp", 30}, // Response to STATUS ENQUIRY
|
|
{"normal", 31}, // Normal, unspecified
|
|
{"congestion", 34}, // No circuit/channel available
|
|
{"channel-congestion", 34},
|
|
{"net-out-of-order", 38}, // Network out of order
|
|
{"noconn", 38},
|
|
{"temporary-failure", 41}, // Temporary failure
|
|
{"congestion", 42}, // Switching equipment congestion
|
|
{"switch-congestion", 42},
|
|
{"access-info-discarded", 43}, // Access information discarded
|
|
{"channel-unavailable", 44}, // Requested channel not available
|
|
{"preempted", 45}, // Preempted
|
|
{"noresource", 47}, // Resource unavailable, unspecified
|
|
{"facility-not-subscribed", 50}, // Requested facility not subscribed
|
|
{"barred-out", 52}, // Outgoing call barred
|
|
{"barred-in", 54}, // Incoming call barred
|
|
{"bearer-cap-not-auth", 57}, // Bearer capability not authorized
|
|
{"bearer-cap-not-available", 58}, // Bearer capability not presently available
|
|
{"nomedia", 58},
|
|
{"service-unavailable", 63}, // Service or option not available
|
|
{"bearer-cap-not-implemented", 65}, // Bearer capability not implemented
|
|
{"channel-type-not-implemented", 66}, // Channel type not implemented
|
|
{"facility-not-implemented", 69}, // Requested facility not implemented
|
|
{"restrict-bearer-cap-avail", 70}, // Only restricted digital information bearer capability is available
|
|
{"service-not-implemented", 79}, // Service or option not implemented, unspecified
|
|
{"invalid-callref", 81}, // Invalid call reference value
|
|
{"unknown-channel", 82}, // Identified channel does not exist
|
|
{"unknown-callid", 83}, // A suspended call exists, but this call identity does not
|
|
{"duplicate-callid", 84}, // Call identity in use
|
|
{"no-call-suspended", 85}, // No call suspended
|
|
{"suspended-call-cleared", 86}, // Call having the requested call identity has been cleared
|
|
{"incompatible-dest", 88}, // Incompatible destination
|
|
{"invalid-transit-net", 91}, // Invalid transit network selection
|
|
{"invalid-message", 95}, // Invalid message, unspecified
|
|
{"missing-mandatory-ie", 96}, // Mandatory information element is missing
|
|
{"unknown-message", 97}, // Message type non-existent or not implemented
|
|
{"wrong-message", 98}, // Message not compatible with call state, non-existent or not implemented
|
|
{"unknown-ie", 99}, // Information element non-existent or not implemented
|
|
{"invalid-ie", 100}, // Invalid information element contents
|
|
{"wrong-state-message", 101}, // Message not compatible with call state
|
|
{"timeout", 102}, // Recovery on timer expiry
|
|
{"mandatory-ie-len", 103}, // Mandatory information element length error
|
|
{"protocol-error", 111}, // Protocol error, unspecified
|
|
{"interworking", 127}, // Interworking, unspecified
|
|
{0,0}
|
|
};
|
|
|
|
const TokenDict IAXInfoElement::s_typeOfNumber[] = {
|
|
{"unknown", 0x00}, // Unknown
|
|
{"international", 0x10}, // International number
|
|
{"national", 0x20}, // National number
|
|
{"net-specific", 0x30}, // Network specific number
|
|
{"subscriber", 0x40}, // Subscriber number
|
|
{"abbreviated", 0x60}, // Abbreviated number
|
|
{"reserved", 0x70}, // Reserved for extension
|
|
{0,0}
|
|
};
|
|
|
|
const TokenDict IAXInfoElement::s_presentation[] = {
|
|
{"allowed", 0x00}, // Presentation allowed
|
|
{"restricted", 0x20}, // Presentation restricted
|
|
{"unavailable", 0x40}, // Number not available due to interworking
|
|
// Aliases for presentation=...
|
|
{"yes", 0x00},
|
|
{"true", 0x00},
|
|
{"no", 0x20},
|
|
{"false", 0x20},
|
|
{0,0}
|
|
};
|
|
|
|
const TokenDict IAXInfoElement::s_screening[] = {
|
|
{"user-provided", 0x00}, // User-provided, not screened
|
|
{"user-provided-passed", 0x01}, // User-provided, verified and passed
|
|
{"user-provided-failed", 0x02}, // User-provided, verified and failed
|
|
{"network-provided", 0x03}, // Network provided
|
|
// Aliases for screening=...
|
|
{"yes", 0x01}, // User-provided, verified and passed
|
|
{"true", 0x01},
|
|
{"no", 0x00}, // User-provided, not screened
|
|
{"false", 0x00},
|
|
{0,0}
|
|
};
|
|
|
|
const TokenDict IAXInfoElement::s_ieData[] = {
|
|
{"CALLED_NUMBER", CALLED_NUMBER},
|
|
{"CALLING_NUMBER", CALLING_NUMBER},
|
|
{"CALLING_ANI", CALLING_ANI},
|
|
{"CALLING_NAME", CALLING_NAME},
|
|
{"CALLED_CONTEXT", CALLED_CONTEXT},
|
|
{"USERNAME", USERNAME},
|
|
{"PASSWORD", PASSWORD},
|
|
{"CAPABILITY", CAPABILITY},
|
|
{"FORMAT", FORMAT},
|
|
{"LANGUAGE", LANGUAGE},
|
|
{"VERSION", VERSION},
|
|
{"ADSICPE", ADSICPE},
|
|
{"DNID", DNID},
|
|
{"AUTHMETHODS", AUTHMETHODS},
|
|
{"CHALLENGE", CHALLENGE},
|
|
{"MD5_RESULT", MD5_RESULT},
|
|
{"RSA_RESULT", RSA_RESULT},
|
|
{"APPARENT_ADDR", APPARENT_ADDR},
|
|
{"REFRESH", REFRESH},
|
|
{"DPSTATUS", DPSTATUS},
|
|
{"CALLNO", CALLNO},
|
|
{"CAUSE", CAUSE},
|
|
{"IAX_UNKNOWN", IAX_UNKNOWN},
|
|
{"MSGCOUNT", MSGCOUNT},
|
|
{"AUTOANSWER", AUTOANSWER},
|
|
{"MUSICONHOLD", MUSICONHOLD},
|
|
{"TRANSFERID", TRANSFERID},
|
|
{"RDNIS", RDNIS},
|
|
{"PROVISIONING", PROVISIONING},
|
|
{"AESPROVISIONING", AESPROVISIONING},
|
|
{"DATETIME", DATETIME},
|
|
{"DEVICETYPE", DEVICETYPE},
|
|
{"SERVICEIDENT", SERVICEIDENT},
|
|
{"FIRMWAREVER", FIRMWAREVER},
|
|
{"FWBLOCKDESC", FWBLOCKDESC},
|
|
{"FWBLOCKDATA", FWBLOCKDATA},
|
|
{"PROVVER", PROVVER},
|
|
{"CALLINGPRES", CALLINGPRES},
|
|
{"CALLINGTON", CALLINGTON},
|
|
{"CALLINGTNS", CALLINGTNS},
|
|
{"SAMPLINGRATE", SAMPLINGRATE},
|
|
{"CAUSECODE", CAUSECODE},
|
|
{"ENCRYPTION", ENCRYPTION},
|
|
{"ENKEY", ENKEY},
|
|
{"CODEC_PREFS", CODEC_PREFS},
|
|
{"RR_JITTER", RR_JITTER},
|
|
{"RR_LOSS", RR_LOSS},
|
|
{"RR_PKTS", RR_PKTS},
|
|
{"RR_DELAY", RR_DELAY},
|
|
{"RR_DROPPED", RR_DROPPED},
|
|
{"RR_OOO", RR_OOO},
|
|
{"CALLTOKEN", CALLTOKEN},
|
|
{"CAPABILITY2", CAPABILITY2},
|
|
{"FORMAT2", FORMAT2},
|
|
{0,0}
|
|
};
|
|
|
|
void IAXInfoElement::toBuffer(DataBlock& buf)
|
|
{
|
|
unsigned char d[2] = {m_type,0};
|
|
buf.assign(d,sizeof(d));
|
|
}
|
|
|
|
void IAXInfoElement::toString(String& buf)
|
|
{
|
|
buf << "";
|
|
}
|
|
|
|
/*
|
|
* IAXInfoElementString
|
|
*/
|
|
void IAXInfoElementString::toBuffer(DataBlock& buf)
|
|
{
|
|
unsigned char d[2] = {type(),(unsigned char)m_strData.length()};
|
|
buf.assign(d,sizeof(d));
|
|
buf.append(data());
|
|
}
|
|
|
|
/*
|
|
* IAXInfoElementNumeric
|
|
*/
|
|
IAXInfoElementNumeric::IAXInfoElementNumeric(Type type, u_int32_t val, u_int8_t len)
|
|
: IAXInfoElement(type),
|
|
m_length(len)
|
|
{
|
|
switch (m_length) {
|
|
case 4:
|
|
m_numericData = (u_int32_t)val;
|
|
break;
|
|
case 2:
|
|
m_numericData = (u_int16_t)val;
|
|
break;
|
|
case 1:
|
|
m_numericData = (u_int8_t)val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void IAXInfoElementNumeric::toBuffer(DataBlock& buf)
|
|
{
|
|
unsigned char d[6] = {type(),m_length};
|
|
|
|
switch (m_length) {
|
|
case 1:
|
|
d[2] = (unsigned char)m_numericData;
|
|
break;
|
|
case 2:
|
|
d[2] = (unsigned char)(m_numericData >> 8);
|
|
d[3] = (unsigned char)m_numericData;
|
|
break;
|
|
case 4:
|
|
d[2] = (unsigned char)(m_numericData >> 24);
|
|
d[3] = (unsigned char)(m_numericData >> 16);
|
|
d[4] = (unsigned char)(m_numericData >> 8);
|
|
d[5] = (unsigned char)m_numericData;
|
|
break;
|
|
}
|
|
buf.assign(d,2 + m_length);
|
|
}
|
|
|
|
void IAXInfoElementNumeric::toString(String& buf)
|
|
{
|
|
String tmp;
|
|
setStringFromInteger(tmp,m_numericData,m_length);
|
|
buf << tmp;
|
|
}
|
|
|
|
/*
|
|
* IAXInfoElementBinary
|
|
*/
|
|
void IAXInfoElementBinary::toBuffer(DataBlock& buf)
|
|
{
|
|
unsigned char d[2] = {type(),(unsigned char)m_data.length()};
|
|
buf.assign(d,sizeof(d));
|
|
buf += m_data;
|
|
}
|
|
|
|
void IAXInfoElementBinary::toString(String& buf)
|
|
{
|
|
if (!m_data.length())
|
|
return;
|
|
String tmp;
|
|
tmp.hexify(m_data.data(),m_data.length(),' ');
|
|
buf << tmp;
|
|
}
|
|
|
|
IAXInfoElementBinary* IAXInfoElementBinary::packIP(const SocketAddr& addr)
|
|
{
|
|
return new IAXInfoElementBinary(IAXInfoElement::APPARENT_ADDR,(unsigned char*)(addr.address()),addr.length());
|
|
}
|
|
|
|
bool IAXInfoElementBinary::unpackIP(SocketAddr& addr, IAXInfoElementBinary* ie)
|
|
{
|
|
addr.clear();
|
|
if (!ie)
|
|
return false;
|
|
addr.assign((struct sockaddr*)(ie->data().data()),ie->data().length());
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* IAXIEList
|
|
*/
|
|
IAXIEList::IAXIEList()
|
|
: m_invalidIEList(false)
|
|
{
|
|
XDebug(DebugInfo,"IAXIEList::IAXIEList() [%p]",this);
|
|
}
|
|
|
|
IAXIEList::IAXIEList(const IAXFullFrame* frame, bool incoming)
|
|
: m_invalidIEList(false)
|
|
{
|
|
XDebug(DebugInfo,"IAXIEList::IAXIEList(%p,%u) [%p]",frame,incoming,this);
|
|
if (frame)
|
|
createFromFrame(frame,incoming);
|
|
}
|
|
|
|
IAXIEList::~IAXIEList()
|
|
{
|
|
XDebug(DebugInfo,"IAXIEList::~IAXIEList() [%p]",this);
|
|
}
|
|
|
|
void IAXIEList::insertVersion()
|
|
{
|
|
if (!getIE(IAXInfoElement::VERSION))
|
|
m_list.insert(new IAXInfoElementNumeric(IAXInfoElement::VERSION,IAX_PROTOCOL_VERSION,2));
|
|
}
|
|
|
|
bool IAXIEList::createFromFrame(const IAXFullFrame* frame, bool incoming)
|
|
{
|
|
m_invalidIEList = false;
|
|
m_list.clear();
|
|
if (!frame)
|
|
return true;
|
|
if (frame->type() == IAXFrame::Voice || frame->type() == IAXFrame::Video)
|
|
return true;
|
|
unsigned char* data = (unsigned char*)(((IAXFullFrame*)frame)->data().data());
|
|
unsigned int len = ((IAXFullFrame*)frame)->data().length();
|
|
// Skip header for outgoing frames
|
|
if (!incoming) {
|
|
data += 12;
|
|
len -= 12;
|
|
}
|
|
u_int16_t i; // Current index of IE buffer
|
|
u_int32_t value;
|
|
if (frame->type() == IAXFrame::Text)
|
|
{
|
|
// Create even if text is empty
|
|
appendString(IAXInfoElement::textframe,data,len);
|
|
return true;
|
|
}
|
|
if (len < 2) {
|
|
m_invalidIEList = len ? true : false;
|
|
return !m_invalidIEList;
|
|
}
|
|
for (i = 1; i < len;) {
|
|
if (i + (unsigned int)data[i] >= len) {
|
|
i = 0xFFFF;
|
|
break;
|
|
}
|
|
switch (data[i-1]) {
|
|
// Text
|
|
case IAXInfoElement::CALLED_NUMBER:
|
|
case IAXInfoElement::CALLING_NUMBER:
|
|
case IAXInfoElement::CALLING_ANI:
|
|
case IAXInfoElement::CALLING_NAME:
|
|
case IAXInfoElement::CALLED_CONTEXT:
|
|
case IAXInfoElement::USERNAME:
|
|
case IAXInfoElement::PASSWORD:
|
|
case IAXInfoElement::LANGUAGE:
|
|
case IAXInfoElement::DNID:
|
|
case IAXInfoElement::CHALLENGE:
|
|
case IAXInfoElement::MD5_RESULT:
|
|
case IAXInfoElement::RSA_RESULT:
|
|
case IAXInfoElement::CAUSE:
|
|
case IAXInfoElement::MUSICONHOLD: // Optional
|
|
case IAXInfoElement::RDNIS:
|
|
case IAXInfoElement::DEVICETYPE:
|
|
if (data[i])
|
|
appendString((IAXInfoElement::Type)data[i-1],data+i+1,data[i]);
|
|
else
|
|
appendString((IAXInfoElement::Type)data[i-1],0,0);
|
|
i += data[i] + 1;
|
|
break;
|
|
// Binary
|
|
case IAXInfoElement::CODEC_PREFS: // LIST of strings
|
|
if (data[i])
|
|
appendBinary((IAXInfoElement::Type)data[i-1],data+i+1,data[i]);
|
|
else
|
|
appendBinary((IAXInfoElement::Type)data[i-1],0,0);
|
|
i += data[i] + 1;
|
|
break;
|
|
case IAXInfoElement::APPARENT_ADDR:
|
|
case IAXInfoElement::PROVISIONING:
|
|
case IAXInfoElement::AESPROVISIONING:
|
|
case IAXInfoElement::SERVICEIDENT: // Length must be 6
|
|
case IAXInfoElement::FWBLOCKDATA: // Length can be 0
|
|
case IAXInfoElement::ENKEY:
|
|
case IAXInfoElement::CALLTOKEN:
|
|
if (data[i-1] == IAXInfoElement::SERVICEIDENT && data[i] != 6) {
|
|
i = 0xFFFF;
|
|
break;
|
|
}
|
|
appendBinary((IAXInfoElement::Type)data[i-1],data+i+1,data[i]);
|
|
i += data[i] + 1;
|
|
break;
|
|
case IAXInfoElement::CAPABILITY2:
|
|
case IAXInfoElement::FORMAT2:
|
|
// Binary: 1 byte version + array of bytes (media format flags)
|
|
appendBinary((IAXInfoElement::Type)data[i-1],data+i+1,data[i]);
|
|
i += data[i] + 1;
|
|
break;
|
|
// 4 bytes
|
|
case IAXInfoElement::CAPABILITY:
|
|
case IAXInfoElement::FORMAT:
|
|
case IAXInfoElement::TRANSFERID:
|
|
case IAXInfoElement::DATETIME:
|
|
case IAXInfoElement::PROVVER:
|
|
case IAXInfoElement::FWBLOCKDESC:
|
|
case IAXInfoElement::SAMPLINGRATE:
|
|
case IAXInfoElement::RR_JITTER:
|
|
case IAXInfoElement::RR_LOSS:
|
|
case IAXInfoElement::RR_PKTS:
|
|
case IAXInfoElement::RR_DROPPED:
|
|
case IAXInfoElement::RR_OOO:
|
|
if (data[i] != 4) {
|
|
i = 0xFFFF;
|
|
break;
|
|
}
|
|
value = (data[i+1] << 24) | (data[i+2] << 16) | (data[i+3] << 8) | data[i+4];
|
|
appendNumeric((IAXInfoElement::Type)data[i-1],value,4);
|
|
i += 5;
|
|
break;
|
|
// 2 bytes
|
|
case IAXInfoElement::VERSION:
|
|
case IAXInfoElement::ADSICPE:
|
|
case IAXInfoElement::AUTHMETHODS:
|
|
case IAXInfoElement::REFRESH:
|
|
case IAXInfoElement::DPSTATUS:
|
|
case IAXInfoElement::CALLNO:
|
|
case IAXInfoElement::MSGCOUNT:
|
|
case IAXInfoElement::CALLINGTNS:
|
|
case IAXInfoElement::FIRMWAREVER:
|
|
case IAXInfoElement::RR_DELAY:
|
|
if (data[i] != 2) {
|
|
i = 0xFFFF;
|
|
break;
|
|
}
|
|
value = (data[i+1] << 8) | data[i+2];
|
|
appendNumeric((IAXInfoElement::Type)data[i-1],value,2);
|
|
i += 3;
|
|
break;
|
|
// 1 byte
|
|
case IAXInfoElement::IAX_UNKNOWN:
|
|
case IAXInfoElement::CALLINGPRES:
|
|
case IAXInfoElement::CALLINGTON:
|
|
case IAXInfoElement::CAUSECODE:
|
|
case IAXInfoElement::ENCRYPTION:
|
|
if (data[i] != 1) {
|
|
i = 0xFFFF;
|
|
break;
|
|
}
|
|
value = data[i+1];
|
|
appendNumeric((IAXInfoElement::Type)data[i-1],value,1);
|
|
i += 2;
|
|
break;
|
|
// None
|
|
case IAXInfoElement::AUTOANSWER:
|
|
if (data[i]) {
|
|
i = 0xFFFF;
|
|
break;
|
|
}
|
|
appendNull(IAXInfoElement::AUTOANSWER);
|
|
i += 1;
|
|
break;
|
|
default:
|
|
Debug(DebugInfo,"IAX Frame(%u,%u) with unknown IE identifier %u [%p]",
|
|
frame->type(),frame->subclass(),data[i-1],frame);
|
|
appendBinary((IAXInfoElement::Type)data[i-1],data+i+1,data[i]);
|
|
i += data[i] + 1;
|
|
}
|
|
if (i == 0xFFFF)
|
|
break;
|
|
if (i == len -1)
|
|
i = 0xFFFF;
|
|
else
|
|
i++;
|
|
}
|
|
m_invalidIEList = i == 0xFFFF;
|
|
if (!m_invalidIEList)
|
|
return true;
|
|
Debug(DebugWarn,"IAXIEList::createFromFrame. Frame(%u,%u) with invalid IE [%p]",
|
|
frame->type(),frame->subclass(),frame);
|
|
return false;
|
|
}
|
|
|
|
void IAXIEList::toBuffer(DataBlock& buf)
|
|
{
|
|
DataBlock data;
|
|
buf.clear();
|
|
ObjList* obj = m_list.skipNull();
|
|
for (; obj; obj = obj->skipNext()) {
|
|
IAXInfoElement* ie = static_cast<IAXInfoElement*>(obj->get());
|
|
ie->toBuffer(data);
|
|
buf.append(data);
|
|
}
|
|
}
|
|
|
|
static inline void addNumericName(String& buf, IAXInfoElement* ie, const TokenDict* dict,
|
|
const char* prefix = " (", const char* suffix = ")")
|
|
{
|
|
int val = (static_cast<IAXInfoElementNumeric*>(ie))->data();
|
|
const char* s = lookup(val,dict);
|
|
if (s)
|
|
buf << prefix << s << suffix;
|
|
}
|
|
|
|
static inline void addCallingPres(String& buf, IAXInfoElement* ie)
|
|
{
|
|
int val = (static_cast<IAXInfoElementNumeric*>(ie))->data();
|
|
const char* pres = lookup(val & 0xf0,IAXInfoElement::s_presentation);
|
|
const char* screen = lookup(val & 0x0f,IAXInfoElement::s_screening);
|
|
if (!(pres || screen))
|
|
return;
|
|
buf << " (";
|
|
if (pres)
|
|
buf << pres;
|
|
if (screen)
|
|
buf << (pres ? "," : "") << screen;
|
|
buf << ")";
|
|
}
|
|
|
|
static inline void addValPadded0(String& buf, unsigned int val, const char* prefix)
|
|
{
|
|
buf << prefix;
|
|
if (val < 10)
|
|
buf << "0";
|
|
buf << val;
|
|
}
|
|
|
|
static void addDateTime(String& buf, IAXInfoElement* ie)
|
|
{
|
|
u_int32_t val = (static_cast<IAXInfoElementNumeric*>(ie))->data();
|
|
unsigned int y,mon,d,h,min,s;
|
|
IAXEngine::decodeDateTime(val,y,mon,d,h,min,s);
|
|
buf << " (" << y;
|
|
addValPadded0(buf,mon,".");
|
|
addValPadded0(buf,d,".");
|
|
addValPadded0(buf,h," ");
|
|
addValPadded0(buf,min,":");
|
|
addValPadded0(buf,s,":");
|
|
buf << ")";
|
|
}
|
|
|
|
void IAXIEList::toString(String& dest, const char* indent)
|
|
{
|
|
ObjList* obj = m_list.skipNull();
|
|
for (; obj; obj = obj->skipNext()) {
|
|
IAXInfoElement* ie = static_cast<IAXInfoElement*>(obj->get());
|
|
dest << indent;
|
|
if (ie->type() == IAXInfoElement::textframe) {
|
|
ie->toString(dest);
|
|
continue;
|
|
}
|
|
const char* name = IAXInfoElement::ieText(ie->type());
|
|
if (name)
|
|
dest << name;
|
|
else {
|
|
u_int8_t t = ie->type();
|
|
String tmp;
|
|
tmp.hexify(&t,1);
|
|
dest << "0x" << tmp;
|
|
}
|
|
if (ie->type() != IAXInfoElement::AUTOANSWER)
|
|
dest << ": ";
|
|
switch (ie->type()) {
|
|
// Text
|
|
case IAXInfoElement::CALLED_NUMBER:
|
|
case IAXInfoElement::CALLING_NUMBER:
|
|
case IAXInfoElement::CALLING_ANI:
|
|
case IAXInfoElement::CALLING_NAME:
|
|
case IAXInfoElement::CALLED_CONTEXT:
|
|
case IAXInfoElement::USERNAME:
|
|
case IAXInfoElement::PASSWORD:
|
|
case IAXInfoElement::LANGUAGE:
|
|
case IAXInfoElement::DNID:
|
|
case IAXInfoElement::CHALLENGE:
|
|
case IAXInfoElement::MD5_RESULT:
|
|
case IAXInfoElement::RSA_RESULT:
|
|
case IAXInfoElement::CAUSE:
|
|
case IAXInfoElement::MUSICONHOLD:
|
|
case IAXInfoElement::RDNIS:
|
|
case IAXInfoElement::DEVICETYPE:
|
|
ie->toString(dest);
|
|
break;
|
|
case IAXInfoElement::CODEC_PREFS: //TODO: LIST of strings ?
|
|
{
|
|
const char* tmp = (const char*)((static_cast<IAXInfoElementBinary*>(ie))->data().data());
|
|
String s(tmp,(static_cast<IAXInfoElementBinary*>(ie))->length());
|
|
dest << s;
|
|
}
|
|
break;
|
|
// Binary
|
|
case IAXInfoElement::APPARENT_ADDR:
|
|
{
|
|
SocketAddr addr;
|
|
IAXInfoElementBinary::unpackIP(addr,static_cast<IAXInfoElementBinary*>(ie));
|
|
dest << addr.host() << ':' << addr.port();
|
|
}
|
|
break;
|
|
case IAXInfoElement::PROVISIONING:
|
|
case IAXInfoElement::AESPROVISIONING:
|
|
case IAXInfoElement::SERVICEIDENT:
|
|
case IAXInfoElement::FWBLOCKDATA:
|
|
case IAXInfoElement::ENKEY:
|
|
case IAXInfoElement::CALLTOKEN:
|
|
ie->toString(dest);
|
|
break;
|
|
// 4 bytes
|
|
case IAXInfoElement::CAPABILITY:
|
|
case IAXInfoElement::FORMAT:
|
|
case IAXInfoElement::AUTHMETHODS:
|
|
{
|
|
ie->toString(dest);
|
|
u_int32_t val = (static_cast<IAXInfoElementNumeric*>(ie))->data();
|
|
String tmp;
|
|
if (ie->type() == IAXInfoElement::AUTHMETHODS)
|
|
IAXAuthMethod::authList(tmp,val,',');
|
|
else
|
|
IAXFormat::formatList(tmp,val);
|
|
dest << " (" << tmp << ')';
|
|
}
|
|
break;
|
|
case IAXInfoElement::DATETIME:
|
|
ie->toString(dest);
|
|
addDateTime(dest,ie);
|
|
break;
|
|
case IAXInfoElement::SAMPLINGRATE:
|
|
dest << (unsigned int)((static_cast<IAXInfoElementNumeric*>(ie))->data()) << " Hz";
|
|
break;
|
|
case IAXInfoElement::RR_LOSS:
|
|
{
|
|
u_int32_t val = (static_cast<IAXInfoElementNumeric*>(ie))->data();
|
|
unsigned int percent = (unsigned int)(val & 0xFF000000);
|
|
unsigned int count = (unsigned int)(val & 0x00FFFFFF);
|
|
dest << count << " (" << percent << "%)";
|
|
}
|
|
break;
|
|
case IAXInfoElement::RR_JITTER:
|
|
case IAXInfoElement::RR_PKTS:
|
|
case IAXInfoElement::RR_DROPPED:
|
|
case IAXInfoElement::RR_OOO:
|
|
case IAXInfoElement::RR_DELAY:
|
|
dest << (unsigned int)((static_cast<IAXInfoElementNumeric*>(ie))->data());
|
|
break;
|
|
case IAXInfoElement::TRANSFERID:
|
|
case IAXInfoElement::PROVVER:
|
|
case IAXInfoElement::FWBLOCKDESC:
|
|
ie->toString(dest);
|
|
break;
|
|
// 2 bytes
|
|
case IAXInfoElement::REFRESH:
|
|
dest << (unsigned int)((static_cast<IAXInfoElementNumeric*>(ie))->data());
|
|
dest << " second(s)";
|
|
break;
|
|
case IAXInfoElement::ADSICPE:
|
|
case IAXInfoElement::DPSTATUS: //TODO: print more data
|
|
case IAXInfoElement::CALLNO:
|
|
case IAXInfoElement::CALLINGTNS: //TODO: print more data
|
|
case IAXInfoElement::FIRMWAREVER:
|
|
case IAXInfoElement::VERSION:
|
|
ie->toString(dest);
|
|
break;
|
|
// 1 byte
|
|
case IAXInfoElement::IAX_UNKNOWN:
|
|
case IAXInfoElement::ENCRYPTION: //TODO: print more data
|
|
ie->toString(dest);
|
|
break;
|
|
case IAXInfoElement::CALLINGPRES:
|
|
ie->toString(dest);
|
|
addCallingPres(dest,ie);
|
|
break;
|
|
case IAXInfoElement::CALLINGTON:
|
|
ie->toString(dest);
|
|
addNumericName(dest,ie,IAXInfoElement::s_typeOfNumber);
|
|
break;
|
|
case IAXInfoElement::CAUSECODE:
|
|
ie->toString(dest);
|
|
addNumericName(dest,ie,IAXInfoElement::s_causeName);
|
|
break;
|
|
case IAXInfoElement::MSGCOUNT:
|
|
{
|
|
u_int16_t val = (static_cast<IAXInfoElementNumeric*>(ie))->data();
|
|
dest << (int)((u_int8_t)val) << "new. " << (int)(val >> 8) << "old";
|
|
}
|
|
break;
|
|
// None
|
|
case IAXInfoElement::AUTOANSWER:
|
|
break;
|
|
default: ;
|
|
ie->toString(dest);
|
|
}
|
|
}
|
|
}
|
|
|
|
IAXInfoElement* IAXIEList::getIE(IAXInfoElement::Type type, bool remove)
|
|
{
|
|
for (ObjList* l = m_list.skipNull(); l; l = l->skipNext()) {
|
|
IAXInfoElement* ie = static_cast<IAXInfoElement*>(l->get());
|
|
if (ie->type() != type)
|
|
continue;
|
|
if (remove)
|
|
l->remove(false);
|
|
return ie;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool IAXIEList::getString(IAXInfoElement::Type type, String& dest)
|
|
{
|
|
IAXInfoElementString* ie = static_cast<IAXInfoElementString*>(getIE(type));
|
|
dest.clear();
|
|
if (!ie)
|
|
return false;
|
|
dest = ie->data();
|
|
return true;
|
|
}
|
|
|
|
bool IAXIEList::getNumeric(IAXInfoElement::Type type, u_int32_t& dest)
|
|
{
|
|
IAXInfoElementNumeric* ie = static_cast<IAXInfoElementNumeric*>(getIE(type));
|
|
if (!ie)
|
|
return false;
|
|
dest = ie->data();
|
|
return true;
|
|
}
|
|
|
|
bool IAXIEList::getBinary(IAXInfoElement::Type type, DataBlock& dest)
|
|
{
|
|
IAXInfoElementBinary* ie = static_cast<IAXInfoElementBinary*>(getIE(type));
|
|
dest.clear();
|
|
if (!ie)
|
|
return false;
|
|
dest = ie->data();
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* IAXAuthMethod
|
|
*/
|
|
TokenDict IAXAuthMethod::s_texts[] = {
|
|
{"Text", Text},
|
|
{"MD5", MD5},
|
|
{"RSA", RSA},
|
|
{0,0}
|
|
};
|
|
|
|
void IAXAuthMethod::authList(String& dest, u_int16_t auth, char sep)
|
|
{
|
|
dest = "";
|
|
bool first = true;
|
|
for (u_int16_t i = 0; s_texts[i].value; i++) {
|
|
if (0 == (s_texts[i].value & auth))
|
|
continue;
|
|
if (first)
|
|
first = false;
|
|
else
|
|
dest += sep;
|
|
dest += s_texts[i].token;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* IAXFormatDesc
|
|
*/
|
|
// Set the format
|
|
void IAXFormatDesc::setFormat(u_int32_t fmt, int type)
|
|
{
|
|
m_format = IAXFormat::mask(fmt,type);
|
|
if (!m_format) {
|
|
m_multiplier = 1;
|
|
return;
|
|
}
|
|
if (type == IAXFormat::Audio) {
|
|
switch (m_format) {
|
|
case IAXFormat::G722:
|
|
// 16kHz samplig rate
|
|
m_multiplier = 16;
|
|
break;
|
|
default:
|
|
// Assume 8kHz sampling rate
|
|
m_multiplier = 8;
|
|
}
|
|
}
|
|
else if (type == IAXFormat::Video)
|
|
// Assume 90kHz sampling rate for video
|
|
m_multiplier = 90;
|
|
else
|
|
m_multiplier = 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* IAXFormat
|
|
*/
|
|
const TokenDict IAXFormat::s_formats[] = {
|
|
{"G.723.1", G723_1},
|
|
{"GSM", GSM},
|
|
{"G.711 mu-law", ULAW},
|
|
{"G.711 a-law", ALAW},
|
|
{"G.726", G726},
|
|
{"IMA ADPCM", ADPCM},
|
|
{"SLIN", SLIN},
|
|
{"LPC10", LPC10},
|
|
{"G.729", G729},
|
|
{"SPEEX", SPEEX},
|
|
{"ILBC", ILBC},
|
|
{"G.726 AAL2", G726AAL2},
|
|
{"G.722", G722},
|
|
{"AMR", AMR},
|
|
{"GSM_HR", GSM_HR},
|
|
{"JPEG", JPEG},
|
|
{"PNG", PNG},
|
|
{"H261", H261},
|
|
{"H263", H263},
|
|
{"H263p", H263p},
|
|
{"H264", H264},
|
|
{0,0}
|
|
};
|
|
|
|
const TokenDict IAXFormat::s_types[] = {
|
|
{"audio", Audio},
|
|
{"video", Video},
|
|
{"image", Image},
|
|
{0,0}
|
|
};
|
|
|
|
const String IAXFormat::s_typesList[IAXFormat::TypeCount] = { "audio", "video", "image" };
|
|
|
|
// Set format
|
|
void IAXFormat::set(u_int32_t* fmt, u_int32_t* fmtIn, u_int32_t* fmtOut)
|
|
{
|
|
if (fmt)
|
|
m_format.setFormat(*fmt,m_type);
|
|
if (fmtIn)
|
|
m_formatIn.setFormat(*fmtIn,m_type);
|
|
if (fmtOut)
|
|
m_formatOut.setFormat(*fmtOut,m_type);
|
|
}
|
|
|
|
void IAXFormat::formatList(String& dest, u_int32_t formats, const TokenDict* dict,
|
|
const char* sep)
|
|
{
|
|
if (!dict)
|
|
dict = s_formats;
|
|
for (; dict->value; dict++)
|
|
if (0 != (dict->value & formats))
|
|
dest.append(dict->token,sep);
|
|
}
|
|
|
|
// Pick a format from a list of capabilities
|
|
u_int32_t IAXFormat::pickFormat(u_int32_t formats, u_int32_t format)
|
|
{
|
|
if (0 != (format & formats))
|
|
return format;
|
|
if (!formats)
|
|
return 0;
|
|
format = 1;
|
|
for (unsigned int i = 0; i < (8 * sizeof(u_int32_t)); i++, format = format << 1)
|
|
if (0 != (format & formats))
|
|
return format;
|
|
return 0;
|
|
}
|
|
|
|
// Encode a formats list
|
|
u_int32_t IAXFormat::encode(const String& formats, const TokenDict* dict, char sep)
|
|
{
|
|
if (!dict)
|
|
return 0;
|
|
u_int32_t mask = 0;
|
|
ObjList* list = formats.split(sep,false);
|
|
for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
|
|
int fmt = lookup(o->get()->toString(),dict);
|
|
mask |= fmt;
|
|
}
|
|
TelEngine::destruct(list);
|
|
return mask;
|
|
}
|
|
|
|
|
|
/*
|
|
* IAXControl
|
|
*/
|
|
TokenDict IAXControl::s_types[] = {
|
|
{"NEW", New},
|
|
{"PING", Ping},
|
|
{"PONG", Pong},
|
|
{"ACK", Ack},
|
|
{"HANGUP", Hangup},
|
|
{"REJECT", Reject},
|
|
{"ACCEPT", Accept},
|
|
{"AUTHREQ", AuthReq},
|
|
{"AUTHREP", AuthRep},
|
|
{"INVAL", Inval},
|
|
{"LAGRQ", LagRq},
|
|
{"LAGRP", LagRp},
|
|
{"REGREQ", RegReq},
|
|
{"REGAUTH", RegAuth},
|
|
{"REGACK", RegAck},
|
|
{"REGREJ", RegRej},
|
|
{"REGREL", RegRel},
|
|
{"VNAK", VNAK},
|
|
{"DPREQ", DpReq},
|
|
{"DPREP", DpRep},
|
|
{"DIAL", Dial},
|
|
{"TXREQ", TxReq},
|
|
{"TXCNT", TxCnt},
|
|
{"TXACC", TxAcc},
|
|
{"TXREADY", TxReady},
|
|
{"TXREL", TxRel},
|
|
{"TXREJ", TxRej},
|
|
{"QUELCH", Quelch},
|
|
{"UNQUELCH", Unquelch},
|
|
{"POKE", Poke},
|
|
{"MWI", MWI},
|
|
{"UNSUPPORT", Unsupport},
|
|
{"TRANSFER", Transfer},
|
|
{"PROVISION", Provision},
|
|
{"FWDOWNL", FwDownl},
|
|
{"FWDATA", FwData},
|
|
{"CALLTOKEN", CallToken},
|
|
{0,0}
|
|
};
|
|
|
|
/*
|
|
* IAXFrame
|
|
*/
|
|
TokenDict IAXFrame::s_types[] = {
|
|
{"DTMF", DTMF},
|
|
{"VOICE", Voice},
|
|
{"VIDEO", Video},
|
|
{"CONTROL", Control},
|
|
{"NULL", Null},
|
|
{"IAX", IAX},
|
|
{"TEXT", Text},
|
|
{"IMAGE", Image},
|
|
{"HTML", HTML},
|
|
{"NOISE", Noise},
|
|
{0,0}
|
|
};
|
|
|
|
IAXFrame::IAXFrame(Type type, u_int16_t sCallNo, u_int32_t tStamp, bool retrans,
|
|
const unsigned char* buf, unsigned int len, bool mark)
|
|
: m_data((char*)buf,len,true), m_retrans(retrans), m_type(type),
|
|
m_sCallNo(sCallNo), m_tStamp(tStamp), m_mark(mark)
|
|
{
|
|
// XDebug(DebugAll,"IAXFrame::IAXFrame(%u) [%p]",type,this);
|
|
}
|
|
|
|
IAXFrame::~IAXFrame()
|
|
{
|
|
// XDebug(DebugAll,"IAXFrame::~IAXFrame() [%p]",this);
|
|
}
|
|
|
|
IAXFrame* IAXFrame::parse(const unsigned char* buf, unsigned int len, IAXEngine* engine, const SocketAddr* addr)
|
|
{
|
|
if (len < 4)
|
|
return 0;
|
|
u_int16_t scn = (buf[0] << 8) | buf[1];
|
|
u_int16_t dcn = (buf[2] << 8) | buf[3];
|
|
// Full frame ?
|
|
if (scn & 0x8000) {
|
|
if (len < 12)
|
|
return 0;
|
|
scn &= 0x7fff;
|
|
bool retrans = false;
|
|
if (dcn & 0x8000) {
|
|
retrans = true;
|
|
dcn &= 0x7fff;
|
|
}
|
|
u_int32_t sc = 0;
|
|
bool mark = false;
|
|
unsigned char type = buf[10];
|
|
if (type != IAXFrame::Video) {
|
|
// Hack: Control StopSounds is sent with subclass 255
|
|
if (type != IAXFrame::Control || buf[11] != 255)
|
|
sc = IAXFrame::unpackSubclass(buf[11]);
|
|
else
|
|
sc = IAXFullFrame::StopSounds;
|
|
}
|
|
else {
|
|
mark = 0 != (buf[11] & 0x40);
|
|
// Clear the mark flag
|
|
sc = IAXFrame::unpackSubclass(buf[11] & 0xbf);
|
|
}
|
|
u_int32_t ts = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
|
|
return new IAXFullFrame((IAXFrame::Type)type,sc,scn,dcn,buf[8],buf[9],ts,retrans,buf+12,len-12,mark);
|
|
}
|
|
// Meta frame ?
|
|
if (scn == 0) {
|
|
if (dcn & 0x8000) {
|
|
// Meta video
|
|
if (len < 6)
|
|
return 0;
|
|
// Timestamp: lowest 15 bits of transmiter timestamp
|
|
scn = ((buf[4] & 0x7f) << 8) | buf[5];
|
|
bool mark = (0 != (buf[4] & 0x80));
|
|
return new IAXFrame(IAXFrame::Video,dcn & 0x7fff,scn & 0x7fff,false,buf+6,len-6,mark);
|
|
}
|
|
// Meta trunk frame - we need to push chunks into the engine
|
|
if (!(engine && addr))
|
|
return 0;
|
|
if (len < 8)
|
|
return 0;
|
|
// "meta command" should be 1
|
|
if (buf[2] != 1)
|
|
return 0;
|
|
bool tstamps = (buf[3] & 1) != 0;
|
|
if (tstamps) {
|
|
// Trunk timestamps (mini frames)
|
|
buf += 8;
|
|
len -= 8;
|
|
while (len >= 6) {
|
|
u_int16_t dlen = (buf[0] << 8) | buf[1];
|
|
if ((unsigned int)(dlen + 6) > len)
|
|
return 0;
|
|
scn = (buf[2] << 8) | buf[3];
|
|
bool retrans = false;
|
|
if (scn & 0x8000) {
|
|
retrans = true;
|
|
scn &= 0x7fff;
|
|
}
|
|
dcn = (buf[4] << 8) | buf[5];
|
|
IAXFrame* frame = new IAXFrame(IAXFrame::Voice,scn,dcn,retrans,buf+6,dlen);
|
|
engine->addFrame(*addr,frame);
|
|
frame->deref();
|
|
dlen += 6;
|
|
buf += dlen;
|
|
len -= dlen;
|
|
}
|
|
}
|
|
else {
|
|
// No trunk timestamps
|
|
u_int32_t ts = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
|
|
buf += 8;
|
|
len -= 8;
|
|
Time now;
|
|
ObjList list;
|
|
while (len >= 4) {
|
|
u_int16_t dlen = (buf[2] << 8) | buf[3];
|
|
if ((unsigned int)(dlen + 4) > len)
|
|
break;
|
|
scn = 0x7fff & ((buf[0] << 8) | buf[1]);
|
|
IAXTrunkFrameTrans* t = IAXTrunkFrameTrans::get(list,scn);
|
|
t->m_blocks.append(new DataBlock((void*)(buf+4),dlen));
|
|
dlen += 4;
|
|
buf += dlen;
|
|
len -= dlen;
|
|
}
|
|
for (ObjList* o = list.skipNull(); o; o = o->skipNext()) {
|
|
IAXTrunkFrameTrans* t = static_cast<IAXTrunkFrameTrans*>(o->get());
|
|
IAXTransaction* tr = engine->findTransaction(*addr,t->m_callNo);
|
|
if (!tr)
|
|
continue;
|
|
tr->processMiniNoTs(ts,t->m_blocks,now);
|
|
TelEngine::destruct(tr);
|
|
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
// Mini frame
|
|
return new IAXFrame(IAXFrame::Voice,scn,dcn,false,buf+4,len-4);
|
|
}
|
|
|
|
// Build a video meta frame buffer
|
|
void IAXFrame::buildVideoMetaFrame(DataBlock& dest, u_int16_t sCallNo, u_int32_t tStamp,
|
|
bool mark, void* data, unsigned int len)
|
|
{
|
|
unsigned char header[6] = {0,0};
|
|
header[2] = 0x80 | ((sCallNo >> 8) & 0x7f);
|
|
header[3] = (unsigned char)sCallNo;
|
|
header[4] = (tStamp >> 8) & 0x7f;
|
|
if (mark)
|
|
header[4] |= 0x80;
|
|
header[5] = tStamp;
|
|
dest.assign(header,6);
|
|
dest.append(data,len);
|
|
}
|
|
|
|
u_int8_t IAXFrame::packSubclass(u_int32_t value)
|
|
{
|
|
if (value < 0x80)
|
|
return (u_int8_t)value;
|
|
if (value == 0x80)
|
|
return 0x87;
|
|
if ((value > 0x9f) && (value <= 0xff)) {
|
|
Debug(DebugMild,"IAXFrame nonstandard pack %u",value);
|
|
return 0;
|
|
}
|
|
// No need to start from zero, we already know it's >= 2^8
|
|
u_int32_t v = 0x100;
|
|
for (u_int8_t i = 8; i < 32; i++) {
|
|
if (v == value)
|
|
return i | 0x80;
|
|
v <<= 1;
|
|
}
|
|
Debug(DebugCrit,"IAXFrame could not pack subclass %u (0x%x)",value,value);
|
|
return 0;
|
|
}
|
|
|
|
u_int32_t IAXFrame::unpackSubclass(u_int8_t value)
|
|
{
|
|
if (value > 0x9f) {
|
|
DDebug(DebugMild,"IAXFrame nonstandard unpack %u",value);
|
|
return 0;
|
|
}
|
|
if (value & 0x80)
|
|
return 1 << (value & 0x7f);
|
|
return value;
|
|
}
|
|
|
|
IAXFullFrame* IAXFrame::fullFrame()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* IAXFullFrame
|
|
*/
|
|
TokenDict IAXFullFrame::s_controlTypes[] = {
|
|
{"HANGUP", Hangup},
|
|
// {"RING", Ring},
|
|
{"RINGING", Ringing},
|
|
{"ANSWER", Answer},
|
|
{"BUSY", Busy},
|
|
{"CONGESTION", Congestion},
|
|
{"FLASHHOOK", FlashHook},
|
|
{"OPTION", Option},
|
|
{"KEYRADIO", KeyRadio},
|
|
{"UNKEYRADIO", UnkeyRadio},
|
|
{"PROGRESSING", Progressing},
|
|
{"PROCEEDING", Proceeding},
|
|
{"HOLD", Hold},
|
|
{"UNHOLD", Unhold},
|
|
{"VIDUPDATE", VidUpdate},
|
|
{"SRCUPDATE", SrcUpdate},
|
|
{"STOPSOUNDS", StopSounds},
|
|
{0,0}
|
|
};
|
|
|
|
IAXFullFrame::IAXFullFrame(Type type, u_int32_t subclass, u_int16_t sCallNo, u_int16_t dCallNo,
|
|
unsigned char oSeqNo, unsigned char iSeqNo,
|
|
u_int32_t tStamp, bool retrans,
|
|
const unsigned char* buf, unsigned int len, bool mark)
|
|
: IAXFrame(type,sCallNo,tStamp,retrans,buf,len,mark),
|
|
m_dCallNo(dCallNo), m_oSeqNo(oSeqNo), m_iSeqNo(iSeqNo), m_subclass(subclass),
|
|
m_ieList(0)
|
|
{
|
|
XDebug(DebugAll,
|
|
"IAXFullFrame() incoming type=%u subclass=%u callno=(%u,%u) seq=(%u,%u) ts=%u retrans=%u [%p]",
|
|
type,subclass,sCallNo,dCallNo,oSeqNo,iSeqNo,tStamp,retrans,this);
|
|
}
|
|
|
|
IAXFullFrame::IAXFullFrame(Type type, u_int32_t subclass, u_int16_t sCallNo, u_int16_t dCallNo,
|
|
unsigned char oSeqNo, unsigned char iSeqNo,
|
|
u_int32_t tStamp,
|
|
const unsigned char* buf, unsigned int len, bool mark)
|
|
: IAXFrame(type,sCallNo,tStamp,false,0,0,mark),
|
|
m_dCallNo(dCallNo), m_oSeqNo(oSeqNo), m_iSeqNo(iSeqNo), m_subclass(subclass),
|
|
m_ieList(0)
|
|
{
|
|
XDebug(DebugAll,
|
|
"IAXFullFrame() outgoing type=%u subclass=%u callno=(%u,%u) seq=(%u,%u) ts=%u [%p]",
|
|
type,subclass,sCallNo,dCallNo,oSeqNo,iSeqNo,tStamp,this);
|
|
setDataHeader();
|
|
if (buf)
|
|
m_data.append((void*)buf,(unsigned int)len);
|
|
}
|
|
|
|
// Constructor. Constructs an outgoing full frame
|
|
IAXFullFrame::IAXFullFrame(Type type, u_int32_t subclass, u_int16_t sCallNo, u_int16_t dCallNo,
|
|
unsigned char oSeqNo, unsigned char iSeqNo,
|
|
u_int32_t tStamp, IAXIEList* ieList, u_int16_t maxlen, bool mark)
|
|
: IAXFrame(type,sCallNo,tStamp,false,0,0,mark),
|
|
m_dCallNo(dCallNo), m_oSeqNo(oSeqNo), m_iSeqNo(iSeqNo), m_subclass(subclass),
|
|
m_ieList(ieList)
|
|
{
|
|
XDebug(DebugAll,
|
|
"IAXFullFrame() outgoing type=%u subclass=%u callno=(%u,%u) seq=(%u,%u) ts=%u [%p]",
|
|
type,subclass,sCallNo,dCallNo,oSeqNo,iSeqNo,tStamp,this);
|
|
updateBuffer(maxlen);
|
|
}
|
|
|
|
void IAXFullFrame::toString(String& dest, const SocketAddr& local,
|
|
const SocketAddr& remote, bool incoming)
|
|
{
|
|
#define STARTLINE(indent) "\r\n" << indent
|
|
const char* enclose = "\r\n-----";
|
|
dest << enclose;
|
|
String stmp;
|
|
setStringFromInteger(stmp,type(),1);
|
|
dest << STARTLINE("") << typeText(type()) << " (" << stmp << ")";
|
|
String extra;
|
|
// Subclass
|
|
String subc;
|
|
switch (type()) {
|
|
case IAXFrame::IAX:
|
|
case IAXFrame::Control:
|
|
subc = (type() == IAXFrame::IAX ? IAXControl::typeText(subclass()) :
|
|
controlTypeText(subclass()));
|
|
break;
|
|
case IAXFrame::DTMF:
|
|
subc << (char)subclass();
|
|
break;
|
|
case IAXFrame::Video:
|
|
extra << "Mark: " << String::boolText(mark());
|
|
// fallthrough
|
|
case IAXFrame::Voice:
|
|
case IAXFrame::Image:
|
|
IAXFormat::formatList(subc,subclass());
|
|
break;
|
|
case IAXFrame::Null:
|
|
subc = "Subclass: ";
|
|
break;
|
|
case IAXFrame::Text:
|
|
subc = "Subclass: ";
|
|
break;
|
|
case IAXFrame::HTML:
|
|
subc = "Subclass: ";
|
|
break;
|
|
case IAXFrame::Noise:
|
|
subc << (unsigned int)(subclass()) << " -dBov";
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
setStringFromInteger(stmp,subclass(),4);
|
|
dest << " - " << (subc ? subc.c_str() : "???") << " (" << stmp << ")";
|
|
// Addresses
|
|
if (incoming)
|
|
dest << STARTLINE(" ") << "Incoming from ";
|
|
else
|
|
dest << STARTLINE(" ") << "Outgoing to ";
|
|
dest << remote.host() << ':' << remote.port();
|
|
dest << " (Local address: ";
|
|
dest << local.host() << ':' << local.port() << ')';
|
|
// Transaction numbers
|
|
dest << STARTLINE(" ") << "Call (Local:Remote): ";
|
|
if (incoming)
|
|
dest << destCallNo();
|
|
else
|
|
dest << sourceCallNo();
|
|
dest << ':';
|
|
if (incoming)
|
|
dest << sourceCallNo();
|
|
else
|
|
dest << destCallNo();
|
|
// Info
|
|
dest << ". Timestamp: " << (unsigned int)(timeStamp());
|
|
dest << ". Retrans: " << String::boolText(retrans());
|
|
dest << ". Sequence numbers: Out: " << oSeqNo() << " In: " << iSeqNo();
|
|
if (extra)
|
|
dest << STARTLINE(" ") << extra;
|
|
// IEs
|
|
updateIEList(incoming);
|
|
if (!m_ieList->empty()) {
|
|
String aux;
|
|
aux << STARTLINE(" ");
|
|
m_ieList->toString(dest,aux);
|
|
}
|
|
if (m_ieList->empty()) {
|
|
dest << STARTLINE(" ");
|
|
if (m_ieList->invalidIEList())
|
|
dest << "Error parsing Information Element(s)";
|
|
else
|
|
dest << "No Information Element(s)";
|
|
}
|
|
dest << enclose;
|
|
#undef STARTLINE
|
|
}
|
|
|
|
// Rebuild frame buffer from the list of IEs
|
|
void IAXFullFrame::updateBuffer(u_int16_t maxlen)
|
|
{
|
|
setDataHeader();
|
|
if (!m_ieList)
|
|
return;
|
|
DataBlock tmp;
|
|
m_ieList->toBuffer(tmp);
|
|
if (tmp.length() <= maxlen)
|
|
m_data += tmp;
|
|
else
|
|
Debug(DebugNote,"Frame(%u,%u) buffer too long (%u > %u) [%p]",
|
|
type(),subclass(),tmp.length(),maxlen,this);
|
|
}
|
|
|
|
// Update IE list from buffer if not already done
|
|
bool IAXFullFrame::updateIEList(bool incoming)
|
|
{
|
|
if (!m_ieList)
|
|
m_ieList = new IAXIEList(this,incoming);
|
|
return !m_ieList->invalidIEList();
|
|
}
|
|
|
|
// Remove the IE list
|
|
IAXIEList* IAXFullFrame::removeIEList(bool delObj)
|
|
{
|
|
if (!m_ieList)
|
|
return 0;
|
|
IAXIEList* old = m_ieList;
|
|
m_ieList = 0;
|
|
if (delObj) {
|
|
delete old;
|
|
old = 0;
|
|
}
|
|
return old;
|
|
}
|
|
|
|
IAXFullFrame::~IAXFullFrame()
|
|
{
|
|
XDebug(DebugAll,"IAXFullFrame::~IAXFullFrame(%u,%u) [%p]",type(),m_subclass,this);
|
|
}
|
|
|
|
IAXFullFrame* IAXFullFrame::fullFrame()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
// Destroyed notification. Clear data
|
|
void IAXFullFrame::destroyed()
|
|
{
|
|
removeIEList();
|
|
IAXFrame::destroyed();
|
|
}
|
|
|
|
// Build frame buffer header
|
|
void IAXFullFrame::setDataHeader()
|
|
{
|
|
unsigned char header[12];
|
|
// Full frame flag + Source call number
|
|
header[0] = 0x80 | (unsigned char)(sourceCallNo() >> 8);
|
|
header[1] = (unsigned char)(sourceCallNo());
|
|
// Retrans + Destination call number
|
|
header[2] = (unsigned char)(destCallNo() >> 8); // retrans is false: bit 7 is 0
|
|
header[3] = (unsigned char)destCallNo();
|
|
// Timestamp
|
|
header[4] = (unsigned char)(timeStamp() >> 24);
|
|
header[5] = (unsigned char)(timeStamp() >> 16);
|
|
header[6] = (unsigned char)(timeStamp() >> 8);
|
|
header[7] = (unsigned char)timeStamp();
|
|
// oSeqNo + iSeqNo
|
|
header[8] = m_oSeqNo;
|
|
header[9] = m_iSeqNo;
|
|
// Type
|
|
header[10] = type();
|
|
// Subclass
|
|
header[11] = packSubclass(m_subclass);
|
|
if (mark())
|
|
header[11] |= 0x40;
|
|
// Set data
|
|
m_data.assign(header,sizeof(header));
|
|
}
|
|
|
|
|
|
/*
|
|
* IAXTrunkInfo
|
|
*/
|
|
// Init all data from parameters
|
|
void IAXTrunkInfo::init(const NamedList& params, const String& prefix,
|
|
const IAXTrunkInfo* def)
|
|
{
|
|
m_retransCount = params.getIntValue(prefix + "retrans_count",
|
|
def ? def->m_retransCount : IAX2_RETRANS_COUNT_DEF,
|
|
IAX2_RETRANS_COUNT_MIN,IAX2_RETRANS_COUNT_MAX);
|
|
m_retransInterval = params.getIntValue(prefix + "retrans_interval",
|
|
def ? def->m_retransInterval : IAX2_RETRANS_INTERVAL_DEF,
|
|
IAX2_RETRANS_INTERVAL_MIN,IAX2_RETRANS_INTERVAL_MAX);
|
|
m_pingInterval = params.getIntValue(prefix + "ping_interval",
|
|
def ? def->m_pingInterval : IAX2_PING_INTERVAL_DEF,
|
|
IAX2_PING_INTERVAL_MIN);
|
|
}
|
|
|
|
// Init from parameters
|
|
void IAXTrunkInfo::initTrunking(const NamedList& params, const String& prefix,
|
|
const IAXTrunkInfo* def, bool out, bool in)
|
|
{
|
|
if (out) {
|
|
m_timestamps = params.getBoolValue(prefix + "timestamps",
|
|
!def || def->m_timestamps);
|
|
m_sendInterval = params.getIntValue(prefix + "sendinterval",
|
|
def ? def->m_sendInterval : IAX2_TRUNKFRAME_SEND_DEF,IAX2_TRUNKFRAME_SEND_MIN);
|
|
m_maxLen = params.getIntValue(prefix + "maxlen",
|
|
def ? def->m_maxLen : IAX2_TRUNKFRAME_LEN_DEF,IAX2_TRUNKFRAME_LEN_MIN);
|
|
m_efficientUse = params.getBoolValue(prefix + "efficient_use",
|
|
def && def->m_efficientUse);
|
|
}
|
|
if (in) {
|
|
m_trunkInSyncUsingTs = params.getBoolValue(prefix + "nominits_sync_use_ts",
|
|
!def || def->m_trunkInSyncUsingTs);
|
|
m_trunkInTsDiffRestart = params.getIntValue(prefix + "nominits_ts_diff_restart",
|
|
def ? m_trunkInTsDiffRestart : 5000,1000);
|
|
}
|
|
}
|
|
|
|
// Update trunking from parameters. Don't change values not present in list
|
|
void IAXTrunkInfo::updateTrunking(const NamedList& params, const String& prefix,
|
|
bool out, bool in)
|
|
{
|
|
if (out) {
|
|
m_timestamps = params.getBoolValue(prefix + "timestamps",m_timestamps);
|
|
m_sendInterval = params.getIntValue(prefix + "sendinterval",
|
|
m_sendInterval,IAX2_TRUNKFRAME_SEND_MIN);
|
|
m_maxLen = params.getIntValue(prefix + "maxlen",m_maxLen,IAX2_TRUNKFRAME_LEN_MIN);
|
|
m_efficientUse = params.getBoolValue(prefix + "efficient_use",m_efficientUse);
|
|
}
|
|
if (in) {
|
|
m_trunkInSyncUsingTs = params.getBoolValue(prefix + "nominits_sync_use_ts",
|
|
m_trunkInSyncUsingTs);
|
|
m_trunkInTsDiffRestart = params.getIntValue(prefix + "nominits_ts_diff_restart",
|
|
m_trunkInTsDiffRestart,1000);
|
|
}
|
|
}
|
|
|
|
// Dump info
|
|
void IAXTrunkInfo::dump(String& buf, const char* sep, bool out, bool in, bool other)
|
|
{
|
|
if (out) {
|
|
buf.append("timestamps=",sep) << String::boolText(m_timestamps);
|
|
buf << sep << "sendinterval=" << m_sendInterval;
|
|
buf << sep << "maxlen=" << m_maxLen;
|
|
buf << sep << "efficient_use=" << String::boolText(m_efficientUse);
|
|
}
|
|
if (in) {
|
|
buf.append("nominits_sync_use_ts=",sep) << String::boolText(m_trunkInSyncUsingTs);
|
|
buf << sep << "nominits_ts_diff_restart=" << m_trunkInTsDiffRestart;
|
|
}
|
|
if (other) {
|
|
buf.append("retrans_count=",sep) << m_retransCount;
|
|
buf << sep << "retrans_interval=" << m_retransInterval;
|
|
buf << sep << "ping_interval=" << m_pingInterval;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* IAXMetaTrunkFrame
|
|
*/
|
|
IAXMetaTrunkFrame::IAXMetaTrunkFrame(IAXEngine* engine, const SocketAddr& addr,
|
|
bool timestamps, unsigned int maxLen, unsigned int sendInterval)
|
|
: Mutex(false,"IAXMetaTrunkFrame"),
|
|
m_calls(0), m_data(0), m_dataAddIdx(IAX2_TRUNKFRAME_HEADERLENGTH),
|
|
m_timeStamp(0), m_send(0), m_lastSentTs(0),
|
|
m_sendInterval(sendInterval),
|
|
m_engine(engine), m_addr(addr),
|
|
m_trunkTimestamps(timestamps), m_maxLen(maxLen), m_maxDataLen(0),
|
|
m_miniHdrLen(timestamps ? 6 : 4)
|
|
{
|
|
// Make sure we don't receive invalid values
|
|
if (m_maxLen < IAX2_TRUNKFRAME_LEN_MIN)
|
|
m_maxLen = IAX2_TRUNKFRAME_LEN_MIN;
|
|
m_data = new u_int8_t[m_maxLen];
|
|
// Audio data length can't be greater the remaining space
|
|
// Also make sure we can put it in 2 bytes
|
|
m_maxDataLen = m_maxLen - IAX2_TRUNKFRAME_HEADERLENGTH - m_miniHdrLen;
|
|
if (m_maxDataLen > 0xffff)
|
|
m_maxDataLen = 0xffff;
|
|
// Meta indicator
|
|
*(u_int16_t*)m_data = 0;
|
|
// Meta command & Command data (use timestamps)
|
|
m_data[2] = 1;
|
|
m_data[3] = m_trunkTimestamps ? 1 : 0;
|
|
XDebug(m_engine,DebugAll,"Trunk frame '%s:%d' created [%p]",
|
|
m_addr.host().c_str(),m_addr.port(),this);
|
|
}
|
|
|
|
IAXMetaTrunkFrame::~IAXMetaTrunkFrame()
|
|
{
|
|
if (!m_calls)
|
|
XDebug(m_engine,DebugAll,"Trunk frame '%s:%d' destroyed [%p]",
|
|
m_addr.host().c_str(),m_addr.port(),this);
|
|
else
|
|
Debug(m_engine,DebugMild,"Trunk frame '%s:%d' destroyed with %u calls [%p]",
|
|
m_addr.host().c_str(),m_addr.port(),m_calls,this);
|
|
delete[] m_data;
|
|
}
|
|
|
|
unsigned int IAXMetaTrunkFrame::add(u_int16_t sCallNo, const DataBlock& data, u_int32_t tStamp)
|
|
{
|
|
// Do we have data ?
|
|
if (!data.length())
|
|
return 0;
|
|
// Avoid buffer overflow
|
|
if (data.length() > m_maxDataLen) {
|
|
Debug(m_engine,DebugWarn,
|
|
"Trunk frame '%s:%d' can't add %u bytes (max=%u) for call %u [%p]",
|
|
m_addr.host().c_str(),m_addr.port(),data.length(),m_maxDataLen,sCallNo,this);
|
|
return 0;
|
|
}
|
|
Lock lck(this);
|
|
if (!m_timeStamp)
|
|
setTimestamp(Time::now());
|
|
// If no more room, send it
|
|
if (m_dataAddIdx + data.length() + m_miniHdrLen > m_maxLen)
|
|
doSend();
|
|
XDebug(m_engine,DebugAll,"Trunk frame '%s:%d' adding %u payload bytes call=%u [%p]",
|
|
m_addr.host().c_str(),m_addr.port(),data.length(),sCallNo,this);
|
|
// Add the mini frame
|
|
if (m_trunkTimestamps) {
|
|
// data length + call no + timestamp
|
|
m_data[m_dataAddIdx++] = (u_int8_t)(data.length() >> 8);
|
|
m_data[m_dataAddIdx++] = (u_int8_t)data.length();
|
|
m_data[m_dataAddIdx++] = (u_int8_t)(sCallNo >> 8);
|
|
m_data[m_dataAddIdx++] = (u_int8_t)sCallNo;
|
|
m_data[m_dataAddIdx++] = (u_int8_t)(tStamp >> 8);
|
|
m_data[m_dataAddIdx++] = (u_int8_t)tStamp;
|
|
}
|
|
else {
|
|
// call no + data length
|
|
m_data[m_dataAddIdx++] = (u_int8_t)(sCallNo >> 8);
|
|
m_data[m_dataAddIdx++] = (u_int8_t)sCallNo;
|
|
m_data[m_dataAddIdx++] = (u_int8_t)(data.length() >> 8);
|
|
m_data[m_dataAddIdx++] = (u_int8_t)data.length();
|
|
}
|
|
memcpy(m_data + m_dataAddIdx,data.data(),data.length());
|
|
m_dataAddIdx += data.length();
|
|
return data.length();
|
|
}
|
|
|
|
// Send this frame to remote peer
|
|
bool IAXMetaTrunkFrame::doSend(const Time& now, bool onTime)
|
|
{
|
|
#define IAX2_TRUNKDATA_DELTA 160
|
|
bool dontSend = (m_dataAddIdx <= IAX2_TRUNKFRAME_HEADERLENGTH);
|
|
u_int64_t ellapsed = (now - m_timeStamp) / 1000;
|
|
if (ellapsed <= 0xffffffff) {
|
|
// Sent on time: set timestamp from send interval
|
|
// Sent on buffer full: set timestamp from ellapsed time
|
|
u_int32_t ts = 0;
|
|
if (onTime) {
|
|
setSendTime(now);
|
|
ts = m_lastSentTs + m_sendInterval;
|
|
if (ts != ellapsed) {
|
|
// Adjust timestamp
|
|
if (ts > ellapsed) {
|
|
if ((ts - ellapsed) >= IAX2_TRUNKDATA_DELTA)
|
|
ts = (u_int32_t)ellapsed;
|
|
}
|
|
else if ((ellapsed - ts) >= IAX2_TRUNKDATA_DELTA)
|
|
ts = (u_int32_t)ellapsed;
|
|
}
|
|
}
|
|
else
|
|
ts = (u_int32_t)ellapsed;
|
|
if (ts > m_lastSentTs || dontSend)
|
|
m_lastSentTs = ts;
|
|
else
|
|
m_lastSentTs++;
|
|
}
|
|
else {
|
|
// Timestamp wraparound: reset
|
|
setTimestamp(now);
|
|
m_lastSentTs = 0;
|
|
}
|
|
if (dontSend)
|
|
return false;
|
|
XDebug(m_engine,DebugAll,"Trunk frame '%s:%d' sending %u tStamp=%u calls=%u [%p]",
|
|
m_addr.host().c_str(),m_addr.port(),m_dataAddIdx,m_lastSentTs,m_calls,this);
|
|
setTimestamp(m_lastSentTs);
|
|
bool b = m_engine->writeSocket(m_data,m_dataAddIdx,m_addr);
|
|
m_dataAddIdx = IAX2_TRUNKFRAME_HEADERLENGTH;
|
|
return b;
|
|
}
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|