Skip to content

Commit 73cdbac

Browse files
added lzss decoder class
1 parent d0f9a39 commit 73cdbac

File tree

3 files changed

+278
-0
lines changed

3 files changed

+278
-0
lines changed

src/ArduinoLzss.h

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#pragma once
2+
3+
#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

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

0 commit comments

Comments
 (0)