RustでBase64処理プログラム

Rust言語でBase64処理プログラムを書いてみた。
Base64は、データを64種類の印字可能な英数字のみを用いて、それ以外の文字を扱うことの出来ない通信環境にてマルチバイト文字やバイナリデータを扱うためのエンコード方式である。MIMEによって規定されていて、7ビットのデータしか扱うことの出来ない電子メールにて広く利用されている。 ウィキペディア

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
これらの64文字を使ってバイナリデータを表現する。
ちなみに"Rust"文字列はBase64で"UnVzdA=="と表される。
24bit毎のデータを6bit単位で分割して4文字で表現するので、データが24bitに満たない場合は'='文字で残りを埋めて4文字単位に調整する。


ネットの連載記事Rustで有名アルゴリズムに挑戦 Base64を実装してみよう を参考に読んでみて、自分なりに書き足してみた。
この記事ではBase64の復元(デコード)処理は割愛されてるので、追加で自作。
またRustらしくclosureを使って、少し書き換えた。
言語に習熟するには、真似る、慣れる、学習する のが上達の近道。

完成したプログラムのプロジェクトはここのGithubにアップロードしてますので、すぐにcargoで実行できます。

// Base64のエンコード/デコード処理を作る
fn main() { // 適当な文字列をBase64に変換して結果を表示して、デコード結果を表示 --- (*1)    
    let s = "hello!";
    let s_b64 = base64_encode(s);
    println!("{} => {}", s, s_b64);
    println!("{} => {}", s_b64, String::from_utf8(base64_decode(&s_b64)).unwrap()); 
 
    let s = "Rust";
    let s_b64 = base64_encode(s);
    println!("{} => {}", s, s_b64);
    println!("{} => {}", s_b64, String::from_utf8(base64_decode(&s_b64)).unwrap()); 
 
    let s = "1234567890";
    let s_b64 = base64_encode(s);
    println!("{} => {}", s, s_b64);
    println!("{} => {}", s_b64, String::from_utf8(base64_decode(&s_b64)).unwrap());  
            
    let s = "生姜焼き定食";
    let s_b64 = base64_encode(s);
    println!("{} => {}", s, s_b64);   
    println!("{} => {}", s_b64, String::from_utf8(base64_decode(&s_b64)).unwrap()); 
 
    let s = "文字列をBase64に変換して、デコードして復元する";
    let s_b64 = base64_encode(s);
    println!("{} => {}", s, s_b64);
    println!("{} => {}", s_b64, String::from_utf8(base64_decode(&s_b64)).unwrap()); 
 
}
// Base64エンコードを行う関数 --- (*2)
fn base64_encode(in_str: &str) -> String {
    // Base64の変換テーブルを1文字ずつに区切る --- (*3)
    let t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    let table: Vec<char> = t.chars().collect::<Vec<char>>();
    // 変換結果を保持する文字列 --- (*4)
    let mut result = String::new();
    // 入力文字列をバイト列に変換 --- (*5)
    let bin8 = in_str.as_bytes();
    // 繰り返し24bitごと(3文字ずつ)に処理する --- (*6)
    let cnt = bin8.len() / 3;
    //Rustらしくclosureを使ってみる
    (0..cnt)
        .into_iter()
        .for_each(|i| {            
            let n = i * 3; // 3文字(24bit)ずつ処理 --- (*7)
            let b24 = ((bin8[n+0] as usize) << 16) +
                      ((bin8[n+1] as usize) <<  8) +
                      (bin8[n+2] as usize);
            result.push(table[(b24 >> 18) & 0x3f]); // 6bitずつ変換 --- (*8)
            result.push(table[(b24 >> 12) & 0x3f]);
            result.push(table[(b24 >>  6) & 0x3f]);
            result.push(table[(b24) & 0x3f]);
        });
    /*
    for i in 0..cnt {
        let n = i * 3; // 3文字(24bit)ずつ処理 --- (*7)
        let b24 = ((bin8[n+0] as usize) << 16) +
                  ((bin8[n+1] as usize) <<  8) +
                  (bin8[n+2] as usize);
        //println!("{:?}", b24);
        //          ((bin8[n+2] as usize) <<  0);
        result.push(table[(b24 >> 18) & 0x3f]); // 6bitずつ変換 --- (*8)
        result.push(table[(b24 >> 12) & 0x3f]);
        result.push(table[(b24 >>  6) & 0x3f]);
        result.push(table[(b24) & 0x3f]);
       // result.push(table[(b24 >>  0) & 0x3f]);
    }*/
    // 3バイトずつに割り切れなかった余りの部分を処理 --- (*9)
    match bin8.len() % 3 {
        1 => {
            let b24 = (bin8[cnt*3] as usize) << 16;
            result.push(table[(b24 >> 18) & 0x3f]);
            result.push(table[(b24 >> 12) & 0x3f]);
            result.push_str("==");
        },
        2 => {
            let b24 = ((bin8[cnt*3+0] as usize) << 16) +
                      ((bin8[cnt*3+1] as usize) << 8);
            result.push(table[(b24 >> 18) & 0x3f]);
            result.push(table[(b24 >> 12) & 0x3f]);
            result.push(table[(b24 >>  6) & 0x3f]);
            result.push('=');
        },
        _ => {},
    }
    result
}
// Base64デコードを行う関数 --- (*2)
fn base64_decode(in_str: &str) -> Vec<u8> {
    // Base64の変換テーブルを1文字ずつに区切る --- (*3)
    let t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    // 変換結果を保持する Vec<u8> --- (*4)
    let mut result = vec![];
    let cnt = in_str.len() / 4;
    //Rustらしくclosureを使ってみる
    (0..cnt)
        .into_iter()
        .for_each(|i| {            
            let str_dec = &in_str[i*4..(i*4)+4];     
            let b24 = (t.find(str_dec.chars().nth(0).unwrap_or('A')).unwrap_or(0) << 18) +
            (t.find(str_dec.chars().nth(1).unwrap_or('A')).unwrap_or(0) << 12) +
            (t.find(str_dec.chars().nth(2).unwrap_or('A')).unwrap_or(0) << 6) +
            (t.find(str_dec.chars().nth(3).unwrap_or('A')).unwrap_or(0)) ;    
            result.push((b24 >>16 & 0xff) as u8);
            result.push((b24 >>8 & 0xff) as u8);
            result.push((b24  & 0xff) as u8); 
        });
    /*
    for i in 0..cnt{
        let str_dec = &in_str[i*4..(i*4)+4];     
        let b24 = (t.find(str_dec.chars().nth(0).unwrap_or('A')).unwrap_or(0) << 18) +
        (t.find(str_dec.chars().nth(1).unwrap_or('A')).unwrap_or(0) << 12) +
        (t.find(str_dec.chars().nth(2).unwrap_or('A')).unwrap_or(0) << 6) +
        (t.find(str_dec.chars().nth(3).unwrap_or('A')).unwrap_or(0)) ;    
       
        result.push((b24 >>16 & 0xff) as u8);
        result.push((b24 >>8 & 0xff) as u8);
        result.push((b24  & 0xff) as u8); 
    }  */     
    result

 }

cargo run
で実行してみると

hello! => aGVsbG8h
aGVsbG8h => hello!
Rust => UnVzdA==
UnVzdA== => Rust
1234567890 => MTIzNDU2Nzg5MA==
MTIzNDU2Nzg5MA== => 1234567890
生姜焼き定食 => 55Sf5aec54S844GN5a6a6aOf
55Sf5aec54S844GN5a6a6aOf => 生姜焼き定食
文字列をBase64に変換して、デコードして復元する => 5paH5a2X5YiX44KSQmFzZTY044Gr5aSJ5o+b44GX44Gm44CB44OH44Kz44O844OJ44GX44Gm5b6p5YWD44GZ44KL

5paH5a2X5YiX44KSQmFzZTY044Gr5aSJ5o+b44GX44Gm44CB44OH44Kz44O844OJ44GX44Gm5b6p5YWD44GZ44KL => 文字列をBase64に変換して、デコードして復元する

ちゃんと変換(エンコード)して復元(デコード)できたことが分かるでしょう。

 

Template Design © Joomla Templates | GavickPro. All rights reserved.