-
Notifications
You must be signed in to change notification settings - Fork 5
/
lzo.c
118 lines (106 loc) · 3.33 KB
/
lzo.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include "endian.h"
#define min(a, b) ((a) > (b) ? (b) : (a))
/* extract n bits starting from position p from value v */
#define bits(v, p, n) (((v) >> (p)) & ((1 << (n)) - 1))
/* extract bit from position p from value v */
#define bit(v, p) bits((v), (p), 1)
/* references:
* - https://www.infradead.org/~mchehab/kernel_docs/unsorted/lzo.html
* - https://github.com/synopse/mORMot/blob/master/SynLZO.pas
*/
static inline unsigned lzo_parse_length(const uint8_t **buf, unsigned bits)
{
unsigned mask = (1 << bits) - 1;
const uint8_t *p = *buf;
unsigned len = *p++ & mask;
if (!len) {
for (; !*p; p++)
len += 0xFF;
len += *p++ + mask;
}
*buf = p;
return len;
}
static inline void lzo_copy(uint8_t **out, const uint8_t **in, size_t n)
{
memcpy(*out, *in, n);
*out += n;
*in += n;
}
static inline void lzo_copy_distance(uint8_t **out, ptrdiff_t dist, size_t n)
{
/* interestingly, memmove() does *not* work here, at least on macOS */
uint8_t *p = *out;
const uint8_t *in = p - dist;
while (n--)
*p++ = *in++;
*out = p;
}
size_t lzo_decompress(const uint8_t *buf, size_t len, uint8_t *out, size_t outlen)
{
const uint8_t *p = buf, *end = buf + len, *oend = out + outlen;
uint8_t *op = out;
uint8_t state = 0;
while (p < end) {
uint8_t instr = *p;
/* first command is special */
if (p == buf) {
if (instr > 17) {
lzo_copy(&op, &p, instr - 17);
state = 4;
continue;
}
}
uint32_t length = 0;
uint16_t follow = 0;
ptrdiff_t distance = 0;
/* general notation for following formats:
* L: length bits
* D: distance bits
* S: state bits
*/
if (instr >= 64) {
/* format: L L L D D D S S;
* follow: D D D D D D D D */
p++;
length = bits(instr, 5, 3) + 1;
follow = *p++;
distance = (follow << 3) + bits(instr, 2, 3) + 1;
state = bits(instr, 0, 2);
} else if (instr >= 32) {
/* format: 0 0 1 L L L L L;
* follow: D D D D D D D D D D D D D D S S */
length = lzo_parse_length(&p, 5) + 2;
follow = le16toh(*(const uint16_t *)p);
distance = (follow >> 2) + 1;
state = bits(follow, 0, 2);
p += 2;
} else if (instr >= 16) {
/* format: 0 0 0 1 D L L L;
* follow: D D D D D D D D D D D D D D S S */
length = lzo_parse_length(&p, 3) + 2;
follow = le16toh(*(const uint16_t *)p);
distance = (bit(instr, 3) << 14) + (follow >> 2) + 0x4000;
state = bits(follow, 0, 2);
p += 2;
} else if (!state) {
/* back up and parse length properly */
length = lzo_parse_length(&p, 4) + 3;
lzo_copy(&op, &p, length);
state = 4;
continue;
} else {
/* this shouldn't happen */
return 0;
}
length = min(length, oend - op);
if (length)
lzo_copy_distance(&op, distance, length);
if (state > 0 && state < 4)
lzo_copy(&op, &p, state);
}
return op - out;
}