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