/*
 *	clipber.c
 *
 *	BER - Basic Encoding Rules
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2018 Naoyuki Sawa
 *
 *	* Fri Apr 06 23:59:59 JST 2018 Naoyuki Sawa
 *	- 1st リリース。
 *	- BERについては、下記の資料を参照して下さい。
 *	  ①「Samayou Oharikui」(http://www5d.biglobe.ne.jp/~stssk/index.html)さんのサイトの、BER(Basic Encoding Rules)』(http://www5d.biglobe.ne.jp/stssk/asn1/ber.html)の記事
 *	  ②「Information technology - ASN.1 encoding rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER)」(https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf)
 *	- 今回、当モジュールを作成したきっかけは、Rubyのpackテンプレート文字列(https://docs.ruby-lang.org/ja/latest/doc/pack_template.html)の勉強をしていて、BER圧縮整数の事を知ったからです。
 *	  これまで、同様の圧縮処理を行う際は、各モジュールでその都度実装していましたが、標準的な方法に沿って共通モジュールとして用意しておく方が良いと思ったので、当モジュールを作成しました。
 *	- ただし当モジュールの機能は、標準仕様に完全に沿っている訳ではなく、おおよその概念が一致しているだけです。
 *	  Rubyのpackテンプレート文字列のBER圧縮整数形式も、標準仕様に厳密に沿っている訳ではない(?)ように思います。
 *	- また、当モジュールの機能と、Rubyのpackテンプレート文字列のBER圧縮整数形式の間にも、違いがあります。
 *	  Rubyのpackテンプレート文字列のBER圧縮整数形式は、負数に対応していないようで、負数を渡すとエラーになります。
 *	  負数に対応していないと使い辛いので、当モジュールでは負数にも対応する事にしました。
 *	  負数をBER圧縮する方法としては、符号無し整数と見なしてエンコードする方法も有るのですが、それでは負数が全て大きなデータにエンコードされてしまうので、その方法は取りませんでした。
 *	  当モジュールでは、出力する7ビット毎の最上位ビットを符号拡張する方法で、負数に対応しました。(言葉で説明すると判り辛いので、コードを読む方が判り易いと思います。)
 */
#include "clip.h"
//*****************************************************************************
//	
//*****************************************************************************
//BERエンコードされたデータを、デコードする。
//[in]
//	buf		入力バッファ。					NULL不可
//	pVal		デコードした値を格納する変数へのポインタ。	NULL不可
//[out]
//	戻り値		デコードしたバイト数。			1～((sizeof(int)*8+6)/7)。一般的には1～5です。
#ifndef PIECE
int BER_Decode(const unsigned char* buf, int* pVal) {
	//符号付きバイト値として読み出すために、ポインタの型を変換する。
	const char* pos = buf;
	//1バイト読み込む。
	int tmp = *pos++;
	//読み込んだ値のビット6を符号拡張した値を、結果の値の初期値とする。
	int val = tmp << (32 - 7) >> (32 - 7);
	//読み込んだ値のMSBがセットされている間、繰り返す。
	while(tmp < 0) {
		//1バイト読み込む。
		tmp = *pos++;
		//読み込んだ値のMSBをクリアした値を、結果の値に合成する。
		val = (val << 7) | (tmp & 0x7F);
	}
	//結果の値を格納する。
	*pVal = val;
	//デコードしたバイト数を返す。
	return pos - (const char*)buf;	//このキャストを行わないと、GCCで「invalid operands to binary -」エラーが出る。VC++6.0では大丈夫なのだが。
}
#else //PIECE
asm("
		.code
		.align		1
		.global		BER_Decode
BER_Decode:
		;//%r12 := buf
		;//%r13 := pVal
		ld.w		%r10, %r12			;//%r10 := pos = buf
		ld.b		%r11, [%r10]+			;//%r11 := tmp = *pos++
		ld.w		%r14, %r11			;//%r14 :=       tmp
		rr		%r14, 7				;//%r14 :=       tmp << (32 - 7)		この時点では%r14[24:0]が不定だが、すぐシフトアウトするので問題無い。
		xsra		%r14, 32-7			;//%r14 := val = tmp << (32 - 7) >> (32 - 7)
BER_Decode_LOOP:						;//for(;;) {
		cmp		%r11, 0				;//  if(tmp >= 0) { break }
		jrge		BER_Decode_RET			;//  
		ld.b		%r11, [%r10]+			;//  %r11 := tmp = *pos++
		sla		%r14, 7				;//  %r14 :=       (val << 7)
		xand		%r9, %r11, 0x7F			;//  %r9  :=                    (tmp & 0x7F)
		jp.d		BER_Decode_LOOP			;//}
		or		%r14, %r9			;//  %r14 := val = (val << 7) | (tmp & 0x7F)	*delay*
BER_Decode_RET:							;//
		ld.w		[%r13], %r14			;//*pVal = val
		ret.d						;//return  pos - buf
		sub		%r10, %r12			;//%r10 := pos - buf				*delay*
");
#endif//PIECE
//-----------------------------------------------------------------------------
//値valをBERエンコードし、バッファbufに格納する。
//[in]
//	val		エンコードする値。			INT_MIN～INT_MAX
//	buf		出力バッファ。				少なくとも((sizeof(int)*8+6)/7)バイト以上確保して下さい。一般的には5バイトです。	NULL不可
//[out]
//	戻り値		出力バッファに格納したバイト数。	1～((sizeof(int)*8+6)/7)。一般的には1～5です。
#ifndef PIECE
int BER_Encode(unsigned char buf[/*(sizeof(int)*8+6)/7*/], int val) {
	//出力バッファの先頭アドレスを記憶しておく。
	unsigned char* pos = buf;
	int len;
	for(;;) {
		//下位7ビットの、MSBをセットした値を格納する。
		*pos++ = (val | ~0x7F);
		//下位6ビットをシフトアウトする。
		val >>= 6;
		//格納済みの7ビット目と、残りのビットが同じならば抜ける。
		if((val == 0) || (val == -1)) { break; }
		//格納済みの7ビット目も、シフトアウトする。
		val >>= 1;
	}
	//最初に格納した下位7ビットの、MSBをクリアする。
	*buf &= 0x7F;	//bclr [%rd],7
	//格納したバイト数を求める。
	len = pos - buf;
	//格納した値の並びを逆にする。
#if 0
	//□無駄な交換は発生しないが、先頭でループ判定を行うためコードサイズが大きいバージョン。
	while(--pos > buf) {
		int a = *pos;
		int b = *buf;
		*buf++ = a;
		*pos = b;
	}
#else
	//□無駄な交換が発生するが、末尾でループ判定を行うためコードサイズが小さいバージョン。(アセンブラ版はこちらを元にしました。)
	do {
		int a = *--pos;
		int b = *buf;
		*buf++ = a;
		*pos = b;
	} while(pos > buf);
#endif
	//格納したバイト数を返す。
	return len;
}
#else //PIECE
asm("
		.code
		.align		1
		.global		BER_Encode
BER_Encode:
		;//%r12 := buf
		;//%r13 := val
		ld.w		%r11, %r12			;//%r11 := pos = buf
BER_Encode_L10:							;//for(;;) {
		xoor		%r9, %r13, ~0x7F		;//  %r9  :=  (val | ~0x7F)
		ld.b		[%r11]+, %r9			;//  *pos++ = (val | ~0x7F)
		sra		%r13, 6				;//  %r13 := val >>= 6
		cmp		%r13, 0				;//  
		jreq		BER_Encode_L20			;//  if(val ==  0) { break }
		cmp		%r13, -1			;//  
		jrne.d		BER_Encode_L10			;//  if(val == -1) { break }
		sra		%r13, 1				;//  %r13 := val >>= 1		*delay*
BER_Encode_L20:							;//}
		bclr		[%r12], 7			;//*buf &= 0x7F
		ld.w		%r10, %r11			;//%r10 :=       pos
		sub		%r10, %r12			;//%r10 := len = pos - buf
BER_Encode_L30:							;//do {
		sub		%r11, 1				;//  %r11 := pos--
		ld.b		%r9, [%r11]			;//  %r9  := a = *pos
		ld.b		%r13, [%r12]			;//  %r13 := b = *buf
		ld.b		[%r12]+, %r9			;//  *buf++ = a
		ld.b		[%r11], %r13			;//  *pos   = b
		cmp		%r11, %r12			;//  
		jrugt		BER_Encode_L30			;//} while(pos > buf)
		ret
");
#endif//PIECE
//*****************************************************************************
//	テストプログラム
//*****************************************************************************
#if 0
void test() {
	int step = 0;
	for(;;) {
		char buf[16];
		int val1, val2, len1, len2;
		//エンコードする値を作成する。
		switch(step) {
		default:DIE();
		case 0: val1 =  RND32(seed); step = 1; break;	//┐
		case 1: val1 = -RND16(seed); step = 2; break;	//│
		case 2: val1 =  RND16(seed); step = 3; break;	//├理論上はRND32()だけでもテスト出来るのだが、それでは大きな値に偏ってしまうので、ある程度均等にテストするためにこうした。
		case 3: val1 = -RND8( seed); step = 4; break;	//│
		case 4: val1 =  RND8( seed); step = 0; break;	//┘
		}
		//エラーチェックのためにバッファを-1でフィルしておく。
		memset(buf, -1, sizeof buf);
		//エンコードする。
		len1 = BER_Encode(buf, val1);
		//データのバイト数が範囲外でない事を確認する。
		if((len1 < 1) || (len1 > ((sizeof(int)*8+6)/7))) { DIE(); }
		//データの範囲外のバッファが破壊されておらず、データの末尾のMSBは0である事を確認する。
		if((buf[len1] != -1) || (buf[len1 - 1] < 0)) { DIE(); }
		//デコードする。
		len2 = BER_Decode(buf, &val2);
		//値と、データのバイト数が正しい事を確認する。
		if((val1 != val2) || (len1 != len2)) { DIE(); }
		printf("%11d %d ok\n", val1, len1);
	}
}
#endif
