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