kokoichi206-sandbox / go-cmd-base64

MIT License
0 stars 0 forks source link

base64 encoding #1

Open kokoichi206 opened 2 years ago

kokoichi206 commented 2 years ago

Logic- RFC4648

The encoding process represents 24-bit groups of input bits as output strings of 4 encoded characters. Proceeding from left to right, a 24-bit input group is formed by concatenating 3 8-bit input groups. These 24 bits are then treated as 4 concatenated 6-bit groups, each of which is translated into a single character in the base 64 alphabet.

6 bit -> 8 bit の1文字に変換するので、33 % は増える(パディングもそれに追加)

仕様

基本は mac に入っている base64 に合わせる

2 つ以上のファイル名が渡されたとき

1 つめのファイルのエンコードの値のみが出力される

kokoichi206 commented 2 years ago

メモ

[]byte to integer

data := binary.BigEndian.Uint32(append(make([]byte, 1), buf...))
// fmt.Println(binary.LittleEndian.Uint32(append(make([]byte, 1), buf...)))
data := int(buf[2]) + int(buf[1])*256 + int(buf[0])*256*256

string に対する index アクセスは別のものが返る

インデックスだとマルチバイト時にどこを指すのかが曖昧なため?

hoge := "hogee"
fmt.Println(string(hoge[0]))
// 104 が出力される

参考

buf := make([]byte, 3) でゼロフィルはされてる?

a だけ書かれたファイルを読み取った時の buf

[97 0 0]

abcd が書かれたファイルを loop で読み取った時の buf 出力

[97 98 99]
[100 98 99]

3 byte にみたない時は前回の値が消えてないので強制的に消してあげる

outer loop を break する

名前をつけてそこを参照しにいく

func ReadAll() {
        ...
    main:
    for {
        v, _ := r.Read(buf)
        switch v {
        case 0:
            break main
        case 1:
            buf[1] = 0
        }
    }
    fmt.Println(buf)
}

参考

go で標準入力を読み込む

func readStandardInput() []byte {
    scanner := bufio.NewScanner(os.Stdin)
    // 1行分スキャン
    scanner.Scan()
    return scanner.Bytes()
}

参考

関数を引数にとる

// ファイルからの読み込みと標準入力からの入力のどちらかで関数を実行する。
func execute(fn func([]byte) string, file []byte, si []byte) string {
    if len(file) != 0 {
        return fn(file)
    }
    return fn(si)
}
kokoichi206 commented 2 years ago

3 byte ごと読む作戦が。。。

EOF が来るまでは確定で 3byte 読み込める想定だったが、どうやらそうではない。。。 例えば jpg だと 1366 * 3byte ごとに、3 byte ちょうどで読み込めてない。

5000 bytes ごとになんかある??

1365
2731
4097
5463
6829
...

解決策

全部読み込んでから、3 byte ずつ使うようにしたらいけた。。。

3 byte ずつの時の方針

    f, err := os.Open("43694.jpg")
    // f, err := os.Open("test.txt")
    if err != nil {
        return
    }
    defer f.Close()

    r := bufio.NewReader(f)
    // 3 bytes ずつ読み込む(24 bits -> 4*6 bits ずつエンコードするため)
    buf := make([]byte, 3)

    var builder strings.Builder

    cnt := 0
READLOOP:
    for {
        v, err := r.Read(buf)
        // if err != nil {
        //  fmt.Println(err)
        // }
        switch v {
        case 0:
            break READLOOP
        case 1:
            buf[1] = 0
            buf[2] = 0
            // fmt.Println(cnt)
        case 2:
            buf[2] = 0
            // fmt.Println(cnt)
        }

        if e := encode3bytes(buf, v, &builder, err); e != nil {
            fmt.Println("unexpected error occured while encoding")
            return
        }
        cnt += 1
    }
    fmt.Println(builder.String())
kokoichi206 commented 2 years ago

現状の decode 方法だと、元が 3byte に満たない時 NULL が入っちゃう

wiki

ヌル終端文字列(ヌルしゅうたんもじれつ、英語: null-terminated string)とは、文字配列に格納し、ヌル文字('\0'、ASCIIコードではNUL)でその終端(番兵)を表した文字列である

現状の decode

// 渡されたバイト列ををデコードする。
func Decode(buf []byte) string {

    sb := string(buf)
    max := len(sb) / 4

    var res bytes.Buffer
    // 最終行以外をエンコード。
    for i := 0; i < max; i++ {
        b := sb[4*i : 4*i+4]

        data := alphabetMap[string(b[3])] + alphabetMap[string(b[2])]*64 + alphabetMap[string(b[1])]*64*64 + alphabetMap[string(b[0])]*64*64*64

        b1 := data / (256 * 256)
        b2 := (data % (256 * 256)) / 256
        b3 := data % 256
        res.Write([]byte{byte(b1), byte(b2), byte(b3)})
    }

    return res.String()
}

単体テスト時に発見

Screen Shot 2022-09-12 at 1 40 49

diff でも見つかるが、ターミナルでの出力の見た目は同じ。。。

$ diff <(base64 -d encoded_test.txt) <(./base64 -d encoded_test.txt)
Binary files /dev/fd/15 and /dev/fd/16 differ
Screen Shot 2022-09-12 at 1 43 29

NULL は悪者?

jpg にはいっぱい隠れてる。

以下のようにして、test_data/cat.jpg に隠れてる NULL を探すと 1636 個あった! (前あった問題と同じかも。。。)

func readFile(fileName string) []byte {
    buf, err := ioutil.ReadFile(fileName)
    if err != nil {
        fmt.Printf("Unable to open '%s': No such file or directory", fileName)
        os.Exit(1)
    }
    cnt := 0
    for _, b := range buf {
        if b == 0 {
            cnt += 1
        }
    }
    fmt.Printf("found NULL string %d\n", cnt)
    return buf
}

方針

途中に見つかる NULL 文字は、元のデータ形式によっては十分あり得るので保持。 ただし、最終デコードが NULL(byte の値が 0)であることは、もともとのバイトが 3 なかったことを意味しているので、そこはデコードさせない。