[FL-3152] Screen streaming improvements (#2498)

* Rpc: reserve some bandwidth when screen streaming
* Move furi_hal_compress to toolbox/comporess
* Lib: heatshrink as external submodule, compile warnings fixes, better buffer management
* Lib: cleanup compressor definitions
* Rpc: add canvas orientation support
* Format Sources
This commit is contained in:
あく 2023-03-23 02:00:48 +09:00 committed by GitHub
parent 8b224ecb15
commit 7bf0a4786c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 242 additions and 1457 deletions

3
.gitmodules vendored
View File

@ -31,3 +31,6 @@
[submodule "applications/external/dap_link/lib/free-dap"]
path = applications/external/dap_link/lib/free-dap
url = https://github.com/ataradov/free-dap.git
[submodule "lib/heatshrink"]
path = lib/heatshrink
url = https://github.com/flipperdevices/heatshrink.git

View File

@ -17,6 +17,7 @@ const CanvasFontParameters canvas_font_params[FontTotalNumber] = {
Canvas* canvas_init() {
Canvas* canvas = malloc(sizeof(Canvas));
canvas->compress_icon = compress_icon_alloc();
// Setup u8g2
u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
@ -35,6 +36,7 @@ Canvas* canvas_init() {
void canvas_free(Canvas* canvas) {
furi_assert(canvas);
compress_icon_free(canvas->compress_icon);
free(canvas);
}
@ -218,7 +220,7 @@ void canvas_draw_bitmap(
x += canvas->offset_x;
y += canvas->offset_y;
uint8_t* bitmap_data = NULL;
furi_hal_compress_icon_decode(compressed_bitmap_data, &bitmap_data);
compress_icon_decode(canvas->compress_icon, compressed_bitmap_data, &bitmap_data);
u8g2_DrawXBM(&canvas->fb, x, y, width, height, bitmap_data);
}
@ -233,7 +235,8 @@ void canvas_draw_icon_animation(
x += canvas->offset_x;
y += canvas->offset_y;
uint8_t* icon_data = NULL;
furi_hal_compress_icon_decode(icon_animation_get_data(icon_animation), &icon_data);
compress_icon_decode(
canvas->compress_icon, icon_animation_get_data(icon_animation), &icon_data);
u8g2_DrawXBM(
&canvas->fb,
x,
@ -250,7 +253,7 @@ void canvas_draw_icon(Canvas* canvas, uint8_t x, uint8_t y, const Icon* icon) {
x += canvas->offset_x;
y += canvas->offset_y;
uint8_t* icon_data = NULL;
furi_hal_compress_icon_decode(icon_get_data(icon), &icon_data);
compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data);
u8g2_DrawXBM(&canvas->fb, x, y, icon_get_width(icon), icon_get_height(icon), icon_data);
}

View File

@ -7,6 +7,7 @@
#include "canvas.h"
#include <u8g2.h>
#include <toolbox/compress.h>
/** Canvas structure
*/
@ -17,6 +18,7 @@ struct Canvas {
uint8_t offset_y;
uint8_t width;
uint8_t height;
CompressIcon* compress_icon;
};
/** Allocate memory and initialize canvas

View File

@ -250,6 +250,7 @@ static void gui_redraw(Gui* gui) {
p->callback(
canvas_get_buffer(gui->canvas),
canvas_get_buffer_size(gui->canvas),
canvas_get_orientation(gui->canvas),
p->context);
}
} while(false);

View File

@ -27,7 +27,11 @@ typedef enum {
} GuiLayer;
/** Gui Canvas Commit Callback */
typedef void (*GuiCanvasCommitCallback)(uint8_t* data, size_t size, void* context);
typedef void (*GuiCanvasCommitCallback)(
uint8_t* data,
size_t size,
CanvasOrientation orientation,
void* context);
#define RECORD_GUI "gui"

View File

@ -33,8 +33,18 @@ typedef struct {
uint32_t input_counter;
} RpcGuiSystem;
static void
rpc_system_gui_screen_stream_frame_callback(uint8_t* data, size_t size, void* context) {
static const PB_Gui_ScreenOrientation rpc_system_gui_screen_orientation_map[] = {
[CanvasOrientationHorizontal] = PB_Gui_ScreenOrientation_HORIZONTAL,
[CanvasOrientationHorizontalFlip] = PB_Gui_ScreenOrientation_HORIZONTAL_FLIP,
[CanvasOrientationVertical] = PB_Gui_ScreenOrientation_VERTICAL,
[CanvasOrientationVerticalFlip] = PB_Gui_ScreenOrientation_VERTICAL_FLIP,
};
static void rpc_system_gui_screen_stream_frame_callback(
uint8_t* data,
size_t size,
CanvasOrientation orientation,
void* context) {
furi_assert(data);
furi_assert(context);
@ -44,6 +54,8 @@ static void
furi_assert(size == rpc_gui->transmit_frame->content.gui_screen_frame.data->size);
memcpy(buffer, data, size);
rpc_gui->transmit_frame->content.gui_screen_frame.orientation =
rpc_system_gui_screen_orientation_map[orientation];
furi_thread_flags_set(furi_thread_get_id(rpc_gui->transmit_thread), RpcGuiWorkerFlagTransmit);
}
@ -53,12 +65,22 @@ static int32_t rpc_system_gui_screen_stream_frame_transmit_thread(void* context)
RpcGuiSystem* rpc_gui = (RpcGuiSystem*)context;
uint32_t transmit_time = 0;
while(true) {
uint32_t flags =
furi_thread_flags_wait(RpcGuiWorkerFlagAny, FuriFlagWaitAny, FuriWaitForever);
if(flags & RpcGuiWorkerFlagTransmit) {
transmit_time = furi_get_tick();
rpc_send(rpc_gui->session, rpc_gui->transmit_frame);
transmit_time = furi_get_tick() - transmit_time;
// Guaranteed bandwidth reserve
uint32_t extra_delay = transmit_time / 20;
if(extra_delay > 500) extra_delay = 500;
if(extra_delay) furi_delay_tick(extra_delay);
}
if(flags & RpcGuiWorkerFlagExit) {
break;
}

@ -1 +1 @@
Subproject commit 6460660237005d02d5c223835659b40e373bade9
Subproject commit 1f6b4a08c5d05c2b17926a3ba79f60109638932f

View File

@ -57,7 +57,6 @@ Header,+,firmware/targets/furi_hal_include/furi_hal.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_compress.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,,
@ -876,12 +875,12 @@ Function,-,furi_hal_clock_resume_tick,void,
Function,-,furi_hal_clock_suspend_tick,void,
Function,-,furi_hal_clock_switch_to_hsi,void,
Function,-,furi_hal_clock_switch_to_pll,void,
Function,-,furi_hal_compress_alloc,FuriHalCompress*,uint16_t
Function,-,furi_hal_compress_decode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
Function,-,furi_hal_compress_encode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
Function,-,furi_hal_compress_free,void,FuriHalCompress*
Function,-,furi_hal_compress_icon_decode,void,"const uint8_t*, uint8_t**"
Function,-,furi_hal_compress_icon_init,void,
Function,-,compress_alloc,Compress*,uint16_t
Function,-,compress_decode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
Function,-,compress_encode,_Bool,"Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
Function,-,compress_free,void,Compress*
Function,-,compress_icon_decode,void,"const uint8_t*, uint8_t**"
Function,-,compress_icon_init,void,
Function,+,furi_hal_console_disable,void,
Function,+,furi_hal_console_enable,void,
Function,+,furi_hal_console_init,void,

1 entry status name type params
57 Header + firmware/targets/furi_hal_include/furi_hal_bt.h
58 Header + firmware/targets/furi_hal_include/furi_hal_bt_hid.h
59 Header + firmware/targets/furi_hal_include/furi_hal_bt_serial.h
Header + firmware/targets/furi_hal_include/furi_hal_compress.h
60 Header + firmware/targets/furi_hal_include/furi_hal_cortex.h
61 Header + firmware/targets/furi_hal_include/furi_hal_crypto.h
62 Header + firmware/targets/furi_hal_include/furi_hal_debug.h
875 Function - furi_hal_clock_suspend_tick void
876 Function - furi_hal_clock_switch_to_hsi void
877 Function - furi_hal_clock_switch_to_pll void
878 Function - furi_hal_compress_alloc compress_alloc FuriHalCompress* Compress* uint16_t
879 Function - furi_hal_compress_decode compress_decode _Bool FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t* Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*
880 Function - furi_hal_compress_encode compress_encode _Bool FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t* Compress*, uint8_t*, size_t, uint8_t*, size_t, size_t*
881 Function - furi_hal_compress_free compress_free void FuriHalCompress* Compress*
882 Function - furi_hal_compress_icon_decode compress_icon_decode void const uint8_t*, uint8_t**
883 Function - furi_hal_compress_icon_init compress_icon_init void
884 Function + furi_hal_console_disable void
885 Function + furi_hal_console_enable void
886 Function + furi_hal_console_init void

View File

@ -1,5 +1,6 @@
#include <furi_hal.h>
#include <furi_hal_mpu.h>
#include <furi_hal_memory.h>
#include <stm32wbxx_ll_cortex.h>
@ -7,29 +8,20 @@
void furi_hal_init_early() {
furi_hal_cortex_init_early();
furi_hal_clock_init_early();
furi_hal_resources_init_early();
furi_hal_os_init();
furi_hal_spi_config_init_early();
furi_hal_i2c_init_early();
furi_hal_light_init();
furi_hal_rtc_init_early();
}
void furi_hal_deinit_early() {
furi_hal_rtc_deinit_early();
furi_hal_i2c_deinit_early();
furi_hal_spi_config_deinit_early();
furi_hal_resources_deinit_early();
furi_hal_clock_deinit_early();
}
@ -38,40 +30,24 @@ void furi_hal_init() {
furi_hal_clock_init();
furi_hal_console_init();
furi_hal_rtc_init();
furi_hal_interrupt_init();
furi_hal_flash_init();
furi_hal_resources_init();
FURI_LOG_I(TAG, "GPIO OK");
furi_hal_version_init();
furi_hal_spi_config_init();
furi_hal_spi_dma_init();
furi_hal_speaker_init();
FURI_LOG_I(TAG, "Speaker OK");
furi_hal_crypto_init();
// USB
#ifndef FURI_RAM_EXEC
furi_hal_usb_init();
FURI_LOG_I(TAG, "USB OK");
#endif
furi_hal_i2c_init();
// High Level
furi_hal_power_init();
furi_hal_light_init();
furi_hal_bt_init();
furi_hal_memory_init();
#ifndef FURI_RAM_EXEC
furi_hal_usb_init();
furi_hal_vibro_init();
#endif
furi_hal_bt_init();
furi_hal_compress_icon_init();
}
void furi_hal_switch(void* address) {

View File

@ -4,6 +4,8 @@
#include <stm32wbxx_ll_rcc.h>
#include <stm32wbxx_ll_pwr.h>
#define TAG "FuriHalResources"
const GpioPin vibro_gpio = {.port = GPIOA, .pin = LL_GPIO_PIN_8};
const GpioPin ibutton_gpio = {.port = GPIOB, .pin = LL_GPIO_PIN_14};
@ -198,6 +200,8 @@ void furi_hal_resources_init() {
NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
NVIC_EnableIRQ(EXTI15_10_IRQn);
FURI_LOG_I(TAG, "Init OK");
}
int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) {

View File

@ -62,7 +62,6 @@ Header,+,firmware/targets/furi_hal_include/furi_hal.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_bt.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_bt_hid.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_bt_serial.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_compress.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_cortex.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_crypto.h,,
Header,+,firmware/targets/furi_hal_include/furi_hal_debug.h,,
@ -1057,12 +1056,6 @@ Function,-,furi_hal_clock_resume_tick,void,
Function,-,furi_hal_clock_suspend_tick,void,
Function,-,furi_hal_clock_switch_to_hsi,void,
Function,-,furi_hal_clock_switch_to_pll,void,
Function,-,furi_hal_compress_alloc,FuriHalCompress*,uint16_t
Function,-,furi_hal_compress_decode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
Function,-,furi_hal_compress_encode,_Bool,"FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*"
Function,-,furi_hal_compress_free,void,FuriHalCompress*
Function,-,furi_hal_compress_icon_decode,void,"const uint8_t*, uint8_t**"
Function,-,furi_hal_compress_icon_init,void,
Function,+,furi_hal_console_disable,void,
Function,+,furi_hal_console_enable,void,
Function,+,furi_hal_console_init,void,

1 entry status name type params
62 Header + firmware/targets/furi_hal_include/furi_hal_bt.h
63 Header + firmware/targets/furi_hal_include/furi_hal_bt_hid.h
64 Header + firmware/targets/furi_hal_include/furi_hal_bt_serial.h
Header + firmware/targets/furi_hal_include/furi_hal_compress.h
65 Header + firmware/targets/furi_hal_include/furi_hal_cortex.h
66 Header + firmware/targets/furi_hal_include/furi_hal_crypto.h
67 Header + firmware/targets/furi_hal_include/furi_hal_debug.h
1056 Function - furi_hal_clock_suspend_tick void
1057 Function - furi_hal_clock_switch_to_hsi void
1058 Function - furi_hal_clock_switch_to_pll void
Function - furi_hal_compress_alloc FuriHalCompress* uint16_t
Function - furi_hal_compress_decode _Bool FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*
Function - furi_hal_compress_encode _Bool FuriHalCompress*, uint8_t*, size_t, uint8_t*, size_t, size_t*
Function - furi_hal_compress_free void FuriHalCompress*
Function - furi_hal_compress_icon_decode void const uint8_t*, uint8_t**
Function - furi_hal_compress_icon_init void
1059 Function + furi_hal_console_disable void
1060 Function + furi_hal_console_enable void
1061 Function + furi_hal_console_init void

View File

@ -8,29 +8,20 @@
void furi_hal_init_early() {
furi_hal_cortex_init_early();
furi_hal_clock_init_early();
furi_hal_resources_init_early();
furi_hal_os_init();
furi_hal_spi_config_init_early();
furi_hal_i2c_init_early();
furi_hal_light_init();
furi_hal_rtc_init_early();
}
void furi_hal_deinit_early() {
furi_hal_rtc_deinit_early();
furi_hal_i2c_deinit_early();
furi_hal_spi_config_deinit_early();
furi_hal_resources_deinit_early();
furi_hal_clock_deinit_early();
}
@ -39,41 +30,24 @@ void furi_hal_init() {
furi_hal_clock_init();
furi_hal_console_init();
furi_hal_rtc_init();
furi_hal_interrupt_init();
furi_hal_flash_init();
furi_hal_resources_init();
FURI_LOG_I(TAG, "GPIO OK");
furi_hal_version_init();
furi_hal_region_init();
furi_hal_spi_config_init();
furi_hal_spi_dma_init();
furi_hal_ibutton_init();
FURI_LOG_I(TAG, "iButton OK");
furi_hal_speaker_init();
FURI_LOG_I(TAG, "Speaker OK");
furi_hal_crypto_init();
furi_hal_i2c_init();
// High Level
furi_hal_power_init();
furi_hal_light_init();
furi_hal_bt_init();
furi_hal_memory_init();
furi_hal_compress_icon_init();
#ifndef FURI_RAM_EXEC
// USB
furi_hal_usb_init();
FURI_LOG_I(TAG, "USB OK");
furi_hal_vibro_init();
furi_hal_subghz_init();
furi_hal_nfc_init();

View File

@ -7,6 +7,7 @@
#include <furi.h>
#define TAG "FuriHalIbutton"
#define FURI_HAL_IBUTTON_TIMER TIM1
#define FURI_HAL_IBUTTON_TIMER_IRQ FuriHalInterruptIdTim1UpTim16
@ -33,6 +34,8 @@ static void furi_hal_ibutton_emulate_isr() {
void furi_hal_ibutton_init() {
furi_hal_ibutton = malloc(sizeof(FuriHalIbutton));
furi_hal_ibutton->state = FuriHalIbuttonStateIdle;
FURI_LOG_I(TAG, "Init OK");
}
void furi_hal_ibutton_emulate_start(

View File

@ -4,6 +4,8 @@
#include <stm32wbxx_ll_rcc.h>
#include <stm32wbxx_ll_pwr.h>
#define TAG "FuriHalResources"
const GpioPin vibro_gpio = {.port = VIBRO_GPIO_Port, .pin = VIBRO_Pin};
const GpioPin ibutton_gpio = {.port = iBTN_GPIO_Port, .pin = iBTN_Pin};
@ -190,6 +192,8 @@ void furi_hal_resources_init() {
NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
NVIC_EnableIRQ(EXTI15_10_IRQn);
FURI_LOG_I(TAG, "Init OK");
}
int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) {

View File

@ -4,10 +4,11 @@
#include <alt_boot.h>
#include <u8g2_glue.h>
#include <assets_icons.h>
#include <toolbox/compress.h>
void flipper_boot_dfu_show_splash() {
// Initialize
furi_hal_compress_icon_init();
CompressIcon* compress_icon = compress_icon_alloc();
u8g2_t* fb = malloc(sizeof(u8g2_t));
memset(fb, 0, sizeof(u8g2_t));
@ -15,13 +16,15 @@ void flipper_boot_dfu_show_splash() {
u8g2_InitDisplay(fb);
u8g2_SetDrawColor(fb, 0x01);
uint8_t* splash_data = NULL;
furi_hal_compress_icon_decode(icon_get_data(&I_DFU_128x50), &splash_data);
compress_icon_decode(compress_icon, icon_get_data(&I_DFU_128x50), &splash_data);
u8g2_DrawXBM(fb, 0, 64 - 50, 128, 50, splash_data);
u8g2_SetFont(fb, u8g2_font_helvB08_tr);
u8g2_DrawStr(fb, 2, 8, "Update & Recovery Mode");
u8g2_DrawStr(fb, 2, 21, "DFU Started");
u8g2_SetPowerSave(fb, 0);
u8g2_SendBuffer(fb);
compress_icon_free(compress_icon);
}
void flipper_boot_dfu_exec() {

View File

@ -4,6 +4,7 @@
#include <alt_boot.h>
#include <u8g2_glue.h>
#include <assets_icons.h>
#include <toolbox/compress.h>
#define COUNTER_VALUE (136U)
@ -27,9 +28,9 @@ void flipper_boot_recovery_exec() {
u8g2_Setup_st756x_flipper(fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
u8g2_InitDisplay(fb);
furi_hal_compress_icon_init();
CompressIcon* compress_icon = compress_icon_alloc();
uint8_t* splash_data = NULL;
furi_hal_compress_icon_decode(icon_get_data(&I_Erase_pin_128x64), &splash_data);
compress_icon_decode(compress_icon, icon_get_data(&I_Erase_pin_128x64), &splash_data);
u8g2_ClearBuffer(fb);
u8g2_SetDrawColor(fb, 0x01);
@ -38,6 +39,7 @@ void flipper_boot_recovery_exec() {
u8g2_DrawXBM(fb, 0, 0, 128, 64, splash_data);
u8g2_SendBuffer(fb);
u8g2_SetPowerSave(fb, 0);
compress_icon_free(compress_icon);
size_t counter = COUNTER_VALUE;
while(counter) {

View File

@ -33,7 +33,6 @@ struct STOP_EXTERNING_ME {};
#include <furi_hal_vibro.h>
#include <furi_hal_usb.h>
#include <furi_hal_usb_hid.h>
#include <furi_hal_compress.h>
#include <furi_hal_uart.h>
#include <furi_hal_info.h>
#include <furi_hal_random.h>

View File

@ -1,87 +0,0 @@
/**
* @file furi_hal_compress.h
* LZSS based compression HAL API
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Defines encoder and decoder window size */
#define FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG (8)
/** Defines encoder and decoder lookahead buffer size */
#define FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG (4)
/** FuriHalCompress control structure */
typedef struct FuriHalCompress FuriHalCompress;
/** Initialize icon decoder
*/
void furi_hal_compress_icon_init();
/** Icon decoder
*
* @param icon_data pointer to icon data
* @param decoded_buff pointer to decoded buffer
*/
void furi_hal_compress_icon_decode(const uint8_t* icon_data, uint8_t** decoded_buff);
/** Allocate encoder and decoder
*
* @param compress_buff_size size of decoder and encoder buffer to allocate
*
* @return FuriHalCompress instance
*/
FuriHalCompress* furi_hal_compress_alloc(uint16_t compress_buff_size);
/** Free encoder and decoder
*
* @param compress FuriHalCompress instance
*/
void furi_hal_compress_free(FuriHalCompress* compress);
/** Encode data
*
* @param compress FuriHalCompress instance
* @param data_in pointer to input data
* @param data_in_size size of input data
* @param data_out maximum size of output data
* @param data_res_size pointer to result output data size
*
* @return true on success
*/
bool furi_hal_compress_encode(
FuriHalCompress* compress,
uint8_t* data_in,
size_t data_in_size,
uint8_t* data_out,
size_t data_out_size,
size_t* data_res_size);
/** Decode data
*
* @param compress FuriHalCompress instance
* @param data_in pointer to input data
* @param data_in_size size of input data
* @param data_out maximum size of output data
* @param data_res_size pointer to result output data size
*
* @return true on success
*/
bool furi_hal_compress_decode(
FuriHalCompress* compress,
uint8_t* data_in,
size_t data_in_size,
uint8_t* data_out,
size_t data_out_size,
size_t* data_res_size);
#ifdef __cplusplus
}
#endif

4
lib/err.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include <furi.h>
#define err(...) FURI_LOG_E("Heatshrink", "Error: %d-%s", __VA_ARGS__)

1
lib/heatshrink Submodule

@ -0,0 +1 @@
Subproject commit 7398ccc91652a33483245200cfa1a83b073bc206

View File

@ -1,20 +0,0 @@
#ifndef HEATSHRINK_H
#define HEATSHRINK_H
#define HEATSHRINK_AUTHOR "Scott Vokes <vokes.s@gmail.com>"
#define HEATSHRINK_URL "https://github.com/atomicobject/heatshrink"
/* Version 0.4.1 */
#define HEATSHRINK_VERSION_MAJOR 0
#define HEATSHRINK_VERSION_MINOR 4
#define HEATSHRINK_VERSION_PATCH 1
#define HEATSHRINK_MIN_WINDOW_BITS 4
#define HEATSHRINK_MAX_WINDOW_BITS 15
#define HEATSHRINK_MIN_LOOKAHEAD_BITS 3
#define HEATSHRINK_LITERAL_MARKER 0x01
#define HEATSHRINK_BACKREF_MARKER 0x00
#endif

View File

@ -1,28 +0,0 @@
#ifndef HEATSHRINK_CONFIG_H
#define HEATSHRINK_CONFIG_H
#include <furi.h>
/* Should functionality assuming dynamic allocation be used? */
#ifndef HEATSHRINK_DYNAMIC_ALLOC
#define HEATSHRINK_DYNAMIC_ALLOC 1
#endif
#if HEATSHRINK_DYNAMIC_ALLOC
/* Optional replacement of malloc/free */
#define HEATSHRINK_MALLOC(SZ) malloc(SZ)
#define HEATSHRINK_FREE(P, SZ) free(P)
#else
/* Required parameters for static configuration */
#define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 1024
#define HEATSHRINK_STATIC_WINDOW_BITS 8
#define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4
#endif
/* Turn on logging for debugging. */
#define HEATSHRINK_DEBUGGING_LOGS 0
/* Use indexing for faster compression. (This requires additional space.) */
#define HEATSHRINK_USE_INDEX 1
#endif

View File

@ -1,364 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include "heatshrink_decoder.h"
/* States for the polling state machine. */
typedef enum {
HSDS_TAG_BIT, /* tag bit */
HSDS_YIELD_LITERAL, /* ready to yield literal byte */
HSDS_BACKREF_INDEX_MSB, /* most significant byte of index */
HSDS_BACKREF_INDEX_LSB, /* least significant byte of index */
HSDS_BACKREF_COUNT_MSB, /* most significant byte of count */
HSDS_BACKREF_COUNT_LSB, /* least significant byte of count */
HSDS_YIELD_BACKREF, /* ready to yield back-reference */
} HSD_state;
#if HEATSHRINK_DEBUGGING_LOGS
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#define ASSERT(X) assert(X)
static const char *state_names[] = {
"tag_bit",
"yield_literal",
"backref_index_msb",
"backref_index_lsb",
"backref_count_msb",
"backref_count_lsb",
"yield_backref",
};
#else
#define LOG(...) /* no-op */
#define ASSERT(X) /* no-op */
#endif
typedef struct {
uint8_t *buf; /* output buffer */
size_t buf_size; /* buffer size */
size_t *output_size; /* bytes pushed to buffer, so far */
} output_info;
#define NO_BITS ((uint16_t)-1)
/* Forward references. */
static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count);
static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte);
#if HEATSHRINK_DYNAMIC_ALLOC
heatshrink_decoder *heatshrink_decoder_alloc(uint8_t* buffer,
uint16_t input_buffer_size,
uint8_t window_sz2,
uint8_t lookahead_sz2) {
if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) ||
(window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) ||
(input_buffer_size == 0) ||
(lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) ||
(lookahead_sz2 >= window_sz2)) {
return NULL;
}
size_t sz = sizeof(heatshrink_decoder);
heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz);
if (hsd == NULL) { return NULL; }
hsd->input_buffer_size = input_buffer_size;
hsd->window_sz2 = window_sz2;
hsd->lookahead_sz2 = lookahead_sz2;
hsd->buffers = buffer;
heatshrink_decoder_reset(hsd);
LOG("-- allocated decoder with buffer size of %zu (%zu + %u + %u)\n",
sz, sizeof(heatshrink_decoder), (1 << window_sz2), input_buffer_size);
return hsd;
}
void heatshrink_decoder_free(heatshrink_decoder *hsd) {
size_t sz = sizeof(heatshrink_decoder);
HEATSHRINK_FREE(hsd, sz);
(void)sz; /* may not be used by free */
}
#endif
void heatshrink_decoder_reset(heatshrink_decoder *hsd) {
hsd->state = HSDS_TAG_BIT;
hsd->input_size = 0;
hsd->input_index = 0;
hsd->bit_index = 0x00;
hsd->current_byte = 0x00;
hsd->output_count = 0;
hsd->output_index = 0;
hsd->head_index = 0;
}
/* Copy SIZE bytes into the decoder's input buffer, if it will fit. */
HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd,
uint8_t *in_buf, size_t size, size_t *input_size) {
if ((hsd == NULL) || (in_buf == NULL) || (input_size == NULL)) {
return HSDR_SINK_ERROR_NULL;
}
size_t rem = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd) - hsd->input_size;
if (rem == 0) {
*input_size = 0;
return HSDR_SINK_FULL;
}
size = rem < size ? rem : size;
LOG("-- sinking %zd bytes\n", size);
/* copy into input buffer (at head of buffers) */
memcpy(&hsd->buffers[hsd->input_size], in_buf, size);
hsd->input_size += size;
*input_size = size;
return HSDR_SINK_OK;
}
/*****************
* Decompression *
*****************/
#define BACKREF_COUNT_BITS(HSD) (HEATSHRINK_DECODER_LOOKAHEAD_BITS(HSD))
#define BACKREF_INDEX_BITS(HSD) (HEATSHRINK_DECODER_WINDOW_BITS(HSD))
// States
static HSD_state st_tag_bit(heatshrink_decoder *hsd);
static HSD_state st_yield_literal(heatshrink_decoder *hsd,
output_info *oi);
static HSD_state st_backref_index_msb(heatshrink_decoder *hsd);
static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd);
static HSD_state st_backref_count_msb(heatshrink_decoder *hsd);
static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd);
static HSD_state st_yield_backref(heatshrink_decoder *hsd,
output_info *oi);
HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd,
uint8_t *out_buf, size_t out_buf_size, size_t *output_size) {
if ((hsd == NULL) || (out_buf == NULL) || (output_size == NULL)) {
return HSDR_POLL_ERROR_NULL;
}
*output_size = 0;
output_info oi;
oi.buf = out_buf;
oi.buf_size = out_buf_size;
oi.output_size = output_size;
while (1) {
LOG("-- poll, state is %d (%s), input_size %d\n",
hsd->state, state_names[hsd->state], hsd->input_size);
uint8_t in_state = hsd->state;
switch (in_state) {
case HSDS_TAG_BIT:
hsd->state = st_tag_bit(hsd);
break;
case HSDS_YIELD_LITERAL:
hsd->state = st_yield_literal(hsd, &oi);
break;
case HSDS_BACKREF_INDEX_MSB:
hsd->state = st_backref_index_msb(hsd);
break;
case HSDS_BACKREF_INDEX_LSB:
hsd->state = st_backref_index_lsb(hsd);
break;
case HSDS_BACKREF_COUNT_MSB:
hsd->state = st_backref_count_msb(hsd);
break;
case HSDS_BACKREF_COUNT_LSB:
hsd->state = st_backref_count_lsb(hsd);
break;
case HSDS_YIELD_BACKREF:
hsd->state = st_yield_backref(hsd, &oi);
break;
default:
return HSDR_POLL_ERROR_UNKNOWN;
}
/* If the current state cannot advance, check if input or output
* buffer are exhausted. */
if (hsd->state == in_state) {
if (*output_size == out_buf_size) { return HSDR_POLL_MORE; }
return HSDR_POLL_EMPTY;
}
}
}
static HSD_state st_tag_bit(heatshrink_decoder *hsd) {
uint32_t bits = get_bits(hsd, 1); // get tag bit
if (bits == NO_BITS) {
return HSDS_TAG_BIT;
} else if (bits) {
return HSDS_YIELD_LITERAL;
} else if (HEATSHRINK_DECODER_WINDOW_BITS(hsd) > 8) {
return HSDS_BACKREF_INDEX_MSB;
} else {
hsd->output_index = 0;
return HSDS_BACKREF_INDEX_LSB;
}
}
static HSD_state st_yield_literal(heatshrink_decoder *hsd,
output_info *oi) {
/* Emit a repeated section from the window buffer, and add it (again)
* to the window buffer. (Note that the repetition can include
* itself.)*/
if (*oi->output_size < oi->buf_size) {
uint16_t byte = get_bits(hsd, 8);
if (byte == NO_BITS) { return HSDS_YIELD_LITERAL; } /* out of input */
uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)];
uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1;
uint8_t c = byte & 0xFF;
LOG("-- emitting literal byte 0x%02x ('%c')\n", c, isprint(c) ? c : '.');
buf[hsd->head_index++ & mask] = c;
push_byte(hsd, oi, c);
return HSDS_TAG_BIT;
} else {
return HSDS_YIELD_LITERAL;
}
}
static HSD_state st_backref_index_msb(heatshrink_decoder *hsd) {
uint8_t bit_ct = BACKREF_INDEX_BITS(hsd);
ASSERT(bit_ct > 8);
uint16_t bits = get_bits(hsd, bit_ct - 8);
LOG("-- backref index (msb), got 0x%04x (+1)\n", bits);
if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_MSB; }
hsd->output_index = bits << 8;
return HSDS_BACKREF_INDEX_LSB;
}
static HSD_state st_backref_index_lsb(heatshrink_decoder *hsd) {
uint8_t bit_ct = BACKREF_INDEX_BITS(hsd);
uint16_t bits = get_bits(hsd, bit_ct < 8 ? bit_ct : 8);
LOG("-- backref index (lsb), got 0x%04x (+1)\n", bits);
if (bits == NO_BITS) { return HSDS_BACKREF_INDEX_LSB; }
hsd->output_index |= bits;
hsd->output_index++;
uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd);
hsd->output_count = 0;
return (br_bit_ct > 8) ? HSDS_BACKREF_COUNT_MSB : HSDS_BACKREF_COUNT_LSB;
}
static HSD_state st_backref_count_msb(heatshrink_decoder *hsd) {
uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd);
ASSERT(br_bit_ct > 8);
uint16_t bits = get_bits(hsd, br_bit_ct - 8);
LOG("-- backref count (msb), got 0x%04x (+1)\n", bits);
if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_MSB; }
hsd->output_count = bits << 8;
return HSDS_BACKREF_COUNT_LSB;
}
static HSD_state st_backref_count_lsb(heatshrink_decoder *hsd) {
uint8_t br_bit_ct = BACKREF_COUNT_BITS(hsd);
uint16_t bits = get_bits(hsd, br_bit_ct < 8 ? br_bit_ct : 8);
LOG("-- backref count (lsb), got 0x%04x (+1)\n", bits);
if (bits == NO_BITS) { return HSDS_BACKREF_COUNT_LSB; }
hsd->output_count |= bits;
hsd->output_count++;
return HSDS_YIELD_BACKREF;
}
static HSD_state st_yield_backref(heatshrink_decoder *hsd,
output_info *oi) {
size_t count = oi->buf_size - *oi->output_size;
if (count > 0) {
size_t i = 0;
if (hsd->output_count < count) count = hsd->output_count;
uint8_t *buf = &hsd->buffers[HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd)];
uint16_t mask = (1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd)) - 1;
uint16_t neg_offset = hsd->output_index;
LOG("-- emitting %zu bytes from -%u bytes back\n", count, neg_offset);
ASSERT(neg_offset <= mask + 1);
ASSERT(count <= (size_t)(1 << BACKREF_COUNT_BITS(hsd)));
for (i=0; i<count; i++) {
uint8_t c = buf[(hsd->head_index - neg_offset) & mask];
push_byte(hsd, oi, c);
buf[hsd->head_index & mask] = c;
hsd->head_index++;
LOG(" -- ++ 0x%02x\n", c);
}
hsd->output_count -= count;
if (hsd->output_count == 0) { return HSDS_TAG_BIT; }
}
return HSDS_YIELD_BACKREF;
}
/* Get the next COUNT bits from the input buffer, saving incremental progress.
* Returns NO_BITS on end of input, or if more than 15 bits are requested. */
static uint16_t get_bits(heatshrink_decoder *hsd, uint8_t count) {
uint16_t accumulator = 0;
int i = 0;
if (count > 15) { return NO_BITS; }
LOG("-- popping %u bit(s)\n", count);
/* If we aren't able to get COUNT bits, suspend immediately, because we
* don't track how many bits of COUNT we've accumulated before suspend. */
if (hsd->input_size == 0) {
if (hsd->bit_index < (1 << (count - 1))) { return NO_BITS; }
}
for (i = 0; i < count; i++) {
if (hsd->bit_index == 0x00) {
if (hsd->input_size == 0) {
LOG(" -- out of bits, suspending w/ accumulator of %u (0x%02x)\n",
accumulator, accumulator);
return NO_BITS;
}
hsd->current_byte = hsd->buffers[hsd->input_index++];
LOG(" -- pulled byte 0x%02x\n", hsd->current_byte);
if (hsd->input_index == hsd->input_size) {
hsd->input_index = 0; /* input is exhausted */
hsd->input_size = 0;
}
hsd->bit_index = 0x80;
}
accumulator <<= 1;
if (hsd->current_byte & hsd->bit_index) {
accumulator |= 0x01;
if (0) {
LOG(" -- got 1, accumulator 0x%04x, bit_index 0x%02x\n",
accumulator, hsd->bit_index);
}
} else {
if (0) {
LOG(" -- got 0, accumulator 0x%04x, bit_index 0x%02x\n",
accumulator, hsd->bit_index);
}
}
hsd->bit_index >>= 1;
}
if (count > 1) { LOG(" -- accumulated %08x\n", accumulator); }
return accumulator;
}
HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd) {
if (hsd == NULL) { return HSDR_FINISH_ERROR_NULL; }
switch (hsd->state) {
case HSDS_TAG_BIT:
return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE;
/* If we want to finish with no input, but are in these states, it's
* because the 0-bit padding to the last byte looks like a backref
* marker bit followed by all 0s for index and count bits. */
case HSDS_BACKREF_INDEX_LSB:
case HSDS_BACKREF_INDEX_MSB:
case HSDS_BACKREF_COUNT_LSB:
case HSDS_BACKREF_COUNT_MSB:
return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE;
/* If the output stream is padded with 0xFFs (possibly due to being in
* flash memory), also explicitly check the input size rather than
* uselessly returning MORE but yielding 0 bytes when polling. */
case HSDS_YIELD_LITERAL:
return hsd->input_size == 0 ? HSDR_FINISH_DONE : HSDR_FINISH_MORE;
default:
return HSDR_FINISH_MORE;
}
}
static void push_byte(heatshrink_decoder *hsd, output_info *oi, uint8_t byte) {
LOG(" -- pushing byte: 0x%02x ('%c')\n", byte, isprint(byte) ? byte : '.');
oi->buf[(*oi->output_size)++] = byte;
(void)hsd;
}

View File

@ -1,100 +0,0 @@
#ifndef HEATSHRINK_DECODER_H
#define HEATSHRINK_DECODER_H
#include <stdint.h>
#include <stddef.h>
#include "heatshrink_common.h"
#include "heatshrink_config.h"
typedef enum {
HSDR_SINK_OK, /* data sunk, ready to poll */
HSDR_SINK_FULL, /* out of space in internal buffer */
HSDR_SINK_ERROR_NULL=-1, /* NULL argument */
} HSD_sink_res;
typedef enum {
HSDR_POLL_EMPTY, /* input exhausted */
HSDR_POLL_MORE, /* more data remaining, call again w/ fresh output buffer */
HSDR_POLL_ERROR_NULL=-1, /* NULL arguments */
HSDR_POLL_ERROR_UNKNOWN=-2,
} HSD_poll_res;
typedef enum {
HSDR_FINISH_DONE, /* output is done */
HSDR_FINISH_MORE, /* more output remains */
HSDR_FINISH_ERROR_NULL=-1, /* NULL arguments */
} HSD_finish_res;
#if HEATSHRINK_DYNAMIC_ALLOC
#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(BUF) \
((BUF)->input_buffer_size)
#define HEATSHRINK_DECODER_WINDOW_BITS(BUF) \
((BUF)->window_sz2)
#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \
((BUF)->lookahead_sz2)
#else
#define HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_) \
HEATSHRINK_STATIC_INPUT_BUFFER_SIZE
#define HEATSHRINK_DECODER_WINDOW_BITS(_) \
(HEATSHRINK_STATIC_WINDOW_BITS)
#define HEATSHRINK_DECODER_LOOKAHEAD_BITS(BUF) \
(HEATSHRINK_STATIC_LOOKAHEAD_BITS)
#endif
typedef struct {
uint16_t input_size; /* bytes in input buffer */
uint16_t input_index; /* offset to next unprocessed input byte */
uint16_t output_count; /* how many bytes to output */
uint16_t output_index; /* index for bytes to output */
uint16_t head_index; /* head of window buffer */
uint8_t state; /* current state machine node */
uint8_t current_byte; /* current byte of input */
uint8_t bit_index; /* current bit index */
#if HEATSHRINK_DYNAMIC_ALLOC
/* Fields that are only used if dynamically allocated. */
uint8_t window_sz2; /* window buffer bits */
uint8_t lookahead_sz2; /* lookahead bits */
uint16_t input_buffer_size; /* input buffer size */
/* Input buffer, then expansion window buffer */
uint8_t* buffers;
#else
/* Input buffer, then expansion window buffer */
uint8_t buffers[(1 << HEATSHRINK_DECODER_WINDOW_BITS(_))
+ HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(_)];
#endif
} heatshrink_decoder;
#if HEATSHRINK_DYNAMIC_ALLOC
/* Allocate a decoder with an input buffer of INPUT_BUFFER_SIZE bytes,
* an expansion buffer size of 2^WINDOW_SZ2, and a lookahead
* size of 2^lookahead_sz2. (The window buffer and lookahead sizes
* must match the settings used when the data was compressed.)
* Returns NULL on error. */
heatshrink_decoder *heatshrink_decoder_alloc(uint8_t* buffer, uint16_t input_buffer_size,
uint8_t expansion_buffer_sz2, uint8_t lookahead_sz2);
/* Free a decoder. */
void heatshrink_decoder_free(heatshrink_decoder *hsd);
#endif
/* Reset a decoder. */
void heatshrink_decoder_reset(heatshrink_decoder *hsd);
/* Sink at most SIZE bytes from IN_BUF into the decoder. *INPUT_SIZE is set to
* indicate how many bytes were actually sunk (in case a buffer was filled). */
HSD_sink_res heatshrink_decoder_sink(heatshrink_decoder *hsd,
uint8_t *in_buf, size_t size, size_t *input_size);
/* Poll for output from the decoder, copying at most OUT_BUF_SIZE bytes into
* OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */
HSD_poll_res heatshrink_decoder_poll(heatshrink_decoder *hsd,
uint8_t *out_buf, size_t out_buf_size, size_t *output_size);
/* Notify the dencoder that the input stream is finished.
* If the return value is HSDR_FINISH_MORE, there is still more output, so
* call heatshrink_decoder_poll and repeat. */
HSD_finish_res heatshrink_decoder_finish(heatshrink_decoder *hsd);
#endif

View File

@ -1,602 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "heatshrink_encoder.h"
typedef enum {
HSES_NOT_FULL, /* input buffer not full enough */
HSES_FILLED, /* buffer is full */
HSES_SEARCH, /* searching for patterns */
HSES_YIELD_TAG_BIT, /* yield tag bit */
HSES_YIELD_LITERAL, /* emit literal byte */
HSES_YIELD_BR_INDEX, /* yielding backref index */
HSES_YIELD_BR_LENGTH, /* yielding backref length */
HSES_SAVE_BACKLOG, /* copying buffer to backlog */
HSES_FLUSH_BITS, /* flush bit buffer */
HSES_DONE, /* done */
} HSE_state;
#if HEATSHRINK_DEBUGGING_LOGS
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#define LOG(...) fprintf(stderr, __VA_ARGS__)
#define ASSERT(X) assert(X)
static const char *state_names[] = {
"not_full",
"filled",
"search",
"yield_tag_bit",
"yield_literal",
"yield_br_index",
"yield_br_length",
"save_backlog",
"flush_bits",
"done",
};
#else
#define LOG(...) /* no-op */
#define ASSERT(X) /* no-op */
#endif
// Encoder flags
enum {
FLAG_IS_FINISHING = 0x01,
};
typedef struct {
uint8_t *buf; /* output buffer */
size_t buf_size; /* buffer size */
size_t *output_size; /* bytes pushed to buffer, so far */
} output_info;
#define MATCH_NOT_FOUND ((uint16_t)-1)
static uint16_t get_input_offset(heatshrink_encoder *hse);
static uint16_t get_input_buffer_size(heatshrink_encoder *hse);
static uint16_t get_lookahead_size(heatshrink_encoder *hse);
static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag);
static int can_take_byte(output_info *oi);
static int is_finishing(heatshrink_encoder *hse);
static void save_backlog(heatshrink_encoder *hse);
/* Push COUNT (max 8) bits to the output buffer, which has room. */
static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits,
output_info *oi);
static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi);
static void push_literal_byte(heatshrink_encoder *hse, output_info *oi);
#if HEATSHRINK_DYNAMIC_ALLOC
heatshrink_encoder *heatshrink_encoder_alloc(uint8_t* buffer, uint8_t window_sz2,
uint8_t lookahead_sz2) {
if ((window_sz2 < HEATSHRINK_MIN_WINDOW_BITS) ||
(window_sz2 > HEATSHRINK_MAX_WINDOW_BITS) ||
(lookahead_sz2 < HEATSHRINK_MIN_LOOKAHEAD_BITS) ||
(lookahead_sz2 >= window_sz2)) {
return NULL;
}
/* Note: 2 * the window size is used because the buffer needs to fit
* (1 << window_sz2) bytes for the current input, and an additional
* (1 << window_sz2) bytes for the previous buffer of input, which
* will be scanned for useful backreferences. */
size_t buf_sz = (2 << window_sz2);
heatshrink_encoder *hse = HEATSHRINK_MALLOC(sizeof(*hse));
if (hse == NULL) { return NULL; }
hse->window_sz2 = window_sz2;
hse->lookahead_sz2 = lookahead_sz2;
hse->buffer = buffer;
heatshrink_encoder_reset(hse);
#if HEATSHRINK_USE_INDEX
size_t index_sz = buf_sz*sizeof(uint16_t);
hse->search_index = HEATSHRINK_MALLOC(index_sz + sizeof(struct hs_index));
if (hse->search_index == NULL) {
HEATSHRINK_FREE(hse, sizeof(*hse) + buf_sz);
return NULL;
}
hse->search_index->size = index_sz;
#endif
LOG("-- allocated encoder with buffer size of %zu (%u byte input size)\n",
buf_sz, get_input_buffer_size(hse));
return hse;
}
void heatshrink_encoder_free(heatshrink_encoder *hse) {
#if HEATSHRINK_USE_INDEX
size_t index_sz = sizeof(struct hs_index) + hse->search_index->size;
HEATSHRINK_FREE(hse->search_index, index_sz);
(void)index_sz;
#endif
HEATSHRINK_FREE(hse, sizeof(heatshrink_encoder));
}
#endif
void heatshrink_encoder_reset(heatshrink_encoder *hse) {
hse->input_size = 0;
hse->state = HSES_NOT_FULL;
hse->match_scan_index = 0;
hse->flags = 0;
hse->bit_index = 0x80;
hse->current_byte = 0x00;
hse->match_length = 0;
hse->outgoing_bits = 0x0000;
hse->outgoing_bits_count = 0;
#ifdef LOOP_DETECT
hse->loop_detect = (uint32_t)-1;
#endif
}
HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse,
uint8_t *in_buf, size_t size, size_t *input_size) {
if ((hse == NULL) || (in_buf == NULL) || (input_size == NULL)) {
return HSER_SINK_ERROR_NULL;
}
/* Sinking more content after saying the content is done, tsk tsk */
if (is_finishing(hse)) { return HSER_SINK_ERROR_MISUSE; }
/* Sinking more content before processing is done */
if (hse->state != HSES_NOT_FULL) { return HSER_SINK_ERROR_MISUSE; }
uint16_t write_offset = get_input_offset(hse) + hse->input_size;
uint16_t ibs = get_input_buffer_size(hse);
uint16_t rem = ibs - hse->input_size;
uint16_t cp_sz = rem < size ? rem : size;
memcpy(&hse->buffer[write_offset], in_buf, cp_sz);
*input_size = cp_sz;
hse->input_size += cp_sz;
LOG("-- sunk %u bytes (of %zu) into encoder at %d, input buffer now has %u\n",
cp_sz, size, write_offset, hse->input_size);
if (cp_sz == rem) {
LOG("-- internal buffer is now full\n");
hse->state = HSES_FILLED;
}
return HSER_SINK_OK;
}
/***************
* Compression *
***************/
static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start,
uint16_t end, const uint16_t maxlen, uint16_t *match_length);
static void do_indexing(heatshrink_encoder *hse);
static HSE_state st_step_search(heatshrink_encoder *hse);
static HSE_state st_yield_tag_bit(heatshrink_encoder *hse,
output_info *oi);
static HSE_state st_yield_literal(heatshrink_encoder *hse,
output_info *oi);
static HSE_state st_yield_br_index(heatshrink_encoder *hse,
output_info *oi);
static HSE_state st_yield_br_length(heatshrink_encoder *hse,
output_info *oi);
static HSE_state st_save_backlog(heatshrink_encoder *hse);
static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse,
output_info *oi);
HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse,
uint8_t *out_buf, size_t out_buf_size, size_t *output_size) {
if ((hse == NULL) || (out_buf == NULL) || (output_size == NULL)) {
return HSER_POLL_ERROR_NULL;
}
if (out_buf_size == 0) {
LOG("-- MISUSE: output buffer size is 0\n");
return HSER_POLL_ERROR_MISUSE;
}
*output_size = 0;
output_info oi;
oi.buf = out_buf;
oi.buf_size = out_buf_size;
oi.output_size = output_size;
while (1) {
LOG("-- polling, state %u (%s), flags 0x%02x\n",
hse->state, state_names[hse->state], hse->flags);
uint8_t in_state = hse->state;
switch (in_state) {
case HSES_NOT_FULL:
return HSER_POLL_EMPTY;
case HSES_FILLED:
do_indexing(hse);
hse->state = HSES_SEARCH;
break;
case HSES_SEARCH:
hse->state = st_step_search(hse);
break;
case HSES_YIELD_TAG_BIT:
hse->state = st_yield_tag_bit(hse, &oi);
break;
case HSES_YIELD_LITERAL:
hse->state = st_yield_literal(hse, &oi);
break;
case HSES_YIELD_BR_INDEX:
hse->state = st_yield_br_index(hse, &oi);
break;
case HSES_YIELD_BR_LENGTH:
hse->state = st_yield_br_length(hse, &oi);
break;
case HSES_SAVE_BACKLOG:
hse->state = st_save_backlog(hse);
break;
case HSES_FLUSH_BITS:
hse->state = st_flush_bit_buffer(hse, &oi);
/* fall through */
case HSES_DONE:
return HSER_POLL_EMPTY;
default:
LOG("-- bad state %s\n", state_names[hse->state]);
return HSER_POLL_ERROR_MISUSE;
}
if (hse->state == in_state) {
/* Check if output buffer is exhausted. */
if (*output_size == out_buf_size) return HSER_POLL_MORE;
}
}
}
HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse) {
if (hse == NULL) { return HSER_FINISH_ERROR_NULL; }
LOG("-- setting is_finishing flag\n");
hse->flags |= FLAG_IS_FINISHING;
if (hse->state == HSES_NOT_FULL) { hse->state = HSES_FILLED; }
return hse->state == HSES_DONE ? HSER_FINISH_DONE : HSER_FINISH_MORE;
}
static HSE_state st_step_search(heatshrink_encoder *hse) {
uint16_t window_length = get_input_buffer_size(hse);
uint16_t lookahead_sz = get_lookahead_size(hse);
uint16_t msi = hse->match_scan_index;
LOG("## step_search, scan @ +%d (%d/%d), input size %d\n",
msi, hse->input_size + msi, 2*window_length, hse->input_size);
bool fin = is_finishing(hse);
if (msi > hse->input_size - (fin ? 1 : lookahead_sz)) {
/* Current search buffer is exhausted, copy it into the
* backlog and await more input. */
LOG("-- end of search @ %d\n", msi);
return fin ? HSES_FLUSH_BITS : HSES_SAVE_BACKLOG;
}
uint16_t input_offset = get_input_offset(hse);
uint16_t end = input_offset + msi;
uint16_t start = end - window_length;
uint16_t max_possible = lookahead_sz;
if (hse->input_size - msi < lookahead_sz) {
max_possible = hse->input_size - msi;
}
uint16_t match_length = 0;
uint16_t match_pos = find_longest_match(hse,
start, end, max_possible, &match_length);
if (match_pos == MATCH_NOT_FOUND) {
LOG("ss Match not found\n");
hse->match_scan_index++;
hse->match_length = 0;
return HSES_YIELD_TAG_BIT;
} else {
LOG("ss Found match of %d bytes at %d\n", match_length, match_pos);
hse->match_pos = match_pos;
hse->match_length = match_length;
ASSERT(match_pos <= 1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse) /*window_length*/);
return HSES_YIELD_TAG_BIT;
}
}
static HSE_state st_yield_tag_bit(heatshrink_encoder *hse,
output_info *oi) {
if (can_take_byte(oi)) {
if (hse->match_length == 0) {
add_tag_bit(hse, oi, HEATSHRINK_LITERAL_MARKER);
return HSES_YIELD_LITERAL;
} else {
add_tag_bit(hse, oi, HEATSHRINK_BACKREF_MARKER);
hse->outgoing_bits = hse->match_pos - 1;
hse->outgoing_bits_count = HEATSHRINK_ENCODER_WINDOW_BITS(hse);
return HSES_YIELD_BR_INDEX;
}
} else {
return HSES_YIELD_TAG_BIT; /* output is full, continue */
}
}
static HSE_state st_yield_literal(heatshrink_encoder *hse,
output_info *oi) {
if (can_take_byte(oi)) {
push_literal_byte(hse, oi);
return HSES_SEARCH;
} else {
return HSES_YIELD_LITERAL;
}
}
static HSE_state st_yield_br_index(heatshrink_encoder *hse,
output_info *oi) {
if (can_take_byte(oi)) {
LOG("-- yielding backref index %u\n", hse->match_pos);
if (push_outgoing_bits(hse, oi) > 0) {
return HSES_YIELD_BR_INDEX; /* continue */
} else {
hse->outgoing_bits = hse->match_length - 1;
hse->outgoing_bits_count = HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse);
return HSES_YIELD_BR_LENGTH; /* done */
}
} else {
return HSES_YIELD_BR_INDEX; /* continue */
}
}
static HSE_state st_yield_br_length(heatshrink_encoder *hse,
output_info *oi) {
if (can_take_byte(oi)) {
LOG("-- yielding backref length %u\n", hse->match_length);
if (push_outgoing_bits(hse, oi) > 0) {
return HSES_YIELD_BR_LENGTH;
} else {
hse->match_scan_index += hse->match_length;
hse->match_length = 0;
return HSES_SEARCH;
}
} else {
return HSES_YIELD_BR_LENGTH;
}
}
static HSE_state st_save_backlog(heatshrink_encoder *hse) {
LOG("-- saving backlog\n");
save_backlog(hse);
return HSES_NOT_FULL;
}
static HSE_state st_flush_bit_buffer(heatshrink_encoder *hse,
output_info *oi) {
if (hse->bit_index == 0x80) {
LOG("-- done!\n");
return HSES_DONE;
} else if (can_take_byte(oi)) {
LOG("-- flushing remaining byte (bit_index == 0x%02x)\n", hse->bit_index);
oi->buf[(*oi->output_size)++] = hse->current_byte;
LOG("-- done!\n");
return HSES_DONE;
} else {
return HSES_FLUSH_BITS;
}
}
static void add_tag_bit(heatshrink_encoder *hse, output_info *oi, uint8_t tag) {
LOG("-- adding tag bit: %d\n", tag);
push_bits(hse, 1, tag, oi);
}
static uint16_t get_input_offset(heatshrink_encoder *hse) {
return get_input_buffer_size(hse);
}
static uint16_t get_input_buffer_size(heatshrink_encoder *hse) {
return (1 << HEATSHRINK_ENCODER_WINDOW_BITS(hse));
(void)hse;
}
static uint16_t get_lookahead_size(heatshrink_encoder *hse) {
return (1 << HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse));
(void)hse;
}
static void do_indexing(heatshrink_encoder *hse) {
#if HEATSHRINK_USE_INDEX
/* Build an index array I that contains flattened linked lists
* for the previous instances of every byte in the buffer.
*
* For example, if buf[200] == 'x', then index[200] will either
* be an offset i such that buf[i] == 'x', or a negative offset
* to indicate end-of-list. This significantly speeds up matching,
* while only using sizeof(uint16_t)*sizeof(buffer) bytes of RAM.
*
* Future optimization options:
* 1. Since any negative value represents end-of-list, the other
* 15 bits could be used to improve the index dynamically.
*
* 2. Likewise, the last lookahead_sz bytes of the index will
* not be usable, so temporary data could be stored there to
* dynamically improve the index.
* */
struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse);
int16_t last[256];
memset(last, 0xFF, sizeof(last));
uint8_t * const data = hse->buffer;
int16_t * const index = hsi->index;
const uint16_t input_offset = get_input_offset(hse);
const uint16_t end = input_offset + hse->input_size;
for (uint16_t i=0; i<end; i++) {
uint8_t v = data[i];
int16_t lv = last[v];
index[i] = lv;
last[v] = i;
}
#else
(void)hse;
#endif
}
static int is_finishing(heatshrink_encoder *hse) {
return hse->flags & FLAG_IS_FINISHING;
}
static int can_take_byte(output_info *oi) {
return *oi->output_size < oi->buf_size;
}
/* Return the longest match for the bytes at buf[end:end+maxlen] between
* buf[start] and buf[end-1]. If no match is found, return -1. */
static uint16_t find_longest_match(heatshrink_encoder *hse, uint16_t start,
uint16_t end, const uint16_t maxlen, uint16_t *match_length) {
LOG("-- scanning for match of buf[%u:%u] between buf[%u:%u] (max %u bytes)\n",
end, end + maxlen, start, end + maxlen - 1, maxlen);
uint8_t *buf = hse->buffer;
uint16_t match_maxlen = 0;
uint16_t match_index = MATCH_NOT_FOUND;
uint16_t len = 0;
uint8_t * const needlepoint = &buf[end];
#if HEATSHRINK_USE_INDEX
struct hs_index *hsi = HEATSHRINK_ENCODER_INDEX(hse);
int16_t pos = hsi->index[end];
while (pos - (int16_t)start >= 0) {
uint8_t * const pospoint = &buf[pos];
len = 0;
/* Only check matches that will potentially beat the current maxlen.
* This is redundant with the index if match_maxlen is 0, but the
* added branch overhead to check if it == 0 seems to be worse. */
if (pospoint[match_maxlen] != needlepoint[match_maxlen]) {
pos = hsi->index[pos];
continue;
}
for (len = 1; len < maxlen; len++) {
if (pospoint[len] != needlepoint[len]) break;
}
if (len > match_maxlen) {
match_maxlen = len;
match_index = pos;
if (len == maxlen) { break; } /* won't find better */
}
pos = hsi->index[pos];
}
#else
for (int16_t pos=end - 1; pos - (int16_t)start >= 0; pos--) {
uint8_t * const pospoint = &buf[pos];
if ((pospoint[match_maxlen] == needlepoint[match_maxlen])
&& (*pospoint == *needlepoint)) {
for (len=1; len<maxlen; len++) {
if (0) {
LOG(" --> cmp buf[%d] == 0x%02x against %02x (start %u)\n",
pos + len, pospoint[len], needlepoint[len], start);
}
if (pospoint[len] != needlepoint[len]) { break; }
}
if (len > match_maxlen) {
match_maxlen = len;
match_index = pos;
if (len == maxlen) { break; } /* don't keep searching */
}
}
}
#endif
const size_t break_even_point =
(1 + HEATSHRINK_ENCODER_WINDOW_BITS(hse) +
HEATSHRINK_ENCODER_LOOKAHEAD_BITS(hse));
/* Instead of comparing break_even_point against 8*match_maxlen,
* compare match_maxlen against break_even_point/8 to avoid
* overflow. Since MIN_WINDOW_BITS and MIN_LOOKAHEAD_BITS are 4 and
* 3, respectively, break_even_point/8 will always be at least 1. */
if (match_maxlen > (break_even_point / 8)) {
LOG("-- best match: %u bytes at -%u\n",
match_maxlen, end - match_index);
*match_length = match_maxlen;
return end - match_index;
}
LOG("-- none found\n");
return MATCH_NOT_FOUND;
}
static uint8_t push_outgoing_bits(heatshrink_encoder *hse, output_info *oi) {
uint8_t count = 0;
uint8_t bits = 0;
if (hse->outgoing_bits_count > 8) {
count = 8;
bits = hse->outgoing_bits >> (hse->outgoing_bits_count - 8);
} else {
count = hse->outgoing_bits_count;
bits = hse->outgoing_bits;
}
if (count > 0) {
LOG("-- pushing %d outgoing bits: 0x%02x\n", count, bits);
push_bits(hse, count, bits, oi);
hse->outgoing_bits_count -= count;
}
return count;
}
/* Push COUNT (max 8) bits to the output buffer, which has room.
* Bytes are set from the lowest bits, up. */
static void push_bits(heatshrink_encoder *hse, uint8_t count, uint8_t bits,
output_info *oi) {
ASSERT(count <= 8);
LOG("++ push_bits: %d bits, input of 0x%02x\n", count, bits);
/* If adding a whole byte and at the start of a new output byte,
* just push it through whole and skip the bit IO loop. */
if (count == 8 && hse->bit_index == 0x80) {
oi->buf[(*oi->output_size)++] = bits;
} else {
for (int i=count - 1; i>=0; i--) {
bool bit = bits & (1 << i);
if (bit) { hse->current_byte |= hse->bit_index; }
if (0) {
LOG(" -- setting bit %d at bit index 0x%02x, byte => 0x%02x\n",
bit ? 1 : 0, hse->bit_index, hse->current_byte);
}
hse->bit_index >>= 1;
if (hse->bit_index == 0x00) {
hse->bit_index = 0x80;
LOG(" > pushing byte 0x%02x\n", hse->current_byte);
oi->buf[(*oi->output_size)++] = hse->current_byte;
hse->current_byte = 0x00;
}
}
}
}
static void push_literal_byte(heatshrink_encoder *hse, output_info *oi) {
uint16_t processed_offset = hse->match_scan_index - 1;
uint16_t input_offset = get_input_offset(hse) + processed_offset;
uint8_t c = hse->buffer[input_offset];
LOG("-- yielded literal byte 0x%02x ('%c') from +%d\n",
c, isprint(c) ? c : '.', input_offset);
push_bits(hse, 8, c, oi);
}
static void save_backlog(heatshrink_encoder *hse) {
size_t input_buf_sz = get_input_buffer_size(hse);
uint16_t msi = hse->match_scan_index;
/* Copy processed data to beginning of buffer, so it can be
* used for future matches. Don't bother checking whether the
* input is less than the maximum size, because if it isn't,
* we're done anyway. */
uint16_t rem = input_buf_sz - msi; // unprocessed bytes
uint16_t shift_sz = input_buf_sz + rem;
memmove(&hse->buffer[0],
&hse->buffer[input_buf_sz - rem],
shift_sz);
hse->match_scan_index = 0;
hse->input_size -= input_buf_sz - rem;
}

View File

@ -1,109 +0,0 @@
#ifndef HEATSHRINK_ENCODER_H
#define HEATSHRINK_ENCODER_H
#include <stdint.h>
#include <stddef.h>
#include "heatshrink_common.h"
#include "heatshrink_config.h"
typedef enum {
HSER_SINK_OK, /* data sunk into input buffer */
HSER_SINK_ERROR_NULL=-1, /* NULL argument */
HSER_SINK_ERROR_MISUSE=-2, /* API misuse */
} HSE_sink_res;
typedef enum {
HSER_POLL_EMPTY, /* input exhausted */
HSER_POLL_MORE, /* poll again for more output */
HSER_POLL_ERROR_NULL=-1, /* NULL argument */
HSER_POLL_ERROR_MISUSE=-2, /* API misuse */
} HSE_poll_res;
typedef enum {
HSER_FINISH_DONE, /* encoding is complete */
HSER_FINISH_MORE, /* more output remaining; use poll */
HSER_FINISH_ERROR_NULL=-1, /* NULL argument */
} HSE_finish_res;
#if HEATSHRINK_DYNAMIC_ALLOC
#define HEATSHRINK_ENCODER_WINDOW_BITS(HSE) \
((HSE)->window_sz2)
#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(HSE) \
((HSE)->lookahead_sz2)
#define HEATSHRINK_ENCODER_INDEX(HSE) \
((HSE)->search_index)
struct hs_index {
uint16_t size;
int16_t index[];
};
#else
#define HEATSHRINK_ENCODER_WINDOW_BITS(_) \
(HEATSHRINK_STATIC_WINDOW_BITS)
#define HEATSHRINK_ENCODER_LOOKAHEAD_BITS(_) \
(HEATSHRINK_STATIC_LOOKAHEAD_BITS)
#define HEATSHRINK_ENCODER_INDEX(HSE) \
(&(HSE)->search_index)
struct hs_index {
uint16_t size;
int16_t index[2 << HEATSHRINK_STATIC_WINDOW_BITS];
};
#endif
typedef struct {
uint16_t input_size; /* bytes in input buffer */
uint16_t match_scan_index;
uint16_t match_length;
uint16_t match_pos;
uint16_t outgoing_bits; /* enqueued outgoing bits */
uint8_t outgoing_bits_count;
uint8_t flags;
uint8_t state; /* current state machine node */
uint8_t current_byte; /* current byte of output */
uint8_t bit_index; /* current bit index */
#if HEATSHRINK_DYNAMIC_ALLOC
uint8_t window_sz2; /* 2^n size of window */
uint8_t lookahead_sz2; /* 2^n size of lookahead */
#if HEATSHRINK_USE_INDEX
struct hs_index *search_index;
#endif
/* input buffer and / sliding window for expansion */
uint8_t* buffer;
#else
#if HEATSHRINK_USE_INDEX
struct hs_index search_index;
#endif
/* input buffer and / sliding window for expansion */
uint8_t buffer[2 << HEATSHRINK_ENCODER_WINDOW_BITS(_)];
#endif
} heatshrink_encoder;
#if HEATSHRINK_DYNAMIC_ALLOC
/* Allocate a new encoder struct and its buffers.
* Returns NULL on error. */
heatshrink_encoder *heatshrink_encoder_alloc(uint8_t* buffer, uint8_t window_sz2,
uint8_t lookahead_sz2);
/* Free an encoder. */
void heatshrink_encoder_free(heatshrink_encoder *hse);
#endif
/* Reset an encoder. */
void heatshrink_encoder_reset(heatshrink_encoder *hse);
/* Sink up to SIZE bytes from IN_BUF into the encoder.
* INPUT_SIZE is set to the number of bytes actually sunk (in case a
* buffer was filled.). */
HSE_sink_res heatshrink_encoder_sink(heatshrink_encoder *hse,
uint8_t *in_buf, size_t size, size_t *input_size);
/* Poll for output from the encoder, copying at most OUT_BUF_SIZE bytes into
* OUT_BUF (setting *OUTPUT_SIZE to the actual amount copied). */
HSE_poll_res heatshrink_encoder_poll(heatshrink_encoder *hse,
uint8_t *out_buf, size_t out_buf_size, size_t *output_size);
/* Notify the encoder that the input stream is finished.
* If the return value is HSER_FINISH_MORE, there is still more output, so
* call heatshrink_encoder_poll and repeat. */
HSE_finish_res heatshrink_encoder_finish(heatshrink_encoder *hse);
#endif

View File

@ -36,7 +36,6 @@ for lib in libs_recurse:
sources += libenv.GlobRecursive("*.c*", lib)
libs_plain = [
"heatshrink",
"nanopb",
]
@ -47,6 +46,12 @@ for lib in libs_plain:
source=True,
)
sources += Glob(
"heatshrink/heatshrink_*.c*",
exclude=GLOB_FILE_EXCLUSION,
source=True,
)
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
libenv.Install("${LIB_DIST_DIR}", lib)
Return("lib")

View File

@ -1,115 +1,112 @@
#include <furi_hal_compress.h>
#include "compress.h"
#include <furi.h>
#include <lib/heatshrink/heatshrink_encoder.h>
#include <lib/heatshrink/heatshrink_decoder.h>
#define TAG "FuriHalCompress"
/** Defines encoder and decoder window size */
#define COMPRESS_EXP_BUFF_SIZE_LOG (8u)
#define FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE (2 * 512)
#define FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE (1024)
/** Defines encoder and decoder lookahead buffer size */
#define COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG (4u)
#define FURI_HAL_COMPRESS_EXP_BUFF_SIZE (1 << FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG)
/** Buffer sizes for input and output data */
#define COMPRESS_ICON_ENCODED_BUFF_SIZE (1024u)
#define COMPRESS_ICON_DECODED_BUFF_SIZE (1024u)
typedef struct {
uint8_t is_compressed;
uint8_t reserved;
uint16_t compressed_buff_size;
} FuriHalCompressHeader;
} CompressHeader;
typedef struct {
heatshrink_decoder* decoder;
uint8_t
compress_buff[FURI_HAL_COMPRESS_EXP_BUFF_SIZE + FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE];
uint8_t decoded_buff[FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE];
} FuriHalCompressIcon;
_Static_assert(sizeof(CompressHeader) == 4, "Incorrect CompressHeader size");
struct FuriHalCompress {
heatshrink_encoder* encoder;
struct CompressIcon {
heatshrink_decoder* decoder;
uint8_t* compress_buff;
uint16_t compress_buff_size;
uint8_t decoded_buff[COMPRESS_ICON_DECODED_BUFF_SIZE];
};
static FuriHalCompressIcon* icon_decoder;
CompressIcon* compress_icon_alloc() {
CompressIcon* instance = malloc(sizeof(CompressIcon));
instance->decoder = heatshrink_decoder_alloc(
COMPRESS_ICON_ENCODED_BUFF_SIZE,
COMPRESS_EXP_BUFF_SIZE_LOG,
COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
heatshrink_decoder_reset(instance->decoder);
memset(instance->decoded_buff, 0, sizeof(instance->decoded_buff));
static void furi_hal_compress_reset(FuriHalCompress* compress) {
furi_assert(compress);
heatshrink_encoder_reset(compress->encoder);
heatshrink_decoder_reset(compress->decoder);
memset(compress->compress_buff, 0, compress->compress_buff_size);
return instance;
}
void furi_hal_compress_icon_init() {
icon_decoder = malloc(sizeof(FuriHalCompressIcon));
icon_decoder->decoder = heatshrink_decoder_alloc(
icon_decoder->compress_buff,
FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE,
FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG,
FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
heatshrink_decoder_reset(icon_decoder->decoder);
memset(icon_decoder->decoded_buff, 0, sizeof(icon_decoder->decoded_buff));
FURI_LOG_I(TAG, "Init OK");
void compress_icon_free(CompressIcon* instance) {
furi_assert(instance);
heatshrink_decoder_free(instance->decoder);
free(instance);
}
void furi_hal_compress_icon_decode(const uint8_t* icon_data, uint8_t** decoded_buff) {
void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff) {
furi_assert(instance);
furi_assert(icon_data);
furi_assert(decoded_buff);
FuriHalCompressHeader* header = (FuriHalCompressHeader*)icon_data;
CompressHeader* header = (CompressHeader*)icon_data;
if(header->is_compressed) {
size_t data_processed = 0;
heatshrink_decoder_sink(
icon_decoder->decoder,
(uint8_t*)&icon_data[4],
instance->decoder,
(uint8_t*)&icon_data[sizeof(CompressHeader)],
header->compressed_buff_size,
&data_processed);
while(1) {
HSD_poll_res res = heatshrink_decoder_poll(
icon_decoder->decoder,
icon_decoder->decoded_buff,
sizeof(icon_decoder->decoded_buff),
instance->decoder,
instance->decoded_buff,
sizeof(instance->decoded_buff),
&data_processed);
furi_assert((res == HSDR_POLL_EMPTY) || (res == HSDR_POLL_MORE));
if(res != HSDR_POLL_MORE) {
break;
}
}
heatshrink_decoder_reset(icon_decoder->decoder);
memset(icon_decoder->compress_buff, 0, sizeof(icon_decoder->compress_buff));
*decoded_buff = icon_decoder->decoded_buff;
heatshrink_decoder_reset(instance->decoder);
*decoded_buff = instance->decoded_buff;
} else {
*decoded_buff = (uint8_t*)&icon_data[1];
}
}
FuriHalCompress* furi_hal_compress_alloc(uint16_t compress_buff_size) {
FuriHalCompress* compress = malloc(sizeof(FuriHalCompress));
compress->compress_buff = malloc(compress_buff_size + FURI_HAL_COMPRESS_EXP_BUFF_SIZE);
compress->encoder = heatshrink_encoder_alloc(
compress->compress_buff,
FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG,
FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
struct Compress {
heatshrink_encoder* encoder;
heatshrink_decoder* decoder;
};
static void compress_reset(Compress* compress) {
furi_assert(compress);
heatshrink_encoder_reset(compress->encoder);
heatshrink_decoder_reset(compress->decoder);
}
Compress* compress_alloc(uint16_t compress_buff_size) {
Compress* compress = malloc(sizeof(Compress));
compress->encoder =
heatshrink_encoder_alloc(COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
compress->decoder = heatshrink_decoder_alloc(
compress->compress_buff,
compress_buff_size,
FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG,
FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
compress_buff_size, COMPRESS_EXP_BUFF_SIZE_LOG, COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
return compress;
}
void furi_hal_compress_free(FuriHalCompress* compress) {
void compress_free(Compress* compress) {
furi_assert(compress);
heatshrink_encoder_free(compress->encoder);
heatshrink_decoder_free(compress->decoder);
free(compress->compress_buff);
free(compress);
}
bool furi_hal_compress_encode(
FuriHalCompress* compress,
bool compress_encode(
Compress* compress,
uint8_t* data_in,
size_t data_in_size,
uint8_t* data_out,
@ -126,7 +123,7 @@ bool furi_hal_compress_encode(
HSE_finish_res finish_res;
bool encode_failed = false;
size_t sunk = 0;
size_t res_buff_size = sizeof(FuriHalCompressHeader);
size_t res_buff_size = sizeof(CompressHeader);
// Sink data to encoding buffer
while((sunk < data_in_size) && !encode_failed) {
@ -174,7 +171,7 @@ bool furi_hal_compress_encode(
bool result = true;
// Write encoded data to output buffer if compression is efficient. Else - write header and original data
if(!encode_failed && (res_buff_size < data_in_size + 1)) {
FuriHalCompressHeader header = {
CompressHeader header = {
.is_compressed = 0x01, .reserved = 0x00, .compressed_buff_size = res_buff_size};
memcpy(data_out, &header, sizeof(header));
*data_res_size = res_buff_size;
@ -186,13 +183,13 @@ bool furi_hal_compress_encode(
*data_res_size = 0;
result = false;
}
furi_hal_compress_reset(compress);
compress_reset(compress);
return result;
}
bool furi_hal_compress_decode(
FuriHalCompress* compress,
bool compress_decode(
Compress* compress,
uint8_t* data_in,
size_t data_in_size,
uint8_t* data_out,
@ -212,11 +209,11 @@ bool furi_hal_compress_decode(
size_t res_buff_size = 0;
size_t poll_size = 0;
FuriHalCompressHeader* header = (FuriHalCompressHeader*)data_in;
CompressHeader* header = (CompressHeader*)data_in;
if(header->is_compressed) {
// Sink data to decoding buffer
size_t compressed_size = header->compressed_buff_size;
size_t sunk = sizeof(FuriHalCompressHeader);
size_t sunk = sizeof(CompressHeader);
while(sunk < compressed_size && !decode_failed) {
sink_res = heatshrink_decoder_sink(
compress->decoder, &data_in[sunk], compressed_size - sunk, &sink_size);
@ -258,7 +255,7 @@ bool furi_hal_compress_decode(
} else {
result = false;
}
furi_hal_compress_reset(compress);
compress_reset(compress);
return result;
}

96
lib/toolbox/compress.h Normal file
View File

@ -0,0 +1,96 @@
/**
* @file compress.h
* LZSS based compression HAL API
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Compress Icon control structure */
typedef struct CompressIcon CompressIcon;
/** Initialize icon compressor
*
* @return Compress Icon instance
*/
CompressIcon* compress_icon_alloc();
/** Free icon compressor
*
* @param instance The Compress Icon instance
*/
void compress_icon_free(CompressIcon* instance);
/** Decompress icon
*
* @warning decoded_buff pointer set by this function is valid till next
* `compress_icon_decode` or `compress_icon_free` call
*
* @param instance The Compress Icon instance
* @param icon_data pointer to icon data
* @param[in] decoded_buff pointer to decoded buffer pointer
*/
void compress_icon_decode(CompressIcon* instance, const uint8_t* icon_data, uint8_t** decoded_buff);
/** Compress control structure */
typedef struct Compress Compress;
/** Allocate encoder and decoder
*
* @param compress_buff_size size of decoder and encoder buffer to allocate
*
* @return Compress instance
*/
Compress* compress_alloc(uint16_t compress_buff_size);
/** Free encoder and decoder
*
* @param compress Compress instance
*/
void compress_free(Compress* compress);
/** Encode data
*
* @param compress Compress instance
* @param data_in pointer to input data
* @param data_in_size size of input data
* @param data_out maximum size of output data
* @param data_res_size pointer to result output data size
*
* @return true on success
*/
bool compress_encode(
Compress* compress,
uint8_t* data_in,
size_t data_in_size,
uint8_t* data_out,
size_t data_out_size,
size_t* data_res_size);
/** Decode data
*
* @param compress Compress instance
* @param data_in pointer to input data
* @param data_in_size size of input data
* @param data_out maximum size of output data
* @param data_res_size pointer to result output data size
*
* @return true on success
*/
bool compress_decode(
Compress* compress,
uint8_t* data_in,
size_t data_in_size,
uint8_t* data_out,
size_t data_out_size,
size_t* data_res_size);
#ifdef __cplusplus
}
#endif