1
1
mirror of https://github.com/wader/fq.git synced 2024-11-23 18:56:52 +03:00
fq/pkg/bitio/bitio.go
2022-02-09 01:35:47 +01:00

235 lines
5.2 KiB
Go

package bitio
import (
"bytes"
"errors"
"fmt"
"io"
"strings"
)
// ErrOffset means seek positions is invalid
var ErrOffset = errors.New("invalid seek offset")
// ErrNegativeNBits means read tried to read negative number of bits
var ErrNegativeNBits = errors.New("negative number of bits")
// Reader is something that reads bits.
// Similar to io.Reader.
type Reader interface {
ReadBits(p []byte, nBits int64) (n int64, err error)
}
// Writer is something that writs bits.
// Similar to io.Writer.
type Writer interface {
WriteBits(p []byte, nBits int64) (n int64, err error)
}
// Seeker is something that seeks bits
// Similar to io.Seeker.
type Seeker interface {
SeekBits(bitOffset int64, whence int) (int64, error)
}
// ReaderAt is something that reads bits at an offset.
// Similar to io.ReaderAt.
type ReaderAt interface {
ReadBitsAt(p []byte, nBits int64, bitOff int64) (n int64, err error)
}
// ReadSeeker is bitio.Reader and bitio.Seeker.
type ReadSeeker interface {
Reader
Seeker
}
// ReadAtSeeker is bitio.ReaderAt and bitio.Seeker.
type ReadAtSeeker interface {
ReaderAt
Seeker
}
// ReaderAtSeeker is bitio.Reader, bitio.ReaderAt and bitio.Seeker
type ReaderAtSeeker interface {
Reader
ReaderAt
Seeker
}
// NewBitReader reader reading nBits bits from a []byte.
// If nBits is -1 all bits will be used.
// Similar to bytes.NewReader.
func NewBitReader(buf []byte, nBits int64) *SectionReader {
if nBits < 0 {
nBits = int64(len(buf)) * 8
}
return NewSectionReader(
NewIOBitReadSeeker(bytes.NewReader(buf)),
0,
nBits,
)
}
// BitsByteCount returns smallest amount of bytes to fit nBits bits.
func BitsByteCount(nBits int64) int64 {
n := nBits / 8
if nBits%8 != 0 {
n++
}
return n
}
// BytesFromBitString from []byte to bit string, ex: "0101" -> ([]byte{0x50}, 4)
func BytesFromBitString(s string) ([]byte, int64) {
r := len(s) % 8
bufLen := len(s) / 8
if r > 0 {
bufLen++
}
buf := make([]byte, bufLen)
for i := 0; i < len(s); i++ {
d := s[i] - '0'
if d != 0 && d != 1 {
panic(fmt.Sprintf("invalid bit string %q at index %d %q", s, i, s[i]))
}
buf[i/8] |= d << (7 - i%8)
}
return buf, int64(len(s))
}
// BitStringFromBytes from string to []byte, ex: ([]byte{0x50}, 4) -> "0101"
func BitStringFromBytes(buf []byte, nBits int64) string {
sb := &strings.Builder{}
for i := int64(0); i < nBits; i++ {
if buf[i/8]&(1<<(7-i%8)) > 0 {
sb.WriteString("1")
} else {
sb.WriteString("0")
}
}
return sb.String()
}
// CopyBuffer bits from src to dst using provided byte buffer.
// Similar to io.CopyBuffer.
func CopyBuffer(dst Writer, src Reader, buf []byte) (n int64, err error) {
// same default size as io.Copy
if buf == nil {
buf = make([]byte, 32*1024)
}
var written int64
for {
rBits, rErr := src.ReadBits(buf, int64(len(buf))*8)
if rBits > 0 {
wBits, wErr := dst.WriteBits(buf, rBits)
written += wBits
if wErr != nil {
err = wErr
break
}
if rBits != wBits {
err = io.ErrShortWrite
break
}
}
if rErr != nil {
if !errors.Is(rErr, io.EOF) {
err = rErr
}
break
}
}
return written, err
}
// Copy bits from src to dst.
// Similar to io.Copy.
func Copy(dst Writer, src Reader) (n int64, err error) {
return CopyBuffer(dst, src, nil)
}
// TODO: make faster, align and use copy()
func copyBufBits(dst []byte, dstStart int64, src []byte, srcStart int64, n int64, zero bool) {
l := n
off := int64(0)
for l > 0 {
c := int64(64)
if l < c {
c = l
}
u := Read64(src, srcStart+off, c)
Write64(u, c, dst, dstStart+off)
off += c
l -= c
}
// zero fill last bits if not aligned
e := dstStart + n
if zero && e%8 != 0 {
Write64(0, 8-(e%8), dst, e)
}
}
// TODO: redo?
func readFull(p []byte, nBits int64, bitOff int64, fn func(p []byte, nBits int64, bitOff int64) (int64, error)) (int64, error) {
if nBits < 0 {
return 0, ErrNegativeNBits
}
readBitOffset := int64(0)
for readBitOffset < nBits {
byteOffset := readBitOffset / 8
byteBitsOffset := readBitOffset % 8
partialByteBitsLeft := (8 - byteBitsOffset) % 8
leftBits := nBits - readBitOffset
if partialByteBitsLeft != 0 || leftBits < 8 {
readBits := partialByteBitsLeft
if partialByteBitsLeft == 0 || leftBits < readBits {
readBits = leftBits
}
var pb [1]byte
rBits, err := fn(pb[:], readBits, bitOff+readBitOffset)
Write64(uint64(pb[0]>>(8-rBits)), rBits, p, readBitOffset)
readBitOffset += rBits
if err != nil {
return nBits - readBitOffset, err
}
continue
}
rBits, err := fn(p[byteOffset:], nBits-readBitOffset, bitOff+readBitOffset)
readBitOffset += rBits
if err != nil {
return nBits - readBitOffset, err
}
}
return nBits, nil
}
// ReadAtFull expects to read nBits from r at bitOff.
// Similar to io.ReadFull.
func ReadAtFull(r ReaderAt, p []byte, nBits int64, bitOff int64) (int64, error) {
return readFull(p, nBits, bitOff, func(p []byte, nBits int64, bitOff int64) (int64, error) {
return r.ReadBitsAt(p, nBits, bitOff)
})
}
// ReadFull expects to read nBits from r.
// Similar to io.ReadFull.
func ReadFull(r Reader, p []byte, nBits int64) (int64, error) {
return readFull(p, nBits, 0, func(p []byte, nBits int64, bitOff int64) (int64, error) {
return r.ReadBits(p, nBits)
})
}