Skip to content

Commit 9c27957

Browse files
added lzss decoder class
1 parent 1426c94 commit 9c27957

File tree

2 files changed

+266
-0
lines changed

2 files changed

+266
-0
lines changed

src/lzss/lzssDecoder.cpp

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

src/lzss/lzssDecoder.h

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

0 commit comments

Comments
 (0)