/
common.go
183 lines (146 loc) · 4.57 KB
/
common.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package compress
import (
"bytes"
"encoding/binary"
"errors"
)
var (
// ErrBadIndex is returned on invalid compression index.
ErrBadIndex = errors.New("Broken compression index")
// ErrHeaderTooSmall is returned if the header is less than 10 bytes.
// It usually indicates a broken file or a non-compressed file.
ErrHeaderTooSmall = errors.New("Header is less than 10 bytes")
// ErrBadMagicNumber is returned if the first 8 bytes of the stream is not
// the expected "elchwald".
ErrBadMagicNumber = errors.New("Bad magic number in compressed stream")
// ErrBadAlgorithm is returned when the algorithm was either not present
// or it had an invalid value
ErrBadAlgorithm = errors.New("Invalid algorithm")
// ErrUnsupportedVersion is returned when we don't have a reader that
// understands that format.
ErrUnsupportedVersion = errors.New("Version of this format is not supported")
)
const (
maxChunkSize = 64 * 1024
indexChunkSize = 16
trailerSize = 12
headerSize = 12
currentVersion = 1
)
const (
// AlgoNone represents a ,,uncompressed'' algorithm.
AlgoNone = iota
// AlgoSnappy represents the snappy compression algorithm:
// https://en.wikipedia.org/wiki/Snappy_(software)
AlgoSnappy
//AlgoLZ4 represents the lz4 compression algorithm:
// https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)
AlgoLZ4
)
// AlgorithmType user defined type to store the algorithm type.
type AlgorithmType byte
// IsValid returns true if `at` is a valid algorithm type.
func (at AlgorithmType) IsValid() bool {
switch at {
case AlgoNone, AlgoSnappy, AlgoLZ4:
return true
}
return false
}
func (at AlgorithmType) String() string {
name, ok := algoToString[at]
if !ok {
return "unknown"
}
return name
}
// record structure reprenents a offset mapping {uncompressed offset, compressedOffset}.
// A chunk of maxChunkSize is defined by two records. The size of a specific
// record can be determinated by a simple substitution of two record offsets.
type record struct {
rawOff int64
zipOff int64
}
// trailer holds basic information about the compressed file.
type trailer struct {
chunksize uint32
indexSize uint64
}
func (t *trailer) marshal(buf []byte) {
binary.LittleEndian.PutUint32(buf[0:4], t.chunksize)
binary.LittleEndian.PutUint64(buf[4:12], t.indexSize)
}
func (t *trailer) unmarshal(buf []byte) {
t.chunksize = binary.LittleEndian.Uint32(buf[0:4])
t.indexSize = binary.LittleEndian.Uint64(buf[4:12])
}
func (rc *record) marshal(buf []byte) {
binary.LittleEndian.PutUint64(buf[0:8], uint64(rc.rawOff))
binary.LittleEndian.PutUint64(buf[8:16], uint64(rc.zipOff))
}
func (rc *record) unmarshal(buf []byte) {
rc.rawOff = int64(binary.LittleEndian.Uint64(buf[0:8]))
rc.zipOff = int64(binary.LittleEndian.Uint64(buf[8:16]))
}
type header struct {
algo AlgorithmType
version uint16
}
func makeHeader(algo AlgorithmType, version byte) []byte {
algoField := make([]byte, 2)
binary.LittleEndian.PutUint16(algoField, uint16(algo))
versionField := make([]byte, 2)
binary.LittleEndian.PutUint16(versionField, uint16(version))
suffix := append(versionField, algoField...)
return append([]byte("elchwald"), suffix...)
}
func readHeader(bheader []byte) (*header, error) {
if len(bheader) < 10 {
return nil, ErrHeaderTooSmall
}
if !bytes.Equal(bheader[:8], []byte("elchwald")) {
return nil, ErrBadMagicNumber
}
// This version only understands itself currently:
version := binary.LittleEndian.Uint16(bheader[8:10])
if version != currentVersion {
return nil, ErrUnsupportedVersion
}
if len(bheader) < 12 {
return nil, ErrBadAlgorithm
}
algo := AlgorithmType(binary.LittleEndian.Uint16(bheader[10:12]))
if !algo.IsValid() {
return nil, ErrBadAlgorithm
}
return &header{
algo: algo,
version: version,
}, nil
}
// Pack compresses `data` with `algo` and returns the resulting data.
// This is a convinience method meant to be used for small data packages.
func Pack(data []byte, algo AlgorithmType) ([]byte, error) {
zipBuf := &bytes.Buffer{}
zipW, err := NewWriter(zipBuf, algo)
if err != nil {
return nil, err
}
if _, err := zipW.ReadFrom(bytes.NewReader(data)); err != nil {
return nil, err
}
if err := zipW.Close(); err != nil {
return nil, err
}
return zipBuf.Bytes(), nil
}
// Unpack unpacks `data` and returns the decompressed data.
// The algorithm is read from the data itself.
// This is a convinience method meant to be used for small data packages.
func Unpack(data []byte) ([]byte, error) {
buf := &bytes.Buffer{}
if _, err := NewReader(bytes.NewReader(data)).WriteTo(buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}