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前後しか変らなかったけど。
エンコードはもう少しどうにかできそう。というか、コード重複等が汚い。