codec: Add codec package to handle encoding and decoding ints, etc

This commit is contained in:
Sasha Koshka 2025-05-30 07:05:55 -04:00
parent 717754644c
commit 83fa77ba13
3 changed files with 201 additions and 0 deletions

100
codec/decode.go Normal file
View File

@ -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
}
}
}

90
codec/encode.go Normal file
View File

@ -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[:])
}

11
codec/measure.go Normal file
View File

@ -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 }
}
}