#ifndef _htlib_h
#define _htlib_h

#ifdef __cplusplus
exern "C" {
#endif

/*
 * Copyright (C) 2009 NISHIMURA Hideki
 */
	
/**
 * HTLIBは、組み込み向けのHTTPライブラリです。
 * <p>
 * 典型的な使い方は以下のとおり。<p>
 * クライアントプログラムの場合：
 * <ol>
 * <li>HTLIB_Init()で初期化
 * <li>HTLIB_SendRequest()でTCP接続、リクエスト送信
 * <li>HTLIB_ReceiveResponse()でレスポンスヘッダ受信
 * <li>HTLIB_ReceiveBody()でボディデータ受信
 * <li>2～4を適当に繰り返す。
 * <li>HTLIB_Uninit()で終了処理
 * </ol>
 * <p>
 * サーバプログラムの場合：
 * <ol>
 * <li>acceptでクライアントからの接続を待つ
 * <li>処理スレッドを開始、以下、そのスレッド内で
 * <li>HTLIB_Init()で初期化
 * <li>HTLIB_Attach()でソケットを結合
 * <li>HTLIB_ReceiveRequest()でリクエストヘッダ受信
 * <li>必要であれば、 HTLIB_ReceiveBody()でボディデータ受信
 * <li>HTLIB_SendResponse()でレスポンスヘッダ送信
 * <li>HTLIB_SendBody()でボディデータ送信
 * <li>5～8を適当に繰り返す。
 * <li>HTLIB_Uninit()で終了処理
 * <li>処理スレッドを終了
 * </ol>
 * @file htlib.h
 * @author NISHIMURA Hideki
 */

/*
 * 基本型の定義
 */

/** 符号なし64ビット整数 */
typedef unsigned long long HTLIB_ULONGLONG;
/** 符号なし32ビット整数 */
typedef unsigned long HTLIB_ULONG;
/** 符号なし16ビット整数 */
typedef unsigned short HTLIB_USHORT;
/** 符号なし8ビット整数 */
typedef unsigned char HTLIB_UCHAR;
/** 論理値（真：０以外、偽：０） */
typedef int HTLIB_BOOL;

/**
 * エラーコード
 */
typedef enum {
	HTLIB_E_NO_ERROR = 0,

	/** 不正な引数が指定された */
	HTLIB_E_INVALID_ARG = 1,

	/** 不正な状態のときに呼び出された */
	HTLIB_E_INVALID_STATE = 2,

	/** 確保済み領域では要領が不足している */
	HTLIB_E_OUT_OF_MEMORY = 3,

	/** 入出力待ちのタイムアウトが発生した */
	HTLIB_E_TIMEOUT = 4,

	/** 受信したヘッダフィールドの文法構造が不正 */
	HTLIB_E_INVALID_SYNTAX = 5,

	/** ユーザによってキャンセルが指示された */
	HTLIB_E_CANCELED = 6,

	/** 接続先から切断されている */
	HTLIB_E_DISCONNECTED = 21,

	/** 致命的なエラー（発生しないはず；おそらくバグ） */
	HTLIB_E_FATAL = 98,

	/** 実装されていない */
	HTLIB_E_NOT_IMPLEMENTED = 99,

	/** システムコールでエラーが発生した。<br>
		（HTLIB.system_errnoを参照のこと） */
	HTLIB_E_SYSTEM = 100,

} HTLIB_ERROR;

/**
 * ログレベルの定義
 * @see HTLIB_SetLogHandler
 */
typedef enum {
	/** 致命的（通常発生しない） */
	HTLIB_LV_FATAL = 0,
	/** エラー */
	HTLIB_LV_ERROR = 1,
	/** 警告 */
	HTLIB_LV_WARN = 2,
	/** 有用な情報 */
	HTLIB_LV_INFO = 3,
	/** デバッグ向け（通常不要） */
	HTLIB_LV_DEBUG = 4,

} HTLIB_LOGLEVEL;

/**
 * HTLIB_NEEDS_FUNCで返す列挙値。
 * <p>
 * HTLIB_ReceiveRequest() / HTLIB_ReceiveResponse()では、
 * ヘッダを解析すべきかどうかをHTLIB_NEEDS_FUNCを使って、
 * ユーザが制御することが出来る。
 * @see HTLIB_NEEDS_FUNC
 */
typedef enum {
	/** このヘッダは不要 */
	HTLIB_N_NONE = 0,
	/** このヘッダは必要だが、パラメータの解析は不要 */
	HTLIB_N_ONLY_VALUE = 1,
	/** このヘッダは必要で、パラメータの解析が必要（;で分割） */
	HTLIB_N_PARSE_PARAM = 2,
} HTLIB_NEEDS_ENUM;


/**
 * 一連の処理を管理するためのデータ構造
 */
typedef struct {
	/** 公開メンバー */

	/** ソケットデスクリプタ */
	int soc;
	/** システムコール失敗時のerrno値 */
	int system_errno;
	/** 接続先アドレスをgetaddinfoするときのhints */
	const struct addrinfo* hints;
	/** ユーザエージェント名（クライアント用）、
		または、サーバー名（サーバー用）
		これを設定すると、自動的に"User-Agent"ヘッダまたは
		"Server"ヘッダが付与される。 */
	const char* agent_or_server_name;

	/** ユーザが自由に利用できる領域 */
	void* user;

	/* 非公開メンバー */

	/* for sender */
	char* _send_buffer;
	int _send_buffer_len;
	long long _send_content_length;
	long long _send_content_transfered;

	/* for receiver */
	char* _rec_buffer;
	int _rec_buffer_len;
	int _read_index;
	long long _rec_content_length;
	long long _rec_content_transfered;
	
	char* _header;
	int _body_index;

	/* common */
	volatile HTLIB_BOOL _is_canceled;
	HTLIB_ULONG _flags;
	
} HTLIB;

/**
 * HTLIB構造体へのポインタ型
 */
typedef HTLIB* HTLIB_HANDLE;

/**
 * HTLIB_ReceiveRequest() / HTLIB_ReceiveResponse()で指定する
 * ヘッダ解析制御用ユーザ定義コールバック関数プロトタイプ。
 * <p>
 * ユーザ定義関数にて、コールバックされたヘッダの解析必要性に応じて
 * 戻り値を返す。
 * @param h HTLIB構造体ポインタ（他のユーザ処理を行うために、o->userを自由に利用してよい）
 * @param header_name ヘッダ名
 * @return HTLIB_NEEDS_ENUM
 * @see HTLIB_NEEDS_ENUM
 * @see HTLIB_ReceiveRequest()
 * @see HTLIB_ReceiveResponse()
 */
typedef HTLIB_NEEDS_ENUM (*HTLIB_NEEDS_FUNC)(HTLIB_HANDLE h,
											 const char* header_name);


/**
 * ヘッダフィールドを表現するデータ構造
 */
typedef struct _HTLIB_Header {
	/** ヘッダ名 */
	const char* name;
	/** 値文字列；なければNULL */
	const char* value;
	/** ;で区切られたパラメータ(param)の数 */
	int param_num;
	/** param_numで要素数が示されるパラメータ配列 */
	const struct _HTLIB_Header* param;
} HTLIB_Header;

/**
 * 初期化関数。
 * <p>
 * スタック、または、静的領域にHTLIB構造体を定義し、
 * そのポインタを渡すことで初期化する。<br>
 * send_buffer, rec_bufferには、
 * ヘッダ部が収まる十分なサイズの領域を指定すること。
 * @param h HTLIB構造体ポインタ
 * @param send_buffer 送信用バッファ領域
 * @param send_len send_bufferの長さ
 * @param rec_buffer 受信用バッファ領域
 * @param rec_len rec_bufferの長さ
 * @param err エラーの種類（失敗時）
 * @return 成功すれば0以外、失敗すれば0
 */
HTLIB_BOOL
HTLIB_Init(HTLIB_HANDLE h,
		   char* send_buffer, HTLIB_USHORT send_len,
		   char* rec_buffer, HTLIB_USHORT rec_len,
		   HTLIB_ERROR* err);

/**
 * 終了処理関数。
 * <p>
 * もしソケットがオープン済みならクローズする。
 * @param h HTLIB構造体ポインタ
 */
void
HTLIB_Uninit(HTLIB_HANDLE h);

/**
 * アドレスを指定して、予め、事前に接続する（クライアント用；オプション）。
 * <p>
 * 接続後、リクエストを送る前にソケットに対して何らかの設定を行うことができる。<br>
 * @param h HTLIB構造体ポインタ
 * @param addr 接続先アドレスデータ
 * @param addr_len addr領域のサイズ
 * @param err エラーの種類（失敗時）
 * @return 成功すれば0以外、失敗すれば0
 */
HTLIB_BOOL
HTLIB_Open(HTLIB_HANDLE h,
		   const struct sockaddr* addr,
		   socklen_t addr_len,
		   HTLIB_ERROR* err);

/**
 * 接続済みソケットを設定する（主にサーバー用）。
 * <p>
 * サーバー用途では、
 * これを行ってから HTLIB_ReceiveRequest()を行う。
 * @param h HTLIB構造体ポインタ
 * @param socket 接続済みソケット
 */
void
HTLIB_Attach(HTLIB_HANDLE h, int socket);

/**
 * ソケットが接続済みならクローズする。<br>
 * （通常は、内部で自動的に行われる。）
 * @param h HTLIB構造体ポインタ
 */
void
HTLIB_Close(HTLIB_HANDLE h);		

/**
 * URLで指定される接続先に対してHTTPリクエストを送信する（クライアント用）。
 * <p>
 * headersには、"Host"ヘッダ、"Content-Length"ヘッダを含めてはならず、
 * この関数内で自動的に判定してつけられる。<br>
 * h->agent_or_server_nameを設定しておくと、自動的に"User-Agent"ヘッダをつける。
 * <p>
 * 既に接続先に接続済みでないか、
 * 前回のリクエストで"Connection: close"が指定されている時、
 * 自動的に（再）接続を行う。<br>
 * この際に行われるgetaddrinfoにて、
 * h->hintsが参照されるので必要であれば事前に設定しておく。<br>
 * 接続はデフォルトでは持続的（Keep-Alive）に行われ、次回のリクエストでは
 * 同じTCP接続に送信する。<br>
 * なお、この時、ホストが同一かどうかはチェックされない。
 * <p>
 * headersに"Connection: close"を含めることが出来、このときには
 * 次回の HTLIB_SendRequest()では持続的接続は行わず、<br>
 * 一旦ソケットをクローズして再接続される。<br>
 * （サーバー側から、"Connection: close"が指定された場合も同様）
 * <p>
 * また、持続的接続にてリクエストを送信する時、
 * サーバー側の都合により既にTCP接続が切断されていることがありうる。<br>
 * この場合、"GET", "HEAD", "PUT", "DELETE"の時には、
 * 一度だけ内部でリトライする。
 *
 * @param h HTLIB構造体ポインタ
 * @param timeout_millis 送信タイムアウト（ミリ秒）
 * @param method HTTPメソッド（"GET"など）
 * @param url 接続先絶対URL
 * @param headers ヘッダフィールドの配列
 * @param header_len headersの要素数
 * @param body POST時、リクエストボディ（NULLにして、 HTLIB_SendBody()でボディデータを送信しても良い）
 * @param body_len bodyの長さ
 * @param err エラーの種類（失敗時）
 * @return 成功すれば0以外、失敗すれば0
 */
HTLIB_BOOL
HTLIB_SendRequest(HTLIB_HANDLE h,
				  int timeout_millis,
				  const char* method,
				  const char* url,
				  const HTLIB_Header* headers,
				  HTLIB_USHORT header_len,
				  const char* body,
				  HTLIB_ULONGLONG body_len,
				  HTLIB_ERROR* err);

/**
 * HTTPリクエストを受信する（サーバー用）。
 * <p>
 * もし"Connection: close"が含まれていれば、対応するレスポンス送信でも自動的に
 * "Connection: close"を付与し、
 * 次回の HTLIB_ReceiveRequest()が失敗するように内部で設定される。
 * <p>
 * その他の多くのヘッダについてはこのライブラリ内で管理されないので、
 * header_bufferの内容を確認し上位で適切に処理すること。
 * <p>
 * "POST", "PUT"を受信した場合は、 HTLIB_ReceiveBody()でボディデータを受信すること
 * （但し、ボディデータを送信する前に100ステータスを返しても良い）。
 * <p>
 * *err = HTLIB_E_TIMEOUTのエラーが発生すれば、必要であれば再度この関数を実行してよい。
 *
 * @param h HTLIB構造体ポインタ
 * @param timeout_millis 受信タイムアウト（ミリ秒）
 * @param method メソッド文字列ポインタを受け取るためのポインタ
 * @param header_buffer ヘッダデータを受け取るための配列
 * @param blen 呼び出し時にはheader_bufferの要素数を指定し、戻り時には取得できた要素数が格納される
 * @param needs HTLIB_NEEDS_FUNC（NULLを指定すると全てHTLIB_N_PARSE_PARAMが指定されたとみなされる）
 * @param err エラーの種類（失敗時）
 * @return 受信したリクエストのURL（失敗すればNULL）
 */
const char*
HTLIB_ReceiveRequest(HTLIB_HANDLE h,
					 int timeout_millis,
					 const char** method,
					 HTLIB_Header* header_buffer, HTLIB_USHORT* blen,
					 HTLIB_NEEDS_FUNC needs,
					 HTLIB_ERROR* err);

/**
 * HTTPレスポンスを送信する（サーバー用）。
 * <p>
 * headersには、"Date"ヘッダ、"Content-Length"ヘッダを含めてはならず、
 * この関数内で自動的に判定してつけられる。<br>
 * h->agent_or_server_nameを設定しておくと、自動的に"Server"ヘッダをつける。
 * <p>
 * もし、クライアント側から、"Connection: close"が指定される場合は自動的に
 * （指定されていなければ）"Connection: close"を設定し、
 * このときには、次回のリクエスト受信を行わないように設定される（ HTLIB_ReceiveRequest()がHTLIB_E_DISCONNECTEDで失敗する）。<br>
 * そうでない場合でも、"Connection: close"を含めることが出来、この時は同じように処理される。
 * <p>
 * もし、 HTLIB_ReceiveRequest()で受信したリクエストが"POST"または"PUT"であり、
 * "Expect: 100-continue"が指定されている時、
 * HTLIB_ReceiveBody()でボディを受信する前に、
 * 受け入れ準備が出来ることを示すためstatus=100を返すことが出来る。<br>
 * この後、 HTLIB_ReceiveBody()でボディを受け取り、改めて最終応答を返せばよい。
 * 
 * @param h HTLIB構造体ポインタ
 * @param timeout_millis 送信タイムアウト
 * @param status HTTPステータスコード
 * @param msg ステータスメッセージ（NULL時、statusより自動的に設定される）
 * @param headers ヘッダフィールドの配列
 * @param header_len headersの要素数
 * @param body レスポンスボディ（NULLにして、 HTLIB_SendBody()でボディデータを送信しても良い）
 * @param body_len bodyの長さ
 * @param err エラーの種類（失敗時）
 * @return 成功すれば0以外、失敗すれば0
 */
HTLIB_BOOL
HTLIB_SendResponse(HTLIB_HANDLE h,
				   int timeout_millis,
				   int status, const char* msg,
				   const HTLIB_Header* headers,
				   HTLIB_USHORT header_len,
				   const char* body,
				   HTLIB_ULONGLONG body_len,
				   HTLIB_ERROR* err);

/**
 * ボディを送信する。
 * <p>
 * 一度に全てを指定する必要はなく、適当なサイズごとに繰り返し呼び出せばよい。<br>
 * また、 HTLIB_SendResponse()で指定したbody_lenを超えてボディを送信することは出来ない。
 * @param h HTLIB構造体ポインタ
 * @param timeout_millis 送信タイムアウト
 * @param body ボディデータ（の一部）
 * @param body_len bodyのサイズ
 * @param err エラーの種類（失敗時）
 * @return 成功すれば0以外、失敗すれば0
 */
HTLIB_BOOL
HTLIB_SendBody(HTLIB_HANDLE h,
			   int timeout_millis,
			   const char* body,
			   HTLIB_USHORT body_len,
			   HTLIB_ERROR* err);

/**
 * HTTPレスポンスを受信する（クライアント用）。
 * <p>
 * もし"Connection: close"が含まれていれば、
 * 次回の HTLIB_SendRequest()では一旦接続をクローズし、
 * 再接続するように内部で設定される。
 * <p>
 * その他の多くのヘッダについてはこのライブラリ内で管理されないので、
 * header_bufferの内容を確認し上位で適切に処理すること。
 * <p>
 * "POST", "PUT"を送信した場合、100ステータスが返されることがある
 * （サーバーの実装によっては返されない）。<br>
 * その際には、必要なら HTLIB_SendBody()でボディデータを送信し、
 * 改めてこの関数でレスポンスを受信すればよい。
 * <p>
 * *err = HTLIB_E_TIMEOUTのエラーが発生すれば、必要であれば再度この関数を実行してよい。
 *
 * @param h HTLIB構造体ポインタ
 * @param timeout_millis 受信タイムアウト（ミリ秒）
 * @param header_buffer ヘッダデータを受け取るための配列
 * @param blen 呼び出し時にはheader_bufferの要素数を指定し、戻り時には取得できた要素数が格納される
 * @param needs HTLIB_NEEDS_FUNC（NULLを指定すると全て HTLIB_N_PARSE_PARAMが指定されたとみなされる）
 * @param err エラーの種類（失敗時）
 * @return ステータスコード（失敗すれば-1）
 */
int
HTLIB_ReceiveResponse(HTLIB_HANDLE h,
					  int timeout_millis,
					  HTLIB_Header* header_buffer, HTLIB_USHORT* blen,
					  HTLIB_NEEDS_FUNC needs,
					  HTLIB_ERROR* err);

/**
 * ボディデータ（の一部）を受信する。
 * <p>
 * ボディデータの全体を取得するために、（recv, freadと同じように）
 *  0が返されるまで繰り返して呼び出すこと。<br>
 * （長さについては、"Content-Length"ヘッダなどで自動的に決定される。）
 *
 * @param h HTLIB構造体ポインタ
 * @param timeout_millis 受信タイムアウト（ミリ秒）
 * @param buffer 受信用バッファ
 * @param buffer_len bufferのサイズ
 * @param err エラーの種類（失敗時）
 * @return 受信サイズ（失敗すれば-1）
 */
int
HTLIB_ReceiveBody(HTLIB_HANDLE h,
				  int timeout_millis,
				  char* buffer,
				  HTLIB_USHORT buffer_len,
				  HTLIB_ERROR* err);


/**
 * hで指定された接続の入出力待ち処理をキャンセルする。
 * <p>
 * 典型的には別のスレッドが送受信を実行しているスレッドの制御を戻すために実行する。
 * @param h HTLIB構造体ポインタ
 * @param cancel_or_not キャンセルを指示するとき0以外、解除するとき0
 */
void
HTLIB_Cancel(HTLIB_HANDLE h, HTLIB_BOOL cancel_or_not);

/**
 * このライブラリで管理されている全ての入出力待ち処理をキャンセルする。
 * <p>
 * 典型的にはプログラムを終了するため、
 * 送受信を実行しているスレッドの制御を戻すためにメインスレッドが実行する。
 * @param cancel_or_not キャンセルを指示するとき0以外、解除するとき0
 */
void
HTLIB_CancelAll(HTLIB_BOOL cancel_or_not);

/*
 * ユーティリティ関数
 */

/**
 * 指定されたヘッダ、またはパラメータ配列から、
 * 指定した名前のヘッダ、またはパラメータの要素を見つける。
 * <p>
 * もし、複数の要素が含まれている場合には、最初の要素のインデックスを返す。
 * @param headers_or_params ヘッダまたはパラメータを格納した配列
 * @param len headers_or_paramsの要素数
 * @param name 見つけたい配列要素
 * @return 見つかった配列要素のインデックス番号（見つからなければ-1）
 */
int
HTLIB_Find(const HTLIB_Header* headers_or_params,
		   HTLIB_USHORT len,
		   const char* name);

/**
 * 指定された時刻を、文字列に変換する。
 * @param buf30 30バイト以上の文字列バッファ
 * @param tm 時刻 (NULLなら現在時刻)
 */
void
HTLIB_SetDate(char* buf30, const time_t* tm);

/**
 * エラーコードに対応する文字列を返す。
 * <p>
 * もし対応しない番号の場合、"(undefined)"を返す。
 * @param err エラーコード
 * @return 文字列
 */
const char*
HTLIB_GetErrorMessage(HTLIB_ERROR err);

/*
 * ログ関連関数
 */

/**
 * ユーザ定義ログハンドラ関数プロトタイプ。
 * <p>
 * 特別なログ出力経路を持つ場合、このプロトタイプの関数を定義し、
 * HTLIB_SetLogHandler()で登録することが出来る。
 * @param file ログ出力を行ったファイル名
 * @param line ログ出力を行ったファイル中の行番号
 * @param level ログレベル
 * @param msg ログメッセージ
 * @see HTLIB_SetLogHandler()
 */
typedef void (*HTLIB_LOG_HANDLER)(const char* file,
								  int line,
								  HTLIB_LOGLEVEL level,
								  const char* msg);

/**
 * ユーザ定義ログハンドラを登録する。
 * <p>
 * デフォルトでは、{ fprintf(stderr, "%s\n", msg); }
 * の処理を行う関数が登録されている。
 */
void
HTLIB_SetLogHandler(HTLIB_LOG_HANDLER handler);

/**
 * ログハンドラを呼び出すログレベルを設定する（デフォルト HTLIB_LV_INFO）。
 * <p>
 * 例えば、 HTLIB_LV_ERRを指定すると、
 *  HTLIB_LV_ERRと HTLIB_LV_FATALのログのみが出力される。
 * @param levelAndHigher ログ出力するログレベル下限
 */
void
HTLIB_SetLogLevel(HTLIB_LOGLEVEL levelAndHigher);


#ifdef __cplusplus
}
#endif

#endif

