mirror of
https://github.com/divan/txqr.git
synced 2026-08-30 23:08:08 +08:00
111 lines
2.3 KiB
Go
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
|
|
}
|