#include <cstdlib>


#ifdef WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h> // used for validting ipv6 addresses (should be replaced w/ boost/asio.hpp for better portability)
#endif


// workaround to compile with cygwin
// (AF_INET6 is not included by arpa/inet.h and we could not find the
//  correct header to include without having conflicting definitions)
#ifdef __CYGWIN__
#define AF_INET6  23
#endif

#include "gen_classes.h"
#include "Regex.h"

namespace t3devlib { namespace gen {


// Definition of character classes used to be used in the regular expressions
// (within [])
#define SDPCHARS_VCHAR "\\x21-\\x7e"
#define SDPCHARS_EMAIL_SAFE "\\x01-\\x09\\x0b\\x0c\\x0e-\\x27\\x2a-\\x3b\\x3d\\x3f-\\xff"
#define SDPCHARS_ATEXT "a-zA-Z0-9!#$%&'*+-/=?^_`{|}~"
#define SDPCHARS_BYTE_STRING "\\x01-\\x09\\x0b\\x0c\\x0e-\\xff"


// Definition of common regular expression reused many times within the
// decoder
//
// Note: these definitions shall not contain any group match '(' ')' to avoid
// side effect (the index of the subsequent group match would be shifted)
// -> all parenthesis group needed should be implemented with '(?:' ')' so
//    that they are not indexed
#define SDPREG_SP " "
#define SDPREG_NON_WS_STRING "[" SDPCHARS_VCHAR "\\x80-\\xff]+"
#define SDPREG_TOKEN "[\\x21\\x23-\\x27\\x2a\\x2b\\x2d\\x2e\\x30-\\x39\\x41-\\x5a\\x5e-\\x7e]+"
#define SDPREG_DECIMAL_UCHAR "(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(?![0-9])"
#define SDPREG_IP4_ADDRESS SDPREG_DECIMAL_UCHAR "(?:[.]" SDPREG_DECIMAL_UCHAR"){3}"
#define SDPREG_INTEGER "[1-9][0-9]*"
#define SDPREG_TIME_INTEGER "[0-9][0-9]*"
#define SDPREG_BYTE_STRING "["SDPCHARS_BYTE_STRING"]+"

// TODO: support the folding whitespaces/obsolete addresses/... ?
#define SDPREG_ADDR_SPEC "[" SDPCHARS_ATEXT "][." SDPCHARS_ATEXT "]*@[" SDPCHARS_ATEXT "][." SDPCHARS_ATEXT "]*"

// This regex differs from the BNF to avoid matching a space or dash at the
// end of the number
#define SDPREG_PHONE "[+]?[0-9](?:[- 0-9]*[0-9])?"


inline bool logical_xor (bool a, bool b)
{
	return (a && !b) || (!a && b);
}



//
// Implementation of the decoder for SDP
//


void SDP_Message::PreDecode (Buffer& buffer) throw (DecodeError)
{
	Get_protocol_version().SetFormat(Integer::AsciiDecimal);
}


void pre_decode_optional_field_multiple_times (RecordOfSetOf& value, int id, Buffer& buffer, const char* field_letter, void (*hyp_size_func)(int), void (*hyp_field_length_func)(int) )
{
	static Regex reg_field ("((?:\r\n)?)(([a-z])=)([^\\r\\n\\x00]*)");
	if (!reg_field.Match (buffer))
		goto finished;

	if (logical_xor (id, reg_field.GetMatchedLength (1)))
		// the first line MUST NOT be preceeded by CRLF
		// the next lines MUST be preceeded by CRLF
		//  -> otherwise we assume this is the last field
		goto finished;

	if (reg_field.GetMatchedString (3) != field_letter) {
		assert (id > 0);// the field letter is already checked in
				// SDP_Message::PreDecodeField
				// the assertion is useful to ensure
				// that we cannot have an empty
				// record of (in that case the field
				// must be omitted in SDP_Message)
		goto finished;
	}

	(*hyp_field_length_func) (reg_field.GetMatchedLength(4));
	reg_field.MovePast (buffer, 2);
	return;

finished:
	// did not match -> last field
	(*hyp_size_func) (-2);
}


void SDP_Message::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	static Regex reg_field("^(([a-z])=)([^\\r\\n\\x00]*)");

	const char* expected = NULL;

	switch (id)
	{
	case id_protocol_version:	expected = "v";		goto mandatory;
	case id_origin:			expected = "o";		goto mandatory;
	case id_session_name:		expected = "s";		goto mandatory;
	case id_information:		expected = "i";		goto optional_once;

	// TODO: check that this is a valid URI ???
	case id_uri:			expected = "u";		goto optional_once;
	case id_emails:			expected = "e";		goto optional_many;
	case id_phone_numbers:		expected = "p";		goto optional_many;
	case id_connection:		expected = "c";		goto optional_once;
	case id_bandwidth:		expected = "b";		goto optional_many;
	case id_times:			return; // times is not optional => always present
	// FIXME: SDP_timezone_list should not be here -> the TTCN-3 types should be changed
	// in the BNF, "z=" is allowed once after each "t=" fields (not after
	// the last one only)
	case id_timezone_adjustments:	expected = "z";		goto optional_once;
	case id_key:			expected = "k";		goto optional_once;
	case id_attributes:		expected = "a";		goto optional_many;
	case id_media_list:		expected = "m";		goto optional_many;
	}

	return;
mandatory:
	reg_field.AssertMatch (buffer, this);

	SetHypFieldLength (id, reg_field.GetMatchedLength(3));

	if (reg_field.GetMatchedString(2) != expected) {
		DecodeError e(this);
		e.Msg() << "missing mandatory field " << expected << "= before field " << reg_field.GetMatchedString(1) << endl;
		throw e;
	}
	reg_field.MovePast (buffer, 1);
	return;

optional_once:
	if (reg_field.Match (buffer) &&
	    reg_field.GetMatchedString(2) == expected)
	{
		// the field is present
		SetHypFieldIsPresent (id, 1);
		SetHypFieldLength (id, reg_field.GetMatchedLength(3));
		reg_field.MovePast (buffer, 1);
	} else {
		// the field is not present
		SetHypFieldIsPresent (id, 0);
	}
	return;

optional_many:
	SetHypFieldIsPresent (id,
		(reg_field.Match (buffer) &&
		 reg_field.GetMatchedString(2) == expected)
		? 1
		: 0);
}

void SDP_Message::PostDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	// Here we match the trailing CRLF in the case the field is present
	static Regex reg_crlf ("^\r\n");


	if (!IsPresent (id))
		return;

	// the time field is not declared as optional
	// therefore we must check that the list is not empty
	if ((id == id_times) && (Get_times().GetSize() == 0))
		return;

	reg_crlf.AssertMatch (buffer, this);
	reg_crlf.MovePast (buffer);
}

void SDP_Origin::PreDecode (Buffer& buffer) throw (DecodeError)
{
	static Regex reg_origin ("("SDPREG_NON_WS_STRING")"	// username
			SDPREG_SP "([0-9]+)"		// session-id
			SDPREG_SP "([0-9]+)"		// session-version
			SDPREG_SP "("SDPREG_TOKEN")"	// net-type
			SDPREG_SP "("SDPREG_TOKEN")"	// addr-type
			SDPREG_SP "("SDPREG_NON_WS_STRING")"); // unicast-address
	reg_origin.AssertMatch (buffer, this);
	for (int i=0 ; i<6 ; i++) {
		SetHypFieldLength (i, reg_origin.GetMatchedLength(i+1));
	}
}

void SDP_Origin::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id) {
		static Regex reg_sp (" ");
		reg_sp.AssertMatch (buffer, this);
		reg_sp.MovePast (buffer);
	}
}

#define SDP_CODET_HEADER_LIST(letter, type) \
	void type::PreDecodeField (int id, Buffer& buffer) throw (DecodeError) \
	{ \
		pre_decode_optional_field_multiple_times (*this, id, buffer, letter, &SetHypSize, &SetHypFieldLength); \
	}

SDP_CODET_HEADER_LIST ("e", SDP_email_list);
SDP_CODET_HEADER_LIST ("p", SDP_phone_list);
SDP_CODET_HEADER_LIST ("b", SDP_bandwidth_list);
SDP_CODET_HEADER_LIST ("r", SDP_repeat_list);
SDP_CODET_HEADER_LIST ("a", SDP_attribute_list);
SDP_CODET_HEADER_LIST ("c", SDP_connection_list);



void dummy (int a) {
}
// SDL_time and SDP_media_desc list are a little unusual compared to other
// headers since these record can contain a list of headers
//
// In order to be able to decode it, we will not put any length
// constraint on this header (thus passing &dummy instead of
// &SetHypFieldLength), then it will be possible to decode the
// repeate headers after the CRLF
void SDP_time_list::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	pre_decode_optional_field_multiple_times (*this, id, buffer, "t", &SetHypSize, &dummy);
}
void SDP_media_desc_list::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	pre_decode_optional_field_multiple_times (*this, id, buffer, "m", &SetHypSize, &dummy);
}


void SDP_contact::PreDecode (Buffer& buffer) throw (DecodeError)
{
	static Regex reg_addrspec_comment ("^(" SDPREG_ADDR_SPEC ")(" SDPREG_SP "[(](["SDPCHARS_EMAIL_SAFE"]+)[)])?");
	static Regex reg_dispname_and_addr ("^(["SDPCHARS_EMAIL_SAFE"]+)" SDPREG_SP "<(" SDPREG_ADDR_SPEC ")>");
	
	static Regex reg_phone_comment ("^(" SDPREG_PHONE ")(" SDPREG_SP "[(](["SDPCHARS_EMAIL_SAFE"]+)[)])?");
	static Regex reg_dispname_and_phone ("^(["SDPCHARS_EMAIL_SAFE"]+)" SDPREG_SP "<(" SDPREG_PHONE ")>");

	Regex *reg_normal, *reg_reverse;
	if (dynamic_cast<SDP_phone_list*>(GetParent())) {
		// this is a phone contact
		reg_normal  = &reg_phone_comment;
		reg_reverse = &reg_dispname_and_phone;
	} else {
		// this is an email contact
		reg_normal  = &reg_addrspec_comment;
		reg_reverse = &reg_dispname_and_addr;
	}

	// by default we assume that there is no name displayed
	SetHypFieldIsPresent (id_disp_name, 0);

	if (reg_normal->Match (buffer)) {
		mReverseOrder = false;
		SetHypFieldLength (id_addr_or_phone, reg_normal->GetMatchedLength (1));
		mHasComment = reg_normal->GetMatchedLength (2);
		if (mHasComment) {
			SetHypFieldIsPresent (id_disp_name, 1);
			SetHypFieldLength (id_disp_name, reg_normal->GetMatchedLength (3));
		}
	} else if (reg_reverse->Match (buffer)) {
		mReverseOrder = true;
		SetHypFieldLength (id_addr_or_phone, reg_reverse->GetMatchedLength (2));
		SetHypFieldIsPresent (id_disp_name, 1);
		SetHypFieldLength (id_disp_name, reg_reverse->GetMatchedLength (1));
		// remember the position of the display name to be able
		// to decode it later
		mNextPosition = buffer.GetPosition();
		// then place the cursor of the buffer at the position of the
		// email address
		reg_reverse->MoveAt (buffer, 2);
	} else {
		throw DecodeError (this, "Cannot match email address or phone number\n");
	}
}

void SDP_contact::PostDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (mReverseOrder) {
		if (id == id_addr_or_phone) {
			int disp_name_position = mNextPosition;
			// remember the position past the closing ">"
			mNextPosition = buffer.GetPosition() + 8;
			buffer.SetPosition (disp_name_position);
		} else {
			// once everything is decoded we move forward to the
			// end of the message field
			buffer.SetPosition (mNextPosition);
		}
	} else if (mHasComment) {
		buffer.SetPosition (buffer.GetPosition() + (
			(id == id_addr_or_phone)
				? 16	// move past the " ("
				: 8	// move past the ")"
		));
	}
}

void SDP_connection::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_net_type) {
		static Regex reg_connection ("^(" SDPREG_TOKEN ") (" SDPREG_TOKEN ") (" SDPREG_NON_WS_STRING ")");

		reg_connection.AssertMatch (buffer, this);
		SetHypFieldLength (id_net_type, reg_connection.GetMatchedLength (1));
		SetHypFieldLength (id_addr_type, reg_connection.GetMatchedLength (2));
		SetHypFieldLength (id_conn_addr, reg_connection.GetMatchedLength (3));
		
	} else {
		buffer.SetPosition (buffer.GetPosition() + 8); // move past the SP
	}
}

void SDP_conn_addr::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_addr) {
		static Regex reg_ip4 ("^("SDPREG_IP4_ADDRESS")((?:[/]("SDPREG_INTEGER"))?((?:[/]("SDPREG_INTEGER"))?))");
		static Regex reg_ip6 ("^([0-9a-fA-F.:]+)((?:[/]("SDPREG_INTEGER"))?)");
		unsigned char ip6_addr[16];
	
		// use ascii format for the integers
		Get_ttl().SetFormat(Integer::AsciiDecimal);
		Get_num_of_addr().SetFormat(Integer::AsciiDecimal);
	
		// ttl and addr_num are not present by default
		SetHypFieldIsPresent (id_ttl, 0);
		SetHypFieldIsPresent (id_num_of_addr, 0);
/*
reg_ip4.AssertMatch (buffer, this);
for (int i=0 ; i<6 ; i++) {
	cerr << "Matched string " << i << " -> " << reg_ip4.GetMatchedString (i) << endl; 
	cerr << "Matched length " << i << " -> " << reg_ip4.GetMatchedLength (i) << endl; 
}
*/
		if (reg_ip4.Match (buffer)) {
			SetHypFieldLength (id_addr, reg_ip4.GetMatchedLength (1));
			if (atoi (reg_ip4.GetMatchedString(1).c_str()) < 224) {
				// unicast address
				if (reg_ip4.GetMatchedLength(2) | reg_ip4.GetMatchedLength(4))
					throw DecodeError (this, "TTL and/or number of connections fields can be present only with multicast addresses\n");

			} else {
				// multicast address
				if (! reg_ip4.GetMatchedLength (2))
					throw DecodeError (this, "IPv4 multicast address must be followed with the TTL");
				SetHypFieldIsPresent (id_ttl, 1);
				SetHypFieldLength (id_ttl, reg_ip4.GetMatchedLength (3));
				if (reg_ip4.GetMatchedLength (4)) {
					SetHypFieldIsPresent (id_num_of_addr, 1);
					SetHypFieldLength (id_num_of_addr, reg_ip4.GetMatchedLength (5));
				}
			}
		} else if (	reg_ip6.Match (buffer) 
#ifndef WIN32
				// FIXME: inet_pton does not exist in WIN32
			&& (inet_pton (AF_INET6, reg_ip6.GetMatchedString(1).c_str(), &ip6_addr) == 1) // if it does not contain a valid ipv6 address, then
				// we will parse it as another address family
#endif
			) {
			SetHypFieldLength (id_addr, reg_ip6.GetMatchedLength (1));
			if (ip6_addr[0] != 0xFF) {
				// unicast address
				if (reg_ip4.GetMatchedLength(2))
					throw DecodeError (this, "The number of connections can be present only with multicast addresses\n");

			} else {
				// multicast address
				if (reg_ip6.GetMatchedLength (2)) {
					SetHypFieldIsPresent (id_num_of_addr, 1);
					SetHypFieldLength (id_num_of_addr, reg_ip6.GetMatchedLength (3));
				}
			}
		} else {
			// if it is not an ipv4 or ipv6 address
			// then we put everything in the addr field
		}
		
	} else if (GetHypFieldIsPresent (id)) {
		buffer.SetPosition (buffer.GetPosition() + 8); // move past the "/"
	}
}

void SDP_bandwidth::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_modifier) {
		static Regex reg_bw ("^(" SDPREG_TOKEN "):([0-9]+)");
		reg_bw.AssertMatch (buffer, this);
		SetHypFieldLength (id_modifier, reg_bw.GetMatchedLength(1));
		SetHypFieldLength (id_bandwidth, reg_bw.GetMatchedLength(2));
	} else {
		Get_bandwidth().SetFormat(Integer::AsciiDecimal);
		Unsigned(8).Decode(buffer); // move past the colon
	}
}

void SDP_time::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_time_repeat) {
		static Regex reg_repeat ("^(\r\n)r=");

		if (reg_repeat.Match (buffer)) {
			SetHypFieldIsPresent (id, 1);
			reg_repeat.MovePast (buffer, 1);
		} else {
			SetHypFieldIsPresent (id, 0);
		}
	}
}

void SDP_time_field::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_start_time) {
		static Regex reg_times ("^(" SDPREG_TIME_INTEGER ")" SDPREG_SP "(" SDPREG_TIME_INTEGER ")");
		reg_times.AssertMatch (buffer, this);
		SetHypFieldLength (id_start_time, reg_times.GetMatchedLength (1));
		SetHypFieldLength (id_stop_time, reg_times.GetMatchedLength (2));
	} else {
		Unsigned(8).Decode (buffer); // move past the SP
	}
}

void SDP_typed_time::PreDecode (Buffer& buffer) throw (DecodeError)
{
	static Regex reg_ttime ("^(-?[0-9]*)([dhms]?)");
	reg_ttime.AssertMatch (buffer, this);

	Get_time().SetFormat(Integer::AsciiDecimal);
	SetHypFieldLength (id_time, reg_ttime.GetMatchedLength (1));

	bool has_unit = reg_ttime.GetMatchedLength (2);
	SetHypFieldIsPresent (id_unit, has_unit ? 1 : 0);
	if (has_unit) {
		SetHypFieldLength (id_unit, 8);
	}
}

void SDP_repeat::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_active) {
		static Regex reg_sp ("^" SDPREG_SP);
		reg_sp.AssertMatch (buffer, this);
		reg_sp.MovePast (buffer, 0);
	}
}

void SDP_typed_time_list::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	static Regex reg_sp ("^(" SDPREG_SP ")[0-9]+");
	if (reg_sp.Match (buffer)) {
		reg_sp.MovePast (buffer, 1);
	} else {
		SetHypSize (-2);
	}
}

void SDP_timezone_list::PostDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	static Regex reg_sp ("^(" SDPREG_SP ")[0-9]");
	if (reg_sp.Match (buffer)) {
		reg_sp.MovePast (buffer, 1);
	} else {
		SetHypSize (-2);
	}
}

void SDP_timezone::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_adjustment_time) {
		static Regex reg_tz ("^([1-9][0-9]*)" SDPREG_SP);
		reg_tz.AssertMatch (buffer, this);
		SetHypFieldLength (id_adjustment_time, reg_tz.GetMatchedLength(1));
	} else {
		Unsigned(8).Decode(buffer); // move past the SP
	}
}

void SDP_key::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_method) {
		static Regex reg_method ("^(prompt|clear|base64|uri)(:?)");
		reg_method.AssertMatch (buffer, this);

		if (reg_method.GetMatchedString(1) == "prompt") {
			if (reg_method.GetMatchedLength(2))
				throw DecodeError (this, "the 'prompt' method must not be followed by a value\n");
		} else {
			if (!reg_method.GetMatchedLength(2))
				throw DecodeError (this, "the method must not be followed by a value\n");
		}
		SetHypFieldLength (id_method, reg_method.GetMatchedLength (1));
		SetHypFieldIsPresent (id_key, reg_method.GetMatchedLength (2) ? 1 : 0);
	} else {
		if (GetHypFieldIsPresent (id)) {
			Unsigned(8).Decode(buffer); // move past the colon
			// TODO: validate the content of the key ?
		}
	}
}

class SdpAttributeMap {
public:
	struct Entry {
		Entry (const char* name, int id_attr)
		 : mName (name), mIdAttribute (id_attr)
		 {}
		const std::string	mName;
		const int		mIdAttribute;
	};

	static const Entry& GetByName (const std::string& key)
	{
		const mMapName_t& m = msInstance.mMapName;
		mMapName_t::const_iterator it = m.find (key);
		if (it != m.end()) {
			return *it->second;
		} else {
			return *msInstance.mUndef;
		}
	}
	
	static const Entry& GetByIdMessageHeader (int key)
	{
		const std::map<int, Entry*>& m = msInstance.mMapIdAttribute;
		std::map <int, Entry*>::const_iterator it = m.find (key);
		if (it != m.end()) {
			return *it->second;
		} else {
			return *msInstance.mUndef;
		}
	}


private:
	void AddEntry (const Entry& entry) {
		mEntries.push_back(entry);
		Entry& e = *mEntries.rbegin();
		
		//TODO: check unicity
		mMapName[e.mName] = &e;
		mMapIdAttribute[e.mIdAttribute] = &e;
	}

	SdpAttributeMap() {

#define SDP_ATTRIBUTE_ADD(name)	AddEntry (Entry (#name, SDP_attribute::id_ ## name));

		//		Name
		SDP_ATTRIBUTE_ADD (cat);
		SDP_ATTRIBUTE_ADD (keywds);
		SDP_ATTRIBUTE_ADD (tool);
		SDP_ATTRIBUTE_ADD (ptime);
		SDP_ATTRIBUTE_ADD (recvonly);
		SDP_ATTRIBUTE_ADD (sendrecv);
		SDP_ATTRIBUTE_ADD (sendonly);
		SDP_ATTRIBUTE_ADD (inactive);
		SDP_ATTRIBUTE_ADD (orient);
		AddEntry (Entry ("type", SDP_attribute::id_sdp_type));
		SDP_ATTRIBUTE_ADD (sdp_type);
		SDP_ATTRIBUTE_ADD (charset);
		SDP_ATTRIBUTE_ADD (sdplang);
		SDP_ATTRIBUTE_ADD (lang);
		SDP_ATTRIBUTE_ADD (framerate);
		SDP_ATTRIBUTE_ADD (quality);
		SDP_ATTRIBUTE_ADD (fmtp);
		SDP_ATTRIBUTE_ADD (rtpmap);
		SDP_ATTRIBUTE_ADD (rtcp);
		SDP_ATTRIBUTE_ADD (msrp);

		// RFC 3312 attributes
		SDP_ATTRIBUTE_ADD (curr);
		SDP_ATTRIBUTE_ADD (des);
		SDP_ATTRIBUTE_ADD (conf);
		{
			mEntries.push_back(Entry("", SDP_attribute::id_unknown));
			Entry& e = *mEntries.rbegin();
			mMapIdAttribute[e.mIdAttribute] = &e;
			mUndef = &e;
		}
	}

	static SdpAttributeMap		msInstance;

	std::list<Entry>		mEntries;
	Entry*				mUndef;

	typedef std::map <std::string, Entry*>	mMapName_t;
	mMapName_t			mMapName;
	std::map <int, Entry*>		mMapIdAttribute;
};
SdpAttributeMap SdpAttributeMap::msInstance;

void SDP_attribute::PreDecode (Buffer& buffer) throw (DecodeError)
{
	Regex reg_attr ("^("SDPREG_TOKEN")(?:(:)("SDPREG_BYTE_STRING"))?");
	reg_attr.AssertMatch (buffer, this);

	int id = SdpAttributeMap::GetByName (reg_attr.GetMatchedString(1)).mIdAttribute;
	
	SetHypChosenId (id);
	if (id != id_unknown) {
		SetHypLength (reg_attr.GetMatchedLength(3));
		reg_attr.MovePast(buffer,
			(reg_attr.GetMatchedLength(2) ? 2 : 1) 
		);
	}
}

void SDP_attribute_unknown::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_name) {
		Regex reg_attr ("^("SDPREG_TOKEN")((?::("SDPREG_BYTE_STRING"))?)");
		reg_attr.AssertMatch (buffer, this);

		SetHypFieldLength (id_name, reg_attr.GetMatchedLength (1));

		int val_len = reg_attr.GetMatchedLength(3);
		SetHypFieldIsPresent (id_attr_value, (val_len ? 1 : 0));
		if (val_len)
			SetHypFieldLength (id_attr_value, val_len);
	} else if (GetHypFieldIsPresent (id)){
		Unsigned(8).Decode(buffer); // move past the colon
	}
}

void SDP_attribute_curr::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == 0) {
		Regex reg_attr ("^("SDPREG_TOKEN")" SDPREG_SP "(e2e|local|remote)" SDPREG_SP "(none|sendrecv|send|recv)");
		reg_attr.AssertMatch (buffer, this);

		SetHypFieldLength (0, reg_attr.GetMatchedLength (1));
		SetHypFieldLength (1, reg_attr.GetMatchedLength (2));
		SetHypFieldLength (2, reg_attr.GetMatchedLength (3));

	} else if (GetHypFieldIsPresent (id)){
		Unsigned(8).Decode(buffer); // move past the space
	}
}

void SDP_attribute_des::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == 0) {
		Regex reg_attr ("^("SDPREG_TOKEN")" SDPREG_SP "(mandatory|optional|none|failure|unknown)" SDPREG_SP "(e2e|local|remote)" SDPREG_SP "(none|sendrecv|send|recv)");
		reg_attr.AssertMatch (buffer, this);

		SetHypFieldLength (0, reg_attr.GetMatchedLength (1));
		SetHypFieldLength (1, reg_attr.GetMatchedLength (2));
		SetHypFieldLength (2, reg_attr.GetMatchedLength (3));
		SetHypFieldLength (3, reg_attr.GetMatchedLength (4));

	} else if (GetHypFieldIsPresent (id)){
		Unsigned(8).Decode(buffer); // move past the space
	}
}

void SDP_attribute_conf::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == 0) {
		Regex reg_attr ("^("SDPREG_TOKEN")" SDPREG_SP "(e2e|local|remote)" SDPREG_SP "(none|sendrecv|send|recv)");
		reg_attr.AssertMatch (buffer, this);

		SetHypFieldLength (0, reg_attr.GetMatchedLength (1));
		SetHypFieldLength (1, reg_attr.GetMatchedLength (2));
		SetHypFieldLength (2, reg_attr.GetMatchedLength (3));

	} else if (GetHypFieldIsPresent (id)){
		Unsigned(8).Decode(buffer); // move past the space
	}
}

void SDP_media_field::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
  	static Regex reg_media ("^("SDPREG_TOKEN")" SDPREG_SP "([0-9/]+)" SDPREG_SP "("SDPREG_TOKEN"(?:/"SDPREG_TOKEN")*)" );

	if (id==0) {
		reg_media.AssertMatch (buffer, this);
		SetHypFieldLength (id_media, reg_media.GetMatchedLength (1));
		SetHypFieldLength (id_ports, reg_media.GetMatchedLength (2));
		SetHypFieldLength (id_transport, reg_media.GetMatchedLength (3));
	} else if (id != id_fmts) {
		Unsigned(8).Decode(buffer); // move past the SP
	}
}

void SDP_media_field::PostDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	static Regex reg_space ("^"SDPREG_SP);
	if(id == id_ports) {
		// Necessary as 2nd part of SDP_media_port regex 
		// could be a mismatch (ex: num-of_ports=0) !
		reg_space.AssertMatch(buffer, this);
	}
}

void SDP_media_port::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	static Regex reg_port ("^([0-9]+)(/("SDPREG_INTEGER"))?");

	if (id == 0) {
		reg_port.AssertMatch (buffer, this);
		
		Get_port_number().SetFormat(Integer::AsciiDecimal);
		SetHypFieldLength (id_port_number, reg_port.GetMatchedLength(1));
		if (reg_port.GetMatchedLength (2)) {
			SetHypFieldIsPresent (id_num_of_ports, 1);
			SetHypFieldLength (id_num_of_ports, reg_port.GetMatchedLength(3));
			Get_num_of_ports().SetFormat(Integer::AsciiDecimal);
		} else {
			SetHypFieldIsPresent (id_num_of_ports, 0);
		}
	} else {
		if (GetHypFieldIsPresent (id)) {
			Unsigned(8).Decode(buffer); // move past the '/'
		}
	}
}

void SDP_fmt_list::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	static Regex reg_fmt ("^"SDPREG_SP"("SDPREG_TOKEN")");
	if (reg_fmt.Match (buffer)) {
		reg_fmt.MoveAt (buffer, 1);
		SetHypFieldLength (reg_fmt.GetMatchedLength(1));
	} else {
		SetHypSize (-2);
	}
}

void SDP_media_desc::PreDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	if (id == id_media_field)
		// media_field is always present and the "m=" was
		// already processed by SDP_message::PreDecodeField()
		return;

	static Regex reg_field("^(([a-z])=)([^\\r\\n\\x00]*)");

	const char* expected = NULL;

	switch (id)
	{
	case id_information:		expected = "i";		goto optional_once;
	case id_connections:		expected = "c";		goto optional_many;
	case id_bandwidth:		expected = "b";		goto optional_once;
	case id_key:			expected = "k";		goto optional_once;
	case id_attributes:		expected = "a";		goto optional_many;
	}

	return;
mandatory:
	reg_field.AssertMatch (buffer, this);

	SetHypFieldLength (id, reg_field.GetMatchedLength(3));

	if (reg_field.GetMatchedString(2) != expected) {
		DecodeError e(this);
		e.Msg() << "missing mandatory field " << expected << "= before field " << reg_field.GetMatchedString(1) << endl;
		throw e;
	}
	reg_field.MovePast (buffer, 1);
	return;

optional_once:
	if (reg_field.Match (buffer) &&
	    reg_field.GetMatchedString(2) == expected)
	{
		// the field is present
		SetHypFieldIsPresent (id, 1);
		SetHypFieldLength (id, reg_field.GetMatchedLength(3));
		reg_field.MovePast (buffer, 1);
	} else {
		// the field is not present
		SetHypFieldIsPresent (id, 0);
	}
	return;

optional_many:
	SetHypFieldIsPresent (id,
		(reg_field.Match (buffer) &&
		 reg_field.GetMatchedString(2) == expected)
		? 1
		: 0);
}

void SDP_media_desc::PostDecodeField (int id, Buffer& buffer) throw (DecodeError)
{
	static Regex reg_crlf ("^\r\n");
	if (IsPresent (id)) {
		reg_crlf.AssertMatch (buffer, this);
		reg_crlf.MovePast (buffer);
	}
}

void SDP_media_desc::PostDecode (Buffer& buffer) throw (DecodeError)
{
	buffer.SetPosition(buffer.GetPosition()-16); // go back 2 chars so that the CRLF is matched by the SDP_Message::PostDecodeField();
}


//
// Implementation of the encoder for SDP
//

void SDP_Message::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	if(IsPresent(field_id)) {
		switch(field_id) {
		case id_protocol_version:
			Get_protocol_version().SetFormat(Integer::AsciiDecimal);
			csHeader.SetValue("v=");
			csHeader.Encode(buffer);
			break;		
		case id_origin:
			csHeader.SetValue("o=");
			csHeader.Encode(buffer);
			break;		
		case id_session_name:
			csHeader.SetValue("s=");
			csHeader.Encode(buffer);
			break;		
		case id_information:
			csHeader.SetValue("i=");
			csHeader.Encode(buffer);
			break;		
		case id_uri:
			csHeader.SetValue("u=");
			csHeader.Encode(buffer);
			break;		
		case id_connection:
			csHeader.SetValue("c=");
			csHeader.Encode(buffer);
			break;		
		case id_key:
			csHeader.SetValue("k=");
			csHeader.Encode(buffer);
			break;		
		case id_timezone_adjustments:
			csHeader.SetValue("z=");
			csHeader.Encode(buffer);
			break;		
		}
	}
}

void SDP_Message::PostEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");


	switch(field_id) {
	case id_protocol_version:
	case id_origin:
	case id_session_name:
	case id_information:
	case id_uri:
	case id_connection:
	case id_key:
	case id_timezone_adjustments:
		if(IsPresent(field_id)) {
			csCRLF.Encode(buffer);
		}
		break;
 	case id_emails:
	case id_phone_numbers:
	case id_bandwidth:
	case id_times:
	case id_attributes:
        case id_media_list:
		break;
	}
}

void SDP_Origin::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");

	if(field_id != 0) {
		csSpace.Encode(buffer);
	}
}

void SDP_connection::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");

	if(field_id != 0) {
		csSpace.Encode(buffer);
	}
}

void SDP_conn_addr::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Get_ttl().SetFormat(Integer::AsciiDecimal);
	Get_num_of_addr().SetFormat(Integer::AsciiDecimal);
}

void SDP_conn_addr::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSlash;
	csSlash.SetValue("/");

	if(IsPresent(field_id)) {
		switch(field_id) {
		case id_ttl:
		case id_num_of_addr:
			csSlash.Encode(buffer);
			break;
		}
	}
}

void SDP_email_list::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	csHeader.SetValue("e=");
	csHeader.Encode(buffer);
}

void SDP_email_list::PostEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");

	csCRLF.Encode(buffer);
}

void SDP_phone_list::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	csHeader.SetValue("p=");
	csHeader.Encode(buffer);
}

void SDP_phone_list::PostEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");

	csCRLF.Encode(buffer);
}

void SDP_bandwidth_list::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
}

void SDP_bandwidth::PostEncode (Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");

	csCRLF.Encode(buffer);
}

void SDP_bandwidth::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	csHeader.SetValue("b=");
	csHeader.Encode(buffer);

	Get_bandwidth().SetFormat(Integer::AsciiDecimal);
}

void SDP_bandwidth::PostEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csColon;
	csColon.SetValue(":");

	if(field_id == id_modifier) {
		csColon.Encode(buffer);
	}
}

void SDP_timezone::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");

	if(field_id != 0) {
		csSpace.Encode(buffer);
	}
}

void SDP_timezone_list::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");

	if(field_id != 0) {
		csSpace.Encode(buffer);
	}
}

void SDP_typed_time::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Get_time().SetFormat(Integer::AsciiDecimal);
}

void SDP_attribute_list::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	csHeader.SetValue("a=");
	csHeader.Encode(buffer);
}

void SDP_attribute_list::PostEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");

	csCRLF.Encode(buffer);
}

void SDP_media_field::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	csHeader.SetValue("m=");
	csHeader.Encode(buffer);
}

void SDP_media_field::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");

	if(field_id != 0) {
		csSpace.Encode(buffer);
	}
}

void SDP_fmt_list::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");

	if(field_id != 0) {
		csSpace.Encode(buffer);
	}
}

void SDP_media_port::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Get_port_number().SetFormat(Integer::AsciiDecimal);
}

void SDP_media_port::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSlash;
	csSlash.SetValue("/");

	if((field_id == id_num_of_ports) && IsPresent(id_num_of_ports)) {
		Get_num_of_ports().SetFormat(Integer::AsciiDecimal);
		csSlash.Encode(buffer);
	}
}

void SDP_media_field::PostEncode (Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");

	csCRLF.Encode(buffer);
}

void SDP_media_desc::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	if(IsPresent(field_id)) {
		switch(field_id) {
		case id_information:
			csHeader.SetValue("i=");
			csHeader.Encode(buffer);
			break;		
		case id_key:
			csHeader.SetValue("k=");
			csHeader.Encode(buffer);
			break;		
		}
	}
}

void SDP_media_desc::PostEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");

	switch(field_id) {
	case id_information:
	case id_key:
		if(IsPresent(field_id)) {
			csCRLF.Encode(buffer);
		}
		break;
	}
}

void SDP_connection_list::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	csHeader.SetValue("c=");
	csHeader.Encode(buffer);
}

void SDP_connection_list::PostEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");

	csCRLF.Encode(buffer);
}

void SDP_key::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csColon;
	csColon.SetValue(":");

	if((field_id == id_key) && IsPresent(id_key)) {
		csColon.Encode(buffer);
	}
}

void SDP_contact::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csLeftPar, csSpace;
	csLeftPar.SetValue("(");
	csSpace.SetValue(" ");
	
	if((field_id == id_disp_name) && IsPresent(id_disp_name)) {
		csSpace.Encode(buffer);
		csLeftPar.Encode(buffer);
	}
}

void SDP_contact::PostEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csRightPar;
	csRightPar.SetValue(")");

	if((field_id == id_disp_name) && IsPresent(id_disp_name)) {
		csRightPar.Encode(buffer);
	}
}

void SDP_repeat_list::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	csHeader.SetValue("r=");
	csHeader.Encode(buffer);
}

void SDP_repeat_list::PostEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");

	csCRLF.Encode(buffer);
}

void SDP_repeat::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");

	if(field_id == id_active) {
		csSpace.Encode(buffer);
	}
}

void SDP_typed_time_list::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");

	csSpace.Encode(buffer);
}

void SDP_time_field::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csHeader;

	csHeader.SetValue("t=");
	csHeader.Encode(buffer);
}

void SDP_time_field::PostEncode (Buffer& buffer) throw (EncodeError)
{
 	Charstring csCRLF;
	csCRLF.SetValue("\r\n");

	csCRLF.Encode(buffer);
}

void SDP_time_field::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");

	if(field_id == id_stop_time) {
		csSpace.Encode(buffer);
	}
}





void SDP_attribute_cat::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("cat:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_keywds::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("keywds:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_tool::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("tool:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_ptime::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("ptime:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_recvonly::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("recvonly");

	csAttrName.Encode(buffer);
}

void SDP_attribute_sendrecv::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("sendrecv");

	csAttrName.Encode(buffer);
}

void SDP_attribute_sendonly::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("sendonly");

	csAttrName.Encode(buffer);
}

void SDP_attribute_inactive::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("inactive");

	csAttrName.Encode(buffer);
}

void SDP_attribute_orient::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("orient:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_type::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("type:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_charset::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("charset:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_sdplang::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("sdplang:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_lang::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("lang:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_framerate::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("framerate:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_quality::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("quality:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_fmtp::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("fmtp:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_curr::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("curr:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_curr::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");
	
	if(field_id != 0) {
		csSpace.Encode(buffer);
	}
}

void SDP_attribute_des::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("des:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_des::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");
	
	if(field_id != 0) {
		csSpace.Encode(buffer);
	}
}

void SDP_attribute_conf::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("conf:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_conf::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csSpace;
	csSpace.SetValue(" ");
	
	if(field_id != 0) {
		csSpace.Encode(buffer);
	}
}

void SDP_attribute_rtpmap::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("rtpmap:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_rtcp::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("rtcp:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_msrp::PreEncode (Buffer& buffer) throw (EncodeError)
{
	Charstring csAttrName;
	csAttrName.SetValue("msrp:");

	csAttrName.Encode(buffer);
}

void SDP_attribute_unknown::PreEncodeField (int field_id, Buffer& buffer) throw (EncodeError)
{
	Charstring csColon;
	csColon.SetValue(":");

	if((field_id == id_attr_value) && IsPresent(id_attr_value)) {
		csColon.Encode(buffer);
	}
}




}} // namespaces

