Adding modding folder with a hacked build.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
const bufferCapacityIncrement = 1024 * 1024
|
||||
|
||||
// ByteStore is implementing a ReadWriteSeeker storing data in memory.
|
||||
type ByteStore struct {
|
||||
data []byte
|
||||
offset int
|
||||
}
|
||||
|
||||
// NewByteStore returns a new byte store instance.
|
||||
func NewByteStore() *ByteStore {
|
||||
return NewByteStoreFromData(make([]byte, 0, bufferCapacityIncrement))
|
||||
}
|
||||
|
||||
// NewByteStoreFromData returns a new ByteStore instance wrapping the provided data.
|
||||
func NewByteStoreFromData(data []byte) *ByteStore {
|
||||
store := &ByteStore{
|
||||
data: data,
|
||||
offset: 0,
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
// Data returns the current data buffer.
|
||||
func (store *ByteStore) Data() []byte {
|
||||
return store.data
|
||||
}
|
||||
|
||||
// Seek implements the Seeker interface.
|
||||
func (store *ByteStore) Seek(offset int64, whence int) (int64, error) {
|
||||
switch whence {
|
||||
case 0:
|
||||
store.offset = int(offset)
|
||||
case 1:
|
||||
store.offset += int(offset)
|
||||
case 2:
|
||||
store.offset = len(store.data) + int(offset)
|
||||
}
|
||||
|
||||
return int64(store.offset), nil
|
||||
}
|
||||
|
||||
// Read implements the Reader interface.
|
||||
func (store *ByteStore) Read(p []byte) (n int, err error) {
|
||||
size := len(p)
|
||||
n = len(store.data) - store.offset
|
||||
if n > size {
|
||||
n = size
|
||||
}
|
||||
if n < size && size > 0 {
|
||||
err = io.EOF
|
||||
}
|
||||
copy(p, store.data[store.offset:store.offset+n])
|
||||
store.offset += n
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Write implements the Writer interface.
|
||||
func (store *ByteStore) Write(p []byte) (n int, err error) {
|
||||
size := len(p)
|
||||
store.ensureAvailable(size)
|
||||
copy(store.data[store.offset:], p)
|
||||
store.offset += size
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (store *ByteStore) ensureAvailable(size int) {
|
||||
expected := store.offset + size
|
||||
available := len(store.data)
|
||||
|
||||
if expected > available {
|
||||
if expected > cap(store.data) {
|
||||
old := store.data
|
||||
|
||||
store.data = make([]byte, expected, expected+bufferCapacityIncrement)
|
||||
copy(store.data, old)
|
||||
} else {
|
||||
store.data = append(store.data, make([]byte, expected-available)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package serial
|
||||
|
||||
// Codable is something than can be serialized with a Coder.
|
||||
type Codable interface {
|
||||
// Code requests to serialize the Codable via the provided Coder.
|
||||
Code(coder Coder)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package serial
|
||||
|
||||
// Coder represents an Encoder/Decoder for binary data.
|
||||
type Coder interface {
|
||||
// FirstError returns the first error this coder encountered.
|
||||
FirstError() error
|
||||
|
||||
// Code serializes the given value.
|
||||
// Any error state will be provided via FirstError(). Code will do nothing
|
||||
// if the coder is already in error state.
|
||||
Code(value interface{})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Decoder is for decoding from a reader.
|
||||
type Decoder struct {
|
||||
source io.Reader
|
||||
firstError error
|
||||
offset uint32
|
||||
}
|
||||
|
||||
// NewDecoder creates a new Decoder from given source.
|
||||
func NewDecoder(source io.Reader) *Decoder {
|
||||
return &Decoder{source: source}
|
||||
}
|
||||
|
||||
// FirstError returns the error this Decoder encountered the first time.
|
||||
func (coder *Decoder) FirstError() error {
|
||||
return coder.firstError
|
||||
}
|
||||
|
||||
// CurPos returns the current decoding position, in bytes.
|
||||
func (coder *Decoder) CurPos() uint32 {
|
||||
return coder.offset
|
||||
}
|
||||
|
||||
// Code serializes the given value in little endian format using binary.Read().
|
||||
func (coder *Decoder) Code(value interface{}) {
|
||||
if coder.firstError != nil {
|
||||
return
|
||||
}
|
||||
if codable, isCodable := value.(Codable); isCodable {
|
||||
codable.Code(coder)
|
||||
} else {
|
||||
coder.firstError = binary.Read(coder, binary.LittleEndian, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads the next bytes from the underlying source.
|
||||
func (coder *Decoder) Read(data []byte) (read int, err error) {
|
||||
if coder.firstError != nil {
|
||||
return 0, coder.firstError
|
||||
}
|
||||
read, err = coder.source.Read(data)
|
||||
coder.offset += uint32(read)
|
||||
|
||||
isErrEOF := errors.Is(err, io.EOF)
|
||||
expectedAmountReturned := read == len(data)
|
||||
errorCanBeIgnored := isErrEOF && expectedAmountReturned
|
||||
if (coder.firstError == nil) && !errorCanBeIgnored {
|
||||
coder.firstError = err
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package serial_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/serial"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type DecoderSuite struct {
|
||||
suite.Suite
|
||||
errorBuf *errorBuffer
|
||||
coder *serial.Decoder
|
||||
}
|
||||
|
||||
func TestDecoderSuite(t *testing.T) {
|
||||
suite.Run(t, new(DecoderSuite))
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) SetupTest() {
|
||||
suite.errorBuf = nil
|
||||
suite.coder = nil
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestImplementsCoderInterface() {
|
||||
instance := interface{}(suite.coder)
|
||||
_, ok := instance.(serial.Coder)
|
||||
assert.True(suite.T(), ok)
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestCodeUint32() {
|
||||
suite.whenDecodingFrom([]byte{0x78, 0x56, 0x34, 0x12})
|
||||
|
||||
var value uint32
|
||||
suite.coder.Code(&value)
|
||||
|
||||
assert.Equal(suite.T(), uint32(0x12345678), value)
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestCodeUint16() {
|
||||
suite.whenDecodingFrom([]byte{0x45, 0x23})
|
||||
|
||||
var value uint16
|
||||
suite.coder.Code(&value)
|
||||
|
||||
assert.Equal(suite.T(), uint16(0x2345), value)
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestCodeByte() {
|
||||
suite.whenDecodingFrom([]byte{0xAB})
|
||||
|
||||
var value byte
|
||||
suite.coder.Code(&value)
|
||||
|
||||
assert.Equal(suite.T(), byte(0xAB), value)
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestCodeByteSlice() {
|
||||
suite.whenDecodingFrom([]byte{0x78, 0x12, 0x34})
|
||||
|
||||
value := make([]byte, 3)
|
||||
suite.coder.Code(value)
|
||||
|
||||
assert.Equal(suite.T(), []byte{0x78, 0x12, 0x34}, value)
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestFirstErrorReturnsFirstError() {
|
||||
suite.whenDecodingWithErrors()
|
||||
|
||||
data := uint32(0)
|
||||
suite.coder.Code(&data)
|
||||
suite.errorBuf.errorOnNextCall = true
|
||||
suite.coder.Code(uint16(0))
|
||||
|
||||
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 2")
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestFirstErrorIgnoresFurtherErrors() {
|
||||
suite.whenDecodingWithErrors()
|
||||
|
||||
suite.errorBuf.errorOnNextCall = true
|
||||
suite.coder.Code(uint32(0))
|
||||
suite.errorBuf.errorOnNextCall = true
|
||||
suite.coder.Code(uint32(0))
|
||||
|
||||
assert.Equal(suite.T(), suite.errorBuf.callCounter, 1)
|
||||
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1")
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestImplementsReaderInterface() {
|
||||
instance := interface{}(suite.coder)
|
||||
_, ok := instance.(io.Reader)
|
||||
assert.True(suite.T(), ok)
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestReadFromReader() {
|
||||
suite.whenDecodingFrom([]byte{0x78, 0x12, 0x34})
|
||||
|
||||
value := make([]byte, 3)
|
||||
read, err := suite.coder.Read(value)
|
||||
|
||||
assert.Nil(suite.T(), err, "no error expected")
|
||||
assert.Equal(suite.T(), 3, read, "unexpected length read")
|
||||
assert.Equal(suite.T(), []byte{0x78, 0x12, 0x34}, value)
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestReadHandlesErrors() {
|
||||
suite.whenDecodingWithErrors()
|
||||
|
||||
suite.errorBuf.errorOnNextCall = true
|
||||
_, _ = suite.coder.Read(make([]byte, 3))
|
||||
_, _ = suite.coder.Read(make([]byte, 5))
|
||||
assert.Equal(suite.T(), 1, suite.errorBuf.callCounter)
|
||||
assert.NotNil(suite.T(), suite.coder.FirstError())
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) TestCodeWithCodable() {
|
||||
codable := new(MockedCodable)
|
||||
|
||||
suite.whenDecodingFrom([]byte{0x78, 0x12, 0x34})
|
||||
suite.coder.Code(codable)
|
||||
|
||||
assert.Equal(suite.T(), suite.coder, codable.calledCoder, "Should be called with coder")
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) whenDecodingFrom(data []byte) {
|
||||
source := bytes.NewReader(data)
|
||||
suite.coder = serial.NewDecoder(source)
|
||||
}
|
||||
|
||||
func (suite *DecoderSuite) whenDecodingWithErrors() {
|
||||
suite.errorBuf = new(errorBuffer)
|
||||
suite.coder = serial.NewDecoder(suite.errorBuf)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Encoder implements the Coder interface to write to a writer.
|
||||
// It also implements the Writer interface.
|
||||
type Encoder struct {
|
||||
target io.Writer
|
||||
firstError error
|
||||
offset uint32
|
||||
}
|
||||
|
||||
// NewEncoder creates and returns a fresh Encoder.
|
||||
func NewEncoder(target io.Writer) *Encoder {
|
||||
return &Encoder{target: target}
|
||||
}
|
||||
|
||||
// FirstError returns the error this Encoder encountered the first time.
|
||||
func (coder *Encoder) FirstError() error {
|
||||
return coder.firstError
|
||||
}
|
||||
|
||||
// CurPos returns the current encoding position, in bytes.
|
||||
func (coder *Encoder) CurPos() uint32 {
|
||||
return coder.offset
|
||||
}
|
||||
|
||||
// Code serializes the given value in little endian format using binary.Write().
|
||||
func (coder *Encoder) Code(value interface{}) {
|
||||
if coder.firstError != nil {
|
||||
return
|
||||
}
|
||||
if codable, isCodable := value.(Codable); isCodable {
|
||||
codable.Code(coder)
|
||||
} else {
|
||||
coder.firstError = binary.Write(coder, binary.LittleEndian, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Write serializes the given data to the contained writer.
|
||||
// If the encoder is in the error state, the result of FirstError() is
|
||||
// returned and nothing else is done.
|
||||
func (coder *Encoder) Write(data []byte) (written int, err error) {
|
||||
if coder.firstError != nil {
|
||||
return 0, coder.firstError
|
||||
}
|
||||
written, err = coder.target.Write(data)
|
||||
coder.offset += uint32(written)
|
||||
if coder.firstError == nil {
|
||||
coder.firstError = err
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package serial_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/serial"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type EncoderSuite struct {
|
||||
suite.Suite
|
||||
store *serial.ByteStore
|
||||
coder *serial.Encoder
|
||||
}
|
||||
|
||||
func TestEncoderSuite(t *testing.T) {
|
||||
suite.Run(t, new(EncoderSuite))
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) SetupTest() {
|
||||
suite.store = serial.NewByteStore()
|
||||
suite.coder = serial.NewEncoder(suite.store)
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestImplementsCoderInterface() {
|
||||
instance := interface{}(suite.coder)
|
||||
_, ok := instance.(serial.Coder)
|
||||
assert.True(suite.T(), ok)
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestDataOnEmptyEncoderReturnsEmptyArray() {
|
||||
suite.verifyData(make([]byte, 0))
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestCodeUint32Pointer() {
|
||||
var value uint32 = 0x12345678
|
||||
suite.verifyCodedData(&value, []byte{0x78, 0x56, 0x34, 0x12})
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestCodeUint32() {
|
||||
var value uint32 = 0x12345678
|
||||
suite.verifyCodedData(value, []byte{0x78, 0x56, 0x34, 0x12})
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestCodeUint16Pointer() {
|
||||
var value uint16 = 0x3456
|
||||
suite.verifyCodedData(&value, []byte{0x56, 0x34})
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestCodeUint16() {
|
||||
var value uint16 = 0x3456
|
||||
suite.verifyCodedData(value, []byte{0x56, 0x34})
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestCodeBytePointer() {
|
||||
var value byte = 0x42
|
||||
suite.verifyCodedData(&value, []byte{0x42})
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestCodeByte() {
|
||||
var value byte = 0x42
|
||||
suite.verifyCodedData(value, []byte{0x42})
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestCodeByteSlice() {
|
||||
value := []byte{0x01, 0x02, 0x03}
|
||||
suite.verifyCodedData(value, []byte{0x01, 0x02, 0x03})
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestFirstErrorByCode() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewEncoder(&target)
|
||||
suite.coder.Code(uint16(0))
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.Code(uint32(0))
|
||||
|
||||
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 2")
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestCodeDoesNothingOnPreviousError() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewEncoder(&target)
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.Code(uint32(0))
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.Code(uint32(0))
|
||||
|
||||
assert.Equal(suite.T(), target.callCounter, 1)
|
||||
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1")
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestImplementsWriterInterface() {
|
||||
instance := interface{}(suite.coder)
|
||||
_, ok := instance.(io.Writer)
|
||||
assert.True(suite.T(), ok)
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestWriteToWriter() {
|
||||
written, err := suite.coder.Write([]byte{0x0A, 0x0B, 0x0C})
|
||||
assert.Nil(suite.T(), err, "no error expected")
|
||||
assert.Equal(suite.T(), 3, written, "unexpected length written")
|
||||
suite.verifyData([]byte{0x0A, 0x0B, 0x0C})
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestWriteDoesNothingOnPreviousError() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewEncoder(&target)
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.Code(uint32(0))
|
||||
_, _ = suite.coder.Write([]byte{0x0A, 0x0B, 0x0C})
|
||||
|
||||
assert.Equal(suite.T(), target.callCounter, 1)
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestWriteReturnsFirstErrorOnPreviousError() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewEncoder(&target)
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.Code(uint32(0))
|
||||
n, err := suite.coder.Write([]byte{0x0A, 0x0B, 0x0C})
|
||||
|
||||
assert.Equal(suite.T(), 0, n, "Should return zero written bytes")
|
||||
assert.EqualError(suite.T(), err, "errorBuffer on call number 1")
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestWriteOnErrorCase() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewEncoder(&target)
|
||||
target.errorOnNextCall = true
|
||||
target.errorByteCount = 2
|
||||
n, err := suite.coder.Write([]byte{0x0A, 0x0B, 0x0C})
|
||||
|
||||
assert.Equal(suite.T(), 2, n, "Should return count of successfully written bytes")
|
||||
assert.EqualError(suite.T(), err, "errorBuffer on call number 1")
|
||||
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1", "should set first error")
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) TestCodeWithCodable() {
|
||||
codable := new(MockedCodable)
|
||||
suite.coder.Code(codable)
|
||||
|
||||
assert.Equal(suite.T(), suite.coder, codable.calledCoder, "Should be called with coder")
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) verifyData(expected []byte) {
|
||||
assert.Nil(suite.T(), suite.coder.FirstError())
|
||||
result := suite.store.Data()
|
||||
assert.Equal(suite.T(), expected, result)
|
||||
}
|
||||
|
||||
func (suite *EncoderSuite) verifyCodedData(value interface{}, expected []byte) {
|
||||
suite.coder.Code(value)
|
||||
suite.verifyData(expected)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package serial_test
|
||||
|
||||
import "github.com/inkyblackness/hacked/ss1/serial"
|
||||
|
||||
type MockedCodable struct {
|
||||
calledCoder serial.Coder
|
||||
}
|
||||
|
||||
func (codable *MockedCodable) Code(coder serial.Coder) {
|
||||
codable.calledCoder = coder
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package serial
|
||||
|
||||
// Positioner specifies a type that knows about a position which can be modified.
|
||||
type Positioner interface {
|
||||
// CurPos gets the current position in the data.
|
||||
CurPos() uint32
|
||||
// SetCurPos sets the current position in the data.
|
||||
SetCurPos(offset uint32)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package serial
|
||||
|
||||
// PositioningCoder is a coder that also knows about positioning.
|
||||
// Any error state regarding repositioning will be provided via FirstError().
|
||||
// SetCurPos will do nothing if the coder is already in error state.
|
||||
type PositioningCoder interface {
|
||||
Positioner
|
||||
Coder
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package serial
|
||||
|
||||
import "io"
|
||||
|
||||
// PositioningDecoder is a Decoder with positioning capabilities.
|
||||
type PositioningDecoder struct {
|
||||
Decoder
|
||||
|
||||
seeker io.Seeker
|
||||
}
|
||||
|
||||
// NewPositioningDecoder creates a new PositioningDecoder from given reader.
|
||||
func NewPositioningDecoder(source io.ReadSeeker) *PositioningDecoder {
|
||||
return &PositioningDecoder{Decoder: Decoder{source: source, offset: 0}, seeker: source}
|
||||
}
|
||||
|
||||
// SetCurPos changes the encoding position to the specified absolute offset.
|
||||
func (coder *PositioningDecoder) SetCurPos(offset uint32) {
|
||||
if coder.firstError != nil {
|
||||
return
|
||||
}
|
||||
_, coder.firstError = coder.seeker.Seek(int64(offset), io.SeekStart)
|
||||
if coder.firstError != nil {
|
||||
return
|
||||
}
|
||||
coder.offset = offset
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package serial_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/serial"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPositioningDecoderSetCurPosRepositionsReadOffset(t *testing.T) {
|
||||
var source = bytes.NewReader([]byte{0x78, 0x12, 0x34})
|
||||
coder := serial.NewPositioningDecoder(source)
|
||||
arrayValue := make([]byte, 3)
|
||||
var intValue uint16
|
||||
|
||||
coder.Code(arrayValue)
|
||||
coder.SetCurPos(1)
|
||||
coder.Code(&intValue)
|
||||
|
||||
assert.Equal(t, uint16(0x3412), intValue)
|
||||
}
|
||||
|
||||
func TestPositioningDecoderCurPosReturnsCurrentOffset(t *testing.T) {
|
||||
var source = bytes.NewReader([]byte{0x78, 0x12, 0x34})
|
||||
coder := serial.NewPositioningDecoder(source)
|
||||
|
||||
coder.Code(uint16(0))
|
||||
|
||||
assert.Equal(t, uint32(2), coder.CurPos())
|
||||
}
|
||||
|
||||
func TestPositioningDecoderFirstErrorBySetCurPos(t *testing.T) {
|
||||
var target errorBuffer
|
||||
coder := serial.NewPositioningDecoder(&target)
|
||||
|
||||
data := uint32(0)
|
||||
coder.Code(&data)
|
||||
target.errorOnNextCall = true
|
||||
coder.SetCurPos(0)
|
||||
|
||||
assert.EqualError(t, coder.FirstError(), "errorBuffer on call number 2")
|
||||
}
|
||||
|
||||
func TestPositioningDecoderSetCurPosDoesNothingOnPreviousError(t *testing.T) {
|
||||
var target errorBuffer
|
||||
coder := serial.NewPositioningDecoder(&target)
|
||||
target.errorOnNextCall = true
|
||||
coder.Code(uint32(0))
|
||||
target.errorOnNextCall = true
|
||||
coder.SetCurPos(0)
|
||||
|
||||
assert.Equal(t, target.callCounter, 1)
|
||||
assert.EqualError(t, coder.FirstError(), "errorBuffer on call number 1")
|
||||
}
|
||||
|
||||
func TestCurPosIsNotChangedOnSetError(t *testing.T) {
|
||||
var target errorBuffer
|
||||
coder := serial.NewPositioningDecoder(&target)
|
||||
target.errorOnNextCall = true
|
||||
coder.SetCurPos(4)
|
||||
assert.Equal(t, uint32(0), coder.CurPos())
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1"
|
||||
)
|
||||
|
||||
// PositioningEncoder is an Encoder with positioning capabilities.
|
||||
type PositioningEncoder struct {
|
||||
Encoder
|
||||
|
||||
seeker io.Seeker
|
||||
}
|
||||
|
||||
// NewPositioningEncoder returns an Encoder that also implements the Positioner interface.
|
||||
// The new Encoder starts with its zero position at the current position in the writer.
|
||||
func NewPositioningEncoder(target io.WriteSeeker) *PositioningEncoder {
|
||||
return &PositioningEncoder{Encoder: Encoder{target: target}, seeker: target}
|
||||
}
|
||||
|
||||
// SetCurPos changes the encoding position to the specified absolute offset.
|
||||
func (coder *PositioningEncoder) SetCurPos(offset uint32) {
|
||||
if coder.firstError != nil {
|
||||
return
|
||||
}
|
||||
_, coder.firstError = coder.seeker.Seek(int64(offset)-int64(coder.offset), io.SeekCurrent)
|
||||
if coder.firstError != nil {
|
||||
return
|
||||
}
|
||||
coder.offset = offset
|
||||
}
|
||||
|
||||
const (
|
||||
errInvalidWhence ss1.StringError = "seek: invalid whence"
|
||||
errSeekBeforeStart ss1.StringError = "seek: seeking before start"
|
||||
)
|
||||
|
||||
// Seek repositions the current encoding offset.
|
||||
// This implementation does not support a whence value of io.SeekEnd.
|
||||
func (coder *PositioningEncoder) Seek(offset int64, whence int) (int64, error) {
|
||||
var newPosition int64
|
||||
switch whence {
|
||||
default:
|
||||
return int64(coder.offset), errInvalidWhence
|
||||
case io.SeekStart:
|
||||
newPosition = offset
|
||||
case io.SeekCurrent:
|
||||
newPosition = int64(coder.offset) + offset
|
||||
}
|
||||
if newPosition < 0 {
|
||||
return int64(coder.offset), errSeekBeforeStart
|
||||
}
|
||||
|
||||
coder.SetCurPos(uint32(newPosition))
|
||||
return int64(coder.offset), coder.firstError
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package serial_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/serial"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type PositioningEncoderSuite struct {
|
||||
suite.Suite
|
||||
coder *serial.PositioningEncoder
|
||||
store *serial.ByteStore
|
||||
}
|
||||
|
||||
func TestPositioningEncoderSuite(t *testing.T) {
|
||||
suite.Run(t, new(PositioningEncoderSuite))
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) SetupTest() {
|
||||
suite.store = serial.NewByteStore()
|
||||
suite.coder = serial.NewPositioningEncoder(suite.store)
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestImplementsPositioningCoderInterface() {
|
||||
instance := interface{}(suite.coder)
|
||||
_, ok := instance.(serial.PositioningCoder)
|
||||
|
||||
assert.True(suite.T(), ok)
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestCurPosReturnsCurrentOffset() {
|
||||
suite.coder.Code(uint32(0))
|
||||
assert.Equal(suite.T(), uint32(4), suite.coder.CurPos())
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestSetCurPosRepositionsWritePointer() {
|
||||
suite.coder.Code(uint32(0))
|
||||
suite.coder.SetCurPos(0)
|
||||
suite.coder.Code(uint32(0x13243546))
|
||||
result := suite.store.Data()
|
||||
|
||||
assert.Equal(suite.T(), []byte{0x46, 0x35, 0x24, 0x13}, result)
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestFirstErrorBySetCurPos() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewPositioningEncoder(&target)
|
||||
|
||||
suite.coder.Code(uint32(0))
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.SetCurPos(0)
|
||||
|
||||
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 2")
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestSetCurPosDoesNothingOnPreviousError() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewPositioningEncoder(&target)
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.Code(uint32(0))
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.SetCurPos(0)
|
||||
|
||||
assert.Equal(suite.T(), target.callCounter, 1)
|
||||
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1")
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestCurPosIsNotChangedOnSetError() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewPositioningEncoder(&target)
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.SetCurPos(4)
|
||||
assert.Equal(suite.T(), uint32(0), suite.coder.CurPos())
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestCurPosIsRelativeToConstructionTime() {
|
||||
_, _ = suite.store.Write([]byte{0x01, 0x02, 0x03})
|
||||
|
||||
assert.Equal(suite.T(), uint32(0), suite.coder.CurPos(), "CurPos should be zero at the start")
|
||||
suite.coder.Code(uint16(0xAAAA))
|
||||
suite.coder.SetCurPos(1)
|
||||
suite.coder.Code(byte(0xCC))
|
||||
|
||||
result := suite.store.Data()
|
||||
assert.Equal(suite.T(), []byte{0x01, 0x02, 0x03, 0xAA, 0xCC}, result, "SetCurPos did not work relative to original start")
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestImplementsSeekerInterface() {
|
||||
instance := interface{}(suite.coder)
|
||||
_, ok := instance.(io.Seeker)
|
||||
|
||||
assert.True(suite.T(), ok)
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestSeekReturnsErrorForWrongWhence() {
|
||||
pos, err := suite.coder.Seek(1, 1234)
|
||||
|
||||
assert.Equal(suite.T(), int64(0), pos, "zero position should be returned")
|
||||
assert.EqualError(suite.T(), err, "seek: invalid whence")
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestSeekAbsolute() {
|
||||
suite.coder.Code(uint32(0))
|
||||
pos, err := suite.coder.Seek(1, io.SeekStart)
|
||||
assert.Nil(suite.T(), err, "no error expected")
|
||||
assert.Equal(suite.T(), int64(1), pos, "invalid position returned")
|
||||
|
||||
suite.coder.Code(uint16(0xAABB))
|
||||
result := suite.store.Data()
|
||||
assert.Equal(suite.T(), []byte{0x00, 0xBB, 0xAA, 0x00}, result)
|
||||
assert.Equal(suite.T(), uint32(3), suite.coder.CurPos(), "should update current position")
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestSeekRelative() {
|
||||
suite.coder.Code(uint32(0))
|
||||
pos, err := suite.coder.Seek(-1, io.SeekCurrent)
|
||||
assert.Equal(suite.T(), int64(3), pos, "should return new position")
|
||||
assert.Nil(suite.T(), err, "should have no error")
|
||||
|
||||
suite.coder.Code(uint16(0xAABB))
|
||||
|
||||
result := suite.store.Data()
|
||||
assert.Equal(suite.T(), []byte{0x00, 0x00, 0x00, 0xBB, 0xAA}, result)
|
||||
assert.Equal(suite.T(), uint32(5), suite.coder.CurPos(), "should update current position")
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestSeekDoesNothingOnPreviousError() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewPositioningEncoder(&target)
|
||||
target.errorOnNextCall = true
|
||||
suite.coder.Code(uint32(0))
|
||||
_, _ = suite.coder.Seek(0, io.SeekStart)
|
||||
|
||||
assert.Equal(suite.T(), target.callCounter, 1)
|
||||
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 1")
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestSeekRegistersReturnedError() {
|
||||
var target errorBuffer
|
||||
suite.coder = serial.NewPositioningEncoder(&target)
|
||||
suite.coder.Code(uint32(0))
|
||||
target.errorOnNextCall = true
|
||||
_, _ = suite.coder.Seek(1, io.SeekStart)
|
||||
|
||||
pos, err := suite.coder.Seek(1, io.SeekStart)
|
||||
|
||||
assert.Equal(suite.T(), int64(4), pos, "Should return old position value for seek")
|
||||
assert.EqualError(suite.T(), err, "errorBuffer on call number 2")
|
||||
assert.EqualError(suite.T(), suite.coder.FirstError(), "errorBuffer on call number 2", "should set first error")
|
||||
}
|
||||
|
||||
func (suite *PositioningEncoderSuite) TestSeekReturnsErrorForSeekBeforeStart() {
|
||||
pos, err := suite.coder.Seek(-1, io.SeekStart)
|
||||
|
||||
assert.Equal(suite.T(), int64(0), pos, "Should return old position value for seek")
|
||||
assert.EqualError(suite.T(), err, "seek: seeking before start")
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package serial
|
||||
|
||||
import "io"
|
||||
|
||||
// SeekingReadCloser combines the interfaces of a Reader, a Seeker and a Closer.
|
||||
type SeekingReadCloser interface {
|
||||
io.Reader
|
||||
io.Seeker
|
||||
io.Closer
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package serial
|
||||
|
||||
import "io"
|
||||
|
||||
// SeekingWriteCloser combines the interfaces of a Writer, a Seeker and a Closer.
|
||||
type SeekingWriteCloser interface {
|
||||
io.Writer
|
||||
io.Seeker
|
||||
io.Closer
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package serial provides serialization utilities for nested serialization.
|
||||
*/
|
||||
package serial
|
||||
@@ -0,0 +1,56 @@
|
||||
package serial_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1"
|
||||
)
|
||||
|
||||
type errorBuffer struct {
|
||||
position int64
|
||||
callCounter int
|
||||
errorOnNextCall bool
|
||||
errorByteCount int
|
||||
}
|
||||
|
||||
func (buf *errorBuffer) check() (err error) {
|
||||
buf.callCounter++
|
||||
if buf.errorOnNextCall {
|
||||
buf.errorOnNextCall = false
|
||||
err = ss1.StringError(fmt.Sprintf("errorBuffer on call number %v", buf.callCounter))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (buf *errorBuffer) Seek(offset int64, whence int) (int64, error) {
|
||||
err := buf.check()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
buf.position = offset
|
||||
case io.SeekCurrent:
|
||||
buf.position += offset
|
||||
case io.SeekEnd:
|
||||
panic("SeekEnd not supported")
|
||||
}
|
||||
return buf.position, nil
|
||||
}
|
||||
|
||||
func (buf *errorBuffer) Read(p []byte) (n int, err error) {
|
||||
err = buf.check()
|
||||
if err != nil {
|
||||
return buf.errorByteCount, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (buf *errorBuffer) Write(p []byte) (n int, err error) {
|
||||
err = buf.check()
|
||||
if err != nil {
|
||||
return buf.errorByteCount, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package rle
|
||||
|
||||
import "io"
|
||||
|
||||
// Compress compresses the given byte array into the given writer.
|
||||
// The optional reference array is used as a delta basis. If provided, bytes will be skipped
|
||||
// where the data equals the reference.
|
||||
func Compress(writer io.Writer, data []byte, reference []byte) error {
|
||||
end := len(data)
|
||||
refLen := len(reference)
|
||||
ref := func(index int) byte {
|
||||
if index < refLen {
|
||||
return reference[index]
|
||||
}
|
||||
return 0x00
|
||||
}
|
||||
|
||||
countIdenticalBytes := func(from int) int {
|
||||
index := from
|
||||
for (index < end) && (data[index] == ref(index)) {
|
||||
index++
|
||||
}
|
||||
return index - from
|
||||
}
|
||||
countConstantBytes := func(from int, value byte) int {
|
||||
index := from
|
||||
for (index < end) && (data[index] == value) {
|
||||
index++
|
||||
}
|
||||
return index - from
|
||||
}
|
||||
|
||||
{
|
||||
trailingSkip := 0
|
||||
|
||||
for temp := end - 1; (temp >= 0) && (data[temp] == ref(temp)); temp-- {
|
||||
trailingSkip++
|
||||
}
|
||||
end -= trailingSkip % 0x7FFF
|
||||
}
|
||||
start := 0
|
||||
for start < end {
|
||||
identicalByteCount := countIdenticalBytes(start)
|
||||
constByteCount := countConstantBytes(start, data[start])
|
||||
|
||||
switch {
|
||||
case identicalByteCount > 3:
|
||||
err := skipBytes(writer, identicalByteCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start += identicalByteCount
|
||||
case constByteCount > 3:
|
||||
err := writeConstant(writer, constByteCount, data[start])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start += constByteCount
|
||||
default:
|
||||
diffByteCount := 0
|
||||
abort := false
|
||||
|
||||
for (start+diffByteCount) < end && !abort {
|
||||
nextIdenticalByteCount := countIdenticalBytes(start + diffByteCount)
|
||||
nextConstByteCount := countConstantBytes(start+diffByteCount, data[start+diffByteCount])
|
||||
|
||||
if nextIdenticalByteCount < 4 && nextConstByteCount < 4 {
|
||||
if nextIdenticalByteCount > nextConstByteCount {
|
||||
diffByteCount += nextIdenticalByteCount
|
||||
} else {
|
||||
diffByteCount += nextConstByteCount
|
||||
}
|
||||
} else {
|
||||
abort = true
|
||||
}
|
||||
}
|
||||
err := writeRaw(writer, data[start:start+diffByteCount])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start += diffByteCount
|
||||
}
|
||||
}
|
||||
return writeExtended(writer, 0x0000)
|
||||
}
|
||||
|
||||
func writeExtended(writer io.Writer, control uint16, extra ...byte) error {
|
||||
_, err := writer.Write([]byte{0x80, byte(control & 0xFF), byte((control >> 8) & 0xFF)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(extra)
|
||||
return err
|
||||
}
|
||||
|
||||
func skipBytes(writer io.Writer, size int) error {
|
||||
remain := size
|
||||
|
||||
for remain > 0 {
|
||||
if remain < 0x80 {
|
||||
_, err := writer.Write([]byte{byte(0x80 + remain)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remain = 0
|
||||
} else {
|
||||
lenControl := 0x7FFF
|
||||
if remain < lenControl {
|
||||
lenControl = remain
|
||||
}
|
||||
err := writeExtended(writer, uint16(lenControl))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remain -= lenControl
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeConstant(writer io.Writer, size int, value byte) error {
|
||||
start := 0
|
||||
|
||||
for start < size {
|
||||
remain := size - start
|
||||
if remain < 0x100 {
|
||||
_, err := writer.Write([]byte{0x00, byte(remain), value})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start = size
|
||||
} else {
|
||||
lenControl := 0x3FFF
|
||||
if remain < lenControl {
|
||||
lenControl = remain
|
||||
}
|
||||
err := writeExtended(writer, 0xC000+uint16(lenControl), value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start += lenControl
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeRaw(writer io.Writer, data []byte) error {
|
||||
end := len(data)
|
||||
start := 0
|
||||
|
||||
for start < end {
|
||||
remain := end - start
|
||||
if remain < 0x80 {
|
||||
_, err := writer.Write([]byte{byte(remain)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(data[start:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start = end
|
||||
} else {
|
||||
lenControl := 0x3FFF
|
||||
|
||||
if remain < lenControl {
|
||||
lenControl = remain
|
||||
}
|
||||
err := writeExtended(writer, 0x8000+uint16(lenControl), data[start:start+lenControl]...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start += lenControl
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package rle_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/serial/rle"
|
||||
)
|
||||
|
||||
func TestCompressEmptyArrayResultsInTerminator(t *testing.T) {
|
||||
writer := bytes.NewBuffer(nil)
|
||||
err := rle.Compress(writer, nil, nil)
|
||||
assert.Nil(t, err, "no error expected")
|
||||
assert.Equal(t, []byte{0x80, 0x00, 0x00}, writer.Bytes())
|
||||
}
|
||||
|
||||
func TestCompressOfZeroBytesLong(t *testing.T) {
|
||||
writer := bytes.NewBuffer(nil)
|
||||
input := make([]byte, 1000)
|
||||
input[len(input)-1] = 1
|
||||
err := rle.Compress(writer, input, nil)
|
||||
assert.Nil(t, err, "no error expected")
|
||||
assert.Equal(t, []byte{0x80, 0xE7, 0x03, 0x01, 0x01, 0x80, 0x0, 0x0}, writer.Bytes())
|
||||
}
|
||||
|
||||
func TestCompressOfEqualBytesLong(t *testing.T) {
|
||||
writer := bytes.NewBuffer(nil)
|
||||
input := make([]byte, 1000)
|
||||
for i := 0; i < len(input); i++ {
|
||||
input[i] = 0xAA
|
||||
}
|
||||
input[len(input)-1] = 1
|
||||
err := rle.Compress(writer, input, nil)
|
||||
assert.Nil(t, err, "no error expected")
|
||||
assert.Equal(t, []byte{0x80, 0xE7, 0xC3, 0xAA, 0x01, 0x01, 0x80, 0x0, 0x0}, writer.Bytes())
|
||||
}
|
||||
|
||||
func TestCompressOfEqualBytesShort(t *testing.T) {
|
||||
writer := bytes.NewBuffer(nil)
|
||||
input := make([]byte, 40)
|
||||
for i := 0; i < len(input); i++ {
|
||||
input[i] = 0xAA
|
||||
}
|
||||
input[len(input)-1] = 1
|
||||
err := rle.Compress(writer, input, nil)
|
||||
assert.Nil(t, err, "no error expected")
|
||||
assert.Equal(t, []byte{0x00, 0x27, 0xAA, 0x01, 0x01, 0x80, 0x0, 0x0}, writer.Bytes())
|
||||
}
|
||||
|
||||
func TestCompressOfRandomBytesShort(t *testing.T) {
|
||||
writer := bytes.NewBuffer(nil)
|
||||
input := make([]byte, 10)
|
||||
for i := 0; i < len(input); i++ {
|
||||
input[i] = byte(i)
|
||||
}
|
||||
err := rle.Compress(writer, input, nil)
|
||||
assert.Nil(t, err, "no error expected")
|
||||
assert.Equal(t, []byte{0x0A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x80, 0x00, 0x00}, writer.Bytes())
|
||||
}
|
||||
|
||||
func TestCompressWithIdenticalReference(t *testing.T) {
|
||||
writer := bytes.NewBuffer(nil)
|
||||
input := []byte{0xAA, 0xBB, 0xCC, 0xDD}
|
||||
reference := input
|
||||
err := rle.Compress(writer, input, reference)
|
||||
assert.Nil(t, err, "no error expected")
|
||||
assert.Equal(t, []byte{0x80, 0x00, 0x00}, writer.Bytes())
|
||||
}
|
||||
|
||||
func TestCompressWithPartlyIdenticalReference(t *testing.T) {
|
||||
writer := bytes.NewBuffer(nil)
|
||||
input := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}
|
||||
reference := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x0A}
|
||||
err := rle.Compress(writer, input, reference)
|
||||
assert.Nil(t, err, "no error expected")
|
||||
assert.Equal(t, []byte{0x85, 0x01, 0xFF, 0x80, 0x00, 0x00}, writer.Bytes())
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package rle
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1"
|
||||
)
|
||||
|
||||
const (
|
||||
errUndefinedCase ss1.StringError = "undefined case 80 nn C0"
|
||||
)
|
||||
|
||||
// Decompress decompresses from the given reader and writes into the provided output buffer.
|
||||
// The output buffer must be pre-allocated.
|
||||
// If it contains non-zero data, this data may be preserved if the compressed data specifies to.
|
||||
func Decompress(reader io.Reader, output []byte) (err error) {
|
||||
outIndex := 0
|
||||
done := false
|
||||
nextByte := func() byte {
|
||||
zz := []byte{0x00}
|
||||
_, err = reader.Read(zz)
|
||||
return zz[0]
|
||||
}
|
||||
|
||||
for !done && (err == nil) {
|
||||
first := nextByte()
|
||||
|
||||
switch {
|
||||
case first == 0x00:
|
||||
nn := nextByte()
|
||||
zz := nextByte()
|
||||
|
||||
outIndex += writeBytesOfValue(output[outIndex:outIndex+int(nn)], func() byte { return zz })
|
||||
case first < 0x80:
|
||||
outIndex += writeBytesOfValue(output[outIndex:outIndex+int(first)], nextByte)
|
||||
case first == 0x80:
|
||||
control := uint16(nextByte())
|
||||
control += uint16(nextByte()) << 8
|
||||
switch {
|
||||
case control == 0x0000:
|
||||
done = true
|
||||
case control < 0x8000:
|
||||
outIndex += int(control)
|
||||
case control < 0xC000:
|
||||
outIndex += writeBytesOfValue(output[outIndex:outIndex+int(control&0x3FFF)], nextByte)
|
||||
case (control & 0xFF00) == 0xC000:
|
||||
err = errUndefinedCase
|
||||
default:
|
||||
zz := nextByte()
|
||||
|
||||
outIndex += writeBytesOfValue(output[outIndex:outIndex+int(control&0x3FFF)], func() byte { return zz })
|
||||
}
|
||||
default:
|
||||
outIndex += int(first & 0x7F)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func writeBytesOfValue(buffer []byte, producer func() byte) int {
|
||||
count := len(buffer)
|
||||
for i := 0; i < count; i++ {
|
||||
buffer[i] = producer()
|
||||
}
|
||||
return count
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package rle_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/serial/rle"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecompressEmptyArrayReturnsError(t *testing.T) {
|
||||
err := rle.Decompress(bytes.NewReader(nil), make([]byte, 100))
|
||||
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestDecompress800000IsEndOfStream(t *testing.T) {
|
||||
var result []byte
|
||||
err := rle.Decompress(bytes.NewReader([]byte{0x80, 0x00, 0x00}), result)
|
||||
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestDecompress800000AppendsRemainingZeroes(t *testing.T) {
|
||||
result := make([]byte, 10)
|
||||
err := rle.Decompress(bytes.NewReader([]byte{0x80, 0x00, 0x00}), result)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, make([]byte, 10), result)
|
||||
}
|
||||
|
||||
func TestDecompress800000IsConsumedAtEnd(t *testing.T) {
|
||||
var result []byte
|
||||
reader := bytes.NewReader([]byte{0x80, 0x00, 0x00})
|
||||
err := rle.Decompress(reader, result)
|
||||
|
||||
require.Nil(t, err)
|
||||
pos, _ := reader.Seek(0, 1)
|
||||
assert.Equal(t, int64(3), pos)
|
||||
}
|
||||
|
||||
func TestDecompress00WritesNNBytesOfColorZZ(t *testing.T) {
|
||||
result := make([]byte, 5)
|
||||
err := rle.Decompress(bytes.NewReader([]byte{0x00, 0x05, 0xCC, 0x80, 0x00, 0x00}), result)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []byte{0xCC, 0xCC, 0xCC, 0xCC, 0xCC}, result)
|
||||
}
|
||||
|
||||
func TestDecompress00ReturnsErrorIfZZIsMissing(t *testing.T) {
|
||||
err := rle.Decompress(bytes.NewReader([]byte{0x00, 0x05}), make([]byte, 5))
|
||||
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestDecompress00ReturnsErrorIfNNIsMissing(t *testing.T) {
|
||||
err := rle.Decompress(bytes.NewReader([]byte{0x00}), make([]byte, 5))
|
||||
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestDecompressNNLess80WritesNNFollowingBytes(t *testing.T) {
|
||||
result := make([]byte, 2)
|
||||
err := rle.Decompress(bytes.NewReader([]byte{0x02, 0xAA, 0xBB, 0x80, 0x00, 0x00}), result)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []byte{0xAA, 0xBB}, result)
|
||||
}
|
||||
|
||||
func TestDecompressNNLess80ReturnsErrorIfEndOfFile(t *testing.T) {
|
||||
err := rle.Decompress(bytes.NewReader([]byte{0x02, 0xAA}), make([]byte, 2))
|
||||
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestDecompress80NNNNSkipsBytes(t *testing.T) {
|
||||
expected := make([]byte, 0x123)
|
||||
result := make([]byte, 0x123)
|
||||
for i := 0; i < len(result); i++ {
|
||||
result[i] = byte(i)
|
||||
}
|
||||
copy(expected, result)
|
||||
err := rle.Decompress(bytes.NewReader([]byte{0x80, 0x23, 0x01, 0x80, 0x00, 0x00}), result)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
|
||||
func TestDecompress80CopiesNNBytes(t *testing.T) {
|
||||
input := bytes.NewBuffer(nil)
|
||||
input.Write([]byte{0x80, 0x04, 0x80})
|
||||
input.Write([]byte{0x01, 0x02, 0x03, 0x04})
|
||||
input.Write([]byte{0x80, 0x00, 0x00})
|
||||
result := make([]byte, 4)
|
||||
err := rle.Decompress(bytes.NewReader(input.Bytes()), result)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, result)
|
||||
}
|
||||
|
||||
func TestDecompress80CopiesNNBytesExtended(t *testing.T) {
|
||||
input := bytes.NewBuffer(nil)
|
||||
expected := make([]byte, 0x3FFF)
|
||||
input.Write([]byte{0x80, 0xFF, 0xBF})
|
||||
input.Write(expected)
|
||||
input.Write([]byte{0x80, 0x00, 0x00})
|
||||
result := make([]byte, len(expected))
|
||||
err := rle.Decompress(bytes.NewReader(input.Bytes()), result)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
|
||||
func TestDecompress80ReturnsErrorForUndefinedCase(t *testing.T) {
|
||||
input := bytes.NewBuffer(nil)
|
||||
input.Write([]byte{0x80, 0x00, 0xC0})
|
||||
input.Write([]byte{0x80, 0x00, 0x00})
|
||||
err := rle.Decompress(bytes.NewReader(input.Bytes()), make([]byte, 1))
|
||||
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestDecompress80WritesNNBytesOfValue(t *testing.T) {
|
||||
input := bytes.NewBuffer(nil)
|
||||
expected := make([]byte, 0x3FFF)
|
||||
for index := range expected {
|
||||
expected[index] = 0xCD
|
||||
}
|
||||
input.Write([]byte{0x80, 0xFF, 0xFF, 0xCD})
|
||||
input.Write([]byte{0x80, 0x00, 0x00})
|
||||
result := make([]byte, len(expected))
|
||||
err := rle.Decompress(bytes.NewReader(input.Bytes()), result)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
}
|
||||
|
||||
func TestDecompressNNMore80WritesZeroes(t *testing.T) {
|
||||
result := make([]byte, 3)
|
||||
err := rle.Decompress(bytes.NewReader([]byte{0x83, 0x80, 0x00, 0x00}), result)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, []byte{0, 0, 0}, result)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package rle_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/serial/rle"
|
||||
)
|
||||
|
||||
func benchmarkRecompression(b *testing.B, size int, chanceLimit byte, nameSuffix string, seed int64) {
|
||||
b.Helper()
|
||||
input := make([]byte, size)
|
||||
reference := make([]byte, size)
|
||||
var chance [1]byte
|
||||
|
||||
rand.Seed(seed) // nolint:gas
|
||||
|
||||
b.ResetTimer()
|
||||
for run := 0; run < b.N; run++ {
|
||||
b.StopTimer()
|
||||
rand.Read(input) // nolint:gas
|
||||
copy(reference, input)
|
||||
for i := 0; i < size; i++ {
|
||||
rand.Read(chance[:]) // nolint:gas
|
||||
if chance[0] < chanceLimit {
|
||||
input[i] = chance[0]
|
||||
}
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
b.StartTimer()
|
||||
|
||||
err := rle.Compress(buf, input, reference)
|
||||
if err != nil {
|
||||
b.Errorf("Failed compression for %s in run %d of seed %v", nameSuffix, run, seed)
|
||||
}
|
||||
|
||||
err = rle.Decompress(bytes.NewReader(buf.Bytes()), reference)
|
||||
if err != nil {
|
||||
b.Errorf("Failed decompression for %s in run %d of seed %v", nameSuffix, run, seed)
|
||||
}
|
||||
if !bytes.Equal(input, reference) {
|
||||
b.Errorf("Data mismatch in %s in run %d of seed %v", nameSuffix, run, seed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRecompression64KB_10(b *testing.B) {
|
||||
benchmarkRecompression(b, 1024*64, 10, "64KB,10", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func BenchmarkRecompression64KB_128(b *testing.B) {
|
||||
benchmarkRecompression(b, 1024*64, 128, "64KB,128", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func BenchmarkRecompression1KB_0(b *testing.B) {
|
||||
benchmarkRecompression(b, 1024, 0, "1KB,0", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func BenchmarkRecompression1KB_10(b *testing.B) {
|
||||
benchmarkRecompression(b, 1024, 10, "1KB,10", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func BenchmarkRecompression1KB_128(b *testing.B) {
|
||||
benchmarkRecompression(b, 1024, 128, "1KB,128", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func BenchmarkRecompression1KB_255(b *testing.B) {
|
||||
benchmarkRecompression(b, 1024, 255, "1KB,255", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func BenchmarkRecompression128B_10(b *testing.B) {
|
||||
benchmarkRecompression(b, 128, 10, "128B,10", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func BenchmarkRecompression128B_128(b *testing.B) {
|
||||
benchmarkRecompression(b, 128, 128, "128B,128", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func BenchmarkRecompression128B_255(b *testing.B) {
|
||||
benchmarkRecompression(b, 128, 255, "128B,255", time.Now().UnixNano())
|
||||
}
|
||||
Reference in New Issue
Block a user