1 /***********************************************************************************************************************************
2 BZ2 Compress
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5 
6 #include <stdio.h>
7 #include <bzlib.h>
8 
9 #include "common/compress/bz2/common.h"
10 #include "common/compress/bz2/compress.h"
11 #include "common/debug.h"
12 #include "common/io/filter/filter.h"
13 #include "common/log.h"
14 #include "common/macro.h"
15 #include "common/memContext.h"
16 #include "common/type/object.h"
17 
18 /***********************************************************************************************************************************
19 Filter type constant
20 ***********************************************************************************************************************************/
21 STRING_EXTERN(BZ2_COMPRESS_FILTER_TYPE_STR,                         BZ2_COMPRESS_FILTER_TYPE);
22 
23 /***********************************************************************************************************************************
24 Object type
25 ***********************************************************************************************************************************/
26 typedef struct Bz2Compress
27 {
28     MemContext *memContext;                                         // Context to store data
29     bz_stream stream;                                               // Compression stream
30 
31     bool inputSame;                                                 // Is the same input required on the next process call?
32     bool flushing;                                                  // Is input complete and flushing in progress?
33     bool done;                                                      // Is compression done?
34 } Bz2Compress;
35 
36 /***********************************************************************************************************************************
37 Render as string for logging
38 ***********************************************************************************************************************************/
39 static String *
bz2CompressToLog(const Bz2Compress * this)40 bz2CompressToLog(const Bz2Compress *this)
41 {
42     return strNewFmt(
43         "{inputSame: %s, done: %s, flushing: %s, avail_in: %u}", cvtBoolToConstZ(this->inputSame), cvtBoolToConstZ(this->done),
44         cvtBoolToConstZ(this->flushing), this->stream.avail_in);
45 }
46 
47 #define FUNCTION_LOG_BZ2_COMPRESS_TYPE                                                                                             \
48     Bz2Compress *
49 #define FUNCTION_LOG_BZ2_COMPRESS_FORMAT(value, buffer, bufferSize)                                                                \
50     FUNCTION_LOG_STRING_OBJECT_FORMAT(value, bz2CompressToLog, buffer, bufferSize)
51 
52 /***********************************************************************************************************************************
53 Free compression stream
54 ***********************************************************************************************************************************/
55 static void
bz2CompressFreeResource(THIS_VOID)56 bz2CompressFreeResource(THIS_VOID)
57 {
58     THIS(Bz2Compress);
59 
60     FUNCTION_LOG_BEGIN(logLevelTrace);
61         FUNCTION_LOG_PARAM(BZ2_COMPRESS, this);
62     FUNCTION_LOG_END();
63 
64     ASSERT(this != NULL);
65 
66     BZ2_bzCompressEnd(&this->stream);
67 
68     FUNCTION_LOG_RETURN_VOID();
69 }
70 
71 /***********************************************************************************************************************************
72 Compress data
73 ***********************************************************************************************************************************/
74 static void
bz2CompressProcess(THIS_VOID,const Buffer * uncompressed,Buffer * compressed)75 bz2CompressProcess(THIS_VOID, const Buffer *uncompressed, Buffer *compressed)
76 {
77     THIS(Bz2Compress);
78 
79     FUNCTION_LOG_BEGIN(logLevelTrace);
80         FUNCTION_LOG_PARAM(BZ2_COMPRESS, this);
81         FUNCTION_LOG_PARAM(BUFFER, uncompressed);
82         FUNCTION_LOG_PARAM(BUFFER, compressed);
83     FUNCTION_LOG_END();
84 
85     ASSERT(this != NULL);
86     ASSERT(!this->done);
87     ASSERT(compressed != NULL);
88     ASSERT(!this->flushing || uncompressed == NULL);
89     ASSERT(this->flushing || (!this->inputSame || this->stream.avail_in != 0));
90 
91     // If input is NULL then start flushing
92     if (uncompressed == NULL)
93     {
94         this->stream.avail_in = 0;
95         this->flushing = true;
96     }
97     // Else still have input data
98     else
99     {
100         // Is new input allowed?
101         if (!this->inputSame)
102         {
103             this->stream.avail_in = (unsigned int)bufUsed(uncompressed);
104 
105             // bzip2 does not accept const input buffers
106             this->stream.next_in = (char *)UNCONSTIFY(unsigned char *, bufPtrConst(uncompressed));
107         }
108     }
109 
110     // Initialize compressed output buffer
111     this->stream.avail_out = (unsigned int)bufRemains(compressed);
112     this->stream.next_out = (char *)bufPtr(compressed) + bufUsed(compressed);
113 
114     // Perform compression, check for error
115     int result = bz2Error(BZ2_bzCompress(&this->stream, this->flushing ? BZ_FINISH : BZ_RUN));
116 
117     // Set buffer used space
118     bufUsedSet(compressed, bufSize(compressed) - (size_t)this->stream.avail_out);
119 
120     // Is compression done?
121     if (this->flushing && result == BZ_STREAM_END)
122         this->done = true;
123 
124     // Can more input be provided on the next call?
125     this->inputSame = this->flushing ? !this->done : this->stream.avail_in != 0;
126 
127     FUNCTION_LOG_RETURN_VOID();
128 }
129 
130 /***********************************************************************************************************************************
131 Is compress done?
132 ***********************************************************************************************************************************/
133 static bool
bz2CompressDone(const THIS_VOID)134 bz2CompressDone(const THIS_VOID)
135 {
136     THIS(const Bz2Compress);
137 
138     FUNCTION_TEST_BEGIN();
139         FUNCTION_TEST_PARAM(BZ2_COMPRESS, this);
140     FUNCTION_TEST_END();
141 
142     ASSERT(this != NULL);
143 
144     FUNCTION_TEST_RETURN(this->done);
145 }
146 
147 /***********************************************************************************************************************************
148 Is the same input required on the next process call?
149 ***********************************************************************************************************************************/
150 static bool
bz2CompressInputSame(const THIS_VOID)151 bz2CompressInputSame(const THIS_VOID)
152 {
153     THIS(const Bz2Compress);
154 
155     FUNCTION_TEST_BEGIN();
156         FUNCTION_TEST_PARAM(BZ2_COMPRESS, this);
157     FUNCTION_TEST_END();
158 
159     ASSERT(this != NULL);
160 
161     FUNCTION_TEST_RETURN(this->inputSame);
162 }
163 
164 /**********************************************************************************************************************************/
165 IoFilter *
bz2CompressNew(int level)166 bz2CompressNew(int level)
167 {
168     FUNCTION_LOG_BEGIN(logLevelTrace);
169         FUNCTION_LOG_PARAM(INT, level);
170     FUNCTION_LOG_END();
171 
172     ASSERT(level > 0);
173 
174     IoFilter *this = NULL;
175 
176     MEM_CONTEXT_NEW_BEGIN("Bz2Compress")
177     {
178         Bz2Compress *driver = memNew(sizeof(Bz2Compress));
179 
180         *driver = (Bz2Compress)
181         {
182             .memContext = MEM_CONTEXT_NEW(),
183             .stream = {.bzalloc = NULL},
184         };
185 
186         // Initialize context
187         bz2Error(BZ2_bzCompressInit(&driver->stream, level, 0, 0));
188 
189         // Set callback to ensure bz2 stream is freed
190         memContextCallbackSet(driver->memContext, bz2CompressFreeResource, driver);
191 
192         // Create param list
193         VariantList *paramList = varLstNew();
194         varLstAdd(paramList, varNewInt(level));
195 
196         // Create filter interface
197         this = ioFilterNewP(
198             BZ2_COMPRESS_FILTER_TYPE_STR, driver, paramList, .done = bz2CompressDone, .inOut = bz2CompressProcess,
199             .inputSame = bz2CompressInputSame);
200     }
201     MEM_CONTEXT_NEW_END();
202 
203     FUNCTION_LOG_RETURN(IO_FILTER, this);
204 }
205