Skip to content

Commit 64ad593

Browse files
added lzss decoder class
1 parent 68345de commit 64ad593

File tree

3 files changed

+286
-0
lines changed

3 files changed

+286
-0
lines changed

src/ArduinoLzss.h

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
This file is part of the Arduino_CloudUtils library.
3+
4+
Copyright (c) 2024 Arduino SA
5+
6+
This Source Code Form is subject to the terms of the Mozilla Public
7+
License, v. 2.0. If a copy of the MPL was not distributed with this
8+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
#pragma once
11+
12+
#include "./lzss/decoder.h"

src/lzss/decoder.h

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
This file is part of the Arduino_CloudUtils library.
3+
4+
Copyright (c) 2024 Arduino SA
5+
6+
This Source Code Form is subject to the terms of the Mozilla Public
7+
License, v. 2.0. If a copy of the MPL was not distributed with this
8+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
11+
#pragma once
12+
13+
#include <functional>
14+
#include <stdint.h>
15+
16+
namespace arduino { namespace lzss {
17+
18+
enum status: uint8_t {
19+
DONE,
20+
IN_PROGRESS,
21+
NOT_COMPLETED
22+
};
23+
24+
constexpr int DefaultEI = 11; /* typically 10..13 */
25+
constexpr int DefaultEJ = 4; /* typically 4..5 */
26+
constexpr int DefaultN = (1 << DefaultEI); /* buffer size */
27+
constexpr int DefaultF = ((1 << DefaultEJ) + 1); /* lookahead buffer size */
28+
29+
template<int EI, int EJ, int N, int F>
30+
class GenericDecoder {
31+
public:
32+
33+
/**
34+
* Build an LZSS decoder by providing a callback for storing the decoded bytes
35+
* @param putc_cbk: a callback that takes a char and stores it e.g. a callback to fwrite
36+
*/
37+
GenericDecoder(std::function<void(const uint8_t)> putc_cbk);
38+
39+
/**
40+
* Build an LZSS decoder providing a callback for getting a char and putting a char
41+
* in this way you need to call decompress with no parameters
42+
* @param putc_cbk: a callback that takes a char and stores it e.g. a callback to fwrite
43+
* @param getc_cbk: a callback that returns the next char to consume
44+
* -1 means EOF, -2 means buffer is temporairly finished
45+
*/
46+
GenericDecoder(std::function<int()> getc_cbk, std::function<void(const uint8_t)> putc_cbk);
47+
48+
/**
49+
* this enum describes the result of the computation of a single FSM computation
50+
* DONE: the decompression is completed
51+
* IN_PROGRESS: the decompression cycle completed successfully, ready to compute next
52+
* NOT_COMPLETED: the current cycle didn't complete because the available data is not enough
53+
*/
54+
// enum status: uint8_t {
55+
// DONE,
56+
// IN_PROGRESS,
57+
// NOT_COMPLETED
58+
// };
59+
60+
/**
61+
* decode the provided buffer until buffer ends, then pause the process
62+
* @return DONE if the decompression is completed, NOT_COMPLETED if not
63+
*/
64+
status decompress(uint8_t* const buffer=nullptr, uint32_t size=0);
65+
66+
static const int LZSS_EOF = -1;
67+
static const int LZSS_BUFFER_EMPTY = -2;
68+
private:
69+
70+
71+
// algorithm specific buffer used to store text that could be later referenced and copied
72+
uint8_t buffer[N * 2];
73+
74+
// this function gets 1 single char from the input buffer
75+
int getc();
76+
uint8_t* in_buffer = nullptr;
77+
uint32_t available = 0;
78+
79+
status handle_state();
80+
81+
// get 1 bit from the available input buffer
82+
int getbit(uint8_t n);
83+
// the following 2 are variables used by getbits
84+
uint32_t buf, buf_size=0;
85+
86+
enum FSM_STATES: uint8_t {
87+
FSM_0 = 0,
88+
FSM_1 = 1,
89+
FSM_2 = 2,
90+
FSM_3 = 3,
91+
FSM_EOF
92+
} state;
93+
94+
// these variable are used in a decode session and specific to the old C implementation
95+
// there is no documentation about their meaning
96+
int i, r;
97+
98+
std::function<void(const uint8_t)> put_char_cbk;
99+
std::function<uint8_t()> get_char_cbk;
100+
101+
inline void putc(const uint8_t c) { if(put_char_cbk) { put_char_cbk(c); } }
102+
103+
// get the number of bits the FSM will require given its state
104+
uint8_t bits_required(FSM_STATES s);
105+
};
106+
107+
using Decoder = GenericDecoder<DefaultEI, DefaultEJ, DefaultN, DefaultF>;
108+
}}
109+
110+
#include "./decoder.ipp"

src/lzss/decoder.ipp

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
This file is part of the Arduino_CloudUtils library.
3+
4+
Copyright (c) 2024 Arduino SA
5+
6+
This Source Code Form is subject to the terms of the Mozilla Public
7+
License, v. 2.0. If a copy of the MPL was not distributed with this
8+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
10+
This implementation took inspiration from https://okumuralab.org/~okumura/compression/lzss.c source code
11+
*/
12+
#include <stdlib.h>
13+
14+
namespace arduino { namespace lzss {
15+
16+
// get the number of bits the algorithm will try to get given the state
17+
template<int EI, int EJ, int N, int F>
18+
uint8_t GenericDecoder<EI, EJ, N, F>::bits_required(GenericDecoder::FSM_STATES s) {
19+
switch(s) {
20+
case FSM_0:
21+
return 1;
22+
case FSM_1:
23+
return 8;
24+
case FSM_2:
25+
return EI;
26+
case FSM_3:
27+
return EJ;
28+
default:
29+
return 0;
30+
}
31+
}
32+
33+
template<int EI, int EJ, int N, int F>
34+
GenericDecoder<EI, EJ, N, F>::GenericDecoder(std::function<int()> getc_cbk, std::function<void(const uint8_t)> putc_cbk)
35+
: available(0), state(FSM_0), put_char_cbk(putc_cbk), get_char_cbk(getc_cbk) {
36+
for (int i = 0; i < N - F; i++) buffer[i] = ' ';
37+
r = N - F;
38+
}
39+
40+
41+
template<int EI, int EJ, int N, int F>
42+
GenericDecoder<EI, EJ, N, F>::GenericDecoder(std::function<void(const uint8_t)> putc_cbk)
43+
: available(0), state(FSM_0), put_char_cbk(putc_cbk), get_char_cbk(nullptr) {
44+
for (int i = 0; i < N - F; i++) buffer[i] = ' ';
45+
r = N - F;
46+
}
47+
48+
template<int EI, int EJ, int N, int F>
49+
status GenericDecoder<EI, EJ, N, F>::handle_state() {
50+
status res = IN_PROGRESS;
51+
52+
int c = getbit(bits_required(this->state));
53+
54+
if(c == LZSS_BUFFER_EMPTY) {
55+
res = NOT_COMPLETED;
56+
} else if (c == LZSS_EOF) {
57+
res = DONE;
58+
this->state = FSM_EOF;
59+
} else {
60+
switch(this->state) {
61+
case FSM_0:
62+
if(c) {
63+
this->state = FSM_1;
64+
} else {
65+
this->state = FSM_2;
66+
}
67+
break;
68+
case FSM_1:
69+
putc(c);
70+
buffer[r++] = c;
71+
r &= (N - 1); // equivalent to r = r % N when N is a power of 2
72+
73+
this->state = FSM_0;
74+
break;
75+
case FSM_2:
76+
this->i = c;
77+
this->state = FSM_3;
78+
break;
79+
case FSM_3: {
80+
int j = c;
81+
82+
// This is where the actual decompression takes place: we look into the local buffer for reuse
83+
// of byte chunks. This can be improved by means of memcpy and by changing the putc function
84+
// into a put_buf function in order to avoid buffering on the other end.
85+
// TODO improve this section of code
86+
for (int k = 0; k <= j + 1; k++) {
87+
c = buffer[(this->i + k) & (N - 1)]; // equivalent to buffer[(i+k) % N] when N is a power of 2
88+
putc(c);
89+
buffer[r++] = c;
90+
r &= (N - 1); // equivalent to r = r % N
91+
}
92+
this->state = FSM_0;
93+
94+
break;
95+
}
96+
case FSM_EOF:
97+
break;
98+
}
99+
}
100+
101+
return res;
102+
}
103+
104+
template<int EI, int EJ, int N, int F>
105+
status GenericDecoder<EI, EJ, N, F>::decompress(uint8_t* const buffer, uint32_t size) {
106+
if(!get_char_cbk) {
107+
this->in_buffer = buffer;
108+
this->available += size;
109+
}
110+
111+
status res = IN_PROGRESS;
112+
113+
while((res = handle_state()) == IN_PROGRESS);
114+
115+
this->in_buffer = nullptr;
116+
117+
return res;
118+
}
119+
120+
template<int EI, int EJ, int N, int F>
121+
int GenericDecoder<EI, EJ, N, F>::getbit(uint8_t n) { // get n bits from buffer
122+
int x=0, c;
123+
124+
// if the local bit buffer doesn't have enough bit get them
125+
while(buf_size < n) {
126+
switch(c=getc()) {
127+
case LZSS_EOF:
128+
case LZSS_BUFFER_EMPTY:
129+
return c;
130+
}
131+
buf <<= 8;
132+
133+
buf |= (uint8_t)c;
134+
buf_size += sizeof(uint8_t)*8;
135+
}
136+
137+
// the result is the content of the buffer starting from msb to n successive bits
138+
x = buf >> (buf_size-n);
139+
140+
// remove from the buffer the read bits with a mask
141+
buf &= (1<<(buf_size-n))-1;
142+
143+
buf_size-=n;
144+
145+
return x;
146+
}
147+
148+
template<int EI, int EJ, int N, int F>
149+
int GenericDecoder<EI, EJ, N, F>::getc() {
150+
int c;
151+
152+
if(get_char_cbk) {
153+
c = get_char_cbk();
154+
} else if(in_buffer == nullptr || available == 0) {
155+
c = LZSS_BUFFER_EMPTY;
156+
} else {
157+
c = *in_buffer;
158+
in_buffer++;
159+
available--;
160+
}
161+
return c;
162+
}
163+
164+
}} // arduino::lzss

0 commit comments

Comments
 (0)