неправильное поведение декодера base64 (реализация io.Reader)

Я попытался в цикле for повторно объявить/назначить декодер base64 и использовать функцию os.Seek, чтобы вернуться к началу файла в конце цикла до этого, чтобы вызываемая функция ( в этом тестовом примере PrintBytes), чтобы иметь возможность обрабатывать файл от начала до конца снова и снова в цикле for.

Вот мой (я уверен, ужасно неидиоматический) код, который не может прочитать 2-й байт в [] байт длины 2 и емкости 2 во время второй итерации основного цикла for в main():

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 байт?


person tomatopeel    schedule 30.05.2017    source источник
comment
Я не думаю, что декодер base64 не имеет состояния, поэтому Seeking базового считывателя между чтениями из считывателя base64 может иметь непредсказуемые результаты.   -  person Adrian    schedule 30.05.2017
comment
назначение этого кода крайне неясно. мне пришлось вернуться к исходной задаче, чтобы понять, что происходит с итерацией по размеру блока (что выглядит странно, потому что он не использует накопленное n, чтобы гарантировать, что он в конечном итоге прочитает байты размера блока). Просто asBytes, err := ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, f)) и приступайте к работе с данными asBytes. (т.е.: расчет расстояния между длинами блоков и т. д.)   -  person Rob    schedule 30.05.2017
comment
@Rob Единственной целью кода здесь была демонстрация вопроса. Это модифицированный отрывок из явно глючного кода для данной задачи. Я просто продолжал пользоваться файлом из челленджа, видимо из-за лени. Я рассмотрю возможность использования накопленного n, спасибо, я новичок в golang.   -  person tomatopeel    schedule 30.05.2017


Ответы (1)


Это не проблема encoding/base64. При использовании io.Reader не гарантируется, что количество прочитанных байт точно равно размеру буфера ( то есть blocksize в вашем примере кода). В документации указано:

Read читает до len(p) байт в p. Он возвращает количество прочитанных байтов (0 ‹= n ‹= len(p)) и обнаруженные ошибки. Даже если функция Read возвращает n ‹ 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, даже при возникновении ошибки.

person putu    schedule 30.05.2017