Files
txqr/protocol/decode.go
T
Ivan Danyliuk e4cf6df2b1 Add decoder
2018-11-10 15:46:38 +01:00

111 lines
2.3 KiB
Go

package protocol
import (
"errors"
"fmt"
"sort"
)
// default errors
var (
ErrInvalidFrame = errors.New("invalid frame")
)
// Decoder represents protocol decode.
type Decoder struct {
buffer []byte
complete bool
total int
frames []frameInfo
}
// frameInfo represents the information about read frames.
// As frames can change size dynamically, we keep size info as well.
type frameInfo struct {
offset, size int
}
// NewDecoder creats and inits a new decoder.
func NewDecoder() *Decoder {
return &Decoder{
buffer: []byte{},
}
}
// NewDecoderSize creats and inits a new decoder for the known size.
// Note, it doesn't limit the size of the input, but optimizes memory allocation.
func NewDecoderSize(size int) *Decoder {
return &Decoder{
buffer: make([]byte, size),
}
}
// DecodeChunk takes a single chunk of data and decodes it.
func (d *Decoder) DecodeChunk(data string) error {
if data == "" || len(data) < 4 {
return ErrInvalidFrame
}
var (
offset, total int
payload []byte
)
_, err := fmt.Sscanf(data, "%x/%x|%s", &offset, &total, &payload)
if err != nil {
return fmt.Errorf("invalid frame: %v", err)
}
// allocate enough memory at first total read
if total > d.total {
d.buffer = make([]byte, total)
d.total = total
}
size := len(payload)
// TODO(divan): optmize memory allocation
d.frames = append(d.frames, frameInfo{offset, size})
copy(d.buffer[offset:offset+size], payload)
// run the integrity check
d.complete = d.isCompleted()
return nil
}
// Data returns decoded data.
func (d *Decoder) Data() string {
return string(d.buffer)
}
// DataBytes returns decoded data as a byte slice.
func (d *Decoder) DataBytes() []byte {
return d.buffer
}
// IsCompleted reports whether the read was completed successfully or not.
func (d *Decoder) IsCompleted() bool {
return d.complete
}
// isCompleted checks if all frames has been read.
// FIXME(divan): this approach might give false negatives in extreme cases, like
// many dynamic changes of chunk sizes.
func (d *Decoder) isCompleted() bool {
sort.Slice(d.frames, func(i, j int) bool {
return d.frames[i].offset < d.frames[j].offset
})
var cur int
for _, frame := range d.frames {
// we found the gap, next frame starts farther then current position
if frame.offset > cur {
return false
}
cur += frame.size
}
return cur == d.total
}