Base64エンコード/デコード

http://ja.wikipedia.org/wiki/Base64


Wikipediaを見て理解余裕でした。言われたことを素直にごり押し。
エンコードではループ中の条件分岐判定を減らすために小細工してみた。

int atob64(const char *src, int srclen, char *dst, int dstlen){
	const char Base64char[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	int i,j;
	int calclength = (srclen/3*4) + (srclen%3?4:0);
	if(calclength > dstlen) return -1;
	
	j=0;
	for(i=0; i+2<srclen; i+=3){
		dst[j++] = Base64char[ (src[i] >> 2) & 0x3F ];
		dst[j++] = Base64char[ (src[i] << 4 | src[i+1] >> 4) & 0x3F ];
		dst[j++] = Base64char[ (src[i+1] << 2 | src[i+2] >> 6) & 0x3F ];
		dst[j++] = Base64char[ (src[i+2]) & 0x3F ];
	}
	
	if(i<srclen){
		dst[j++] = Base64char[ (src[i] >> 2) & 0x3F ];
		if(i+1<srclen){
			dst[j++] = Base64char[ (src[i] << 4 | src[i+1] >> 4) & 0x3F ];
			if(i+2<srclen){
				dst[j++] = Base64char[ (src[i+1] << 2 | src[i+2] >> 6) & 0x3F ];
			}else{
				dst[j++] = Base64char[ (src[i+1] << 2) & 0x3F ];
			}
		}else{
			dst[j++] = Base64char[ (src[i] << 4) & 0x3F ];
		}
	}
	while(j%4) dst[j++] = '=';
	
	if(j<dstlen) dst[j] = '\0';
	return j;
}

// src:base64code, dst:string
int b64toa(const char *src, int srclen, char *dst, int dstlen){
	const unsigned char Base64num[256] = {
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3E,0xFF,0xFF,0xFF,0x3F,
		0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,
		0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
		0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
		0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
		
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
		0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
	};
	int calclength = (srclen/4*3);
	int i,j;
	if(calclength > dstlen || srclen % 4 != 0) return 0;
	
	j=0;
	for(i=0; i+3<srclen; i+=4){
		if((Base64num[src[i+0]]|Base64num[src[i+1]]|Base64num[src[i+2]]|Base64num[src[i+3]]) > 0x3F){
			return -1;
		}
		dst[j++] = Base64num[src[i+0]]<<2 | Base64num[src[i+1]] >> 4;
		dst[j++] = Base64num[src[i+1]]<<4 | Base64num[src[i+2]] >> 2;
		dst[j++] = Base64num[src[i+2]]<<6 | Base64num[src[i+3]];
	}
	
	if(j<dstlen) dst[j] = '\0';
	return j;
}

使い方

int atob64(入力(データ), 入力長, 出力先(Base64), 出力先バッファ長);
int b64toa(入力(Base64), 入力長, 出力先(データ), 出力先バッファ長);

仕様

戻り値は変換した長さ。変換失敗で-1を返す。
b64toaの戻り値!=元データ長(Base64の'='の扱いのせい)
Base64エンコード時のMIMEの76文字毎の改行は未サポート
出力先に余裕があるとヌル文字を付与します。
バイナリもいける?

べんちまーく

ちょっとベンチマークを走らせようと比較対象を探したところ真っ先に以下がヒット。
ちゃらっと書いてみたBase64デコードのソース(C言語) - ほんまの走り書き技術メモ
ちゃらっと書いてみたBase64エンコードのソース(C言語) - ほんまの走り書き技術メモ
どこも、エンコードかデコードだけだったりなので、こうすぐ見つかると嬉しい。


自前のコードをatob64,b64toa,見つけたコードをato64_,b64toa_とした。(アンダーバーつけただけ)
26文字(A-Z)と1986文字の2つのデータを、エンコード=>デコードを10万回やって比較してみた。
ベンチマークを行ったコードはこんな感じ。
http://codepad.org/P7H3bv0C
ハンデで変換テーブルの生成はベンチマークを行う前にやっておいてやった(キリッ
環境は MinGW/GCC 4.5.0

最適化オプション -O0 -O1 -O2 -O3
atob64/b64toa 2875 1812 1468 1109
atob64_/b64toa_ 4485 1531 1453 1563

(単位: ms)


最適化オプションが無い場合は高速だが、有効にするとほぼ同等。
-O1では負け-O3では勝っているが、最適化によって正しくベンチマークを測れていない可能性も。
*dst^=*dst2としているのは、最適化による除去を回避するためなのだが、役をしているのかどうか。

最適化してみた

もう何が最適化なのかわからない。コンパイラに任せたほうが早くなったんじゃry


Base64文字=>数値への変換は256byteのテーブルを用意しておく。
エラーチェックに使うために、使わない部分は0xFFで埋める。
そうすればデコード時に論理和取れば0xFFが出たときに、
Base64のデータとして正しくない事になるのでreturnする。
別に論理和取らずに、普通に0xFFかどうかを調べればいいんだけど、
論理和のほうが速度としては最適化オプション(-O3)の時に限っては早かったり。
といっても、上のベンチマークで70clock前後しか変らなかったけど。
エンコードはもう少しどうにかできそう。というか、コード重複等が汚い。