1219354Spjd/*-
2219354Spjd * Copyright (c) 2011 Pawel Jakub Dawidek <pawel@dawidek.net>
3219354Spjd * All rights reserved.
4219354Spjd *
5219354Spjd * Redistribution and use in source and binary forms, with or without
6219354Spjd * modification, are permitted provided that the following conditions
7219354Spjd * are met:
8219354Spjd * 1. Redistributions of source code must retain the above copyright
9219354Spjd *    notice, this list of conditions and the following disclaimer.
10219354Spjd * 2. Redistributions in binary form must reproduce the above copyright
11219354Spjd *    notice, this list of conditions and the following disclaimer in the
12219354Spjd *    documentation and/or other materials provided with the distribution.
13219354Spjd *
14219354Spjd * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15219354Spjd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16219354Spjd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17219354Spjd * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18219354Spjd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19219354Spjd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20219354Spjd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21219354Spjd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22219354Spjd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23219354Spjd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24219354Spjd * SUCH DAMAGE.
25219354Spjd */
26219354Spjd
27219354Spjd#include <sys/cdefs.h>
28219354Spjd__FBSDID("$FreeBSD$");
29219354Spjd
30219354Spjd#include <sys/endian.h>
31219354Spjd
32219354Spjd#include <errno.h>
33219354Spjd#include <string.h>
34219354Spjd#include <strings.h>
35219354Spjd
36219354Spjd#include <hast.h>
37219354Spjd#include <lzf.h>
38219354Spjd#include <nv.h>
39219354Spjd#include <pjdlog.h>
40219354Spjd
41219354Spjd#include "hast_compression.h"
42219354Spjd
43219354Spjdstatic bool
44219354Spjdallzeros(const void *data, size_t size)
45219354Spjd{
46219354Spjd	const uint64_t *p = data;
47219354Spjd	unsigned int i;
48219354Spjd	uint64_t v;
49219354Spjd
50219354Spjd	PJDLOG_ASSERT((size % sizeof(*p)) == 0);
51219354Spjd
52219354Spjd	/*
53219354Spjd	 * This is the fastest method I found for checking if the given
54219354Spjd	 * buffer contain all zeros.
55219354Spjd	 * Because inside the loop we don't check at every step, we would
56219354Spjd	 * get an answer only after walking through entire buffer.
57219354Spjd	 * To return early if the buffer doesn't contain all zeros, we probe
58231017Strociny	 * 8 bytes at the beginning, in the middle and at the end of the buffer
59219354Spjd	 * first.
60219354Spjd	 */
61219354Spjd
62219354Spjd	size >>= 3;	/* divide by 8 */
63219354Spjd	if ((p[0] | p[size >> 1] | p[size - 1]) != 0)
64219354Spjd		return (false);
65219354Spjd	v = 0;
66219354Spjd	for (i = 0; i < size; i++)
67219354Spjd		v |= *p++;
68219354Spjd	return (v == 0);
69219354Spjd}
70219354Spjd
71219354Spjdstatic void *
72219354Spjdhast_hole_compress(const unsigned char *data, size_t *sizep)
73219354Spjd{
74219354Spjd	uint32_t size;
75219354Spjd	void *newbuf;
76219354Spjd
77219354Spjd	if (!allzeros(data, *sizep))
78219354Spjd		return (NULL);
79219354Spjd
80219354Spjd	newbuf = malloc(sizeof(size));
81219354Spjd	if (newbuf == NULL) {
82219354Spjd		pjdlog_warning("Unable to compress (no memory: %zu).",
83219354Spjd		    (size_t)*sizep);
84219354Spjd		return (NULL);
85219354Spjd	}
86219354Spjd	size = htole32((uint32_t)*sizep);
87219354Spjd	bcopy(&size, newbuf, sizeof(size));
88219354Spjd	*sizep = sizeof(size);
89219354Spjd
90219354Spjd	return (newbuf);
91219354Spjd}
92219354Spjd
93219354Spjdstatic void *
94219354Spjdhast_hole_decompress(const unsigned char *data, size_t *sizep)
95219354Spjd{
96219354Spjd	uint32_t size;
97219354Spjd	void *newbuf;
98219354Spjd
99219354Spjd	if (*sizep != sizeof(size)) {
100219354Spjd		pjdlog_error("Unable to decompress (invalid size: %zu).",
101219354Spjd		    *sizep);
102219354Spjd		return (NULL);
103219354Spjd	}
104219354Spjd
105219354Spjd	bcopy(data, &size, sizeof(size));
106219354Spjd	size = le32toh(size);
107219354Spjd
108219354Spjd	newbuf = malloc(size);
109219354Spjd	if (newbuf == NULL) {
110219354Spjd		pjdlog_error("Unable to decompress (no memory: %zu).",
111219354Spjd		    (size_t)size);
112219354Spjd		return (NULL);
113219354Spjd	}
114219354Spjd	bzero(newbuf, size);
115219354Spjd	*sizep = size;
116219354Spjd
117219354Spjd	return (newbuf);
118219354Spjd}
119219354Spjd
120219354Spjd/* Minimum block size to try to compress. */
121219354Spjd#define	HAST_LZF_COMPRESS_MIN	1024
122219354Spjd
123219354Spjdstatic void *
124219354Spjdhast_lzf_compress(const unsigned char *data, size_t *sizep)
125219354Spjd{
126219354Spjd	unsigned char *newbuf;
127219354Spjd	uint32_t origsize;
128219354Spjd	size_t newsize;
129219354Spjd
130219354Spjd	origsize = *sizep;
131219354Spjd
132219354Spjd	if (origsize <= HAST_LZF_COMPRESS_MIN)
133219354Spjd		return (NULL);
134219354Spjd
135219354Spjd	newsize = sizeof(origsize) + origsize - HAST_LZF_COMPRESS_MIN;
136219354Spjd	newbuf = malloc(newsize);
137219354Spjd	if (newbuf == NULL) {
138219354Spjd		pjdlog_warning("Unable to compress (no memory: %zu).",
139219354Spjd		    newsize);
140219354Spjd		return (NULL);
141219354Spjd	}
142219354Spjd	newsize = lzf_compress(data, *sizep, newbuf + sizeof(origsize),
143219354Spjd	    newsize - sizeof(origsize));
144219354Spjd	if (newsize == 0) {
145219354Spjd		free(newbuf);
146219354Spjd		return (NULL);
147219354Spjd	}
148219354Spjd	origsize = htole32(origsize);
149219354Spjd	bcopy(&origsize, newbuf, sizeof(origsize));
150219354Spjd
151219354Spjd	*sizep = sizeof(origsize) + newsize;
152219354Spjd	return (newbuf);
153219354Spjd}
154219354Spjd
155219354Spjdstatic void *
156219354Spjdhast_lzf_decompress(const unsigned char *data, size_t *sizep)
157219354Spjd{
158219354Spjd	unsigned char *newbuf;
159219354Spjd	uint32_t origsize;
160219354Spjd	size_t newsize;
161219354Spjd
162219354Spjd	PJDLOG_ASSERT(*sizep > sizeof(origsize));
163219354Spjd
164219354Spjd	bcopy(data, &origsize, sizeof(origsize));
165219354Spjd	origsize = le32toh(origsize);
166219354Spjd	PJDLOG_ASSERT(origsize > HAST_LZF_COMPRESS_MIN);
167219354Spjd
168219354Spjd	newbuf = malloc(origsize);
169219354Spjd	if (newbuf == NULL) {
170219354Spjd		pjdlog_error("Unable to decompress (no memory: %zu).",
171219354Spjd		    (size_t)origsize);
172219354Spjd		return (NULL);
173219354Spjd	}
174219354Spjd	newsize = lzf_decompress(data + sizeof(origsize),
175219354Spjd	    *sizep - sizeof(origsize), newbuf, origsize);
176219354Spjd	if (newsize == 0) {
177219354Spjd		free(newbuf);
178219354Spjd		pjdlog_error("Unable to decompress.");
179219354Spjd		return (NULL);
180219354Spjd	}
181219354Spjd	PJDLOG_ASSERT(newsize == origsize);
182219354Spjd
183219354Spjd	*sizep = newsize;
184219354Spjd	return (newbuf);
185219354Spjd}
186219354Spjd
187219354Spjdconst char *
188219354Spjdcompression_name(int num)
189219354Spjd{
190219354Spjd
191219354Spjd	switch (num) {
192219354Spjd	case HAST_COMPRESSION_NONE:
193219354Spjd		return ("none");
194219354Spjd	case HAST_COMPRESSION_HOLE:
195219354Spjd		return ("hole");
196219354Spjd	case HAST_COMPRESSION_LZF:
197219354Spjd		return ("lzf");
198219354Spjd	}
199219354Spjd	return ("unknown");
200219354Spjd}
201219354Spjd
202219354Spjdint
203219354Spjdcompression_send(const struct hast_resource *res, struct nv *nv, void **datap,
204219354Spjd    size_t *sizep, bool *freedatap)
205219354Spjd{
206219354Spjd	unsigned char *newbuf;
207219354Spjd	int compression;
208219354Spjd	size_t size;
209219354Spjd
210219354Spjd	size = *sizep;
211219354Spjd	compression = res->hr_compression;
212219354Spjd
213219354Spjd	switch (compression) {
214219354Spjd	case HAST_COMPRESSION_NONE:
215219354Spjd		return (0);
216219354Spjd	case HAST_COMPRESSION_HOLE:
217219354Spjd		newbuf = hast_hole_compress(*datap, &size);
218219354Spjd		break;
219219354Spjd	case HAST_COMPRESSION_LZF:
220219354Spjd		/* Try 'hole' compression first. */
221219354Spjd		newbuf = hast_hole_compress(*datap, &size);
222219354Spjd		if (newbuf != NULL)
223219354Spjd			compression = HAST_COMPRESSION_HOLE;
224219354Spjd		else
225219354Spjd			newbuf = hast_lzf_compress(*datap, &size);
226219354Spjd		break;
227219354Spjd	default:
228219354Spjd		PJDLOG_ABORT("Invalid compression: %d.", res->hr_compression);
229219354Spjd	}
230219354Spjd
231219354Spjd	if (newbuf == NULL) {
232219354Spjd		/* Unable to compress the data. */
233219354Spjd		return (0);
234219354Spjd	}
235219354Spjd	nv_add_string(nv, compression_name(compression), "compression");
236219354Spjd	if (nv_error(nv) != 0) {
237219354Spjd		free(newbuf);
238219354Spjd		errno = nv_error(nv);
239219354Spjd		return (-1);
240219354Spjd	}
241219354Spjd	if (*freedatap)
242219354Spjd		free(*datap);
243219354Spjd	*freedatap = true;
244219354Spjd	*datap = newbuf;
245219354Spjd	*sizep = size;
246219354Spjd
247219354Spjd	return (0);
248219354Spjd}
249219354Spjd
250219354Spjdint
251219354Spjdcompression_recv(const struct hast_resource *res __unused, struct nv *nv,
252219354Spjd    void **datap, size_t *sizep, bool *freedatap)
253219354Spjd{
254219354Spjd	unsigned char *newbuf;
255219354Spjd	const char *algo;
256219354Spjd	size_t size;
257219354Spjd
258219354Spjd	algo = nv_get_string(nv, "compression");
259219354Spjd	if (algo == NULL)
260219354Spjd		return (0);	/* No compression. */
261219354Spjd
262219354Spjd	newbuf = NULL;
263219354Spjd	size = *sizep;
264219354Spjd
265219354Spjd	if (strcmp(algo, "hole") == 0)
266219354Spjd		newbuf = hast_hole_decompress(*datap, &size);
267219354Spjd	else if (strcmp(algo, "lzf") == 0)
268219354Spjd		newbuf = hast_lzf_decompress(*datap, &size);
269219354Spjd	else {
270219354Spjd		pjdlog_error("Unknown compression algorithm '%s'.", algo);
271219354Spjd		return (-1);	/* Unknown compression algorithm. */
272219354Spjd	}
273219354Spjd
274219354Spjd	if (newbuf == NULL)
275219354Spjd		return (-1);
276219354Spjd	if (*freedatap)
277219354Spjd		free(*datap);
278219354Spjd	*freedatap = true;
279219354Spjd	*datap = newbuf;
280219354Spjd	*sizep = size;
281219354Spjd
282219354Spjd	return (0);
283219354Spjd}
284