-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added crunched package and implemented a simple RLE crunching interface
elf SRAM is crunched with this new package on Snapshot() .gitigore updated with */testdata added fuzz target to Makefile
- Loading branch information
1 parent
61baee5
commit dc084be
Showing
8 changed files
with
396 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
*.swo | ||
.DS_Store | ||
Session.vim | ||
*/testdata | ||
vendor/* | ||
tags | ||
test_roms/* | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// This file is part of Gopher2600. | ||
// | ||
// Gopher2600 is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Gopher2600 is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
package crunched | ||
|
||
// Data provides the interface to a crunched data type | ||
type Data interface { | ||
// IsCrunched returns true if data is currently crunched | ||
IsCrunched() bool | ||
|
||
// Size returns the uncrunched size and the current size of the data. If the | ||
// data is currently crunched then the two values will be the same | ||
Size() (int, int) | ||
|
||
// Data returns a pointer to the uncrunched data | ||
Data() *[]byte | ||
|
||
// Snapshot makes a copy of the data and crunching it if required. The data will | ||
// be uncrunched automatically when Data() function is called | ||
Snapshot() Data | ||
} | ||
|
||
// Inspection provides the interface to the crunched data type and provides the | ||
// ability to inspect the data in its current form | ||
type Inspection interface { | ||
Data | ||
|
||
// Inspect returns data in the current state. In other words, the data will | ||
// not be decrunched as it would be with the Data() function | ||
Inspect() *[]byte | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// This file is part of Gopher2600. | ||
// | ||
// Gopher2600 is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Gopher2600 is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
// Package crunched provides the Data interface. Implementations of this | ||
// interface can store data in a crunched and uncrunched state. | ||
// | ||
// The intention is that implementations crunch data after a call to Snapshot() | ||
// and transparently uncrunch it on a call to Data(). | ||
package crunched |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// This file is part of Gopher2600. | ||
// | ||
// Gopher2600 is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Gopher2600 is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
package crunched | ||
|
||
type quick struct { | ||
crunched bool | ||
data []byte | ||
uncrunchedSize int | ||
} | ||
|
||
// NewQuick returns an implementation of the Data interface that is intended to | ||
// perform quickly on both crunching and decrunching. | ||
// | ||
// For simplicity, the minimum amount of data allocated will be 4 bytes. | ||
func NewQuick(size int) Data { | ||
size = max(size, 4) | ||
return &quick{ | ||
data: make([]byte, size), | ||
uncrunchedSize: size, | ||
} | ||
} | ||
|
||
// IsCrunched returns true if data is currently crunched | ||
// | ||
// This function implements the Data interface | ||
func (c *quick) IsCrunched() bool { | ||
return c.crunched | ||
} | ||
|
||
// Size returns the uncrunched size and the current size of the data. If the | ||
// data is currently crunched then the two values will be the same | ||
// | ||
// This function implements the Data interface | ||
func (c *quick) Size() (int, int) { | ||
return c.uncrunchedSize, len(c.data) | ||
} | ||
|
||
// Data returns a pointer to the uncrunched data | ||
// | ||
// This function implements the Data interface | ||
func (c *quick) Data() *[]byte { | ||
if c.crunched { | ||
// sanity check. with the current RLE method the number of bytes in the | ||
// crunched data should be a multiple of two | ||
if len(c.data)&0x01 == 0x01 { | ||
panic("crunched data should have an even number of bytes") | ||
} | ||
|
||
// make a reference to the crunched data before creating space for the | ||
// uncrunched data | ||
working := c.data | ||
c.data = make([]byte, c.uncrunchedSize) | ||
|
||
// undo the RLE process | ||
var idx int | ||
for i := 0; i < len(working); i += 2 { | ||
for r := 0; r <= int(working[i+1]); r++ { | ||
c.data[idx] = working[i] | ||
idx++ | ||
} | ||
} | ||
|
||
// data is now uncrunched | ||
c.crunched = false | ||
} | ||
|
||
return &c.data | ||
} | ||
|
||
// Snapshot makes a copy of the data and crunching it if required. The data will | ||
// be uncrunched automatically when Data() function is called | ||
// | ||
// This function implements the Data interface | ||
func (c *quick) Snapshot() Data { | ||
d := *c | ||
|
||
if !d.crunched { | ||
working := make([]byte, d.uncrunchedSize) | ||
|
||
var ct int | ||
var idx int | ||
working[idx] = c.data[0] | ||
|
||
// assume crunching has succeeded unless explicitely told otherwise | ||
d.crunched = true | ||
|
||
// very basic RLE algorithm: | ||
// 1) each byte is followed by a count value | ||
// 2) maximum count value is 255 | ||
for _, v := range c.data[1:] { | ||
if v == working[idx] && ct < 255 { | ||
ct++ | ||
} else { | ||
// check that the crunched data isn't getting too large. we'll | ||
// be adding two bytes to the crunch stream so the check here is | ||
// to make sure that won't overflow the size of the array | ||
if idx >= len(working)-2 { | ||
d.crunched = false | ||
break // for loop | ||
} | ||
|
||
// output count to the crunch stream | ||
idx++ | ||
working[idx] = byte(ct) | ||
|
||
// output new byte to crunch stream | ||
idx++ | ||
working[idx] = v | ||
|
||
// count will begin again with the new byte | ||
ct = 0 | ||
} | ||
} | ||
|
||
// if the data has been crunched then allocate just enough memory to | ||
// store the crunched data before returning | ||
if d.crunched { | ||
idx++ | ||
working[idx] = byte(ct) | ||
d.data = make([]byte, idx+1) | ||
copy(d.data, working[:idx+1]) | ||
return &d | ||
} | ||
|
||
// if data is not crunched then we intentionally fall through to the | ||
// plain data copy below | ||
} | ||
|
||
// copy data as it exists now. this may be crunched data or uncrunched data. | ||
// it doesn't matter either way | ||
d.data = make([]byte, len(c.data)) | ||
copy(d.data, c.data) | ||
|
||
return &d | ||
} | ||
|
||
// Inspect returns data in the current state. In other words, the data will | ||
// not be decrunched as it would be with the Data() function | ||
// | ||
// This function implements the Peep interface | ||
func (c *quick) Inspect() *[]byte { | ||
return &c.data | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// This file is part of Gopher2600. | ||
// | ||
// Gopher2600 is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Gopher2600 is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
package crunched_test | ||
|
||
import ( | ||
"crypto/md5" | ||
"math/rand" | ||
"testing" | ||
|
||
"github.com/jetsetilly/gopher2600/crunched" | ||
"github.com/jetsetilly/gopher2600/test" | ||
) | ||
|
||
func TestEmptyData_Quick(t *testing.T) { | ||
// create 100 bytes of empty data | ||
qa := crunched.NewQuick(100) | ||
|
||
// take hash of data before crunching | ||
preCrunchHash := md5.Sum(*qa.Data()) | ||
|
||
// data should not be crunched | ||
test.ExpectFailure(t, qa.IsCrunched()) | ||
|
||
// take a snapshot of the data | ||
qb := qa.Snapshot() | ||
|
||
// the snapshotted data should be crunched | ||
test.ExpectSuccess(t, qb.IsCrunched()) | ||
|
||
// the original data should be left uncrunched | ||
test.ExpectSuccess(t, !qa.IsCrunched()) | ||
|
||
// inspect the crunched data | ||
inspection := qb.(crunched.Inspection).Inspect() | ||
expectedData := []byte{0, 99} | ||
test.DemandEquality(t, len(*inspection), len(expectedData)) | ||
for i, v := range *inspection { | ||
test.ExpectEquality(t, v, expectedData[i]) | ||
} | ||
|
||
// check that hash of uncrunched data is the same as it was before | ||
postCrunchedHash := md5.Sum(*qb.Data()) | ||
test.ExpectEquality(t, preCrunchHash, postCrunchedHash) | ||
|
||
// obtaining the data from the snapshot should leave the data in the | ||
// snapshot in an uncrunched state | ||
test.ExpectSuccess(t, !qb.IsCrunched()) | ||
} | ||
|
||
func TestUncompressableData_quick(t *testing.T) { | ||
// create 256 bytes of empty data | ||
qa := crunched.NewQuick(256) | ||
|
||
// insert data that can't be compressed by the quick method | ||
data := qa.Data() | ||
for i := 0; i < len(*data); i++ { | ||
(*data)[i] = byte(i) | ||
} | ||
|
||
// take hash of data before crunching | ||
preCrunchHash := md5.Sum(*data) | ||
|
||
// take a snapshot of the data | ||
qb := qa.Snapshot() | ||
|
||
// the snapshotted data should not be crunched | ||
test.ExpectSuccess(t, !qb.IsCrunched()) | ||
|
||
// check that hash of uncrunched data is the same as it was before | ||
postCrunchedHash := md5.Sum(*qb.Data()) | ||
test.ExpectEquality(t, preCrunchHash, postCrunchedHash) | ||
} | ||
|
||
func TestEmptyData_ExampleData(t *testing.T) { | ||
// create 100 bytes of empty data | ||
qa := crunched.NewQuick(20) | ||
|
||
// insert data that can't be compressed by the quick method | ||
data := qa.Data() | ||
copy(*data, []byte{1, 2, 3, 3, 3, 3, 4, 4, 5, 6}) | ||
|
||
// snapshot should successfully crunch the data | ||
qb := qa.Snapshot() | ||
test.ExpectSuccess(t, qb.IsCrunched()) | ||
|
||
inspection := qb.(crunched.Inspection).Inspect() | ||
|
||
expectedData := []byte{1, 0, 2, 0, 3, 3, 4, 1, 5, 0, 6, 0, 0, 9} | ||
test.DemandEquality(t, len(*inspection), len(expectedData)) | ||
for i, v := range *inspection { | ||
test.ExpectEquality(t, v, expectedData[i]) | ||
} | ||
} | ||
|
||
func FuzzQuick(f *testing.F) { | ||
f.Fuzz(func(t *testing.T, size uint) { | ||
qa := crunched.NewQuick(int(size)) | ||
|
||
// insert data that can't be compressed by the quick method | ||
data := qa.Data() | ||
(*data)[0] = byte(rand.Intn(255)) | ||
for i := 1; i < len(*data); i++ { | ||
b := byte(rand.Intn(255)) | ||
for b == (*data)[i-1] { | ||
b = byte(rand.Intn(255)) | ||
} | ||
(*data)[i] = b | ||
} | ||
|
||
// take hash of data before crunching | ||
preCrunchHash := md5.Sum(*data) | ||
|
||
// take a snapshot of the data | ||
qb := qa.Snapshot() | ||
|
||
// the snapshotted data should not be crunched because it should be | ||
// impossible with the data we've given it | ||
test.ExpectSuccess(t, !qb.IsCrunched()) | ||
|
||
// check that hash of uncrunched data is the same as it was before | ||
postCrunchedHash := md5.Sum(*qb.Data()) | ||
test.ExpectEquality(t, preCrunchHash, postCrunchedHash) | ||
}) | ||
} |
Oops, something went wrong.