Chris Pepper
5 years ago
committed by
Scott Lahteine
9 changed files with 1026 additions and 256 deletions
@ -0,0 +1,36 @@ |
|||
/**
|
|||
* Marlin 3D Printer Firmware |
|||
* Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
|||
* |
|||
* Based on Sprinter and grbl. |
|||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU General Public License as published by |
|||
* the Free Software Foundation, either version 3 of the License, or |
|||
* (at your option) any later version. |
|||
* |
|||
* This program 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 General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
|
|||
#include "../inc/MarlinConfigPre.h" |
|||
|
|||
#if ENABLED(BINARY_FILE_TRANSFER) |
|||
|
|||
#include "../sd/cardreader.h" |
|||
#include "binary_protocol.h" |
|||
|
|||
char* SDFileTransferProtocol::Packet::Open::data = nullptr; |
|||
size_t SDFileTransferProtocol::data_waiting, SDFileTransferProtocol::transfer_timeout, SDFileTransferProtocol::idle_timeout; |
|||
bool SDFileTransferProtocol::transfer_active, SDFileTransferProtocol::dummy_transfer, SDFileTransferProtocol::compression; |
|||
|
|||
BinaryStream binaryStream[NUM_SERIAL]; |
|||
|
|||
#endif // BINARY_FILE_TRANSFER
|
@ -0,0 +1,471 @@ |
|||
/**
|
|||
* Marlin 3D Printer Firmware |
|||
* Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
|||
* |
|||
* Based on Sprinter and grbl. |
|||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm |
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU General Public License as published by |
|||
* the Free Software Foundation, either version 3 of the License, or |
|||
* (at your option) any later version. |
|||
* |
|||
* This program 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 General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
*/ |
|||
#pragma once |
|||
|
|||
#include "../inc/MarlinConfig.h" |
|||
|
|||
#define BINARY_STREAM_COMPRESSION |
|||
|
|||
#if ENABLED(BINARY_STREAM_COMPRESSION) |
|||
#include "../libs/heatshrink/heatshrink_decoder.h" |
|||
#endif |
|||
|
|||
inline bool bs_serial_data_available(const uint8_t index) { |
|||
switch (index) { |
|||
case 0: return MYSERIAL0.available(); |
|||
#if NUM_SERIAL > 1 |
|||
case 1: return MYSERIAL1.available(); |
|||
#endif |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
inline int bs_read_serial(const uint8_t index) { |
|||
switch (index) { |
|||
case 0: return MYSERIAL0.read(); |
|||
#if NUM_SERIAL > 1 |
|||
case 1: return MYSERIAL1.read(); |
|||
#endif |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
#if ENABLED(BINARY_STREAM_COMPRESSION) |
|||
static heatshrink_decoder hsd; |
|||
static uint8_t decode_buffer[512] = {}; |
|||
#endif |
|||
|
|||
class SDFileTransferProtocol { |
|||
private: |
|||
struct Packet { |
|||
struct [[gnu::packed]] Open { |
|||
static bool validate(char* buffer, size_t length) { |
|||
return (length > sizeof(Open) && buffer[length - 1] == '\0'); |
|||
} |
|||
static Open& decode(char* buffer) { |
|||
data = &buffer[2]; |
|||
return *reinterpret_cast<Open*>(buffer); |
|||
} |
|||
bool compression_enabled() { return compression & 0x1; } |
|||
bool dummy_transfer() { return dummy & 0x1; } |
|||
static char* filename() { return data; } |
|||
private: |
|||
uint8_t dummy, compression; |
|||
static char* data; // variable length strings complicate things
|
|||
}; |
|||
}; |
|||
|
|||
static bool file_open(char* filename) { |
|||
if (!dummy_transfer) { |
|||
card.initsd(); |
|||
card.openFile(filename, false); |
|||
if (!card.isFileOpen()) return false; |
|||
} |
|||
transfer_active = true; |
|||
data_waiting = 0; |
|||
#if ENABLED(BINARY_STREAM_COMPRESSION) |
|||
heatshrink_decoder_reset(&hsd); |
|||
#endif |
|||
return true; |
|||
} |
|||
|
|||
static bool file_write(char* buffer, const size_t length) { |
|||
#if ENABLED(BINARY_STREAM_COMPRESSION) |
|||
if (compression) { |
|||
size_t total_processed = 0, processed_count = 0; |
|||
HSD_poll_res presult; |
|||
|
|||
while (total_processed < length) { |
|||
heatshrink_decoder_sink(&hsd, reinterpret_cast<uint8_t*>(&buffer[total_processed]), length - total_processed, &processed_count); |
|||
total_processed += processed_count; |
|||
do { |
|||
presult = heatshrink_decoder_poll(&hsd, &decode_buffer[data_waiting], sizeof(decode_buffer) - data_waiting, &processed_count); |
|||
data_waiting += processed_count; |
|||
if (data_waiting == sizeof(decode_buffer)) { |
|||
if (!dummy_transfer) |
|||
if (card.write(decode_buffer, data_waiting) < 0) { |
|||
return false; |
|||
} |
|||
data_waiting = 0; |
|||
} |
|||
} while (presult == HSDR_POLL_MORE); |
|||
} |
|||
return true; |
|||
} |
|||
#endif |
|||
return (dummy_transfer || card.write(buffer, length) >= 0); |
|||
} |
|||
|
|||
static bool file_close() { |
|||
if (!dummy_transfer) { |
|||
#if ENABLED(BINARY_STREAM_COMPRESSION) |
|||
// flush any buffered data
|
|||
if (data_waiting) { |
|||
if (card.write(decode_buffer, data_waiting) < 0) return false; |
|||
data_waiting = 0; |
|||
} |
|||
#endif |
|||
card.closefile(); |
|||
card.release(); |
|||
} |
|||
#if ENABLED(BINARY_STREAM_COMPRESSION) |
|||
heatshrink_decoder_finish(&hsd); |
|||
#endif |
|||
transfer_active = false; |
|||
return true; |
|||
} |
|||
|
|||
static void transfer_abort() { |
|||
if (!dummy_transfer) { |
|||
card.closefile(); |
|||
card.removeFile(card.filename); |
|||
card.release(); |
|||
#if ENABLED(BINARY_STREAM_COMPRESSION) |
|||
heatshrink_decoder_finish(&hsd); |
|||
#endif |
|||
} |
|||
transfer_active = false; |
|||
return; |
|||
} |
|||
|
|||
enum class FileTransfer : uint8_t { QUERY, OPEN, CLOSE, WRITE, ABORT }; |
|||
|
|||
static size_t data_waiting, transfer_timeout, idle_timeout; |
|||
static bool transfer_active, dummy_transfer, compression; |
|||
|
|||
public: |
|||
|
|||
static void idle() { |
|||
// If a transfer is interrupted and a file is left open, abort it after TIMEOUT ms
|
|||
const millis_t ms = millis(); |
|||
if (transfer_active && ELAPSED(ms, idle_timeout)) { |
|||
idle_timeout = ms + IDLE_PERIOD; |
|||
if (ELAPSED(ms, transfer_timeout)) transfer_abort(); |
|||
} |
|||
} |
|||
|
|||
static void process(uint8_t packet_type, char* buffer, const uint16_t length) { |
|||
transfer_timeout = millis() + TIMEOUT; |
|||
switch (static_cast<FileTransfer>(packet_type)) { |
|||
case FileTransfer::QUERY: |
|||
SERIAL_ECHOPAIR("PFT:version:", VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH); |
|||
#if ENABLED(BINARY_STREAM_COMPRESSION) |
|||
SERIAL_ECHOLNPAIR(":compresion:heatshrink,", HEATSHRINK_STATIC_WINDOW_BITS, ",", HEATSHRINK_STATIC_LOOKAHEAD_BITS); |
|||
#else |
|||
SERIAL_ECHOLNPGM(":compresion:none"); |
|||
#endif |
|||
break; |
|||
case FileTransfer::OPEN: |
|||
if (transfer_active) |
|||
SERIAL_ECHOLNPGM("PFT:busy"); |
|||
else { |
|||
if (Packet::Open::validate(buffer, length)) { |
|||
auto packet = Packet::Open::decode(buffer); |
|||
compression = packet.compression_enabled(); |
|||
dummy_transfer = packet.dummy_transfer(); |
|||
if (file_open(packet.filename())) { |
|||
SERIAL_ECHOLNPGM("PFT:success"); |
|||
break; |
|||
} |
|||
} |
|||
SERIAL_ECHOLNPGM("PFT:fail"); |
|||
} |
|||
break; |
|||
case FileTransfer::CLOSE: |
|||
if (transfer_active) { |
|||
if (file_close()) |
|||
SERIAL_ECHOLNPGM("PFT:success"); |
|||
else |
|||
SERIAL_ECHOLNPGM("PFT:ioerror"); |
|||
} |
|||
else SERIAL_ECHOLNPGM("PFT:invalid"); |
|||
break; |
|||
case FileTransfer::WRITE: |
|||
if (!transfer_active) |
|||
SERIAL_ECHOLNPGM("PFT:invalid"); |
|||
else if (!file_write(buffer, length)) |
|||
SERIAL_ECHOLNPGM("PFT:ioerror"); |
|||
break; |
|||
case FileTransfer::ABORT: |
|||
transfer_abort(); |
|||
SERIAL_ECHOLNPGM("PFT:success"); |
|||
break; |
|||
default: |
|||
SERIAL_ECHOLNPGM("PTF:invalid"); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
static const uint16_t VERSION_MAJOR = 0, VERSION_MINOR = 1, VERSION_PATCH = 0, TIMEOUT = 10000, IDLE_PERIOD = 1000; |
|||
}; |
|||
|
|||
class BinaryStream { |
|||
public: |
|||
enum class Protocol : uint8_t { CONTROL, FILE_TRANSFER }; |
|||
|
|||
enum class ProtocolControl : uint8_t { SYNC = 1, CLOSE }; |
|||
|
|||
enum class StreamState : uint8_t { PACKET_RESET, PACKET_WAIT, PACKET_HEADER, PACKET_DATA, PACKET_FOOTER, |
|||
PACKET_PROCESS, PACKET_RESEND, PACKET_TIMEOUT, PACKET_ERROR }; |
|||
|
|||
struct Packet { // 10 byte protocol overhead, ascii with checksum and line number has a minimum of 7 increasing with line
|
|||
struct [[gnu::packed]] Header { |
|||
static constexpr uint16_t HEADER_TOKEN = 0xB5AD; |
|||
uint16_t token; // packet start token
|
|||
uint8_t sync; // stream sync, resend id and packet loss detection
|
|||
uint8_t meta; // 4 bit protocol,
|
|||
// 4 bit packet type
|
|||
uint16_t size; // data length
|
|||
uint16_t checksum; // header checksum
|
|||
|
|||
uint8_t protocol() { return (meta >> 4) & 0xF; } |
|||
uint8_t type() { return meta & 0xF; } |
|||
void reset() { token = 0; sync = 0; meta = 0; size = 0; checksum = 0; } |
|||
}; |
|||
|
|||
struct [[gnu::packed]] Footer { |
|||
uint16_t checksum; // full packet checksum
|
|||
void reset() { checksum = 0; } |
|||
}; |
|||
|
|||
uint8_t header_data[sizeof(Header)], |
|||
footer_data[sizeof(Footer)]; |
|||
uint32_t bytes_received; |
|||
uint16_t checksum, header_checksum; |
|||
millis_t timeout; |
|||
char* buffer; |
|||
|
|||
Header& header() { return *reinterpret_cast<Header*>(header_data); } |
|||
Footer& footer() { return *reinterpret_cast<Footer*>(footer_data); } |
|||
void reset() { |
|||
header().reset(); |
|||
footer().reset(); |
|||
bytes_received = 0; |
|||
checksum = 0; |
|||
header_checksum = 0; |
|||
timeout = millis() + PACKET_MAX_WAIT; |
|||
buffer = nullptr; |
|||
} |
|||
} packet{}; |
|||
|
|||
void reset() { |
|||
sync = 0; |
|||
packet_retries = 0; |
|||
buffer_next_index = 0; |
|||
} |
|||
|
|||
// fletchers 16 checksum
|
|||
uint32_t checksum(uint32_t cs, uint8_t value) { |
|||
uint16_t cs_low = (((cs & 0xFF) + value) % 255); |
|||
return ((((cs >> 8) + cs_low) % 255) << 8) | cs_low; |
|||
} |
|||
|
|||
// read the next byte from the data stream keeping track of
|
|||
// whether the stream times out from data starvation
|
|||
// takes the data variable by reference in order to return status
|
|||
bool stream_read(uint8_t& data) { |
|||
if (stream_state != StreamState::PACKET_WAIT && ELAPSED(millis(), packet.timeout)) { |
|||
stream_state = StreamState::PACKET_TIMEOUT; |
|||
return false; |
|||
} |
|||
if (!bs_serial_data_available(card.transfer_port_index)) return false; |
|||
data = bs_read_serial(card.transfer_port_index); |
|||
packet.timeout = millis() + PACKET_MAX_WAIT; |
|||
return true; |
|||
} |
|||
|
|||
template<const size_t buffer_size> |
|||
void receive(char (&buffer)[buffer_size]) { |
|||
uint8_t data = 0; |
|||
millis_t transfer_window = millis() + RX_TIMESLICE; |
|||
|
|||
#if ENABLED(SDSUPPORT) |
|||
PORT_REDIRECT(card.transfer_port_index); |
|||
#endif |
|||
|
|||
while (PENDING(millis(), transfer_window)) { |
|||
switch (stream_state) { |
|||
/**
|
|||
* Data stream packet handling |
|||
*/ |
|||
case StreamState::PACKET_RESET: |
|||
packet.reset(); |
|||
stream_state = StreamState::PACKET_WAIT; |
|||
case StreamState::PACKET_WAIT: |
|||
if (!stream_read(data)) { idle(); return; } // no active packet so don't wait
|
|||
packet.header_data[1] = data; |
|||
if (packet.header().token == Packet::Header::HEADER_TOKEN) { |
|||
packet.bytes_received = 2; |
|||
stream_state = StreamState::PACKET_HEADER; |
|||
} |
|||
else { |
|||
// stream corruption drop data
|
|||
packet.header_data[0] = data; |
|||
} |
|||
break; |
|||
case StreamState::PACKET_HEADER: |
|||
if (!stream_read(data)) break; |
|||
|
|||
packet.header_data[packet.bytes_received++] = data; |
|||
packet.checksum = checksum(packet.checksum, data); |
|||
|
|||
// header checksum calculation can't contain the checksum
|
|||
if (packet.bytes_received == sizeof(Packet::Header) - 2) |
|||
packet.header_checksum = packet.checksum; |
|||
|
|||
if (packet.bytes_received == sizeof(Packet::Header)) { |
|||
if (packet.header().checksum == packet.header_checksum) { |
|||
// The SYNC control packet is a special case in that it doesn't require the stream sync to be correct
|
|||
if (static_cast<Protocol>(packet.header().protocol()) == Protocol::CONTROL && static_cast<ProtocolControl>(packet.header().type()) == ProtocolControl::SYNC) { |
|||
SERIAL_ECHOLNPAIR("ss", sync, ",", buffer_size, ",", VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH); |
|||
stream_state = StreamState::PACKET_RESET; |
|||
break; |
|||
} |
|||
if (packet.header().sync == sync) { |
|||
buffer_next_index = 0; |
|||
packet.bytes_received = 0; |
|||
if (packet.header().size) { |
|||
stream_state = StreamState::PACKET_DATA; |
|||
packet.buffer = static_cast<char *>(&buffer[0]); // multipacket buffering not implemented, always allocate whole buffer to packet
|
|||
} |
|||
else |
|||
stream_state = StreamState::PACKET_PROCESS; |
|||
} |
|||
else if (packet.header().sync == sync - 1) { // ok response must have been lost
|
|||
SERIAL_ECHOLNPAIR("ok", packet.header().sync); // transmit valid packet received and drop the payload
|
|||
stream_state = StreamState::PACKET_RESET; |
|||
} |
|||
else if (packet_retries) { |
|||
stream_state = StreamState::PACKET_RESET; // could be packets already buffered on flow controlled connections, drop them without ack
|
|||
} |
|||
else { |
|||
SERIAL_ECHO_MSG("Datastream packet out of order"); |
|||
stream_state = StreamState::PACKET_RESEND; |
|||
} |
|||
} |
|||
else { |
|||
SERIAL_ECHO_START(); |
|||
SERIAL_ECHOLNPAIR("Packet Header(", packet.header().sync, "?) Corrupt"); |
|||
stream_state = StreamState::PACKET_RESEND; |
|||
} |
|||
} |
|||
break; |
|||
case StreamState::PACKET_DATA: |
|||
if (!stream_read(data)) break; |
|||
|
|||
if (buffer_next_index < buffer_size) |
|||
packet.buffer[buffer_next_index] = data; |
|||
else { |
|||
SERIAL_ECHO_MSG("Datastream packet data buffer overrun"); |
|||
stream_state = StreamState::PACKET_ERROR; |
|||
break; |
|||
} |
|||
|
|||
packet.checksum = checksum(packet.checksum, data); |
|||
packet.bytes_received++; |
|||
buffer_next_index++; |
|||
|
|||
if (packet.bytes_received == packet.header().size) { |
|||
stream_state = StreamState::PACKET_FOOTER; |
|||
packet.bytes_received = 0; |
|||
} |
|||
break; |
|||
case StreamState::PACKET_FOOTER: |
|||
if (!stream_read(data)) break; |
|||
|
|||
packet.footer_data[packet.bytes_received++] = data; |
|||
if (packet.bytes_received == sizeof(Packet::Footer)) { |
|||
if (packet.footer().checksum == packet.checksum) { |
|||
stream_state = StreamState::PACKET_PROCESS; |
|||
} |
|||
else { |
|||
SERIAL_ECHO_START(); |
|||
SERIAL_ECHOLNPAIR("Packet(", packet.header().sync, ") Payload Corrupt"); |
|||
stream_state = StreamState::PACKET_RESEND; |
|||
} |
|||
} |
|||
break; |
|||
case StreamState::PACKET_PROCESS: |
|||
sync++; |
|||
packet_retries = 0; |
|||
bytes_received += packet.header().size; |
|||
|
|||
SERIAL_ECHOLNPAIR("ok", packet.header().sync); // transmit valid packet received
|
|||
dispatch(); |
|||
stream_state = StreamState::PACKET_RESET; |
|||
break; |
|||
case StreamState::PACKET_RESEND: |
|||
if (packet_retries < MAX_RETRIES || MAX_RETRIES == 0) { |
|||
packet_retries++; |
|||
stream_state = StreamState::PACKET_RESET; |
|||
SERIAL_ECHO_START(); |
|||
SERIAL_ECHOLNPAIR("Resend request ", int(packet_retries)); |
|||
SERIAL_ECHOLNPAIR("rs", sync); |
|||
} |
|||
else |
|||
stream_state = StreamState::PACKET_ERROR; |
|||
break; |
|||
case StreamState::PACKET_TIMEOUT: |
|||
SERIAL_ECHO_MSG("Datastream timeout"); |
|||
stream_state = StreamState::PACKET_RESEND; |
|||
break; |
|||
case StreamState::PACKET_ERROR: |
|||
SERIAL_ECHOLNPAIR("fe", packet.header().sync); |
|||
reset(); // reset everything, resync required
|
|||
stream_state = StreamState::PACKET_RESET; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void dispatch() { |
|||
switch(static_cast<Protocol>(packet.header().protocol())) { |
|||
case Protocol::CONTROL: |
|||
switch(static_cast<ProtocolControl>(packet.header().type())) { |
|||
case ProtocolControl::CLOSE: // revert back to ASCII mode
|
|||
card.flag.binary_mode = false; |
|||
break; |
|||
default: |
|||
SERIAL_ECHO_MSG("Unknown BinaryProtocolControl Packet"); |
|||
} |
|||
break; |
|||
case Protocol::FILE_TRANSFER: |
|||
SDFileTransferProtocol::process(packet.header().type(), packet.buffer, packet.header().size); // send user data to be processed
|
|||
break; |
|||
default: |
|||
SERIAL_ECHO_MSG("Unsupported Binary Protocol"); |
|||
} |
|||
} |
|||
|
|||
void idle() { |
|||
// Some Protocols may need periodic updates without new data
|
|||
SDFileTransferProtocol::idle(); |
|||
} |
|||
|
|||
static const uint16_t PACKET_MAX_WAIT = 500, RX_TIMESLICE = 20, MAX_RETRIES = 0, VERSION_MAJOR = 0, VERSION_MINOR = 1, VERSION_PATCH = 0; |
|||
uint8_t packet_retries, sync; |
|||
uint16_t buffer_next_index; |
|||
uint32_t bytes_received; |
|||
StreamState stream_state = StreamState::PACKET_RESET; |
|||
}; |
|||
|
|||
extern BinaryStream binaryStream[NUM_SERIAL]; |
@ -0,0 +1,14 @@ |
|||
Copyright (c) 2013-2015, Scott Vokes <vokes.s@gmail.com> |
|||
All rights reserved. |
|||
|
|||
Permission to use, copy, modify, and/or distribute this software for any |
|||
purpose with or without fee is hereby granted, provided that the above |
|||
copyright notice and this permission notice appear in all copies. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
|||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
@ -0,0 +1,20 @@ |
|||
/**
|
|||
* libs/heatshrink/heatshrink_common.h |
|||
*/ |
|||
#pragma once |
|||
|
|||
#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 |
@ -0,0 +1,26 @@ |
|||
/**
|
|||
* libs/heatshrink/heatshrink_config.h |
|||
*/ |
|||
#pragma once |
|||
|
|||
// 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 32 |
|||
#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 |
@ -0,0 +1,355 @@ |
|||
/**
|
|||
* libs/heatshrink/heatshrink_decoder.cpp |
|||
*/ |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include "heatshrink_decoder.h" |
|||
|
|||
#pragma GCC optimize ("O3") |
|||
|
|||
/* 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(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 nullptr; |
|||
} |
|||
size_t buffers_sz = (1 << window_sz2) + input_buffer_size; |
|||
size_t sz = sizeof(heatshrink_decoder) + buffers_sz; |
|||
heatshrink_decoder *hsd = HEATSHRINK_MALLOC(sz); |
|||
if (hsd == nullptr) return nullptr; |
|||
hsd->input_buffer_size = input_buffer_size; |
|||
hsd->window_sz2 = window_sz2; |
|||
hsd->lookahead_sz2 = lookahead_sz2; |
|||
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 buffers_sz = (1 << hsd->window_sz2) + hsd->input_buffer_size; |
|||
size_t sz = sizeof(heatshrink_decoder) + buffers_sz; |
|||
HEATSHRINK_FREE(hsd, sz); |
|||
(void)sz; /* may not be used by free */ |
|||
} |
|||
#endif |
|||
|
|||
void heatshrink_decoder_reset(heatshrink_decoder *hsd) { |
|||
size_t buf_sz = 1 << HEATSHRINK_DECODER_WINDOW_BITS(hsd); |
|||
size_t input_sz = HEATSHRINK_DECODER_INPUT_BUFFER_SIZE(hsd); |
|||
memset(hsd->buffers, 0, buf_sz + input_sz); |
|||
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 == nullptr || in_buf == nullptr || input_size == nullptr) |
|||
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 == nullptr || out_buf == nullptr || output_size == nullptr) |
|||
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) |
|||
return (*output_size == out_buf_size) ? HSDR_POLL_MORE : 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; |
|||
} |
|||
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 && 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 == nullptr) { 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; |
|||
} |
@ -0,0 +1,96 @@ |
|||
/**
|
|||
* libs/heatshrink/heatshrink_decoder.h |
|||
*/ |
|||
#pragma once |
|||
|
|||
#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(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); |
Loading…
Reference in new issue