base64 decoder(io.Reader implementation)misbehavior

时间:2017-05-30 13:36:31

标签: file go io reader

我已经尝试在for循环中重新声明/分配base64解码器并使用os.Seek函数在此循环结束之前返回到文件的开头,以便被调用函数(在此测试用例中为PrintBytes)能够在for循环中从头到尾一次又一次地处理文件。

这是我的(我确定非常非惯用)代码,它无法在main的主for循环的第二次迭代中将第二个字节读入长度为2的[]字节和容量2( ):

package main

import (
    "encoding/base64"
    "io"
    "log"
    "net/http"
    "os"
)

var (
    remote_file string = "http://cryptopals.com/static/challenge-data/6.txt"
    local_file  string = "secrets_01_06.txt"
)

func main() {
    f, err := os.Open(local_file)
    if err != nil {
        DownloadFile(local_file, remote_file)
        f, err = os.Open(local_file)
        if err != nil {
            log.Fatal(err)
        }
    }
    defer f.Close()

    for blocksize := 1; blocksize <= 5; blocksize++ {
        decoder := base64.NewDecoder(base64.StdEncoding, f)
        PrintBytes(decoder, blocksize)
        _, err := f.Seek(0, 0)
        if err != nil {
            log.Fatal(err)
        }
    }
}

func PrintBytes(reader io.Reader, blocksize int) {
    block := make([]byte, blocksize)
    for {
        n, err := reader.Read(block)
        if err != nil && err != io.EOF {
            log.Fatal(err)
        }
        if n != blocksize {
            log.Printf("n=%d\tblocksize=%d\tbreaking...", n, blocksize)
            break
        }
        log.Printf("%x\tblocksize=%d", block, blocksize)
    }
}

func DownloadFile(local string, url string) {
    f, err := os.Create(local)
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    resp, err := http.Get(url)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    _, err = io.Copy(f, resp.Body)
    if err != nil {
        log.Fatal(err)
    }
}

此代码的输出可在此处查看https://gist.github.com/tomatopeel/b8e2f04179c7613e2a8c8973a72ec085

这种行为我不明白: https://gist.github.com/tomatopeel/b8e2f04179c7613e2a8c8973a72ec085#file-bad_reader_log-L5758

我原以为它只是从头到尾简单地将文件读取为2字节切片中的2个字节。出于什么原因,它只读取1个字节?

1 个答案:

答案 0 :(得分:0)

这不是encoding/base64的问题。使用io.Reader时,不能保证读取的字节数完全等于缓冲区大小(即示例代码中的blocksize)。文档说明:

  

读取读取最多len(p)个字节到p。它返回读取的字节数(0 <= n <= len(p))和遇到的任何错误。即使Read返回n&lt; len(p),它可以在调用期间将所有p用作临时空间。如果某些数据可用但不是len(p)字节,则Read通常返回可用而不是等待更多的数据。

在您的示例中,将PrintBytes更改为

func PrintBytes(reader io.Reader, blocksize int) {
    block := make([]byte, blocksize)
    for {
        n, err := reader.Read(block)
        //Process the data if n > 0, even when err != nil
        if n > 0 {
            log.Printf("%x\tblocksize=%d", block[:n], blocksize)
        }

        //Check for error
        if err != nil {
            if err != io.EOF {
                log.Fatal(err)
            } else if err == io.EOF {
                break
            }
        } else if n == 0 {
            //Considered as nothing happened
            log.Printf("WARNING: read return 0,nil")
        }
    }
}

<强>更新

正确使用io.Reader,修改代码以便即使发生错误也始终处理n > 0数据。