Skip to content

Commit 007628e

Browse files
added lzss decoder class
1 parent 1426c94 commit 007628e

File tree

2 files changed

+263
-0
lines changed

2 files changed

+263
-0
lines changed

src/lzss/lzssDecoder.cpp

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

src/lzss/lzssDecoder.h

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

0 commit comments

Comments
 (0)