/
lzop.go
156 lines (120 loc) · 4.34 KB
/
lzop.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
package lzop
// Copyright 2016 MediaMath <http://www.mediamath.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import (
"bytes"
"encoding/binary"
"hash/adler32"
)
const (
version = 0x1030
libVersion = 0x2080
versionForExtract = 0x0940
method = 2
level = 1
flags = 0x3000001
fileMode = 0x000081B4
)
var lzopMagic = []byte{0x89, 0x4c, 0x5a, 0x4f, 0x00, 0x0d, 0x0a, 0x1a, 0x0a}
var endBytes = []byte{0x00, 0x00, 0x00, 0x00}
//WriteHeader Writes only the header start of an lzop file
func WriteHeader(buff *bytes.Buffer, fileTime int64, fileName string) error {
//this is started at the file but not included in the checksum
//hence bytes[len(lzopMagic):]
//if you include this you will get invalid checksum error on lzop files
err := binary.Write(buff, binary.BigEndian, lzopMagic)
err = binary.Write(buff, binary.BigEndian, uint16(version))
err = binary.Write(buff, binary.BigEndian, uint16(libVersion))
err = binary.Write(buff, binary.BigEndian, uint16(versionForExtract))
err = binary.Write(buff, binary.BigEndian, uint8(method))
err = binary.Write(buff, binary.BigEndian, uint8(level))
err = binary.Write(buff, binary.BigEndian, uint32(flags))
err = binary.Write(buff, binary.BigEndian, uint32(fileMode))
err = binary.Write(buff, binary.BigEndian, uint32(fileTime))
err = binary.Write(buff, binary.BigEndian, uint32(0)) //timeHigh
err = binary.Write(buff, binary.BigEndian, uint8(len(fileName)))
_, err = buff.Write([]byte(fileName))
bytes := buff.Bytes()
err = binary.Write(buff, binary.BigEndian, uint32(adler32.Checksum(bytes[len(lzopMagic):])))
if err != nil {
return err
}
return nil
}
//WriteBytes Writes your bytes to a buffer via compression function
func WriteBytes(buff *bytes.Buffer, data []byte, compressionFunction func([]byte) []byte) error {
blockSize := 256 * 1024
if len(data) < blockSize {
blockSize = len(data)
}
iterations := len(data) / blockSize
for i := 0; i < iterations+1; i++ {
var compressed []byte
var unCompressed []byte
leftOver := len(data) - (i * blockSize)
if leftOver < blockSize {
unCompressed = data[(i * blockSize):]
} else {
unCompressed = data[(i * blockSize):((i + 1) * blockSize)]
}
if len(unCompressed) == 0 {
continue
}
compressed = compressionFunction(unCompressed)
// did you actually compress anything?
//this is to stop the compression library from sticking in extra
//characters which from what I can tell just messes with things
//and you end up with a corrupted lzop file
if len(compressed) > len(unCompressed) {
compressed = unCompressed
}
err := binary.Write(buff, binary.BigEndian, uint32(len(unCompressed)))
err = binary.Write(buff, binary.BigEndian, uint32(len(compressed)))
err = binary.Write(buff, binary.BigEndian, uint32(adler32.Checksum(unCompressed)))
if err != nil {
return err
}
_, err = buff.Write(compressed)
if err != nil {
return err
}
}
return nil
}
//WriteEnd writes the ending bytes of an lzop file
func WriteEnd(buff *bytes.Buffer) error {
_, err := buff.Write(endBytes)
return err
}
//write data is a helper to just do all three (header/body/end)
func writeData(buff *bytes.Buffer, fileTime int64, fileName string, data []byte,
compressionFunction func([]byte) []byte) error {
err := WriteHeader(buff, fileTime, fileName)
if err != nil {
return err
}
err = WriteBytes(buff, data, compressionFunction)
if err != nil {
return err
}
return WriteEnd(buff)
}
//CompressData Will Compress your data expecting an LZO 1X1 compression. Creates buffer for you
func CompressData(fileTime int64, fileName string, data []byte, compressionFunction func([]byte) []byte) ([]byte, error) {
buff := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
err := writeData(buff, fileTime, fileName, data, compressionFunction)
if err != nil {
return nil, err
}
return buff.Bytes(), err
}
//CompressDataWithBuffer Allows you to specify the buffer to use for compression allowing re-use
func CompressDataWithBuffer(buff *bytes.Buffer, fileTime int64, fileName string, data []byte, compressionFunction func([]byte) []byte) ([]byte, error) {
buff.Reset()
err := writeData(buff, fileTime, fileName, data, compressionFunction)
if err != nil {
return nil, err
}
return buff.Bytes(), err
}