yate/libs/ymgcp/message.cpp

494 lines
13 KiB
C++

/**
* message.cpp
* Yet Another MGCP Stack
* This file is part of the YATE Project http://YATE.null.ro
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 Null Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <yatemgcp.h>
#include <stdio.h>
using namespace TelEngine;
#ifdef XDEBUG
#define PARSER_DEBUG
#endif
// Ensure response code string representation is 3 digit long
inline void setCode(String& dest, unsigned int code)
{
char c[4];
sprintf(c,"%03u",code);
dest = c;
}
/**
* MGCPMessage
*/
// Construct an outgoing command message
MGCPMessage::MGCPMessage(MGCPEngine* engine, const char* name, const char* ep, const char* ver)
: params(""),
m_name(name),
m_valid(false),
m_code(-1),
m_transaction(0),
m_endpoint(ep),
m_version(ver)
{
if (!(engine && (engine->allowUnkCmd() || engine->knownCommand(name)))) {
Debug(engine,DebugNote,"MGCPMessage. Unknown cmd=%s [%p]",name,this);
return;
}
// Command names MUST be 4 character long
if (m_name.length() != 4) {
Debug(engine,DebugNote,
"MGCPMessage. Invalid command length cmd=%s len=%u [%p]",
m_name.c_str(),m_name.length(),this);
return;
}
m_transaction = engine->getNextId();
m_valid = true;
DDebug(engine,DebugAll,"MGCPMessage. cmd=%s trans=%u ep=%s [%p]",
name,m_transaction,ep,this);
}
// Construct a response message
MGCPMessage::MGCPMessage(MGCPTransaction* trans, unsigned int code, const char* comment)
: params(""),
m_valid(false),
m_code(code),
m_transaction(0),
m_comment(comment)
{
if (!trans) {
Debug(DebugNote,
"MGCPMessage. Can't create response without transaction [%p]",this);
return;
}
if (code > 999) {
Debug(trans->engine(),DebugNote,
"MGCPMessage. Invalid response code=%u [%p]",code,this);
return;
}
setCode(m_name,code);
m_transaction = trans->id();
if (!m_comment.length())
m_comment = lookup(code,MGCPEngine::mgcp_responses);
m_valid = true;
DDebug(trans->engine(),DebugAll,"MGCPMessage code=%d trans=%u comment=%s [%p]",
code,m_transaction,m_comment.c_str(),this);
}
// Constructor. Used by the parser to construct an incoming message
MGCPMessage::MGCPMessage(MGCPEngine* engine, const char* name, int code,
unsigned int transId, const char* epId, const char* ver)
: params(""),
m_valid(true),
m_code(code),
m_transaction(transId),
m_endpoint(epId),
m_version(ver)
{
if (code < 0)
m_name = name;
else {
setCode(m_name,code);
m_comment = name;
if (!m_comment.length())
m_comment = lookup(code,MGCPEngine::mgcp_responses);
}
DDebug(engine,DebugAll,
"Incoming MGCPMessage %s=%s trans=%u ep=%s ver=%s comment=%s [%p]",
isCommand()?"cmd":"rsp",this->name().c_str(),
transId,epId,ver,m_comment.safe(),this);
}
MGCPMessage::~MGCPMessage()
{
DDebug(DebugAll,"MGCPMessage::~MGCPMessage [%p]",this);
}
// Convert this message to a string representation
void MGCPMessage::toString(String& dest) const
{
// Construct first line
dest << name() << " " << transactionId();
if (isCommand())
dest << " " << endpointId() << " " << m_version;
else if (m_comment)
dest << " " << m_comment;
dest << "\r\n";
// Append message parameters
unsigned int n = params.count();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = params.getParam(i);
if (!ns)
continue;
dest << ns->name() << ": " << *ns << "\r\n";
}
// Append SDP(s)
for (ObjList* obj = sdp.skipNull(); obj; obj = obj->skipNext()) {
String s;
MimeSdpBody* tmp = static_cast<MimeSdpBody*>(obj->get());
for (ObjList* o = tmp->lines().skipNull(); o; o = o->skipNext()) {
NamedString* ns = static_cast<NamedString*>(o->get());
if (*ns)
s << ns->name() << "=" << *ns << "\r\n";
}
if (s)
dest << "\r\n" << s;
}
}
// Check if a character is an end-of-line one
static inline bool isEoln(char c)
{
return c == '\r' || c == '\n';
}
// Check if a character is blank: space or tab
static inline bool isBlank(char c)
{
return c == ' ' || c == '\t';
}
// Skip blank characters. The buffer is assumed to be valid
// Return false if the end of line was reached
static inline bool skipBlanks(const char*& buffer, unsigned int& len)
{
for (; len && isBlank(*buffer); buffer++, len--)
;
return len;
}
// Get a line from a buffer until the first valid end-of-line or end of buffer was reached,
// starting with current index in buffer
// Set the current index to the first character after the end-of-line or at the end of the buffer
// The buffer is assumed to be valid
// Return the line and length or 0 if an invalid end-of-line was found
// RFC 3435 3.1: end-of-line may be CR/LF or LF
static inline const char* getLine(const unsigned char* buffer, unsigned int len,
unsigned int& crt, unsigned int& count, bool skip = true)
{
count = 0;
const char* line = (const char*)buffer + crt;
while (crt < len && !isEoln(buffer[crt])) {
crt++;
count++;
}
if (skip)
skipBlanks(line,count);
// Check end of buffer or end-of-line
if (crt == len)
return line;
if (buffer[crt] == '\r' && (++crt) == len)
return 0;
return buffer[crt++] == '\n' ? line : 0;
}
// Parse a received buffer according to RFC 3435
// See Appendix A for the grammar
bool MGCPMessage::parse(MGCPEngine* engine, ObjList& dest,
const unsigned char* buffer, unsigned int len, const char* sdpType)
{
if (!buffer)
return false;
#ifdef PARSER_DEBUG
String t((const char*)buffer,len);
Debug(engine,DebugAll,"Parse received buffer\r\n%s",t.c_str());
#endif
int errorCode = 510; // Protocol error
unsigned int trans = 0;
String error;
unsigned int crt = 0;
while (crt < len && !error) {
unsigned int count = 0;
const char* line = 0;
// Skip empty lines before a message line and skip trailing blanks on the message line
while (crt < len) {
line = getLine(buffer,len,crt,count);
if (!line) {
error = "Invalid end-of-line";
break;
}
// Exit loop if the line is not empty
if (count)
break;
}
if (!count || error)
break;
// *** Decode the message line
MGCPMessage* msg = decodeMessage(line,count,trans,error,engine);
if (!msg)
break;
dest.append(msg);
#ifdef PARSER_DEBUG
String m((const char*)line,count);
Debug(engine,DebugAll,"Decoded message: %s",m.c_str());
#endif
// *** Decode parameters
if (decodeParams(buffer,len,crt,msg,error,engine))
continue;
if (error) {
if (msg->isCommand())
trans = msg->transactionId();
break;
}
if (crt >= len)
break;
// *** Decode SDP
// Decode SDPs until the end of buffer or
// a line containing a dot (message separator in a piggybacked block)
// SDPs are separated by an empty line
int empty = 0;
while (empty < 2) {
// Skip until an empty line, a line containing a dot or end of buffer
unsigned int start = crt;
unsigned int sdpLen = 0;
while (true) {
line = getLine(buffer,len,crt,count);
if (!line) {
error = "Invalid end-of-line";
break;
}
if (!count || (count == 1 && *line == '.')) {
if (!count)
empty++;
else
empty = 3;
break;
}
empty = 0;
sdpLen = crt - start;
}
if (error)
break;
if (sdpLen)
msg->sdp.append(new MimeSdpBody(sdpType,(const char*)buffer+start,sdpLen));
}
// Found 2 empty lines: skip until end of buffer or line containing '.' or non empty line
if (empty == 2) {
unsigned int start = crt;
while (true) {
line = getLine(buffer,len,crt,count);
if (!line) {
error = "Invalid end-of-line";
break;
}
if (!count) {
if (crt == len)
break;
continue;
}
// Fallback with current index if found non empty line which doesn't start with '.'
if (*line != '.')
crt = start;
break;
}
}
}
if (!error)
return true;
dest.clear();
if (trans && trans <= 999999999)
dest.append(new MGCPMessage(engine,0,errorCode,trans,0,0));
Debug(engine,DebugNote,"Parser error: %s",error.c_str());
return false;
}
// Decode the message line
// Command: verb transaction endpoint proto_name proto_version ...
// Response: code transaction comment ...
MGCPMessage* MGCPMessage::decodeMessage(const char* line, unsigned int len, unsigned int& trans,
String& error, MGCPEngine* engine)
{
String name, ver;
int code = -1;
unsigned int trID = 0;
MGCPEndpointId id;
#ifdef PARSER_DEBUG
String msgLine(line,len);
Debug(engine,DebugAll,"Parse message line (len=%u): %s",
msgLine.length(),msgLine.c_str());
#endif
for (unsigned int item = 1; true; item++) {
if (item == 6) {
#ifdef DEBUG
if (len) {
String rest(line,len);
Debug(engine,DebugAll,"Unparsed data on message line: '%s'",rest.c_str());
}
#endif
break;
}
// Get current item
if (!skipBlanks(line,len)) {
error = "Unexpected end of line";
return 0;
}
unsigned int itemLen = 0;
// Response: the 3rd item is the comment
if (item == 3 && code != -1)
itemLen = len;
else
for (; itemLen < len && !isBlank(line[itemLen]); itemLen++)
;
String tmp(line,itemLen);
len -= itemLen;
line += itemLen;
switch (item) {
// 1st item: verb (command or notification) or response code
// Verbs must be 4-character long. Responses must be numbers in the interval [0..999]
case 1:
if (tmp.length() == 3) {
code = tmp.toInteger(-1,10);
if (code < 0 || code > 999)
error << "Invalid response code " << tmp;
}
else if (tmp.length() == 4)
name = tmp.toUpper();
else
error << "Invalid first item '" << tmp << "' length " << tmp.length();
break;
// 2nd item: the transaction id
// Restriction: must be a number in the interval [1..999999999]
case 2:
trID = tmp.toInteger(-1,10);
if (!trID || trID > 999999999)
error << "Invalid transaction id '" << tmp << "'";
// Set trans for command messages so they can be responded on error
else if (code == -1)
trans = trID;
break;
// 3rd item: endpoint id (code is -1) or response comment (code != -1)
case 3:
if (code != -1)
name = tmp;
else {
id.set(tmp);
if (!id.valid())
error << "Invalid endpoint id '" << tmp << "'";
}
break;
// 4th item: protocol name if this is a verb (command)
case 4:
if (tmp.toUpper() == "MGCP")
ver = "MGCP 1.0";
else
error << "Invalid protocol '" << tmp << "'";
break;
// 5th item: protocol version if this is a verb (command)
case 5:
if (tmp != "1.0")
error << "Invalid protocol version '" << tmp << "'";
break;
}
if (error)
return 0;
// Stop parse the rest if this is a response
if (item == 3 && code != -1)
break;
}
// Check known commands
if (code == -1 &&
!(engine && (engine->allowUnkCmd() || engine->knownCommand(name)))) {
error << "Unknown cmd '" << name << "'";
return 0;
}
return new MGCPMessage(engine,name,code,trID,id.id(),ver);
}
// Decode message parameters. Return true if found a line containing a dot
// Decode parameters until the end of buffer, empty line or
// a line containing a dot (message separator in a piggybacked block)
// Parameters names and values are separated by a ':' character
// See RFC 3435 3.1
bool MGCPMessage::decodeParams(const unsigned char* buffer, unsigned int len,
unsigned int& crt, MGCPMessage* msg, String& error, MGCPEngine* engine)
{
while (crt < len) {
unsigned int count = 0;
const char* line = getLine(buffer,len,crt,count);
if (!line) {
error = "Invalid end-of-line";
break;
}
// Terminate if the line is empty or is a message separator
if (!count)
break;
if (count == 1 && *line == '.')
return true;
#ifdef PARSER_DEBUG
String paramLine(line,count);
Debug(engine,DebugAll,"Parse parameter line(len=%u): %s",count,paramLine.c_str());
#endif
// Decode parameter
int pos = -1;
for (int i = 0; i < (int)count; i++)
if (line[i] == ':')
pos = i;
if (pos == -1) {
error = "Parameter separator is missing";
break;
}
String param(line,pos);
param.trimBlanks();
if (!param) {
error = "Parameter name is empty";
break;
}
String value(line+pos+1,count-pos-1);
value.trimBlanks();
if (engine && engine->parseParamToLower())
msg->params.addParam(param.toLower(),value);
else
msg->params.addParam(param,value);
}
return false;
}
/* vi: set ts=8 sw=4 sts=4 noet: */