// SPDX-License-Identifier: 0BSD
///////////////////////////////////////////////////////////////////////////////
//
/// \file microlzma_encoder.c
/// \brief Encode into MicroLZMA format
//
// Author: Lasse Collin
//
///////////////////////////////////////////////////////////////////////////////
#include "lzma_encoder.h"
typedef struct {
/// LZMA1 encoder
lzma_next_coder lzma;
/// LZMA properties byte (lc/lp/pb)
uint8_t props;
} lzma_microlzma_coder;
static lzma_ret
microlzma_encode(void *coder_ptr, const lzma_allocator *allocator,
const uint8_t *restrict in, size_t *restrict in_pos,
size_t in_size, uint8_t *restrict out,
size_t *restrict out_pos, size_t out_size, lzma_action action)
{
lzma_microlzma_coder *coder = coder_ptr;
// Remember *out_pos so that we can overwrite the first byte with
// the LZMA properties byte.
const size_t out_start = *out_pos;
// Remember *in_pos so that we can set it based on how many
// uncompressed bytes were actually encoded.
const size_t in_start = *in_pos;
// Set the output size limit based on the available output space.
// We know that the encoder supports set_out_limit() so
// LZMA_OPTIONS_ERROR isn't possible. LZMA_BUF_ERROR is possible
// but lzma_code() has an assertion to not allow it to be returned
// from here and I don't want to change that for now, so
// LZMA_BUF_ERROR becomes LZMA_PROG_ERROR.
uint64_t uncomp_size;
if (coder->lzma.set_out_limit(coder->lzma.coder,
&uncomp_size, out_size - *out_pos) != LZMA_OK)
return LZMA_PROG_ERROR;
// set_out_limit fails if this isn't true.
assert(out_size - *out_pos >= 6);
// Encode as much as possible.
const lzma_ret ret = coder->lzma.code(coder->lzma.coder, allocator,
in, in_pos, in_size, out, out_pos, out_size, action);
if (ret != LZMA_STREAM_END) {
if (ret == LZMA_OK) {
assert(0);
return LZMA_PROG_ERROR;
}
return ret;
}
// The first output byte is bitwise-negation of the properties byte.
// We know that there is space for this byte because set_out_limit
// and the actual encoding succeeded.
out[out_start] = (uint8_t)(~coder->props);
// The LZMA encoder likely read more input than it was able to encode.
// Set *in_pos based on uncomp_size.
assert(uncomp_size <= in_size - in_start);
*in_pos = in_start + (size_t)(uncomp_size);
return ret;
}
static void
microlzma_encoder_end(void *coder_ptr, const lzma_allocator *allocator)
{
lzma_microlzma_coder *coder = coder_ptr;
lzma_next_end(&coder->lzma, allocator);
lzma_free(coder, allocator);
return;
}
static lzma_ret
microlzma_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator,
const lzma_options_lzma *options)
{
lzma_next_coder_init(µlzma_encoder_init, next, allocator);
lzma_microlzma_coder *coder = next->coder;
if (coder == NULL) {
coder = lzma_alloc(sizeof(lzma_microlzma_coder), allocator);
if (coder == NULL)
return LZMA_MEM_ERROR;
next->coder = coder;
next->code = µlzma_encode;
next->end = µlzma_encoder_end;
coder->lzma = LZMA_NEXT_CODER_INIT;
}
// Encode the properties byte. Bitwise-negation of it will be the
// first output byte.
if (lzma_lzma_lclppb_encode(options, &coder->props))
return LZMA_OPTIONS_ERROR;
// Initialize the LZMA encoder.
const lzma_filter_info filters[2] = {
{
.id = LZMA_FILTER_LZMA1,
.init = &lzma_lzma_encoder_init,
.options = (void *)(options),
}, {
.init = NULL,
}
};
return lzma_next_filter_init(&coder->lzma, allocator, filters);
}
extern LZMA_API(lzma_ret)
lzma_microlzma_encoder(lzma_stream *strm, const lzma_options_lzma *options)
{
lzma_next_strm_init(microlzma_encoder_init, strm, options);
strm->internal->supported_actions[LZMA_FINISH] = true;
return LZMA_OK;
}