mirror of
https://gitlab.uni-freiburg.de/opensourcevdi/spice-common
synced 2026-01-09 22:25:28 +00:00
The "more_space" callback is used in both decode and encode path to provide either more space to read or more space to write. The current implementation was dealing only to provide more space to write (that is encoding) crashing in case it was used for decoding. Signed-off-by: Frediano Ziglio <freddy77@gmail.com> Acked-by: Uri Lublin <uril@redhat.com>
426 lines
13 KiB
C
426 lines
13 KiB
C
/*
|
|
Copyright (C) 2017 Red Hat, Inc.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <config.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <glib.h>
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
|
|
#include "common/quic.h"
|
|
|
|
typedef enum {
|
|
COLOR_MODE_RGB,
|
|
COLOR_MODE_RGB16,
|
|
COLOR_MODE_GRAY,
|
|
|
|
COLOR_MODE_END
|
|
} color_mode_t;
|
|
|
|
static color_mode_t color_mode = COLOR_MODE_RGB;
|
|
|
|
typedef struct {
|
|
QuicUsrContext usr;
|
|
GByteArray *dest;
|
|
} QuicData;
|
|
|
|
static SPICE_GNUC_NORETURN SPICE_GNUC_PRINTF(2, 3) void
|
|
quic_usr_error(QuicUsrContext *usr, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, fmt, ap);
|
|
va_end(ap);
|
|
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
static SPICE_GNUC_PRINTF(2, 3) void
|
|
quic_usr_warn(QuicUsrContext *usr, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void *quic_usr_malloc(QuicUsrContext *usr, int size)
|
|
{
|
|
return g_malloc(size);
|
|
}
|
|
|
|
|
|
static void quic_usr_free(QuicUsrContext *usr, void *ptr)
|
|
{
|
|
g_free(ptr);
|
|
}
|
|
|
|
static int quic_usr_more_space_encode(QuicUsrContext *usr, uint32_t **io_ptr, int rows_completed)
|
|
{
|
|
QuicData *quic_data = (QuicData *)usr;
|
|
int initial_len = quic_data->dest->len;
|
|
|
|
g_byte_array_set_size(quic_data->dest, quic_data->dest->len*2);
|
|
|
|
*io_ptr = (uint32_t *)(quic_data->dest->data + initial_len);
|
|
return (quic_data->dest->len - initial_len)/4;
|
|
}
|
|
|
|
static int quic_usr_more_space_decode(QuicUsrContext *usr, uint32_t **io_ptr, int rows_completed)
|
|
{
|
|
// currently all data are passed at initialization, decoder are not expected to
|
|
// read more data, beside during fuzzing, in this case quic_usr_error will
|
|
// be called
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int quic_usr_more_lines(QuicUsrContext *usr, uint8_t **lines)
|
|
{
|
|
g_return_val_if_reached(0);
|
|
}
|
|
|
|
|
|
static void init_quic_data(QuicData *quic_data, bool encode)
|
|
{
|
|
quic_data->usr.error = quic_usr_error;
|
|
quic_data->usr.warn = quic_usr_warn;
|
|
quic_data->usr.info = quic_usr_warn;
|
|
quic_data->usr.malloc = quic_usr_malloc;
|
|
quic_data->usr.free = quic_usr_free;
|
|
quic_data->usr.more_space = encode ? quic_usr_more_space_encode : quic_usr_more_space_decode;
|
|
quic_data->usr.more_lines = quic_usr_more_lines;
|
|
quic_data->dest = g_byte_array_new();
|
|
}
|
|
|
|
// RGB luminosity (sum 256) 54, 184, 18
|
|
static inline uint8_t pixel_to_gray(uint8_t r, uint8_t g, uint8_t b)
|
|
{
|
|
return (54u * r + 184u * g + 18u * b) / 256u;
|
|
}
|
|
|
|
static inline void gray_to_pixel(uint8_t gray, uint8_t *p)
|
|
{
|
|
p[0] = p[1] = p[2] = gray;
|
|
}
|
|
|
|
static inline uint16_t pixel_to_rgb16(uint8_t r, uint8_t g, uint8_t b)
|
|
{
|
|
r = (r >> 3) & 0x1Fu;
|
|
g = (g >> 3) & 0x1Fu;
|
|
b = (b >> 3) & 0x1Fu;
|
|
return r * (32u*32u) + g * 32u + b;
|
|
}
|
|
|
|
static inline void rgb16_to_pixel(uint16_t color, uint8_t *p)
|
|
{
|
|
uint8_t comp;
|
|
comp = (color >> 10) & 0x1Fu;
|
|
*p++ = (comp << 3) | (comp >> 2);
|
|
color <<= 5;
|
|
comp = (color >> 10) & 0x1Fu;
|
|
*p++ = (comp << 3) | (comp >> 2);
|
|
color <<= 5;
|
|
comp = (color >> 10) & 0x1Fu;
|
|
*p++ = (comp << 3) | (comp >> 2);
|
|
}
|
|
|
|
#define CONVERT_PROC(TYPE, FUNC) \
|
|
static void pixbuf_convert_to_##FUNC(GdkPixbuf *pixbuf, TYPE *dest_line, int dest_stride) \
|
|
{ \
|
|
int width = gdk_pixbuf_get_width(pixbuf); \
|
|
int height = gdk_pixbuf_get_height(pixbuf); \
|
|
int n_channels = gdk_pixbuf_get_n_channels (pixbuf); \
|
|
int stride = gdk_pixbuf_get_rowstride(pixbuf); \
|
|
uint8_t *line = gdk_pixbuf_get_pixels(pixbuf); \
|
|
\
|
|
for (int y = 0; y < height; y++) { \
|
|
uint8_t *p = line; \
|
|
TYPE *dest = dest_line; \
|
|
for (int x = 0; x < width; x++) { \
|
|
\
|
|
*dest = pixel_to_##FUNC(p[0], p[1], p[2]); \
|
|
FUNC##_to_pixel(*dest, p); \
|
|
++dest; \
|
|
p += n_channels; \
|
|
} \
|
|
line += stride; \
|
|
dest_line = (TYPE*)((char*) dest_line + dest_stride); \
|
|
} \
|
|
}
|
|
|
|
CONVERT_PROC(uint8_t, gray)
|
|
CONVERT_PROC(uint16_t, rgb16)
|
|
|
|
#define UNCONVERT_PROC(TYPE, FUNC) \
|
|
static void pixbuf_unconvert_to_##FUNC(GdkPixbuf *pixbuf) \
|
|
{ \
|
|
const int width = gdk_pixbuf_get_width(pixbuf); \
|
|
const int height = gdk_pixbuf_get_height(pixbuf); \
|
|
const int n_channels = gdk_pixbuf_get_n_channels(pixbuf); \
|
|
const int stride = gdk_pixbuf_get_rowstride(pixbuf); \
|
|
uint8_t *line = gdk_pixbuf_get_pixels(pixbuf) + stride*height; \
|
|
\
|
|
for (int y = 0; y < height; y++) { \
|
|
line -= stride; \
|
|
uint8_t *p = line + width*n_channels; \
|
|
const TYPE *dest = (TYPE*) line + width; \
|
|
for (int x = 0; x < width; x++) { \
|
|
--dest; \
|
|
p -= n_channels; \
|
|
FUNC##_to_pixel(*dest, p); \
|
|
if (n_channels == 4) { \
|
|
p[3] = 255; \
|
|
} \
|
|
} \
|
|
} \
|
|
g_assert(line == gdk_pixbuf_get_pixels(pixbuf)); \
|
|
}
|
|
|
|
UNCONVERT_PROC(uint8_t, gray)
|
|
UNCONVERT_PROC(uint16_t, rgb16)
|
|
|
|
typedef struct {
|
|
QuicImageType quic_type;
|
|
uint8_t *pixels;
|
|
int stride;
|
|
GdkPixbuf *pixbuf;
|
|
} ImageBuf;
|
|
|
|
static ImageBuf *image_buf_init(ImageBuf *imgbuf, GdkPixbuf *pixbuf)
|
|
{
|
|
imgbuf->pixbuf = pixbuf;
|
|
switch (gdk_pixbuf_get_n_channels(pixbuf)) {
|
|
case 3:
|
|
imgbuf->quic_type = QUIC_IMAGE_TYPE_RGB24;
|
|
break;
|
|
case 4:
|
|
imgbuf->quic_type = QUIC_IMAGE_TYPE_RGBA;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
imgbuf->pixels = gdk_pixbuf_get_pixels(pixbuf);
|
|
imgbuf->stride = gdk_pixbuf_get_rowstride(pixbuf);
|
|
|
|
if (color_mode == COLOR_MODE_GRAY) {
|
|
int stride = gdk_pixbuf_get_width(pixbuf);
|
|
uint8_t *pixels = g_malloc(stride * gdk_pixbuf_get_height(pixbuf));
|
|
pixbuf_convert_to_gray(pixbuf, pixels, stride);
|
|
imgbuf->stride = stride;
|
|
imgbuf->pixels = pixels;
|
|
imgbuf->quic_type = QUIC_IMAGE_TYPE_GRAY;
|
|
} else if (color_mode == COLOR_MODE_RGB16) {
|
|
int stride = gdk_pixbuf_get_width(pixbuf)*2;
|
|
uint16_t *pixels = g_malloc(stride * gdk_pixbuf_get_height(pixbuf));
|
|
pixbuf_convert_to_rgb16(pixbuf, pixels, stride);
|
|
imgbuf->stride = stride;
|
|
imgbuf->pixels = (uint8_t*)pixels;
|
|
imgbuf->quic_type = QUIC_IMAGE_TYPE_RGB16;
|
|
}
|
|
|
|
return imgbuf;
|
|
}
|
|
|
|
static void image_buf_free(ImageBuf *imgbuf, GdkPixbuf *pixbuf)
|
|
{
|
|
if (imgbuf->quic_type == QUIC_IMAGE_TYPE_GRAY) {
|
|
pixbuf_unconvert_to_gray(pixbuf);
|
|
}
|
|
|
|
if (imgbuf->quic_type == QUIC_IMAGE_TYPE_RGB16) {
|
|
pixbuf_unconvert_to_rgb16(pixbuf);
|
|
}
|
|
|
|
if (imgbuf->pixels != gdk_pixbuf_get_pixels(imgbuf->pixbuf)) {
|
|
g_free(imgbuf->pixels);
|
|
}
|
|
}
|
|
|
|
static GByteArray *quic_encode_from_pixbuf(GdkPixbuf *pixbuf, const ImageBuf *imgbuf)
|
|
{
|
|
QuicData quic_data;
|
|
QuicContext *quic;
|
|
int encoded_size;
|
|
|
|
init_quic_data(&quic_data, true);
|
|
g_byte_array_set_size(quic_data.dest, 1024);
|
|
|
|
quic = quic_create(&quic_data.usr);
|
|
g_assert(quic != NULL);
|
|
encoded_size = quic_encode(quic, imgbuf->quic_type,
|
|
gdk_pixbuf_get_width(pixbuf),
|
|
gdk_pixbuf_get_height(pixbuf),
|
|
imgbuf->pixels,
|
|
gdk_pixbuf_get_height(pixbuf),
|
|
imgbuf->stride,
|
|
(uint32_t *)quic_data.dest->data,
|
|
quic_data.dest->len/sizeof(uint32_t));
|
|
g_assert(encoded_size > 0);
|
|
encoded_size *= 4;
|
|
g_byte_array_set_size(quic_data.dest, encoded_size);
|
|
quic_destroy(quic);
|
|
|
|
return quic_data.dest;
|
|
}
|
|
|
|
static GdkPixbuf *quic_decode_to_pixbuf(GByteArray *compressed_data)
|
|
{
|
|
QuicData quic_data;
|
|
QuicContext *quic;
|
|
GdkPixbuf *pixbuf;
|
|
QuicImageType type;
|
|
int width;
|
|
int height;
|
|
int status;
|
|
|
|
init_quic_data(&quic_data, false);
|
|
g_byte_array_free(quic_data.dest, TRUE);
|
|
quic_data.dest = NULL;
|
|
|
|
quic = quic_create(&quic_data.usr);
|
|
g_assert(quic != NULL);
|
|
|
|
status = quic_decode_begin(quic,
|
|
(uint32_t *)compressed_data->data, compressed_data->len/4,
|
|
&type, &width, &height);
|
|
g_assert(status == QUIC_OK);
|
|
|
|
pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB,
|
|
(type == QUIC_IMAGE_TYPE_RGBA), 8,
|
|
width, height);
|
|
status = quic_decode(quic, type,
|
|
gdk_pixbuf_get_pixels(pixbuf),
|
|
gdk_pixbuf_get_rowstride(pixbuf));
|
|
g_assert(status == QUIC_OK);
|
|
quic_destroy(quic);
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
static void pixbuf_compare(GdkPixbuf *pixbuf_a, GdkPixbuf *pixbuf_b)
|
|
{
|
|
int width = gdk_pixbuf_get_width(pixbuf_a);
|
|
int height = gdk_pixbuf_get_height(pixbuf_a);
|
|
int n_channels_a = gdk_pixbuf_get_n_channels(pixbuf_a);
|
|
int n_channels_b = gdk_pixbuf_get_n_channels(pixbuf_b);
|
|
int x;
|
|
int y;
|
|
guint8 *pixels_a = gdk_pixbuf_get_pixels(pixbuf_a);
|
|
guint8 *pixels_b = gdk_pixbuf_get_pixels(pixbuf_b);
|
|
bool check_alpha = gdk_pixbuf_get_has_alpha(pixbuf_a);
|
|
|
|
g_assert(width == gdk_pixbuf_get_width(pixbuf_b));
|
|
g_assert(height == gdk_pixbuf_get_height(pixbuf_b));
|
|
if (color_mode != COLOR_MODE_RGB) {
|
|
check_alpha = false;
|
|
} else {
|
|
g_assert(n_channels_a == n_channels_b);
|
|
g_assert(gdk_pixbuf_get_byte_length(pixbuf_a) == gdk_pixbuf_get_byte_length(pixbuf_b));
|
|
}
|
|
for (y = 0; y < height; y++) {
|
|
for (x = 0; x < width; x++) {
|
|
guint8 *p_a = pixels_a + y*gdk_pixbuf_get_rowstride(pixbuf_a) + x*n_channels_a;
|
|
guint8 *p_b = pixels_b + y*gdk_pixbuf_get_rowstride(pixbuf_b) + x*n_channels_b;
|
|
|
|
g_assert(p_a[0] == p_b[0]);
|
|
g_assert(p_a[1] == p_b[1]);
|
|
g_assert(p_a[2] == p_b[2]);
|
|
if (check_alpha) {
|
|
g_assert(p_a[3] == p_b[3]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static GdkPixbuf *pixbuf_new_random(int alpha, bool fixed)
|
|
{
|
|
gboolean has_alpha = alpha >= 0 ? alpha : g_random_boolean();
|
|
gint width = g_random_int_range(100, 2000);
|
|
gint height = g_random_int_range(100, 500);
|
|
GdkPixbuf *random_pixbuf;
|
|
guint i, size;
|
|
guint8 *pixels;
|
|
|
|
random_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8, width, height);
|
|
pixels = gdk_pixbuf_get_pixels(random_pixbuf);
|
|
size = gdk_pixbuf_get_byte_length(random_pixbuf);
|
|
for (i = 0; i < size; i++) {
|
|
pixels[i] = fixed ? (gint)((i%(has_alpha?4:3))) << 5 : g_random_int_range(0, 256);
|
|
}
|
|
|
|
return random_pixbuf;
|
|
}
|
|
|
|
static void test_pixbuf(GdkPixbuf *pixbuf)
|
|
{
|
|
GdkPixbuf *uncompressed_pixbuf;
|
|
GByteArray *compressed_data;
|
|
g_assert(pixbuf != NULL);
|
|
g_assert(gdk_pixbuf_get_colorspace(pixbuf) == GDK_COLORSPACE_RGB);
|
|
g_assert(gdk_pixbuf_get_bits_per_sample(pixbuf) == 8);
|
|
|
|
ImageBuf imgbuf[1];
|
|
image_buf_init(imgbuf, pixbuf);
|
|
compressed_data = quic_encode_from_pixbuf(pixbuf, imgbuf);
|
|
|
|
uncompressed_pixbuf = quic_decode_to_pixbuf(compressed_data);
|
|
image_buf_free(imgbuf, uncompressed_pixbuf);
|
|
|
|
//g_assert(memcmp(gdk_pixbuf_get_pixels(pixbuf), gdk_pixbuf_get_pixels(uncompressed_pixbuf), gdk_pixbuf_get_byte_length(uncompressed_pixbuf)));
|
|
pixbuf_compare(pixbuf, uncompressed_pixbuf);
|
|
|
|
g_byte_array_free(compressed_data, TRUE);
|
|
g_object_unref(uncompressed_pixbuf);
|
|
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
if (argc >= 2) {
|
|
for (int i = 1; i < argc; ++i) {
|
|
GdkPixbuf *source_pixbuf;
|
|
|
|
source_pixbuf = gdk_pixbuf_new_from_file(argv[i], NULL);
|
|
test_pixbuf(source_pixbuf);
|
|
g_object_unref(source_pixbuf);
|
|
}
|
|
} else if (argc == 1) {
|
|
int test;
|
|
for (test = 0; test < 4; test++) {
|
|
int alpha = test % 2;
|
|
bool fixed = (test / 2) % 2;
|
|
|
|
for (color_mode = COLOR_MODE_RGB; color_mode < COLOR_MODE_END; color_mode++) {
|
|
/* alpha affects only COLOR_MODE_RGB more, reduce number of tests */
|
|
if (color_mode != COLOR_MODE_RGB && alpha) {
|
|
continue;
|
|
}
|
|
GdkPixbuf *pixbuf = pixbuf_new_random(alpha, fixed);
|
|
test_pixbuf(pixbuf);
|
|
g_object_unref(pixbuf);
|
|
}
|
|
}
|
|
} else {
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
return 0;
|
|
}
|