blob: ca8dc8c7f0c62f9327100faca2df73b48028b909 [file] [log] [blame]
kesavandc71914f2022-03-25 11:19:03 +05301package lz4
2
3import (
4 "encoding/binary"
5 "io"
6)
7
8// WriterLegacy implements the LZ4Demo frame decoder.
9type WriterLegacy struct {
10 Header
11 // Handler called when a block has been successfully read.
12 // It provides the number of bytes read.
13 OnBlockDone func(size int)
14
15 dst io.Writer // Destination.
16 data []byte // Data to be compressed + buffer for compressed data.
17 idx int // Index into data.
18 hashtable [winSize]int // Hash table used in CompressBlock().
19}
20
21// NewWriterLegacy returns a new LZ4 encoder for the legacy frame format.
22// No access to the underlying io.Writer is performed.
23// The supplied Header is checked at the first Write.
24// It is ok to change it before the first Write but then not until a Reset() is performed.
25func NewWriterLegacy(dst io.Writer) *WriterLegacy {
26 z := new(WriterLegacy)
27 z.Reset(dst)
28 return z
29}
30
31// Write compresses data from the supplied buffer into the underlying io.Writer.
32// Write does not return until the data has been written.
33func (z *WriterLegacy) Write(buf []byte) (int, error) {
34 if !z.Header.done {
35 if err := z.writeHeader(); err != nil {
36 return 0, err
37 }
38 }
39 if debugFlag {
40 debug("input buffer len=%d index=%d", len(buf), z.idx)
41 }
42
43 zn := len(z.data)
44 var n int
45 for len(buf) > 0 {
46 if z.idx == 0 && len(buf) >= zn {
47 // Avoid a copy as there is enough data for a block.
48 if err := z.compressBlock(buf[:zn]); err != nil {
49 return n, err
50 }
51 n += zn
52 buf = buf[zn:]
53 continue
54 }
55 // Accumulate the data to be compressed.
56 m := copy(z.data[z.idx:], buf)
57 n += m
58 z.idx += m
59 buf = buf[m:]
60 if debugFlag {
61 debug("%d bytes copied to buf, current index %d", n, z.idx)
62 }
63
64 if z.idx < len(z.data) {
65 // Buffer not filled.
66 if debugFlag {
67 debug("need more data for compression")
68 }
69 return n, nil
70 }
71
72 // Buffer full.
73 if err := z.compressBlock(z.data); err != nil {
74 return n, err
75 }
76 z.idx = 0
77 }
78
79 return n, nil
80}
81
82// writeHeader builds and writes the header to the underlying io.Writer.
83func (z *WriterLegacy) writeHeader() error {
84 // Legacy has fixed 8MB blocksizes
85 // https://github.com/lz4/lz4/blob/dev/doc/lz4_Frame_format.md#legacy-frame
86 bSize := 2 * blockSize4M
87
88 buf := make([]byte, 2*bSize, 2*bSize)
89 z.data = buf[:bSize] // Uncompressed buffer is the first half.
90
91 z.idx = 0
92
93 // Header consists of one mageic number, write it out.
94 if err := binary.Write(z.dst, binary.LittleEndian, frameMagicLegacy); err != nil {
95 return err
96 }
97 z.Header.done = true
98 if debugFlag {
99 debug("wrote header %v", z.Header)
100 }
101
102 return nil
103}
104
105// compressBlock compresses a block.
106func (z *WriterLegacy) compressBlock(data []byte) error {
107 bSize := 2 * blockSize4M
108 zdata := z.data[bSize:cap(z.data)]
109 // The compressed block size cannot exceed the input's.
110 var zn int
111
112 if level := z.Header.CompressionLevel; level != 0 {
113 zn, _ = CompressBlockHC(data, zdata, level)
114 } else {
115 zn, _ = CompressBlock(data, zdata, z.hashtable[:])
116 }
117
118 if debugFlag {
119 debug("block compression %d => %d", len(data), zn)
120 }
121 zdata = zdata[:zn]
122
123 // Write the block.
124 if err := binary.Write(z.dst, binary.LittleEndian, uint32(zn)); err != nil {
125 return err
126 }
127 written, err := z.dst.Write(zdata)
128 if err != nil {
129 return err
130 }
131 if h := z.OnBlockDone; h != nil {
132 h(written)
133 }
134 return nil
135}
136
137// Flush flushes any pending compressed data to the underlying writer.
138// Flush does not return until the data has been written.
139// If the underlying writer returns an error, Flush returns that error.
140func (z *WriterLegacy) Flush() error {
141 if debugFlag {
142 debug("flush with index %d", z.idx)
143 }
144 if z.idx == 0 {
145 return nil
146 }
147
148 data := z.data[:z.idx]
149 z.idx = 0
150 return z.compressBlock(data)
151}
152
153// Close closes the WriterLegacy, flushing any unwritten data to the underlying io.Writer, but does not close the underlying io.Writer.
154func (z *WriterLegacy) Close() error {
155 if !z.Header.done {
156 if err := z.writeHeader(); err != nil {
157 return err
158 }
159 }
160 if err := z.Flush(); err != nil {
161 return err
162 }
163
164 if debugFlag {
165 debug("writing last empty block")
166 }
167
168 return nil
169}
170
171// Reset clears the state of the WriterLegacy z such that it is equivalent to its
172// initial state from NewWriterLegacy, but instead writing to w.
173// No access to the underlying io.Writer is performed.
174func (z *WriterLegacy) Reset(w io.Writer) {
175 z.Header.Reset()
176 z.dst = w
177 z.idx = 0
178 // reset hashtable to ensure deterministic output.
179 for i := range z.hashtable {
180 z.hashtable[i] = 0
181 }
182}