1 /***********************************************************************************************************************************
2 LZ4 Decompress
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5 
6 #ifdef HAVE_LIBLZ4
7 
8 #include <stdio.h>
9 #include <lz4frame.h>
10 
11 #include "common/compress/lz4/common.h"
12 #include "common/compress/lz4/decompress.h"
13 #include "common/debug.h"
14 #include "common/io/filter/filter.h"
15 #include "common/log.h"
16 #include "common/memContext.h"
17 #include "common/type/object.h"
18 
19 /***********************************************************************************************************************************
20 Filter type constant
21 ***********************************************************************************************************************************/
22 STRING_EXTERN(LZ4_DECOMPRESS_FILTER_TYPE_STR,                       LZ4_DECOMPRESS_FILTER_TYPE);
23 
24 /***********************************************************************************************************************************
25 Object type
26 ***********************************************************************************************************************************/
27 typedef struct Lz4Decompress
28 {
29     MemContext *memContext;                                         // Context to store data
30     LZ4F_decompressionContext_t context;                            // LZ4 decompression context
31     IoFilter *filter;                                               // Filter interface
32 
33     bool inputSame;                                                 // Is the same input required on the next process call?
34     size_t inputOffset;                                             // Current offset from the start of the buffer
35     bool frameDone;                                                 // Has the current frame completed?
36     bool done;                                                      // Is decompression done?
37 } Lz4Decompress;
38 
39 /***********************************************************************************************************************************
40 Render as string for logging
41 ***********************************************************************************************************************************/
42 static String *
lz4DecompressToLog(const Lz4Decompress * this)43 lz4DecompressToLog(const Lz4Decompress *this)
44 {
45     return strNewFmt(
46         "{inputSame: %s, inputOffset: %zu, frameDone %s, done: %s}", cvtBoolToConstZ(this->inputSame), this->inputOffset,
47         cvtBoolToConstZ(this->frameDone), cvtBoolToConstZ(this->done));
48 }
49 
50 #define FUNCTION_LOG_LZ4_DECOMPRESS_TYPE                                                                                           \
51     Lz4Decompress *
52 #define FUNCTION_LOG_LZ4_DECOMPRESS_FORMAT(value, buffer, bufferSize)                                                              \
53     FUNCTION_LOG_STRING_OBJECT_FORMAT(value, lz4DecompressToLog, buffer, bufferSize)
54 
55 /***********************************************************************************************************************************
56 Free decompression context
57 ***********************************************************************************************************************************/
58 static void
lz4DecompressFreeResource(THIS_VOID)59 lz4DecompressFreeResource(THIS_VOID)
60 {
61     THIS(Lz4Decompress);
62 
63     FUNCTION_LOG_BEGIN(logLevelTrace);
64         FUNCTION_LOG_PARAM(LZ4_DECOMPRESS, this);
65     FUNCTION_LOG_END();
66 
67     ASSERT(this != NULL);
68 
69     LZ4F_freeDecompressionContext(this->context);
70 
71     FUNCTION_LOG_RETURN_VOID();
72 }
73 
74 /***********************************************************************************************************************************
75 Decompress data
76 ***********************************************************************************************************************************/
77 static void
lz4DecompressProcess(THIS_VOID,const Buffer * compressed,Buffer * decompressed)78 lz4DecompressProcess(THIS_VOID, const Buffer *compressed, Buffer *decompressed)
79 {
80     THIS(Lz4Decompress);
81 
82     FUNCTION_LOG_BEGIN(logLevelTrace);
83         FUNCTION_LOG_PARAM(LZ4_DECOMPRESS, this);
84         FUNCTION_LOG_PARAM(BUFFER, compressed);
85         FUNCTION_LOG_PARAM(BUFFER, decompressed);
86     FUNCTION_LOG_END();
87 
88     ASSERT(this != NULL);
89     ASSERT(this->context != NULL);
90     ASSERT(decompressed != NULL);
91 
92     // When there is no more input then decompression is done
93     if (compressed == NULL)
94     {
95         // If the current frame being decompressed was not completed then error
96         if (!this->frameDone)
97             THROW(FormatError, "unexpected eof in compressed data");
98 
99         this->done = true;
100     }
101     else
102     {
103         // Decompress as much data as possible
104         size_t srcSize = bufUsed(compressed) - this->inputOffset;
105         size_t dstSize = bufRemains(decompressed);
106 
107         this->frameDone = lz4Error(
108             LZ4F_decompress(
109                 this->context, bufRemainsPtr(decompressed), &dstSize, bufPtrConst(compressed) + this->inputOffset, &srcSize,
110                 NULL)) == 0;
111 
112         bufUsedInc(decompressed, dstSize);
113 
114         // If the compressed data was not fully processed then update the offset and set inputSame
115         if (srcSize < bufUsed(compressed) - this->inputOffset)
116         {
117             this->inputOffset += srcSize;
118             this->inputSame = true;
119         }
120         // Else all compressed data was processed
121         else
122         {
123             this->inputOffset = 0;
124             this->inputSame = false;
125         }
126     }
127 
128     FUNCTION_LOG_RETURN_VOID();
129 }
130 
131 /***********************************************************************************************************************************
132 Is decompress done?
133 ***********************************************************************************************************************************/
134 static bool
lz4DecompressDone(const THIS_VOID)135 lz4DecompressDone(const THIS_VOID)
136 {
137     THIS(const Lz4Decompress);
138 
139     FUNCTION_TEST_BEGIN();
140         FUNCTION_TEST_PARAM(LZ4_DECOMPRESS, this);
141     FUNCTION_TEST_END();
142 
143     ASSERT(this != NULL);
144 
145     FUNCTION_TEST_RETURN(this->done);
146 }
147 
148 /***********************************************************************************************************************************
149 Is the same input required on the next process call?
150 ***********************************************************************************************************************************/
151 static bool
lz4DecompressInputSame(const THIS_VOID)152 lz4DecompressInputSame(const THIS_VOID)
153 {
154     THIS(const Lz4Decompress);
155 
156     FUNCTION_TEST_BEGIN();
157         FUNCTION_TEST_PARAM(LZ4_DECOMPRESS, this);
158     FUNCTION_TEST_END();
159 
160     ASSERT(this != NULL);
161 
162     FUNCTION_TEST_RETURN(this->inputSame);
163 }
164 
165 /**********************************************************************************************************************************/
166 IoFilter *
lz4DecompressNew(void)167 lz4DecompressNew(void)
168 {
169     FUNCTION_LOG_VOID(logLevelTrace);
170 
171     IoFilter *this = NULL;
172 
173     MEM_CONTEXT_NEW_BEGIN("Lz4Decompress")
174     {
175         Lz4Decompress *driver = memNew(sizeof(Lz4Decompress));
176 
177         *driver = (Lz4Decompress)
178         {
179             .memContext = MEM_CONTEXT_NEW(),
180         };
181 
182         // Create lz4 context
183         lz4Error(LZ4F_createDecompressionContext(&driver->context, LZ4F_VERSION));
184 
185         // Set callback to ensure lz4 context is freed
186         memContextCallbackSet(driver->memContext, lz4DecompressFreeResource, driver);
187 
188         // Create filter interface
189         this = ioFilterNewP(
190             LZ4_DECOMPRESS_FILTER_TYPE_STR, driver, NULL, .done = lz4DecompressDone, .inOut = lz4DecompressProcess,
191             .inputSame = lz4DecompressInputSame);
192     }
193     MEM_CONTEXT_NEW_END();
194 
195     FUNCTION_LOG_RETURN(IO_FILTER, this);
196 }
197 
198 #endif // HAVE_LIBLZ4
199