/*
 * Copyright (C) 2011, 2012, 2013 Citrix Systems
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef __LIB_TURN_MSG_CPP__
#define __LIB_TURN_MSG_CPP__

#include "ns_turn_ioaddr.h"
#include "ns_turn_msg.h"

#include <string>

namespace turn {

class StunAttr;

/**
 * Exception "end of buffer"
 */
class EndOfStunMsgException {
public:
	EndOfStunMsgException() {}
	virtual ~EndOfStunMsgException() {}
};

/**
 * Exception "wrong format of StunAttr"
 */
class WrongStunAttrFormatException {
public:
	WrongStunAttrFormatException() {}
	virtual ~WrongStunAttrFormatException() {}
};

/**
 * Exception "wrong format of StunBuffer"
 */
class WrongStunBufferFormatException {
public:
	WrongStunBufferFormatException() {}
	virtual ~WrongStunBufferFormatException() {}
};

/**
 * Iterator class for attributes
 */
class StunAttrIterator {
public:
	/**
	 * Iterator constructor: creates iterator on raw messagebuffer.
	 */
	StunAttrIterator(uint8_t *buf, size_t sz) :
		_buf(buf), _sz(sz)  {
		if(!stun_is_command_message_str(_buf, _sz)) {
			throw WrongStunBufferFormatException();
		}
		_sar = stun_attr_get_first_str(_buf, _sz);
	}

	/**
	 * Iterator constructor: create iterator over message.
	 */
	template<class T>
	StunAttrIterator(T &msg) :
		_buf(msg.getRawBuffer()), _sz(msg.getSize())  {
		if(!stun_is_command_message_str(_buf, _sz)) {
			throw WrongStunBufferFormatException();
		}
		_sar = stun_attr_get_first_str(_buf, _sz);
	}

	/**
	 * Iterator constructor: creates iterator over raw buffer, starting from first
	 * location of an attribute of particular type.
	 */
	StunAttrIterator(uint8_t *buf, size_t sz, uint16_t attr_type) :
			_buf(buf), _sz(sz)  {
		if(!stun_is_command_message_str(_buf, _sz)) {
			throw WrongStunBufferFormatException();
		}
		_sar = stun_attr_get_first_by_type_str(_buf, _sz, attr_type);
	}

	/**
	 * Iterator constructor: creates iterator over message, starting from first
	 * location of an attribute of particular type.
	 */
	template<class T>
	StunAttrIterator(T &msg, uint16_t attr_type) :
			_buf(msg.getRawBuffer()), _sz(msg.getSize())  {
		if(!stun_is_command_message_str(_buf, _sz)) {
			throw WrongStunBufferFormatException();
		}
		_sar = stun_attr_get_first_by_type_str(_buf, _sz, attr_type);
	}

	/**
	 * Moves iterator to next attribute location
	 */
	void next() {
		if(!_sar) {
			throw EndOfStunMsgException();
		}
		_sar = stun_attr_get_next_str(_buf,_sz,_sar);
	}

	/**
	 * Is the iterator finished
	 */
	bool eof() const {
		return (!_sar);
	}

	/**
	 * Is the iterator at an address attribute
	 */
	bool isAddr() const {
		return stun_attr_is_addr(_sar);
	}

	/**
	 * Return address family attribute value (if the iterator at the "address family" attribute.
	 */
	int getAddressFamily() const {
		return stun_get_requested_address_family(_sar);
	}

	/**
	 * Get attribute type
	 */
	int getType() const {
		return stun_attr_get_type(_sar);
	}

	/**
	 * Destructor
	 */
	virtual ~StunAttrIterator() {}

	/**
	 * Return raw memroy field of the attribute value.
	 * If the attribute value length is zero (0), then return NULL.
	 */
	const uint8_t *getRawBuffer(size_t &sz) const {
		int len = stun_attr_get_len(_sar);
		if(len<0)
			throw WrongStunAttrFormatException();
		sz = (size_t)len;
		const uint8_t *value = stun_attr_get_value(_sar);
		return value;
	}
	friend class StunAttr;
private:
	uint8_t *_buf;
	size_t _sz;
	stun_attr_ref _sar;
};

/**
 * Root class of all STUN attributes.
 * Can be also used for a generic attribute object.
 */
class StunAttr {
public:
	/**
	 * Empty constructor
	 */
	StunAttr() : _attr_type(0), _value(0), _sz(0) {}

	/**
	 * Constructs attribute from iterator
	 */
	StunAttr(const StunAttrIterator &iter) {
		if(iter.eof()) {
			throw EndOfStunMsgException();
		}
		size_t sz = 0;
		const uint8_t *ptr = iter.getRawBuffer(sz);
		if(sz>=0xFFFF)
			throw WrongStunAttrFormatException();
		int at = iter.getType();
		if(at<0)
			throw WrongStunAttrFormatException();
		_attr_type = (uint16_t)at;
		_sz = sz;
		_value=(uint8_t*)malloc(_sz);
		if(ptr)
			bcopy(ptr,_value,_sz);
	}

	/**
	 * Destructor
	 */
	virtual ~StunAttr() {
		if(_value)
			free(_value);
	}

	/**
	 * Return raw data representation of the attribute
	 */
	const uint8_t *getRawValue(size_t &sz) const {
		sz=_sz;
		return _value;
	}

	/**
	 * Set raw data value
	 */
	void setRawValue(uint8_t *value, size_t sz) {
		if(sz>0xFFFF)
			throw WrongStunAttrFormatException();
		if(_value)
			free(_value);
		_sz = sz;
		_value=(uint8_t*)malloc(_sz);
		if(value)
			bcopy(value,_value,_sz);
	}

	/**
	 * Get attribute type
	 */
	uint16_t getType() const {
		return _attr_type;
	}

	/**
	 * Set attribute type
	 */
	void setType(uint16_t at) {
		_attr_type = at;
	}

	/**
	 * Add attribute to a message
	 */
	template<class T>
	int addToMsg(T &msg) {
		if(!_attr_type)
			throw WrongStunAttrFormatException();
		uint8_t *buffer = msg.getRawBuffer();
		if(buffer) {
			size_t sz = msg.getSize();
			if(addToBuffer(buffer, sz)<0) {
				throw WrongStunBufferFormatException();
			}
			msg.setSize(sz);
			return 0;
		}
		throw WrongStunBufferFormatException();
	}
protected:

	/**
	 * Virtual function member to add attribute to a raw buffer
	 */
	virtual int addToBuffer(uint8_t *buffer, size_t &sz) {
		if(buffer) {
			if(!_value)
				throw WrongStunAttrFormatException();
			if(stun_attr_add_str(buffer, &sz, _attr_type, _value, _sz)<0) {
				throw WrongStunBufferFormatException();
			}
			return 0;
		}
		throw WrongStunBufferFormatException();
	}

	/**
	 * Get low-level iterator object
	 */
	static stun_attr_ref getSar(const StunAttrIterator &iter) {
		return iter._sar;
	}
private:
	uint16_t _attr_type;
	uint8_t *_value;
	size_t _sz;
};

/**
 * Channel number attribute class
 */
class StunAttrChannelNumber : public StunAttr {
public:
	StunAttrChannelNumber() : _cn(0) {
		setType(STUN_ATTRIBUTE_CHANNEL_NUMBER);
	}
	StunAttrChannelNumber(const StunAttrIterator &iter) :
		StunAttr(iter) {

		if(iter.eof())
			throw EndOfStunMsgException();
		_cn = stun_attr_get_channel_number(getSar(iter));
		if(!_cn)
			throw WrongStunAttrFormatException();
	}
	virtual ~StunAttrChannelNumber() {}
	uint16_t getChannelNumber() const {
		return _cn;
	}
	void setChannelNumber(uint16_t cn) {
		_cn = cn;
	}
protected:
	virtual int addToBuffer(uint8_t *buffer, size_t &sz) {
		return stun_attr_add_channel_number_str(buffer,&sz,_cn);
	}
private:
	uint16_t _cn;
};

/**
 * Even port attribute class
 */
class StunAttrEvenPort : public StunAttr {
public:
	StunAttrEvenPort() : _ep(0) {
		setType(STUN_ATTRIBUTE_EVEN_PORT);
	}
	StunAttrEvenPort(const StunAttrIterator &iter) :
		StunAttr(iter) {

		if(iter.eof())
			throw EndOfStunMsgException();
		_ep = stun_attr_get_even_port(getSar(iter));
	}
	virtual ~StunAttrEvenPort() {}
	uint8_t getEvenPort() const {
		return _ep;
	}
	void setEvenPort(uint8_t ep) {
		_ep = ep;
	}
protected:
	virtual int addToBuffer(uint8_t *buffer, size_t &sz) {
		return stun_attr_add_str(buffer, &sz, STUN_ATTRIBUTE_EVEN_PORT, &_ep, 1);
	}
private:
	uint8_t _ep;
};

/**
 * Reservation token attribute class
 */
class StunAttrReservationToken : public StunAttr {
public:
	StunAttrReservationToken() : _rt(0) {
		setType(STUN_ATTRIBUTE_RESERVATION_TOKEN);
	}
	StunAttrReservationToken(const StunAttrIterator &iter) :
		StunAttr(iter) {

		if(iter.eof())
			throw EndOfStunMsgException();
		_rt = stun_attr_get_reservation_token_value(getSar(iter));
	}
	virtual ~StunAttrReservationToken() {}
	uint64_t getReservationToken() const {
		return _rt;
	}
	void setReservationToken(uint64_t rt) {
		_rt = rt;
	}
protected:
	virtual int addToBuffer(uint8_t *buffer, size_t &sz) {
		uint64_t reservation_token = ioa_ntoh64(_rt);
		return stun_attr_add_str(buffer, &sz, STUN_ATTRIBUTE_RESERVATION_TOKEN, (uint8_t*) (&reservation_token), 8);
	}
private:
	uint64_t _rt;
};

/**
 * This attribute class is used for all address attributes
 */
class StunAttrAddr : public StunAttr {
public:
	StunAttrAddr(uint16_t attr_type = 0) {
		addr_set_any(&_addr);
		setType(attr_type);
	}
	StunAttrAddr(const StunAttrIterator &iter) :
		StunAttr(iter) {

		if(iter.eof())
			throw EndOfStunMsgException();
		size_t sz = 0;
		const uint8_t *buf = iter.getRawBuffer(sz);
		if(stun_attr_get_addr_str(buf,sz,getSar(iter),&_addr,NULL)<0) {
			throw WrongStunAttrFormatException();
		}
	}
	virtual ~StunAttrAddr() {}
	void getAddr(ioa_addr &addr) const {
		addr_cpy(&addr,&_addr);
	}
	void setAddr(ioa_addr &addr) {
		addr_cpy(&_addr,&addr);
	}
protected:
	virtual int addToBuffer(uint8_t *buffer, size_t &sz) {
		return stun_attr_add_addr_str(buffer, &sz, getType(), &_addr);
	}
private:
	ioa_addr _addr;
};

/**
 * Change Request attribute class
 */
class StunAttrChangeRequest : public StunAttr {
public:
	StunAttrChangeRequest() : _changeIp(0), _changePort(0) {
		setType(STUN_ATTRIBUTE_CHANGE_REQUEST);
	}
	StunAttrChangeRequest(const StunAttrIterator &iter) :
		StunAttr(iter) {

		if(iter.eof())
			throw EndOfStunMsgException();

		if(stun_attr_get_change_request_str(getSar(iter), &_changeIp, &_changePort)<0) {
			throw WrongStunAttrFormatException();
		}
	}
	virtual ~StunAttrChangeRequest() {}
	bool getChangeIp() const {
		return _changeIp;
	}
	void setChangeIp(bool ci) {
		if(ci)
			_changeIp = 1;
		else
			_changeIp = 0;
	}
	bool getChangePort() const {
		return _changePort;
	}
	void setChangePort(bool cp) {
		if(cp)
			_changePort = 1;
		else
			_changePort = 0;
	}
protected:
	virtual int addToBuffer(uint8_t *buffer, size_t &sz) {
		return stun_attr_add_change_request_str(buffer, &sz, _changeIp, _changePort);
	}
private:
	int _changeIp;
	int _changePort;
};

/**
 * Change Request attribute class
 */
class StunAttrResponsePort : public StunAttr {
public:
	StunAttrResponsePort() : _rp(0) {
		setType(STUN_ATTRIBUTE_RESPONSE_PORT);
	}
	StunAttrResponsePort(const StunAttrIterator &iter) :
		StunAttr(iter) {

		if(iter.eof())
			throw EndOfStunMsgException();

		int rp = stun_attr_get_response_port_str(getSar(iter));
		if(rp<0) {
			throw WrongStunAttrFormatException();
		}
		_rp = (uint16_t)rp;
	}
	virtual ~StunAttrResponsePort() {}
	uint16_t getResponsePort() const {
		return _rp;
	}
	void setResponsePort(uint16_t p) {
		_rp = p;
	}
protected:
	virtual int addToBuffer(uint8_t *buffer, size_t &sz) {
		return stun_attr_add_response_port_str(buffer, &sz, _rp);
	}
private:
	uint16_t _rp;
};

/**
 * Padding attribute class
 */
class StunAttrPadding : public StunAttr {
public:
	StunAttrPadding() : _p(0) {
		setType(STUN_ATTRIBUTE_PADDING);
	}
	StunAttrPadding(const StunAttrIterator &iter) :
		StunAttr(iter) {

		if(iter.eof())
			throw EndOfStunMsgException();

		int p = stun_attr_get_padding_len_str(getSar(iter));
		if(p<0) {
			throw WrongStunAttrFormatException();
		}
		_p = (uint16_t)p;
	}
	virtual ~StunAttrPadding() {}
	uint16_t getPadding() const {
		return _p;
	}
	/**
	 * Set length of padding
	 */
	void setPadding(uint16_t p) {
		_p = p;
	}
protected:
	virtual int addToBuffer(uint8_t *buffer, size_t &sz) {
		return stun_attr_add_padding_str(buffer, &sz, _p);
	}
private:
	uint16_t _p;
};

/**
 * Generic "STUN Message" class, base class for all messages
 */
class StunMsg {
public:
	/**
	 * Empty constructor
	 */
	StunMsg() {
		_allocated_sz = 0xFFFF;
		_buffer = (uint8_t*)malloc(_allocated_sz);
		_deallocate = true;
		_sz = 0;
		_constructed = 0;
	}

	/**
	 * Construct message over raw buffer.
	 * Parameter "construct" is true if the buffer is initialized.
	 */
	StunMsg(uint8_t *buffer, size_t total_sz, size_t sz, bool constructed) :
		_buffer(buffer), _deallocate(false), _allocated_sz(total_sz),
		_sz(sz), _constructed(constructed) {}

	/**
	 * Destructor
	 */
	virtual ~StunMsg() {
		if(_deallocate && _buffer) {
			free(_buffer);
		}
	}

	/**
	 * Initialize buffer
	 */
	void construct() {
		constructBuffer();
	}

	/**
	 * Checks if the message is properly constructed
	 */
	bool isValid() {
		return check();
	}

	/**
	 * get raw buffer
	 */
	uint8_t *getRawBuffer() {
		return _buffer;
	}

	/**
	 * Get message size in the buffer (message can be mnuch smaller than the whole buffer)
	 */
	size_t getSize() const {
		return _sz;
	}

	/**
	 * Set message size
	 */
	void setSize(size_t sz) {
		if(sz>_allocated_sz)
			throw WrongStunBufferFormatException();
		_sz = sz;
	}

	/**
	 * Check if the raw buffer is a TURN "command" (request, response or indication).
	 */
	static bool isCommand(uint8_t *buffer, size_t sz) {
		return stun_is_command_message_str(buffer, sz);
	}

	/**
	 * Check if the current message object is a "command" (request, response, or indication).
	 */
	bool isCommand() const {
		return stun_is_command_message_str(_buffer, _sz);
	}

	static bool isIndication(uint8_t *buffer, size_t sz) {
		return stun_is_indication_str(buffer, sz);
	}

	static bool isRequest(uint8_t *buffer, size_t sz) {
		return stun_is_request_str(buffer, sz);
	}

	static bool isSuccessResponse(uint8_t *buffer, size_t sz) {
		return stun_is_success_response_str(buffer, sz);
	}

	static bool isErrorResponse(uint8_t *buffer, size_t sz,
					int &err_code, uint8_t *err_msg, size_t err_msg_size) {
		return stun_is_error_response_str(buffer, sz, &err_code, err_msg, err_msg_size);
	}

	/**
	 * Check if the raw buffer is a challenge response (the one with 401 error and realm and nonce values).
	 */
	static bool isChallengeResponse(const uint8_t* buf, size_t sz,
					int &err_code, uint8_t *err_msg, size_t err_msg_size,
					uint8_t *realm, uint8_t *nonce,
					uint8_t *server_name, int *oauth) {
		return stun_is_challenge_response_str(buf, sz, &err_code, err_msg, err_msg_size, realm, nonce, server_name, oauth);
	}

	/**
	 * Check if the message is a channel message
	 */
	static bool isChannel(uint8_t *buffer, size_t sz) {
		return is_channel_msg_str(buffer, sz);
	}

	/**
	 * Check if the fingerprint is present.
	 */
	static bool isFingerprintPresent(uint8_t *buffer, size_t sz) {
		if(!stun_is_command_message_str(buffer,sz))
			return false;
		stun_attr_ref sar = stun_attr_get_first_by_type_str(buffer, sz, STUN_ATTRIBUTE_FINGERPRINT);
		if(!sar)
			return false;

		return true;
	}

	/**
	 * Check the fingerprint
	 */
	static bool checkFingerprint(uint8_t *buffer, size_t sz) {
		return stun_is_command_message_full_check_str(buffer, sz, 1, NULL);
	}

	/**
	 * Add attribute to the message
	 */
	int addAttr(StunAttr &attr) {
		return attr.addToMsg(*this);
	}

	/**
	 * Get transaction ID
	 */
	virtual stun_tid getTid() const {
		if(!_constructed || !isCommand())
			throw WrongStunBufferFormatException();
		stun_tid tid;
		stun_tid_from_message_str(_buffer,_sz,&tid);
		return tid;
	}

	/**
	 * Set transaction ID
	 */
	virtual void setTid(stun_tid &tid) {
		if(!_constructed || !isCommand())
			throw WrongStunBufferFormatException();
		stun_tid_message_cpy(_buffer, &tid);
	}

	/**
	 * Add fingerprint to the message
	 */
	void addFingerprint() {
		if(!_constructed || !isCommand())
			throw WrongStunBufferFormatException();
		stun_attr_add_fingerprint_str(_buffer,&_sz);
	}

	/**
	 * Check message integrity, in secure communications.
	 */
	bool checkMessageIntegrity(turn_credential_type ct, std::string &uname, std::string &realm, std::string &upwd) const {
		if(!_constructed || !isCommand())
			throw WrongStunBufferFormatException();
		uint8_t *suname=(uint8_t*)strdup(uname.c_str());
		uint8_t *srealm=(uint8_t*)strdup(realm.c_str());
		uint8_t *supwd=(uint8_t*)strdup(upwd.c_str());
		SHATYPE sht = SHATYPE_SHA1;
		bool ret = (0< stun_check_message_integrity_str(ct,_buffer, _sz, suname, srealm, supwd, sht));
		free(suname);
		free(srealm);
		free(supwd);
		return ret;
	}

	/**
	 * Adds long-term message integrity data to the message.
	 */
	void addLTMessageIntegrity(std::string &uname, std::string &realm, std::string &upwd, std::string &nonce) {

		if(!_constructed || !isCommand())
			throw WrongStunBufferFormatException();

		uint8_t *suname=(uint8_t*)strdup(uname.c_str());
		uint8_t *srealm=(uint8_t*)strdup(realm.c_str());
		uint8_t *supwd=(uint8_t*)strdup(upwd.c_str());
		uint8_t *snonce=(uint8_t*)strdup(nonce.c_str());

		stun_attr_add_integrity_by_user_str(_buffer, &_sz, suname, srealm, supwd, snonce, SHATYPE_SHA1);

		free(suname);
		free(srealm);
		free(supwd);
		free(snonce);
	}

	/**
	 * Adds short-term message integrity data to the message.
	 */
	void addSTMessageIntegrity(std::string &uname, std::string &upwd) {

		if(!_constructed || !isCommand())
			throw WrongStunBufferFormatException();

		uint8_t *suname=(uint8_t*)strdup(uname.c_str());
		uint8_t *supwd=(uint8_t*)strdup(upwd.c_str());

		stun_attr_add_integrity_by_user_short_term_str(_buffer, &_sz, suname, supwd, SHATYPE_SHA1);

		free(suname);
		free(supwd);
	}

protected:
	virtual void constructBuffer() = 0;
	virtual bool check() = 0;
protected:
	uint8_t *_buffer;
	bool _deallocate;
	size_t _allocated_sz;
	size_t _sz;
	bool _constructed;
};

/**
 * Class that represents the "request" flavor of STUN/TURN messages.
 */
class StunMsgRequest : public StunMsg {
public:
	StunMsgRequest(uint16_t method) : _method(method) {};
	StunMsgRequest(uint8_t *buffer, size_t total_sz, size_t sz, bool constructed) :
			StunMsg(buffer,total_sz,sz,constructed),_method(0) {

		if(constructed) {
			if(!stun_is_request_str(buffer,sz)) {
				throw WrongStunBufferFormatException();
			}
			_method = stun_get_method_str(buffer,sz);
		}
	}
	virtual ~StunMsgRequest() {}

	/**
	 * Get request method
	 */
	uint16_t getMethod() const {
		return _method;
	}

	/**
	 * Set method
	 */
	void setMethod(uint16_t method) {
		_method = method;
	}

	/**
	 * Construct binding request
	 */
	void constructBindingRequest() {
		stun_set_binding_request_str(_buffer, &_sz);
	}

	bool isBindingRequest() const {
		return stun_is_binding_request_str(_buffer,_sz,0);
	}

	/**
	 * Construct allocate request
	 */
	void constructAllocateRequest(uint32_t lifetime, int af4, int af6, uint8_t transport, int mobile, const char* rt, int ep) {
		stun_set_allocate_request_str(_buffer, &_sz, lifetime, af4, af6, transport, mobile, rt, ep);
	}

	/**
	 * Construct channel bind request
	 */
	void constructChannelBindRequest(const ioa_addr &peer_addr, uint16_t channel_number) {
		stun_set_channel_bind_request_str(_buffer, &_sz,
					&peer_addr, channel_number);
	}

protected:
	virtual void constructBuffer() {
		stun_init_request_str(_method,_buffer,&_sz);
		_constructed = true;
	}

	virtual bool check() {
		if(!_constructed)
			return false;
		if(!stun_is_request_str(_buffer,_sz)) {
			return false;
		}
		if(_method != stun_get_method_str(_buffer,_sz)) {
			return false;
		}
		return true;
	}

private:
	uint16_t _method;
};

/**
 * Class for STUN/TURN responses
 */
class StunMsgResponse : public StunMsg {
public:
	StunMsgResponse(uint16_t method, stun_tid &tid) : _method(method), _err(0), _reason(""), _tid(tid) {};
	StunMsgResponse(uint16_t method, int error_code, std::string reason, stun_tid &tid) :
		_method(method), _err(error_code), _reason(reason), _tid(tid) {

	};
	StunMsgResponse(uint8_t *buffer, size_t total_sz, size_t sz, bool constructed) :
			StunMsg(buffer,total_sz,sz,constructed),_method(0),_err(0),_reason("") {

		if(constructed) {
			if(!stun_is_success_response_str(buffer,sz)) {
				uint8_t errtxt[0xFFFF];
				if(!stun_is_error_response_str(buffer,sz,&_err,errtxt,sizeof(errtxt))) {
					throw WrongStunBufferFormatException();
				}
				_reason = (char*)errtxt;
			}
			_method = stun_get_method_str(buffer,sz);
			stun_tid_from_message_str(_buffer,_sz,&_tid);
		}
	}

	uint16_t getMethod() const {
		return _method;
	}

	void setMethod(uint16_t method) {
		_method = method;
	}

	/**
	 * Get error code
	 */
	int getError() const {
		return _err;
	}

	/**
	 * Set error code
	 */
	void setError(int err) {
		_err = err;
	}

	/**
	 * Get error message
	 */
	std::string getReason() const {
		return _reason;
	}

	/**
	 * Set error message
	 */
	void setReason(std::string reason) {
		_reason = reason;
	}

	/**
	 * Set transaction ID
	 */
	void setTid(stun_tid &tid) {
		_tid = tid;
	}

	/**
	 * Get transaction ID
	 */
	virtual stun_tid getTid() const {
		return _tid;
	}

	/**
	 * Check if this is a challenge response, and return realm and nonce
	 */
	bool isChallenge(std::string &realm, std::string &nonce) const {
		bool ret = false;
		if(_constructed) {
			int err_code;
			uint8_t err_msg[1025];
			size_t err_msg_size=sizeof(err_msg);
			uint8_t srealm[0xFFFF];
			uint8_t snonce[0xFFFF];
			ret = stun_is_challenge_response_str(_buffer, _sz, &err_code, err_msg, err_msg_size, srealm, snonce, NULL, NULL);
			if(ret) {
				realm = (char*)srealm;
				nonce = (char*)snonce;
			}
		}
		return ret;
	}

	bool isChallenge() const {
		std::string realm, nonce;
		return isChallenge(realm, nonce);
	}

	/**
	 * Check if this is a success response
	 */
	bool isSuccess() const {
		return (_err == 0);
	}

	/**
	 * Construct binding response
	 */
	void constructBindingResponse(stun_tid &tid,
				const ioa_addr &reflexive_addr, int error_code,
				const uint8_t *reason) {

		stun_set_binding_response_str(_buffer, &_sz, &tid,
					&reflexive_addr, error_code,
					reason, 0 , 0 , 1);
	}

	bool isBindingResponse() const {
		return stun_is_binding_response_str(_buffer,_sz);
	}

	/**
	 * Construct allocate response
	 */
	void constructAllocateResponse(stun_tid &tid,
					   const ioa_addr &relayed_addr1,
					   const ioa_addr &relayed_addr2,
					   const ioa_addr &reflexive_addr,
					   uint32_t lifetime, int error_code, const uint8_t *reason,
					   uint64_t reservation_token, char *mobile_id) {

		stun_set_allocate_response_str(_buffer, &_sz, &tid,
						   &relayed_addr1, &relayed_addr2,
						   &reflexive_addr,
						   lifetime, STUN_DEFAULT_MAX_ALLOCATE_LIFETIME, error_code, reason,
						   reservation_token, mobile_id);
	}

	/**
	 * Construct channel bind response
	 */
	void constructChannelBindResponse(stun_tid &tid, int error_code, const uint8_t *reason) {
		stun_set_channel_bind_response_str(_buffer, &_sz, &tid, error_code, reason);
	}

protected:
	virtual void constructBuffer() {
		if(_err) {
			stun_init_error_response_str(_method, _buffer, &_sz, _err, (const uint8_t*)_reason.c_str(), &_tid);
		} else {
			stun_init_success_response_str(_method, _buffer, &_sz, &_tid);
		}
		_constructed = true;
	}

	virtual bool check() {
		if(!_constructed)
			return false;
		if(!stun_is_success_response_str(_buffer,_sz)) {
			uint8_t errtxt[0xFFFF];
			int cerr=0;
			if(!stun_is_error_response_str(_buffer,_sz,&cerr,errtxt,sizeof(errtxt))) {
				throw WrongStunBufferFormatException();
			}
			if(cerr != _err) {
				throw WrongStunBufferFormatException();
			}
		}
		if(_method != stun_get_method_str(_buffer,_sz)) {
			return false;
		}
		return true;
	}

private:
	uint16_t _method;
	int _err;
	std::string _reason;
	stun_tid _tid;
};

/**
 * Class for STUN/TURN indications
 */
class StunMsgIndication : public StunMsg {
public:
	StunMsgIndication(uint16_t method) : _method(method) {};
	StunMsgIndication(uint8_t *buffer, size_t total_sz, size_t sz, bool constructed) :
			StunMsg(buffer,total_sz,sz,constructed),_method(0) {

		if(constructed) {
			if(!stun_is_indication_str(buffer,sz)) {
				throw WrongStunBufferFormatException();
			}
			_method = stun_get_method_str(buffer,sz);
		}
	}
	virtual ~StunMsgIndication() {}

	uint16_t getMethod() const {
		return _method;
	}

	void setMethod(uint16_t method) {
		_method = method;
	}

protected:
	virtual void constructBuffer() {
		stun_init_indication_str(_method,_buffer,&_sz);
		_constructed = true;
	}

	virtual bool check() {
		if(!_constructed)
			return false;
		if(!stun_is_indication_str(_buffer,_sz)) {
			return false;
		}
		if(_method != stun_get_method_str(_buffer,_sz)) {
			return false;
		}
		return true;
	}

private:
	uint16_t _method;
};

/**
 * Channel message
 */
class StunMsgChannel : public StunMsg {
public:
	StunMsgChannel(uint16_t cn, int length) : _cn(cn), _len(length) {};
	StunMsgChannel(uint8_t *buffer, size_t total_sz, size_t sz, bool constructed) :
			StunMsg(buffer,total_sz,sz,constructed),_cn(0) {

		if(constructed) {
			if(!stun_is_channel_message_str(buffer,&_sz,&_cn,0)) {
				throw WrongStunBufferFormatException();
			}
			if(_sz>0xFFFF || _sz<4)
				throw WrongStunBufferFormatException();

			_len = _sz-4;
		} else {
			if(total_sz>0xFFFF || total_sz<4)
				throw WrongStunBufferFormatException();

			_len = 0;
		}
	}
	virtual ~StunMsgChannel() {}

	uint16_t getChannelNumber() const {
		return _cn;
	}

	void setChannelNumber(uint16_t cn) {
		_cn = cn;
	}

	/**
	 * Get length of message itself (excluding the 4 channel number bytes)
	 */
	size_t getLength() const {
		return _len;
	}

	/**
	 * Set length of message itself (excluding the 4 channel number bytes)
	 */
	void setLength(size_t len) {
		_len = len;
	}

protected:
	virtual void constructBuffer() {
		stun_init_channel_message_str(_cn,_buffer,&_sz,(int)_len,0);
		_constructed = true;
	}

	virtual bool check() {
		if(!_constructed)
			return false;
		uint16_t cn = 0;
		if(!stun_is_channel_message_str(_buffer,&_sz,&cn,0)) {
			return false;
		}
		if(_cn != cn) {
			return false;
		}
		return true;
	}

private:
	uint16_t _cn;
	size_t _len;
};

}
/* namespace */

#endif
/* __LIB_TURN_MSG_CPP__ */
