/*

Copyright (c) 2002-2005, Yauheni Akhotnikau
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

- 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.

- The name of the author may not be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.

*/

/*!
	\file oess_1/tlv/h/base.hpp
	\brief Базовый классы и интерфейсы компонента tlv.
*/

#if !defined( _OESS_1__TLV__BASE_HPP_ )
#define _OESS_1__TLV__BASE_HPP_

#include <oess_1/tlv/h/declspec.hpp>

#include <oess_1/io/h/binstream.hpp>

namespace oess_1
{

namespace tlv
{

//! Тип, к которому должны приводится все идентификаторы TLV.
/*!
	В зависимости от конкретной ситуации поле Tag может
	представляться 8-битовым, 16-битовым, 32-битовым или,
	например, 24-битовым значением. Может быть, поле Tag
	может быть представлено какой-либо структурой.

	Но для компонента tlv необходимо, чтобы любое значение
	поля Tag можно было представить в виде уникального
	значения типа defaul_tag_type_t.
*/
typedef oess_1::uint_t	default_tag_type_t;

//
// tag_processor_t
//

//! Интерфейс обработчика поля Tag в двоичном потоке.
/*!
	Отвечает за чтение и запись поля Tag.

	В некоторых случаях Tag может требовать нескольких
	значений в потоке. Например, на поле Tag отводится
	один байт, если Tag находится в диапазоне от 0 до 127.
	Два байта, если Tag находится в диапазоне от 128 до
	382. Тогда первый байт (b0) содержит 0x80, a второй (b1)
	дополнительное значение. Результирующее значение
	Tag получается сложением b0 и b1.

	Для того чтобы TLV-классы не зависели от способа
	представления Tag, каждый из TLV-классов будет связываться
	с конкретной реализацией tag_processor_t.
*/
class OESS_1__TLV__TYPE	tag_processor_t
{
	public :
		virtual ~tag_processor_t();

		//! Определить количество байт, которые требуются
		//! для представления поля Tag.
		virtual size_t
		tlv_tag_size() const = 0;

		//! Прочитать Tag из входного потока.
		virtual void
		tlv_unpack_tag(
			//! Поток, с текущей позиции которого будет
			//! осуществляться чтение поля Tag.
			oess_1::io::ibinstream_t & s ) = 0;

		//! Записать Tag в выходной поток.
		virtual void
		tlv_pack_tag(
			//! Поток, с текущей позиции которого будет
			//! осуществляться запись поля Tag.
			oess_1::io::obinstream_t & s ) const = 0;

		//! Получить текущее значение Tag.
		virtual default_tag_type_t
		query_tag() const = 0;

		//! Установить текущее значение Tag.
		virtual void
		set_tag( const default_tag_type_t & value ) = 0;
};

//
// length_processor_t
//

//! Интерфейс обработчика поля Length в двоичном потоке.
/*!
	Отвечает за чтение и запись поля Length.

	В некоторых случаях Length может требовать нескольких
	значений в потоке. Например, на поле Length отводится
	один байт, если Length находится в диапазоне от 0 до 127.
	Два байта, если Length находится в диапазоне от 128 до
	255. Тогда первый байт (b0) содержит 0x81, a второй
	дополнительное значение (0x80-0xFF). См. GSM 11.14 v.8.3.0 1999
	Annex D: Structure of SIM Application Toolkit communications
	(http://www.etsi.org).

	Для того чтобы TLV-классы не зависели от способа
	представления Length, каждый из TLV-классов будет связываться
	с конкретной реализацией length_processor_t.
*/
class OESS_1__TLV__TYPE	length_processor_t
{
	public :
		virtual ~length_processor_t();

		//! Определить количество байт, которые требуются
		//! для представления поля Length.
		virtual size_t
		tlv_length_size(
			//! Длина тела TLV.
			size_t value_length ) const = 0;

		//! Прочитать Length из входного потока.
		virtual void
		tlv_unpack_length(
			//! Поток, с текущей позиции которого будет
			//! осуществляться чтение поля Length.
			oess_1::io::ibinstream_t & s,
			//! Приемник значения поля Length
			size_t & value_length ) = 0;

		//! Записать Length в выходной поток.
		virtual void
		tlv_pack_length(
			//! Поток, с текущей позиции которого будет
			//! осуществляться запись поля Length.
			oess_1::io::obinstream_t & s,
			//! Значение поля Length.
			size_t value_length ) const = 0;
};

//
// tlv_base_t
//

//! Базовый класс для TLV.
class OESS_1__TLV__TYPE	tlv_base_t
{
	public :
		virtual ~tlv_base_t();

		//! Определить значение поля Tag для TLV.
		/*!
			Вполне могут быть объекты TLV, которые имеют несколько
			значений для поля Tag. Например, объекты,
			имеющие несколько представлений в виде TLV. В этом
			случае данный метод должен либо возвращать
			корректное значение Tag ("предпочтительное" значение
			Tag из всех доступных значений), либо должен
			порождать исключение.

			\throw std::exception В случае ошибок.
		*/
		virtual default_tag_type_t
		tlv_tag() const = 0;

		//! Определить количество байт, которые требуются
		//! для представления всего TLV (с учетом Tag, Length,
		//! Value).
		/*!
			Данный метод необходим для эффективной
			реализации записи и чтения TLV в потоки, которые
			не позволяют осуществлять позиционирование
			на произвольную позицию.
		*/
		virtual size_t
		tlv_size() const = 0;

		//! Прочитать поля Length и Value из входного потока.
		/*!
			Данный метод вызывается уже после того, как из
			входного потока будет прочитано поле Tag.
		*/
		virtual void
		tlv_unpack(
			//! Поток, с текущей позиции которого будет
			//! осуществляться чтение полей Length и Value.
			oess_1::io::ibinstream_t & s,
			//! Значение поля Tag, которое было извлечено из
			//! входного потока перед тем, как был вызван метод
			//! tlv_unpack.
			default_tag_type_t tag_id ) = 0;

		//! Записать TLV в выходной поток.
		/*!
			Записываются все поля (Tag, Length, Value).
		*/
		virtual void
		tlv_pack(
			//! Поток, с текущей позиции которого будет
			//! осуществляться запись TLV.
			oess_1::io::obinstream_t & s ) const = 0;
};

//
// tlv_extractor_t
//

//! Класс для извлечения TLV из входного потока при
//! помощи оператора сдвига (>>).
/*!
	В метод tlv_base_t::tlv_unpack() нужно предать значение
	ранее прочитанного поля Tag. Это приводит к тому, что код
	чтения TLV из входного потока становится похож на:
\code
some_tag_processor_t tag;
some_tlv_t tlv;
istream >> tag;
tlv.tlv_unpack( istream, tag.query_tag() );
\endcode

	Класс tlv_extractor_t позволяет связать экземпляры
	tag_processor_t и tlv_base_t в один операнд оператора
	сдвига:
\code
some_tag_processor_t tag;
some_tlv_t tlv;
istream >> tag >> tlv_extractor_t( tag, tlv );
\endcode

*/
class  OESS_1__TLV__TYPE	tlv_extractor_t
{
	public :
		inline
		tlv_extractor_t(
			const tag_processor_t & tag_processor,
			tlv_base_t & tlv )
			:
				m_tag_processor( tag_processor ),
				m_tlv( tlv )
		{
		}

		inline void
		tlv_unpack( oess_1::io::ibinstream_t & s )
		{
			m_tlv.tlv_unpack( s, m_tag_processor.query_tag() );
		}

	private :
		const tag_processor_t &	m_tag_processor;
		tlv_base_t &	m_tlv;
};

//! Чтение TLV из входного потока.
/*!
	Должен использоваться уже после того, как из потока
	будет прочитано поле Tag.
*/
inline oess_1::io::ibinstream_t &
operator>>(
	//! Поток, с текущей позиции которого будет
	//! осуществляться чтение полей Length и Value.
	oess_1::io::ibinstream_t & s,
	//! Описатель объекта для извлечения.
	tlv_extractor_t & o )
{
	o.tlv_unpack( s );
	return s;
}

//! Вызывает метод tlv_pack у \a o.
inline oess_1::io::obinstream_t &
operator<<(
	//! Поток, с текущей позиции которого будет
	//! осуществляться запись TLV.
	oess_1::io::obinstream_t & s,
	//! Объект для записи.
	const tlv_base_t & o )
{
	o.tlv_pack( s );
	return s;
}

} /* namespace tlv */

} /* namespace oess_1 */

#endif

