Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
Geth-Modification
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张蕾
Geth-Modification
Commits
82defe5c
Unverified
Commit
82defe5c
authored
May 08, 2017
by
Péter Szilágyi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
common/compress: internalize encoders, add length wrappers
parent
cf19586c
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
125 additions
and
70 deletions
+125
-70
compress.go
common/bitutil/compress.go
+49
-23
compress_fuzz.go
common/bitutil/compress_fuzz.go
+12
-12
compress_test.go
common/bitutil/compress_test.go
+64
-35
No files found.
common/bitutil/compress.go
View file @
82defe5c
...
...
@@ -19,21 +19,21 @@ package bitutil
import
"errors"
var
(
//
E
rrMissingData is returned from decompression if the byte referenced by
//
e
rrMissingData is returned from decompression if the byte referenced by
// the bitset header overflows the input data.
E
rrMissingData
=
errors
.
New
(
"missing bytes on input"
)
e
rrMissingData
=
errors
.
New
(
"missing bytes on input"
)
//
E
rrUnreferencedData is returned from decompression if not all bytes were used
//
e
rrUnreferencedData is returned from decompression if not all bytes were used
// up from the input data after decompressing it.
E
rrUnreferencedData
=
errors
.
New
(
"extra bytes on input"
)
e
rrUnreferencedData
=
errors
.
New
(
"extra bytes on input"
)
//
E
rrExceededTarget is returned from decompression if the bitset header has
//
e
rrExceededTarget is returned from decompression if the bitset header has
// more bits defined than the number of target buffer space available.
E
rrExceededTarget
=
errors
.
New
(
"target data size exceeded"
)
e
rrExceededTarget
=
errors
.
New
(
"target data size exceeded"
)
//
E
rrZeroContent is returned from decompression if a data byte referenced in
//
e
rrZeroContent is returned from decompression if a data byte referenced in
// the bitset header is actually a zero byte.
E
rrZeroContent
=
errors
.
New
(
"zero byte in input content"
)
e
rrZeroContent
=
errors
.
New
(
"zero byte in input content"
)
)
// The compression algorithm implemented by CompressBytes and DecompressBytes is
...
...
@@ -55,8 +55,20 @@ var (
// nonZeroBytes(data) contains the non-zero bytes of data in the same order
// CompressBytes compresses the input byte slice according to the sparse bitset
// representation algorithm.
// representation algorithm. If the result is bigger than the original input, no
// compression is done.
func
CompressBytes
(
data
[]
byte
)
[]
byte
{
if
out
:=
bitsetEncodeBytes
(
data
);
len
(
out
)
<
len
(
data
)
{
return
out
}
cpy
:=
make
([]
byte
,
len
(
data
))
copy
(
cpy
,
data
)
return
cpy
}
// bitsetEncodeBytes compresses the input byte slice according to the sparse
// bitset representation algorithm.
func
bitsetEncodeBytes
(
data
[]
byte
)
[]
byte
{
// Empty slices get compressed to nil
if
len
(
data
)
==
0
{
return
nil
...
...
@@ -81,27 +93,41 @@ func CompressBytes(data []byte) []byte {
if
len
(
nonZeroBytes
)
==
0
{
return
nil
}
return
append
(
Compress
Bytes
(
nonZeroBitset
),
nonZeroBytes
...
)
return
append
(
bitsetEncode
Bytes
(
nonZeroBitset
),
nonZeroBytes
...
)
}
// DecompressBytes decompresses data with a known target size. I
n addition to the
//
decompressed output, the function returns the length of compressed input data
//
corresponding to the output as the input slice may be longer
.
// DecompressBytes decompresses data with a known target size. I
f the input data
//
matches the size of the target, it means no compression was done in the first
//
place
.
func
DecompressBytes
(
data
[]
byte
,
target
int
)
([]
byte
,
error
)
{
out
,
size
,
err
:=
decompressBytes
(
data
,
target
)
if
len
(
data
)
>
target
{
return
nil
,
errExceededTarget
}
if
len
(
data
)
==
target
{
cpy
:=
make
([]
byte
,
len
(
data
))
copy
(
cpy
,
data
)
return
cpy
,
nil
}
return
bitsetDecodeBytes
(
data
,
target
)
}
// bitsetDecodeBytes decompresses data with a known target size.
func
bitsetDecodeBytes
(
data
[]
byte
,
target
int
)
([]
byte
,
error
)
{
out
,
size
,
err
:=
bitsetDecodePartialBytes
(
data
,
target
)
if
err
!=
nil
{
return
nil
,
err
}
if
size
!=
len
(
data
)
{
return
nil
,
E
rrUnreferencedData
return
nil
,
e
rrUnreferencedData
}
return
out
,
nil
}
// decompressBytes decompresses data with a known target size. In addition to the
// decompressed output, the function returns the length of compressed input data
// corresponding to the output as the input slice may be longer.
func
decompressBytes
(
data
[]
byte
,
target
int
)
([]
byte
,
int
,
error
)
{
// bitsetDecodePartialBytes decompresses data with a known target size, but does
// not enforce consuming all the input bytes. In addition to the decompressed
// output, the function returns the length of compressed input data corresponding
// to the output as the input slice may be longer.
func
bitsetDecodePartialBytes
(
data
[]
byte
,
target
int
)
([]
byte
,
int
,
error
)
{
// Sanity check 0 targets to avoid infinite recursion
if
target
==
0
{
return
nil
,
0
,
nil
...
...
@@ -119,7 +145,7 @@ func decompressBytes(data []byte, target int) ([]byte, int, error) {
return
decomp
,
0
,
nil
}
// Decompress the bitset of set bytes and distribute the non zero bytes
nonZeroBitset
,
ptr
,
err
:=
decompress
Bytes
(
data
,
(
target
+
7
)
/
8
)
nonZeroBitset
,
ptr
,
err
:=
bitsetDecodePartial
Bytes
(
data
,
(
target
+
7
)
/
8
)
if
err
!=
nil
{
return
nil
,
ptr
,
err
}
...
...
@@ -127,14 +153,14 @@ func decompressBytes(data []byte, target int) ([]byte, int, error) {
if
nonZeroBitset
[
i
/
8
]
&
(
1
<<
byte
(
7
-
i
%
8
))
!=
0
{
// Make sure we have enough data to push into the correct slot
if
ptr
>=
len
(
data
)
{
return
nil
,
0
,
E
rrMissingData
return
nil
,
0
,
e
rrMissingData
}
if
i
>=
len
(
decomp
)
{
return
nil
,
0
,
E
rrExceededTarget
return
nil
,
0
,
e
rrExceededTarget
}
// Make sure the data is valid and push into the slot
if
data
[
ptr
]
==
0
{
return
nil
,
0
,
E
rrZeroContent
return
nil
,
0
,
e
rrZeroContent
}
decomp
[
i
]
=
data
[
ptr
]
ptr
++
...
...
common/bitutil/compress_fuzz.go
View file @
82defe5c
...
...
@@ -20,36 +20,36 @@ package bitutil
import
"bytes"
// Fuzz implements a go-fuzz fuzzer method to test various
compression
method
// Fuzz implements a go-fuzz fuzzer method to test various
encoding
method
// invocations.
func
Fuzz
(
data
[]
byte
)
int
{
if
len
(
data
)
==
0
{
return
-
1
}
if
data
[
0
]
%
2
==
0
{
return
fuzz
Compress
(
data
[
1
:
])
return
fuzz
Encode
(
data
[
1
:
])
}
return
fuzzDeco
mpress
(
data
[
1
:
])
return
fuzzDeco
de
(
data
[
1
:
])
}
// fuzz
Compress implements a go-fuzz fuzzer method to test the bit compression
and
// deco
mpression
algorithm.
func
fuzz
Compress
(
data
[]
byte
)
int
{
proc
,
_
:=
DecompressBytes
(
Compress
Bytes
(
data
),
len
(
data
))
// fuzz
Encode implements a go-fuzz fuzzer method to test the bitset encoding
and
// deco
ding
algorithm.
func
fuzz
Encode
(
data
[]
byte
)
int
{
proc
,
_
:=
bitsetDecodeBytes
(
bitsetEncode
Bytes
(
data
),
len
(
data
))
if
!
bytes
.
Equal
(
data
,
proc
)
{
panic
(
"content mismatch"
)
}
return
0
}
// fuzzDeco
mpress implements a go-fuzz fuzzer method to test the bit decompression
//
and recompression
algorithm.
func
fuzzDeco
mpress
(
data
[]
byte
)
int
{
blob
,
err
:=
Decompress
Bytes
(
data
,
1024
)
// fuzzDeco
de implements a go-fuzz fuzzer method to test the bit decoding and
//
reencoding
algorithm.
func
fuzzDeco
de
(
data
[]
byte
)
int
{
blob
,
err
:=
bitsetDecode
Bytes
(
data
,
1024
)
if
err
!=
nil
{
return
0
}
if
comp
:=
Compress
Bytes
(
blob
);
!
bytes
.
Equal
(
comp
,
data
)
{
if
comp
:=
bitsetEncode
Bytes
(
blob
);
!
bytes
.
Equal
(
comp
,
data
)
{
panic
(
"content mismatch"
)
}
return
0
...
...
common/bitutil/compress_test.go
View file @
82defe5c
...
...
@@ -24,8 +24,8 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
)
// Tests that data
compression and decompression works correctly
.
func
Test
Compress
Cycle
(
t
*
testing
.
T
)
{
// Tests that data
bitset encoding and decoding works and is bijective
.
func
Test
Encoding
Cycle
(
t
*
testing
.
T
)
{
tests
:=
[]
string
{
// Tests generated by go-fuzz to maximize code coverage
"0x000000000000000000"
,
...
...
@@ -50,7 +50,7 @@ func TestCompressCycle(t *testing.T) {
for
i
,
tt
:=
range
tests
{
data
:=
hexutil
.
MustDecode
(
tt
)
proc
,
err
:=
DecompressBytes
(
Compress
Bytes
(
data
),
len
(
data
))
proc
,
err
:=
bitsetDecodeBytes
(
bitsetEncode
Bytes
(
data
),
len
(
data
))
if
err
!=
nil
{
t
.
Errorf
(
"test %d: failed to decompress compressed data: %v"
,
i
,
err
)
continue
...
...
@@ -61,8 +61,8 @@ func TestCompressCycle(t *testing.T) {
}
}
// Tests that data
decompression works
func
TestDeco
mpress
(
t
*
testing
.
T
)
{
// Tests that data
bitset decoding and rencoding works and is bijective.
func
TestDeco
dingCycle
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
size
int
input
string
...
...
@@ -71,22 +71,22 @@ func TestDecompress(t *testing.T) {
{
size
:
0
,
input
:
"0x"
},
// Crashers generated by go-fuzz
{
size
:
0
,
input
:
"0x0020"
,
fail
:
E
rrUnreferencedData
},
{
size
:
0
,
input
:
"0x30"
,
fail
:
E
rrUnreferencedData
},
{
size
:
1
,
input
:
"0x00"
,
fail
:
E
rrUnreferencedData
},
{
size
:
2
,
input
:
"0x07"
,
fail
:
E
rrMissingData
},
{
size
:
1024
,
input
:
"0x8000"
,
fail
:
E
rrZeroContent
},
{
size
:
0
,
input
:
"0x0020"
,
fail
:
e
rrUnreferencedData
},
{
size
:
0
,
input
:
"0x30"
,
fail
:
e
rrUnreferencedData
},
{
size
:
1
,
input
:
"0x00"
,
fail
:
e
rrUnreferencedData
},
{
size
:
2
,
input
:
"0x07"
,
fail
:
e
rrMissingData
},
{
size
:
1024
,
input
:
"0x8000"
,
fail
:
e
rrZeroContent
},
// Tests generated by go-fuzz to maximize code coverage
{
size
:
29490
,
input
:
"0x343137343733323134333839373334323073333930783e3078333930783e70706336346c65303e"
,
fail
:
E
rrMissingData
},
{
size
:
59395
,
input
:
"0x00"
,
fail
:
E
rrUnreferencedData
},
{
size
:
52574
,
input
:
"0x70706336346c65c0de"
,
fail
:
E
rrExceededTarget
},
{
size
:
42264
,
input
:
"0x07"
,
fail
:
E
rrMissingData
},
{
size
:
52
,
input
:
"0xa5045bad48f4"
,
fail
:
E
rrExceededTarget
},
{
size
:
52574
,
input
:
"0xc0de"
,
fail
:
E
rrMissingData
},
{
size
:
29490
,
input
:
"0x343137343733323134333839373334323073333930783e3078333930783e70706336346c65303e"
,
fail
:
e
rrMissingData
},
{
size
:
59395
,
input
:
"0x00"
,
fail
:
e
rrUnreferencedData
},
{
size
:
52574
,
input
:
"0x70706336346c65c0de"
,
fail
:
e
rrExceededTarget
},
{
size
:
42264
,
input
:
"0x07"
,
fail
:
e
rrMissingData
},
{
size
:
52
,
input
:
"0xa5045bad48f4"
,
fail
:
e
rrExceededTarget
},
{
size
:
52574
,
input
:
"0xc0de"
,
fail
:
e
rrMissingData
},
{
size
:
52574
,
input
:
"0x"
},
{
size
:
29490
,
input
:
"0x34313734373332313433383937333432307333393078073034333839373334323073333930783e3078333937333432307333393078073061333930783e70706336346c65303e"
,
fail
:
E
rrMissingData
},
{
size
:
29491
,
input
:
"0x3973333930783e30783e"
,
fail
:
E
rrMissingData
},
{
size
:
29490
,
input
:
"0x34313734373332313433383937333432307333393078073034333839373334323073333930783e3078333937333432307333393078073061333930783e70706336346c65303e"
,
fail
:
e
rrMissingData
},
{
size
:
29491
,
input
:
"0x3973333930783e30783e"
,
fail
:
e
rrMissingData
},
{
size
:
1024
,
input
:
"0x808080608080"
},
{
size
:
1024
,
input
:
"0x808470705e3632383337363033313434303137393130306c6580ef46806380635a80"
},
...
...
@@ -101,37 +101,66 @@ func TestDecompress(t *testing.T) {
for
i
,
tt
:=
range
tests
{
data
:=
hexutil
.
MustDecode
(
tt
.
input
)
orig
,
err
:=
Decompress
Bytes
(
data
,
tt
.
size
)
orig
,
err
:=
bitsetDecode
Bytes
(
data
,
tt
.
size
)
if
err
!=
tt
.
fail
{
t
.
Errorf
(
"test %d: failure mismatch: have %v, want %v"
,
i
,
err
,
tt
.
fail
)
}
if
err
!=
nil
{
continue
}
if
comp
:=
Compress
Bytes
(
orig
);
!
bytes
.
Equal
(
comp
,
data
)
{
if
comp
:=
bitsetEncode
Bytes
(
orig
);
!
bytes
.
Equal
(
comp
,
data
)
{
t
.
Errorf
(
"test %d: decompress/compress mismatch: have %x, want %x"
,
i
,
comp
,
data
)
}
}
}
// TestCompression tests that compression works by returning either the bitset
// encoded input, or the actual input if the bitset version is longer.
func
TestCompression
(
t
*
testing
.
T
)
{
// Check the the compression returns the bitset encoding is shorter
in
:=
hexutil
.
MustDecode
(
"0x4912385c0e7b64000000"
)
out
:=
hexutil
.
MustDecode
(
"0x80fe4912385c0e7b64"
)
if
data
:=
CompressBytes
(
in
);
bytes
.
Compare
(
data
,
out
)
!=
0
{
t
.
Errorf
(
"encoding mismatch for sparse data: have %x, want %x"
,
data
,
out
)
}
if
data
,
err
:=
DecompressBytes
(
out
,
len
(
in
));
err
!=
nil
||
bytes
.
Compare
(
data
,
in
)
!=
0
{
t
.
Errorf
(
"decoding mismatch for sparse data: have %x, want %x, error %v"
,
data
,
in
,
err
)
}
// Check the the compression returns the input if the bitset encoding is longer
in
=
hexutil
.
MustDecode
(
"0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb"
)
out
=
hexutil
.
MustDecode
(
"0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb"
)
if
data
:=
CompressBytes
(
in
);
bytes
.
Compare
(
data
,
out
)
!=
0
{
t
.
Errorf
(
"encoding mismatch for dense data: have %x, want %x"
,
data
,
out
)
}
if
data
,
err
:=
DecompressBytes
(
out
,
len
(
in
));
err
!=
nil
||
bytes
.
Compare
(
data
,
in
)
!=
0
{
t
.
Errorf
(
"decoding mismatch for dense data: have %x, want %x, error %v"
,
data
,
in
,
err
)
}
// Check that decompressing a longer input than the target fails
if
_
,
err
:=
DecompressBytes
([]
byte
{
0xc0
,
0x01
,
0x01
},
2
);
err
!=
errExceededTarget
{
t
.
Errorf
(
"decoding error mismatch for long data: have %v, want %v"
,
err
,
errExceededTarget
)
}
}
// Crude benchmark for compressing random slices of bytes.
func
Benchmark
Compress1KBVerySparse
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
1024
,
0.0001
)
}
func
Benchmark
Compress2KBVerySparse
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
2048
,
0.0001
)
}
func
Benchmark
Compress4KBVerySparse
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
4096
,
0.0001
)
}
func
Benchmark
Encoding1KBVerySparse
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
1024
,
0.0001
)
}
func
Benchmark
Encoding2KBVerySparse
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
2048
,
0.0001
)
}
func
Benchmark
Encoding4KBVerySparse
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
4096
,
0.0001
)
}
func
Benchmark
Compress1KBSparse
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
1024
,
0.001
)
}
func
Benchmark
Compress2KBSparse
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
2048
,
0.001
)
}
func
Benchmark
Compress4KBSparse
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
4096
,
0.001
)
}
func
Benchmark
Encoding1KBSparse
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
1024
,
0.001
)
}
func
Benchmark
Encoding2KBSparse
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
2048
,
0.001
)
}
func
Benchmark
Encoding4KBSparse
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
4096
,
0.001
)
}
func
Benchmark
Compress1KBDense
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
1024
,
0.1
)
}
func
Benchmark
Compress2KBDense
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
2048
,
0.1
)
}
func
Benchmark
Compress4KBDense
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
4096
,
0.1
)
}
func
Benchmark
Encoding1KBDense
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
1024
,
0.1
)
}
func
Benchmark
Encoding2KBDense
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
2048
,
0.1
)
}
func
Benchmark
Encoding4KBDense
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
4096
,
0.1
)
}
func
Benchmark
Compress1KBSaturated
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
1024
,
0.5
)
}
func
Benchmark
Compress2KBSaturated
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
2048
,
0.5
)
}
func
Benchmark
Compress4KBSaturated
(
b
*
testing
.
B
)
{
benchmarkCompress
(
b
,
4096
,
0.5
)
}
func
Benchmark
Encoding1KBSaturated
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
1024
,
0.5
)
}
func
Benchmark
Encoding2KBSaturated
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
2048
,
0.5
)
}
func
Benchmark
Encoding4KBSaturated
(
b
*
testing
.
B
)
{
benchmarkEncoding
(
b
,
4096
,
0.5
)
}
func
benchmark
Compress
(
b
*
testing
.
B
,
bytes
int
,
fill
float64
)
{
func
benchmark
Encoding
(
b
*
testing
.
B
,
bytes
int
,
fill
float64
)
{
// Generate a random slice of bytes to compress
random
:=
rand
.
NewSource
(
0
)
// reproducible and comparable
...
...
@@ -143,10 +172,10 @@ func benchmarkCompress(b *testing.B, bytes int, fill float64) {
bit
:=
uint
(
random
.
Int63
()
%
8
)
data
[
idx
]
|=
1
<<
bit
}
// Reset the benchmark and measure
compression/decompression
// Reset the benchmark and measure
encoding/decoding
b
.
ResetTimer
()
b
.
ReportAllocs
()
for
i
:=
0
;
i
<
b
.
N
;
i
++
{
DecompressBytes
(
Compress
Bytes
(
data
),
len
(
data
))
bitsetDecodeBytes
(
bitsetEncode
Bytes
(
data
),
len
(
data
))
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment