mirror of
https://github.com/wader/fq.git
synced 2024-12-25 22:34:14 +03:00
322 lines
7.5 KiB
Go
322 lines
7.5 KiB
Go
package decode
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/wader/fq/pkg/bitio"
|
|
"golang.org/x/text/encoding/unicode"
|
|
)
|
|
|
|
// TODO: FP64,unsigned/BE/LE? rename SFP32?
|
|
|
|
func (d *D) TryUTF8Fn(nBytes int, fn func(s string) string) (string, error) {
|
|
s, err := d.bitBuf.BytesLen(nBytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return fn(string(s)), nil
|
|
}
|
|
|
|
func (d *D) TryUTF8(nBytes int) (string, error) {
|
|
return d.TryUTF8Fn(nBytes, func(s string) string { return s })
|
|
}
|
|
|
|
func (d *D) TryUTF16BE(nBytes int) (string, error) {
|
|
b, err := d.bitBuf.BytesLen(nBytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM).NewDecoder().String(string(b))
|
|
}
|
|
|
|
func (d *D) TryUTF16LE(nBytes int) (string, error) {
|
|
b, err := d.bitBuf.BytesLen(nBytes)
|
|
// TODO: len check
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder().String(string(b))
|
|
}
|
|
|
|
// TryUTF8ShortString read pascal short string, max nBytes
|
|
func (d *D) TryUTF8ShortString(nBytes int) (string, error) {
|
|
l, err := d.TryU8()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
n := int(l)
|
|
if nBytes != -1 {
|
|
n = nBytes - 1
|
|
}
|
|
|
|
s, err := d.bitBuf.BytesLen(n)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(s[0:l]), nil
|
|
}
|
|
|
|
// PeekBits peek nBits bits from buffer
|
|
// TODO: share code?
|
|
func (d *D) TryPeekBits(nBits int) (uint64, error) {
|
|
start, err := d.bitBuf.SeekBits(0, io.SeekCurrent)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
n, err := d.bits(nBits)
|
|
if _, err := d.bitBuf.SeekBits(start, io.SeekStart); err != nil {
|
|
return 0, err
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// Bits reads nBits bits from buffer
|
|
func (d *D) bits(nBits int) (uint64, error) {
|
|
// 64 bits max, 9 byte worse case if not byte aligned
|
|
buf := d.SharedReadBuf(9)
|
|
_, err := bitio.ReadFull(d.bitBuf, buf, nBits)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return bitio.Read64(buf[:], 0, nBits), nil
|
|
}
|
|
|
|
// Bits reads nBits bits from buffer
|
|
func (d *D) Bits(nBits int) (uint64, error) {
|
|
n, err := d.bits(nBits)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (d *D) TryPeekFind(nBits int, seekBits int64, maxLen int64, fn func(v uint64) bool) (int64, uint64, error) {
|
|
start, err := d.bitBuf.SeekBits(0, io.SeekCurrent)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
found := false
|
|
var count int64
|
|
var v uint64
|
|
for {
|
|
if maxLen >= 0 && count >= maxLen {
|
|
break
|
|
}
|
|
v, err = d.TryU(nBits)
|
|
if err != nil {
|
|
if _, err := d.bitBuf.SeekBits(start, io.SeekStart); err != nil {
|
|
return 0, 0, err
|
|
}
|
|
return 0, 0, err
|
|
}
|
|
if fn(v) {
|
|
found = true
|
|
break
|
|
}
|
|
count += seekBits
|
|
if _, err := d.bitBuf.SeekBits(start+count, io.SeekStart); err != nil {
|
|
return 0, 0, err
|
|
}
|
|
}
|
|
if _, err := d.bitBuf.SeekBits(start, io.SeekStart); err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
if !found {
|
|
return -1, 0, nil
|
|
}
|
|
|
|
return count, v, nil
|
|
}
|
|
|
|
func (d *D) ZeroPadding(nBits int) bool {
|
|
isZero := true
|
|
left := nBits
|
|
for {
|
|
// TODO: smart skip?
|
|
rBits := left
|
|
if rBits == 0 {
|
|
break
|
|
}
|
|
if rBits > 64 {
|
|
rBits = 64
|
|
}
|
|
n, err := d.Bits(rBits)
|
|
if err != nil {
|
|
panic(IOError{Err: err, Op: "ZeroPadding", Size: int64(rBits), Pos: d.Pos()})
|
|
}
|
|
isZero = isZero && n == 0
|
|
left -= rBits
|
|
}
|
|
return isZero
|
|
}
|
|
|
|
func (d *D) FieldOptionalFillFn(name string, fn func(d *D)) int64 {
|
|
start := d.Pos()
|
|
fn(d)
|
|
fillLen := d.Pos() - start
|
|
if fillLen > 0 {
|
|
d.FieldBitBufRange(name, start, fillLen)
|
|
}
|
|
|
|
return fillLen
|
|
}
|
|
|
|
func (d *D) FieldOptionalZeroBytes(name string) int64 {
|
|
return d.FieldOptionalFillFn(name, func(d *D) {
|
|
for d.BitsLeft() >= 8 && d.PeekBits(8) == 0 {
|
|
d.SeekRel(8)
|
|
}
|
|
})
|
|
}
|
|
|
|
func (d *D) fieldZeroPadding(name string, nBits int, panicOnNonZero bool) {
|
|
pos := d.Pos()
|
|
var isZero bool
|
|
d.FieldFn(name, func() *Value {
|
|
isZero = d.ZeroPadding(nBits)
|
|
s := "Correct"
|
|
if !isZero {
|
|
s = "Incorrect"
|
|
}
|
|
// TODO: proper warnings
|
|
return &Value{Symbol: s, Description: "zero padding"}
|
|
})
|
|
if panicOnNonZero && !isZero {
|
|
panic(ValidateError{Reason: "expected zero padding", Pos: pos})
|
|
}
|
|
}
|
|
|
|
func (d *D) FieldValidateZeroPadding(name string, nBits int) {
|
|
d.fieldZeroPadding(name, nBits, true)
|
|
}
|
|
|
|
func (d *D) FieldZeroPadding(name string, nBits int) {
|
|
d.fieldZeroPadding(name, nBits, false)
|
|
}
|
|
|
|
// Bool reads one bit as a boolean
|
|
func (d *D) TryBool() (bool, error) {
|
|
n, err := d.TryU(1)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return n == 1, nil
|
|
}
|
|
|
|
func (d *D) Bool() bool {
|
|
b, err := d.TryBool()
|
|
if err != nil {
|
|
panic(IOError{Err: err, Op: "Bool", Size: 1, Pos: d.Pos()})
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (d *D) FieldBool(name string) bool {
|
|
return d.FieldBoolFn(name, func() (bool, string) {
|
|
b, err := d.TryBool()
|
|
if err != nil {
|
|
panic(IOError{Err: err, Name: name, Op: "FieldBool", Size: 1, Pos: d.Pos()})
|
|
}
|
|
return b, ""
|
|
})
|
|
}
|
|
|
|
func (d *D) FieldBytesLen(name string, nBytes int) []byte {
|
|
return d.FieldBytesFn(name, func() ([]byte, string) {
|
|
bs, err := d.bitBuf.BytesLen(nBytes)
|
|
if err != nil {
|
|
panic(IOError{Err: err, Name: name, Op: "FieldBytesLen", Size: int64(nBytes) * 8, Pos: d.Pos()})
|
|
}
|
|
return bs, ""
|
|
})
|
|
}
|
|
|
|
// UTF8Fn read nBytes utf8 string and transform it using fn
|
|
func (d *D) UTF8Fn(nBytes int, fn func(s string) string) string {
|
|
s, err := d.TryUTF8Fn(nBytes, fn)
|
|
if err != nil {
|
|
panic(IOError{Err: err, Op: "UTF8", Size: int64(nBytes) * 8, Pos: d.Pos()})
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (d *D) UTF8(nBytes int) string {
|
|
return d.UTF8Fn(nBytes, func(s string) string { return s })
|
|
}
|
|
|
|
// UTF16BE read nBytes utf16be string
|
|
func (d *D) UTF16BE(nBytes int) string {
|
|
s, err := d.TryUTF16BE(nBytes)
|
|
if err != nil {
|
|
panic(IOError{Err: err, Op: "UTF16BE", Size: int64(nBytes) * 8, Pos: d.Pos()})
|
|
}
|
|
return s
|
|
}
|
|
|
|
// UTF16LE read nBytes utf16le string
|
|
func (d *D) UTF16LE(nBytes int) string {
|
|
s, err := d.TryUTF16LE(nBytes)
|
|
if err != nil {
|
|
panic(IOError{Err: err, Op: "UTF16LE", Size: int64(nBytes) * 8, Pos: d.Pos()})
|
|
}
|
|
return s
|
|
}
|
|
|
|
// FieldUTF8 read nBytes utf8 string and add a field
|
|
func (d *D) FieldUTF8Fn(name string, nBytes int, fn func(s string) string) string {
|
|
return d.FieldStrFn(name, func() (string, string) {
|
|
str, err := d.TryUTF8Fn(nBytes, fn)
|
|
if err != nil {
|
|
panic(IOError{Err: err, Name: name, Op: "FieldUTF8", Size: int64(nBytes) * 8, Pos: d.Pos()})
|
|
}
|
|
return str, ""
|
|
})
|
|
}
|
|
|
|
func (d *D) FieldUTF8Null(name string, nBytes int) string {
|
|
return d.FieldUTF8Fn(name, nBytes, func(s string) string { return strings.TrimRight(s, "\x00") })
|
|
}
|
|
|
|
func (d *D) FieldUTF8(name string, nBytes int) string {
|
|
return d.FieldUTF8Fn(name, nBytes, func(s string) string { return s })
|
|
}
|
|
|
|
// FieldUTF16BE read nBytes utf16be string and add a field
|
|
func (d *D) FieldUTF16BE(name string, nBytes int) string {
|
|
return d.FieldStrFn(name, func() (string, string) {
|
|
str, err := d.TryUTF16BE(nBytes)
|
|
if err != nil {
|
|
panic(IOError{Err: err, Name: name, Op: "FieldUTF16BE", Size: int64(nBytes) * 8, Pos: d.Pos()})
|
|
}
|
|
return str, ""
|
|
})
|
|
}
|
|
|
|
// FieldUTF16LE read nBytes utf16le string and add a field
|
|
func (d *D) FieldUTF16LE(name string, nBytes int) string {
|
|
return d.FieldStrFn(name, func() (string, string) {
|
|
str, err := d.TryUTF16LE(nBytes)
|
|
if err != nil {
|
|
panic(IOError{Err: err, Name: name, Op: "FieldUTF16LE", Size: int64(nBytes) * 8, Pos: d.Pos()})
|
|
}
|
|
return str, ""
|
|
})
|
|
}
|
|
|
|
// FieldUTF8ShortString read nBytes utf8 pascal short string and add a field
|
|
func (d *D) FieldUTF8ShortString(name string, nBytes int) string {
|
|
return d.FieldStrFn(name, func() (string, string) {
|
|
str, err := d.TryUTF8ShortString(nBytes)
|
|
if err != nil {
|
|
panic(IOError{Err: err, Name: name, Op: "FieldUTF8ShortString", Size: int64(nBytes) * 8, Pos: d.Pos()})
|
|
}
|
|
return str, ""
|
|
})
|
|
}
|