Home | History | Annotate | Line # | Download | only in common
      1 // SPDX-License-Identifier: 0BSD
      2 
      3 ///////////////////////////////////////////////////////////////////////////////
      4 //
      5 /// \file       filter_common.c
      6 /// \brief      Filter-specific stuff common for both encoder and decoder
      7 //
      8 //  Author:     Lasse Collin
      9 //
     10 ///////////////////////////////////////////////////////////////////////////////
     11 
     12 #include "filter_common.h"
     13 
     14 
     15 static const struct {
     16 	/// Filter ID
     17 	lzma_vli id;
     18 
     19 	/// Size of the filter-specific options structure
     20 	size_t options_size;
     21 
     22 	/// True if it is OK to use this filter as non-last filter in
     23 	/// the chain.
     24 	bool non_last_ok;
     25 
     26 	/// True if it is OK to use this filter as the last filter in
     27 	/// the chain.
     28 	bool last_ok;
     29 
     30 	/// True if the filter may change the size of the data (that is, the
     31 	/// amount of encoded output can be different than the amount of
     32 	/// uncompressed input).
     33 	bool changes_size;
     34 
     35 } features[] = {
     36 #if defined (HAVE_ENCODER_LZMA1) || defined(HAVE_DECODER_LZMA1)
     37 	{
     38 		.id = LZMA_FILTER_LZMA1,
     39 		.options_size = sizeof(lzma_options_lzma),
     40 		.non_last_ok = false,
     41 		.last_ok = true,
     42 		.changes_size = true,
     43 	},
     44 	{
     45 		.id = LZMA_FILTER_LZMA1EXT,
     46 		.options_size = sizeof(lzma_options_lzma),
     47 		.non_last_ok = false,
     48 		.last_ok = true,
     49 		.changes_size = true,
     50 	},
     51 #endif
     52 #if defined(HAVE_ENCODER_LZMA2) || defined(HAVE_DECODER_LZMA2)
     53 	{
     54 		.id = LZMA_FILTER_LZMA2,
     55 		.options_size = sizeof(lzma_options_lzma),
     56 		.non_last_ok = false,
     57 		.last_ok = true,
     58 		.changes_size = true,
     59 	},
     60 #endif
     61 #if defined(HAVE_ENCODER_X86) || defined(HAVE_DECODER_X86)
     62 	{
     63 		.id = LZMA_FILTER_X86,
     64 		.options_size = sizeof(lzma_options_bcj),
     65 		.non_last_ok = true,
     66 		.last_ok = false,
     67 		.changes_size = false,
     68 	},
     69 #endif
     70 #if defined(HAVE_ENCODER_POWERPC) || defined(HAVE_DECODER_POWERPC)
     71 	{
     72 		.id = LZMA_FILTER_POWERPC,
     73 		.options_size = sizeof(lzma_options_bcj),
     74 		.non_last_ok = true,
     75 		.last_ok = false,
     76 		.changes_size = false,
     77 	},
     78 #endif
     79 #if defined(HAVE_ENCODER_IA64) || defined(HAVE_DECODER_IA64)
     80 	{
     81 		.id = LZMA_FILTER_IA64,
     82 		.options_size = sizeof(lzma_options_bcj),
     83 		.non_last_ok = true,
     84 		.last_ok = false,
     85 		.changes_size = false,
     86 	},
     87 #endif
     88 #if defined(HAVE_ENCODER_ARM) || defined(HAVE_DECODER_ARM)
     89 	{
     90 		.id = LZMA_FILTER_ARM,
     91 		.options_size = sizeof(lzma_options_bcj),
     92 		.non_last_ok = true,
     93 		.last_ok = false,
     94 		.changes_size = false,
     95 	},
     96 #endif
     97 #if defined(HAVE_ENCODER_ARMTHUMB) || defined(HAVE_DECODER_ARMTHUMB)
     98 	{
     99 		.id = LZMA_FILTER_ARMTHUMB,
    100 		.options_size = sizeof(lzma_options_bcj),
    101 		.non_last_ok = true,
    102 		.last_ok = false,
    103 		.changes_size = false,
    104 	},
    105 #endif
    106 #if defined(HAVE_ENCODER_ARM64) || defined(HAVE_DECODER_ARM64)
    107 	{
    108 		.id = LZMA_FILTER_ARM64,
    109 		.options_size = sizeof(lzma_options_bcj),
    110 		.non_last_ok = true,
    111 		.last_ok = false,
    112 		.changes_size = false,
    113 	},
    114 #endif
    115 #if defined(HAVE_ENCODER_SPARC) || defined(HAVE_DECODER_SPARC)
    116 	{
    117 		.id = LZMA_FILTER_SPARC,
    118 		.options_size = sizeof(lzma_options_bcj),
    119 		.non_last_ok = true,
    120 		.last_ok = false,
    121 		.changes_size = false,
    122 	},
    123 #endif
    124 #if defined(HAVE_ENCODER_RISCV) || defined(HAVE_DECODER_RISCV)
    125 	{
    126 		.id = LZMA_FILTER_RISCV,
    127 		.options_size = sizeof(lzma_options_bcj),
    128 		.non_last_ok = true,
    129 		.last_ok = false,
    130 		.changes_size = false,
    131 	},
    132 #endif
    133 #if defined(HAVE_ENCODER_DELTA) || defined(HAVE_DECODER_DELTA)
    134 	{
    135 		.id = LZMA_FILTER_DELTA,
    136 		.options_size = sizeof(lzma_options_delta),
    137 		.non_last_ok = true,
    138 		.last_ok = false,
    139 		.changes_size = false,
    140 	},
    141 #endif
    142 	{
    143 		.id = LZMA_VLI_UNKNOWN
    144 	}
    145 };
    146 
    147 
    148 extern LZMA_API(lzma_ret)
    149 lzma_filters_copy(const lzma_filter *src, lzma_filter *real_dest,
    150 		const lzma_allocator *allocator)
    151 {
    152 	if (src == NULL || real_dest == NULL)
    153 		return LZMA_PROG_ERROR;
    154 
    155 	// Use a temporary destination so that the real destination
    156 	// will never be modified if an error occurs.
    157 	lzma_filter dest[LZMA_FILTERS_MAX + 1];
    158 
    159 	lzma_ret ret;
    160 	size_t i;
    161 	for (i = 0; src[i].id != LZMA_VLI_UNKNOWN; ++i) {
    162 		// There must be a maximum of four filters plus
    163 		// the array terminator.
    164 		if (i == LZMA_FILTERS_MAX) {
    165 			ret = LZMA_OPTIONS_ERROR;
    166 			goto error;
    167 		}
    168 
    169 		dest[i].id = src[i].id;
    170 
    171 		if (src[i].options == NULL) {
    172 			dest[i].options = NULL;
    173 		} else {
    174 			// See if the filter is supported only when the
    175 			// options is not NULL. This might be convenient
    176 			// sometimes if the app is actually copying only
    177 			// a partial filter chain with a place holder ID.
    178 			//
    179 			// When options is not NULL, the Filter ID must be
    180 			// supported by us, because otherwise we don't know
    181 			// how big the options are.
    182 			size_t j;
    183 			for (j = 0; src[i].id != features[j].id; ++j) {
    184 				if (features[j].id == LZMA_VLI_UNKNOWN) {
    185 					ret = LZMA_OPTIONS_ERROR;
    186 					goto error;
    187 				}
    188 			}
    189 
    190 			// Allocate and copy the options.
    191 			dest[i].options = lzma_alloc(features[j].options_size,
    192 					allocator);
    193 			if (dest[i].options == NULL) {
    194 				ret = LZMA_MEM_ERROR;
    195 				goto error;
    196 			}
    197 
    198 			memcpy(dest[i].options, src[i].options,
    199 					features[j].options_size);
    200 		}
    201 	}
    202 
    203 	// Terminate the filter array.
    204 	assert(i < LZMA_FILTERS_MAX + 1);
    205 	dest[i].id = LZMA_VLI_UNKNOWN;
    206 	dest[i].options = NULL;
    207 
    208 	// Copy it to the caller-supplied array now that we know that
    209 	// no errors occurred.
    210 	memcpy(real_dest, dest, (i + 1) * sizeof(lzma_filter));
    211 
    212 	return LZMA_OK;
    213 
    214 error:
    215 	// Free the options which we have already allocated.
    216 	while (i > 0)
    217 		lzma_free(dest[--i].options, allocator);
    218 
    219 	return ret;
    220 }
    221 
    222 
    223 extern LZMA_API(void)
    224 lzma_filters_free(lzma_filter *filters, const lzma_allocator *allocator)
    225 {
    226 	if (filters == NULL)
    227 		return;
    228 
    229 	for (size_t i = 0; filters[i].id != LZMA_VLI_UNKNOWN; ++i) {
    230 		if (i == LZMA_FILTERS_MAX) {
    231 			// The API says that LZMA_FILTERS_MAX + 1 is the
    232 			// maximum allowed size including the terminating
    233 			// element. Thus, we should never get here but in
    234 			// case there is a bug and we do anyway, don't go
    235 			// past the (probable) end of the array.
    236 			assert(0);
    237 			break;
    238 		}
    239 
    240 		lzma_free(filters[i].options, allocator);
    241 		filters[i].options = NULL;
    242 		filters[i].id = LZMA_VLI_UNKNOWN;
    243 	}
    244 
    245 	return;
    246 }
    247 
    248 
    249 extern lzma_ret
    250 lzma_validate_chain(const lzma_filter *filters, size_t *count)
    251 {
    252 	// There must be at least one filter.
    253 	if (filters == NULL || filters[0].id == LZMA_VLI_UNKNOWN)
    254 		return LZMA_PROG_ERROR;
    255 
    256 	// Number of non-last filters that may change the size of the data
    257 	// significantly (that is, more than 1-2 % or so).
    258 	size_t changes_size_count = 0;
    259 
    260 	// True if it is OK to add a new filter after the current filter.
    261 	bool non_last_ok = true;
    262 
    263 	// True if the last filter in the given chain is actually usable as
    264 	// the last filter. Only filters that support embedding End of Payload
    265 	// Marker can be used as the last filter in the chain.
    266 	bool last_ok = false;
    267 
    268 	size_t i = 0;
    269 	do {
    270 		size_t j;
    271 		for (j = 0; filters[i].id != features[j].id; ++j)
    272 			if (features[j].id == LZMA_VLI_UNKNOWN)
    273 				return LZMA_OPTIONS_ERROR;
    274 
    275 		// If the previous filter in the chain cannot be a non-last
    276 		// filter, the chain is invalid.
    277 		if (!non_last_ok)
    278 			return LZMA_OPTIONS_ERROR;
    279 
    280 		non_last_ok = features[j].non_last_ok;
    281 		last_ok = features[j].last_ok;
    282 		changes_size_count += features[j].changes_size;
    283 
    284 	} while (filters[++i].id != LZMA_VLI_UNKNOWN);
    285 
    286 	// There must be 1-4 filters. The last filter must be usable as
    287 	// the last filter in the chain. A maximum of three filters are
    288 	// allowed to change the size of the data.
    289 	if (i > LZMA_FILTERS_MAX || !last_ok || changes_size_count > 3)
    290 		return LZMA_OPTIONS_ERROR;
    291 
    292 	*count = i;
    293 	return LZMA_OK;
    294 }
    295 
    296 
    297 extern lzma_ret
    298 lzma_raw_coder_init(lzma_next_coder *next, const lzma_allocator *allocator,
    299 		const lzma_filter *options,
    300 		lzma_filter_find coder_find, bool is_encoder)
    301 {
    302 	// Do some basic validation and get the number of filters.
    303 	size_t count;
    304 	return_if_error(lzma_validate_chain(options, &count));
    305 
    306 	// Set the filter functions and copy the options pointer.
    307 	lzma_filter_info filters[LZMA_FILTERS_MAX + 1];
    308 	if (is_encoder) {
    309 		for (size_t i = 0; i < count; ++i) {
    310 			// The order of the filters is reversed in the
    311 			// encoder. It allows more efficient handling
    312 			// of the uncompressed data.
    313 			const size_t j = count - i - 1;
    314 
    315 			const lzma_filter_coder *const fc
    316 					= coder_find(options[i].id);
    317 			if (fc == NULL || fc->init == NULL)
    318 				return LZMA_OPTIONS_ERROR;
    319 
    320 			filters[j].id = options[i].id;
    321 			filters[j].init = fc->init;
    322 			filters[j].options = options[i].options;
    323 		}
    324 	} else {
    325 		for (size_t i = 0; i < count; ++i) {
    326 			const lzma_filter_coder *const fc
    327 					= coder_find(options[i].id);
    328 			if (fc == NULL || fc->init == NULL)
    329 				return LZMA_OPTIONS_ERROR;
    330 
    331 			filters[i].id = options[i].id;
    332 			filters[i].init = fc->init;
    333 			filters[i].options = options[i].options;
    334 		}
    335 	}
    336 
    337 	// Terminate the array.
    338 	filters[count].id = LZMA_VLI_UNKNOWN;
    339 	filters[count].init = NULL;
    340 
    341 	// Initialize the filters.
    342 	const lzma_ret ret = lzma_next_filter_init(next, allocator, filters);
    343 	if (ret != LZMA_OK)
    344 		lzma_next_end(next, allocator);
    345 
    346 	return ret;
    347 }
    348 
    349 
    350 extern uint64_t
    351 lzma_raw_coder_memusage(lzma_filter_find coder_find,
    352 		const lzma_filter *filters)
    353 {
    354 	// The chain has to have at least one filter.
    355 	{
    356 		size_t tmp;
    357 		if (lzma_validate_chain(filters, &tmp) != LZMA_OK)
    358 			return UINT64_MAX;
    359 	}
    360 
    361 	uint64_t total = 0;
    362 	size_t i = 0;
    363 
    364 	do {
    365 		const lzma_filter_coder *const fc
    366 				 = coder_find(filters[i].id);
    367 		if (fc == NULL)
    368 			return UINT64_MAX; // Unsupported Filter ID
    369 
    370 		if (fc->memusage == NULL) {
    371 			// This filter doesn't have a function to calculate
    372 			// the memory usage and validate the options. Such
    373 			// filters need only little memory, so we use 1 KiB
    374 			// as a good estimate. They also accept all possible
    375 			// options, so there's no need to worry about lack
    376 			// of validation.
    377 			total += 1024;
    378 		} else {
    379 			// Call the filter-specific memory usage calculation
    380 			// function.
    381 			const uint64_t usage
    382 					= fc->memusage(filters[i].options);
    383 			if (usage == UINT64_MAX)
    384 				return UINT64_MAX; // Invalid options
    385 
    386 			total += usage;
    387 		}
    388 	} while (filters[++i].id != LZMA_VLI_UNKNOWN);
    389 
    390 	// Add some fixed amount of extra. It's to compensate memory usage
    391 	// of Stream, Block etc. coders, malloc() overhead, stack etc.
    392 	return total + LZMA_MEMUSAGE_BASE;
    393 }
    394