1// SPDX-License-Identifier: 0BSD
2
3///////////////////////////////////////////////////////////////////////////////
4//
5/// \file       microlzma_encoder.c
6/// \brief      Encode into MicroLZMA format
7//
8//  Author:     Lasse Collin
9//
10///////////////////////////////////////////////////////////////////////////////
11
12#include "lzma_encoder.h"
13
14
15typedef struct {
16	/// LZMA1 encoder
17	lzma_next_coder lzma;
18
19	/// LZMA properties byte (lc/lp/pb)
20	uint8_t props;
21} lzma_microlzma_coder;
22
23
24static lzma_ret
25microlzma_encode(void *coder_ptr, const lzma_allocator *allocator,
26		const uint8_t *restrict in, size_t *restrict in_pos,
27		size_t in_size, uint8_t *restrict out,
28		size_t *restrict out_pos, size_t out_size, lzma_action action)
29{
30	lzma_microlzma_coder *coder = coder_ptr;
31
32	// Remember *out_pos so that we can overwrite the first byte with
33	// the LZMA properties byte.
34	const size_t out_start = *out_pos;
35
36	// Remember *in_pos so that we can set it based on how many
37	// uncompressed bytes were actually encoded.
38	const size_t in_start = *in_pos;
39
40	// Set the output size limit based on the available output space.
41	// We know that the encoder supports set_out_limit() so
42	// LZMA_OPTIONS_ERROR isn't possible. LZMA_BUF_ERROR is possible
43	// but lzma_code() has an assertion to not allow it to be returned
44	// from here and I don't want to change that for now, so
45	// LZMA_BUF_ERROR becomes LZMA_PROG_ERROR.
46	uint64_t uncomp_size;
47	if (coder->lzma.set_out_limit(coder->lzma.coder,
48			&uncomp_size, out_size - *out_pos) != LZMA_OK)
49		return LZMA_PROG_ERROR;
50
51	// set_out_limit fails if this isn't true.
52	assert(out_size - *out_pos >= 6);
53
54	// Encode as much as possible.
55	const lzma_ret ret = coder->lzma.code(coder->lzma.coder, allocator,
56			in, in_pos, in_size, out, out_pos, out_size, action);
57
58	if (ret != LZMA_STREAM_END) {
59		if (ret == LZMA_OK) {
60			assert(0);
61			return LZMA_PROG_ERROR;
62		}
63
64		return ret;
65	}
66
67	// The first output byte is bitwise-negation of the properties byte.
68	// We know that there is space for this byte because set_out_limit
69	// and the actual encoding succeeded.
70	out[out_start] = (uint8_t)(~coder->props);
71
72	// The LZMA encoder likely read more input than it was able to encode.
73	// Set *in_pos based on uncomp_size.
74	assert(uncomp_size <= in_size - in_start);
75	*in_pos = in_start + (size_t)(uncomp_size);
76
77	return ret;
78}
79
80
81static void
82microlzma_encoder_end(void *coder_ptr, const lzma_allocator *allocator)
83{
84	lzma_microlzma_coder *coder = coder_ptr;
85	lzma_next_end(&coder->lzma, allocator);
86	lzma_free(coder, allocator);
87	return;
88}
89
90
91static lzma_ret
92microlzma_encoder_init(lzma_next_coder *next, const lzma_allocator *allocator,
93		const lzma_options_lzma *options)
94{
95	lzma_next_coder_init(&microlzma_encoder_init, next, allocator);
96
97	lzma_microlzma_coder *coder = next->coder;
98
99	if (coder == NULL) {
100		coder = lzma_alloc(sizeof(lzma_microlzma_coder), allocator);
101		if (coder == NULL)
102			return LZMA_MEM_ERROR;
103
104		next->coder = coder;
105		next->code = &microlzma_encode;
106		next->end = &microlzma_encoder_end;
107
108		coder->lzma = LZMA_NEXT_CODER_INIT;
109	}
110
111	// Encode the properties byte. Bitwise-negation of it will be the
112	// first output byte.
113	if (lzma_lzma_lclppb_encode(options, &coder->props))
114		return LZMA_OPTIONS_ERROR;
115
116	// Initialize the LZMA encoder.
117	const lzma_filter_info filters[2] = {
118		{
119			.id = LZMA_FILTER_LZMA1,
120			.init = &lzma_lzma_encoder_init,
121			.options = (void *)(options),
122		}, {
123			.init = NULL,
124		}
125	};
126
127	return lzma_next_filter_init(&coder->lzma, allocator, filters);
128}
129
130
131extern LZMA_API(lzma_ret)
132lzma_microlzma_encoder(lzma_stream *strm, const lzma_options_lzma *options)
133{
134	lzma_next_strm_init(microlzma_encoder_init, strm, options);
135
136	strm->internal->supported_actions[LZMA_FINISH] = true;
137
138	return LZMA_OK;
139
140}
141