希望使用Reader接口来使我的二进制文件导入代码更干净

问题描述 投票:0回答:1

我很清楚

encoding/binary
不适用于可变参数类型(如字符串)。我的问题如下:

我有一些用 pascal 语言编写的程序生成的旧二进制文件。如果不是字符串类型,我本来可以轻松阅读它。 Pascal允许你声明一个最大长度的字符串(长度不能超过255个字符),但它会将其保存到字节数较少的文件中以节省磁盘空间。例如,如果我有以下声明:

var a_string: string[200];
a_string := "AAAA"

然后文件将以以下字节结束:

0x04 0x41 0x41 0x41 0x41
^^^^ ^^^^^^^^^^^^^^^^^^^
 |           +---------- content
 +---------------------- length

到目前为止一切都很好,没有什么特别争议的。我的问题是,我试图将其连接到 binary.Read ,但它显然失败了,因为 binary.Read 只能解析固定大小的结构。然而,我的问题是,读取此结构的唯一方法是以下方式:

https://go.dev/play/p/OZRgaDqh9cc

它确实有效,但看起来很丑。假设该结构中有 13 个字段,但只有 3 个字段是 pascal 字符串类型。我有什么遗漏的吗?换句话说,是否有一些我无法通过谷歌搜索到的阅读器界面的用途?基本上我在这里想做的唯一一件事就是覆盖这个自定义类型的 bytes.Reader 的行为。

我想到了两种方法。首先是通过反射迭代结构体,并对恰好是 pascal 字符串类型的字段做出相应的反应。但这似乎太过分了。

第二种方法:

1/将“纯”类型包装起来,我可以用常规的

binary.Read
在子结构中读取它

2/使用

totalBytesRead, _ = reader.Seek(0, os.SEEK_CUR)
获取缓冲区内的“当前”位置

3/ 使用自定义

.Read()
(将返回
bytesRead
)读取帕斯卡字符串,并将
totalBytesRead
增加
bytesRead

4/使用

reader.Seek(totalBytesRead, os.SEEK_SET)
并继续使用
binary.Read

但它看起来仍然......尴尬,我的直觉告诉我我做错了

我也尝试过:

  • 创建一个像这样的类型别名,希望欺骗编码/二进制认为它只需要分配一个更大的缓冲区:
type PString [255]byte
  • 查看二进制协议的现有实现。例如,我看了一下这段代码: https://github.com/elcapitansam/gostruct/tree/master 但这是略有不同的问题,因为它解码为“纯”接口。我的问题略有不同,因为我已经知道我想要解析的目标接口是什么。
go parsing decode binaryfiles pascal
1个回答
0
投票

我尝试了反射方法,这是我能够实现的解决方案:

https://go.dev/play/p/MPBOmQAmECl

(它使用编解码器的动态映射,而不是对函数内的所有内容进行硬编码)

对我来说,它仍然显得非常复杂。我的意思是,我一直认为,如果其他一切都失败了,使用反射将是解决方案,但我的问题似乎很简单,以至于这个解决方案在某种程度上似乎是错误的,至少在直觉上是这样。另外,在这种方法中,仍然需要为所有 int / uint 和 float 类型实现解码器功能

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "reflect"
)

// decoder function receives a source byte slice and a destination pointer
// it returns number of bytes that have been parsed and optionally an error
type Decoder func(src []byte, dest reflect.Value, structField reflect.StructField) (int, error)

type BinaryDecoder struct {
    Codecs map[reflect.Kind]Decoder
}

func (d *BinaryDecoder) Unpack(src []byte, dst interface{}) (int, error) {
    return d.unpackRecursive(src, dst, reflect.StructField{})
}

func (d *BinaryDecoder) unpackRecursive(src []byte, dst interface{}, structField reflect.StructField) (int, error) {
    var bytesRead int = 0
    var err error

    dstValue := reflect.ValueOf(dst)
    dstType := dstValue.Type()

    if dstValue.Kind() == reflect.Ptr {
        dstValue = dstValue.Elem()
        dstType = dstType.Elem()
    }

    valueType := dstValue.Kind()

    switch valueType {
    case reflect.Struct:
        // let's unpack the struct field by field.
        for i := 0; i < dstValue.NumField(); i += 1 {
            structField := dstValue.Field(i)
            fmt.Printf("%d'th field has a name of %s and is of type %v\n", i, structField.Type().Name(), structField.Kind())
            fmt.Printf("bin tag: %v\n", dstType.Field(i).Tag.Get("bin"))
            if bytesRead, err = d.unpackRecursive(src, structField.Addr().Interface(), dstType.Field(i)); err != nil {
                return -1, fmt.Errorf("unable to unpack struct: %v", err)
            }
            fmt.Printf("Read %d bytes from %s\n", bytesRead, structField.Type().Name())
            src = src[bytesRead:]
        }
    case reflect.Array:
        declaredSize := dstType.Size()
        fmt.Printf("begin to unpack array; declaredSize=%d\n", declaredSize)
        for i := 0; i < int(declaredSize); i += 1 {
            if bytesRead, err = d.unpackRecursive(src, dstValue.Index(i).Addr().Interface(), structField); err != nil {
                return -1, fmt.Errorf("unable to unpack array at position %d: %v", i, err)
            }
            src = src[bytesRead:]
        }

    default:
        decoder, exists := d.Codecs[valueType]
        if !exists {
            return -1, fmt.Errorf("unsupported value type: %v", valueType)
        }

        return decoder(src, dstValue, structField)
    }

    return bytesRead, nil
}

type NestedStruct struct {
    PasString string `bin:"p"`
}

type BinaryStruct struct {
    IntField uint16
    Nested   NestedStruct
    Array    [10]byte
}

func decodeUint8(src []byte, dest reflect.Value, structField reflect.StructField) (int, error) {
    dest.SetUint(uint64(src[0]))
    return 1, nil
}

func decodeUint16(src []byte, dest reflect.Value, structField reflect.StructField) (int, error) {
    var tmpVal uint16
    if err := binary.Read(bytes.NewReader(src), binary.LittleEndian, &tmpVal); err != nil {
        return -1, fmt.Errorf("unable to decode uint16: %v", err)
    }
    dest.SetUint(uint64(tmpVal))
    return 2, nil
}

func decodeString(src []byte, dest reflect.Value, structField reflect.StructField) (int, error) {
    if structField.Tag.Get("bin") != "p" {
        return -1, fmt.Errorf("unsupported string type")
    }
    declaredLength := int(src[0])
    dest.SetString(string(src[1 : declaredLength+1]))

    return declaredLength + 1, nil
}

func main() {
    var srcBytes = []byte{
        0x00, 0x01, // uint16
        0x04, 0x41, 0x41, 0x41, 0x41, // string
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // array of 10 bytes
    }

    var bin BinaryStruct
    var numBytes int
    var err error

    decoder := &BinaryDecoder{
        Codecs: map[reflect.Kind]Decoder{
            reflect.Uint8:  decodeUint8,
            reflect.Uint16: decodeUint16,
            reflect.String: decodeString,
        },
    }

    numBytes, err = decoder.Unpack(srcBytes, &bin)

    if err != nil {
        fmt.Printf("Error unpacking structure: %v\n", err)
    }

    fmt.Printf("Read %d bytes; end structure: %+v\n", numBytes, bin)
}
© www.soinside.com 2019 - 2024. All rights reserved.