diff --git a/codec/decode.go b/codec/decode.go new file mode 100644 index 0000000..e665e99 --- /dev/null +++ b/codec/decode.go @@ -0,0 +1,100 @@ +package codec + +import "io" + +// Decoder wraps an [io.Reader] and decodes data from it. +type Decoder struct { + io.Reader +} + +// ReadFull calls [io.ReadFull] on the reader. +func (this *Decoder) ReadFull(buffer []byte) (n int, err error) { + return io.ReadFull(this, buffer) +} + +// ReadByte decodes a single byte from the input reader. +func (this *Decoder) ReadByte() (value byte, n int, err error) { + uncasted, n, err := this.ReadUint8() + return byte(uncasted), n, err +} + +// ReadInt8 decodes an 8-bit signed integer from the input reader. +func (this *Decoder) ReadInt8() (value int8, n int, err error) { + uncasted, n, err := this.ReadUint8() + return int8(uncasted), n, err +} + +// ReadUint8 decodes an 8-bit unsigned integer from the input reader. +func (this *Decoder) ReadUint8() (value uint8, n int, err error) { + buffer := [1]byte { } + n, err = this.ReadFull(buffer[:]) + return uint8(buffer[0]), n, err +} + +// ReadInt16 decodes an 16-bit signed integer from the input reader. +func (this *Decoder) ReadInt16() (value int16, n int, err error) { + uncasted, n, err := this.ReadUint16() + return int16(uncasted), n, err +} + +// ReadUint16 decodes an 16-bit unsigned integer from the input reader. +func (this *Decoder) ReadUint16() (value uint16, n int, err error) { + buffer := [2]byte { } + n, err = this.ReadFull(buffer[:]) + return uint16(buffer[0]) << 8 | + uint16(buffer[1]), n, err +} + +// ReadInt32 decodes an 32-bit signed integer from the input reader. +func (this *Decoder) ReadInt32() (value int32, n int, err error) { + uncasted, n, err := this.ReadUint32() + return int32(uncasted), n, err +} + +// ReadUint32 decodes an 32-bit unsigned integer from the input reader. +func (this *Decoder) ReadUint32() (value uint32, n int, err error) { + buffer := [4]byte { } + n, err = this.ReadFull(buffer[:]) + return uint32(buffer[0]) << 24 | + uint32(buffer[1]) << 16 | + uint32(buffer[2]) << 8 | + uint32(buffer[3]), n, err +} + +// ReadInt64 decodes an 64-bit signed integer from the input reader. +func (this *Decoder) ReadInt64() (value int64, n int, err error) { + uncasted, n, err := this.ReadUint64() + return int64(uncasted), n, err +} + +// ReadUint64 decodes an 64-bit unsigned integer from the input reader. +func (this *Decoder) ReadUint64() (value uint64, n int, err error) { + buffer := [8]byte { } + n, err = this.ReadFull(buffer[:]) + return uint64(buffer[0]) << 56 | + uint64(buffer[1]) << 48 | + uint64(buffer[2]) << 48 | + uint64(buffer[3]) << 32 | + uint64(buffer[4]) << 24 | + uint64(buffer[5]) << 16 | + uint64(buffer[6]) << 8 | + uint64(buffer[7]), n, err +} + +// ReadGBEU decodes a growing unsigned integer of up to 64 bits from the input +// reader. +func (this *Decoder) ReadGBEU() (value uint64, n int, err error) { + var fullValue uint64 + for { + chunk, nn, err := this.ReadByte() + if err != nil { return 0, n, err } + n += nn + + fullValue *= 0x80 + fullValue += uint64(chunk & 0x7F) + ccb := chunk >> 7 + if ccb == 0 { + return fullValue, n, nil + } + } +} diff --git a/codec/encode.go b/codec/encode.go new file mode 100644 index 0000000..a18febd --- /dev/null +++ b/codec/encode.go @@ -0,0 +1,90 @@ +package codec + +import "io" + +// Encoder wraps an [io.Writer] and encodes data to it. +type Encoder struct { + io.Writer +} + +// WriteInt8 encodes an 8-bit signed integer to the output writer. +func (this *Encoder) WriteInt8(value int8) (n int, err error) { + return this.WriteUint8(uint8(value)) +} + +// WriteUint8 encodes an 8-bit unsigned integer to the output writer. +func (this *Encoder) WriteUint8(value uint8) (n int, err error) { + return this.Write([]byte { byte(value) }) +} + +// WriteInt16 encodes an 16-bit signed integer to the output writer. +func (this *Encoder) WriteInt16(value int16) (n int, err error) { + return this.WriteUint16(uint16(value)) +} + +// WriteUint16 encodes an 16-bit unsigned integer to the output writer. +func (this *Encoder) WriteUint16(value uint16) (n int, err error) { + return this.Write([]byte { + byte(value >> 8), + byte(value), + }) +} + +// WriteInt32 encodes an 32-bit signed integer to the output writer. +func (this *Encoder) WriteInt32(value int32) (n int, err error) { + return this.WriteUint32(uint32(value)) +} + +// WriteUint32 encodes an 32-bit unsigned integer to the output writer. +func (this *Encoder) WriteUint32(value uint32) (n int, err error) { + return this.Write([]byte { + byte(value >> 24), + byte(value >> 16), + byte(value >> 8), + byte(value), + }) +} + +// WriteInt64 encodes an 64-bit signed integer to the output writer. +func (this *Encoder) WriteInt64(value int64) (n int, err error) { + return this.WriteUint64(uint64(value)) +} + +// WriteUint64 encodes an 64-bit unsigned integer to the output writer. +func (this *Encoder) WriteUint64(value uint64) (n int, err error) { + return this.Write([]byte { + byte(value >> 56), + byte(value >> 48), + byte(value >> 40), + byte(value >> 32), + byte(value >> 24), + byte(value >> 16), + byte(value >> 8), + byte(value), + }) +} + +// EncodeGBEU encodes a growing unsigned integer of up to 64 bits to the output +// writer. +func (this *Encoder) EncodeGBEU(value uint64) (n int, err error) { + // increase if go somehow gets support for over 64 bit integers. we + // could also make an expanding int type in goutil to use here, or maybe + // there is one in the stdlib. keep this int64 version as well though + // because its ergonomic. + buffer := [16]byte { } + + window := (GBEUSize(value) - 1) * 7 + index := 0 + for window >= 0 { + chunk := uint8(value >> window) & 0x7F + if window > 0 { + chunk |= 0x80 + } + buffer[index] = chunk + + index += 1 + window -= 7 + } + + return this.Write(buffer[:]) +} diff --git a/codec/measure.go b/codec/measure.go new file mode 100644 index 0000000..777a982 --- /dev/null +++ b/codec/measure.go @@ -0,0 +1,11 @@ +package codec + +// GBEUSize returns the size (in octets) of a GBEU integer. +func GBEUSize(value uint64) int { + length := 0 + for { + value >>= 7 + length ++ + if value == 0 { return length } + } +}